Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "nsNumberControlFrame.h"
9 #include "nsRangeFrame.h"
10 #include "nsRenderingContext.h"
11 #include "nsRect.h"
12 #include "nsSize.h"
13 #include "nsThemeConstants.h"
14 #include "nsIPresShell.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.h"
17 #include "nsIDocument.h"
18 #include "nsIFrame.h"
19 #include "nsIAtom.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsPresContext.h"
22 #include "nsGkAtoms.h"
23 #include "nsCocoaFeatures.h"
24 #include "nsCocoaWindow.h"
25 #include "nsNativeThemeColors.h"
26 #include "nsIScrollableFrame.h"
27 #include "mozilla/EventStates.h"
28 #include "mozilla/dom/Element.h"
29 #include "mozilla/dom/HTMLMeterElement.h"
30 #include "nsLookAndFeel.h"
32 #include "gfxContext.h"
33 #include "gfxQuartzSurface.h"
34 #include "gfxQuartzNativeDrawing.h"
35 #include <algorithm>
37 using namespace mozilla;
38 using namespace mozilla::gfx;
39 using mozilla::dom::HTMLMeterElement;
41 #define DRAW_IN_FRAME_DEBUG 0
42 #define SCROLLBARS_VISUAL_DEBUG 0
44 // private Quartz routines needed here
45 extern "C" {
46 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
47 }
49 // Workaround for NSCell control tint drawing
50 // Without this workaround, NSCells are always drawn with the clear control tint
51 // as long as they're not attached to an NSControl which is a subview of an active window.
52 // XXXmstange Why doesn't Webkit need this?
53 @implementation NSCell (ControlTintWorkaround)
54 - (int)_realControlTint { return [self controlTint]; }
55 @end
57 // The purpose of this class is to provide objects that can be used when drawing
58 // NSCells using drawWithFrame:inView: without causing any harm. The only
59 // messages that will be sent to such an object are "isFlipped" and
60 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
61 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
62 // NSView) will be called when drawing search fields, and we only provide it in
63 // order to prevent "unrecognized selector" exceptions.
64 // There's no need to pass the actual NSView that we're drawing into to
65 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
66 // invalidations as soon as we draw a focusring!
67 @interface CellDrawView : NSView
69 @end;
71 @implementation CellDrawView
73 - (BOOL)isFlipped
74 {
75 return YES;
76 }
78 - (NSText*)currentEditor
79 {
80 return nil;
81 }
83 @end
85 static void
86 DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
87 {
88 [aCell drawWithFrame:aWithFrame inView:aInView];
90 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
91 // When building with the 10.8 SDK or higher, focus rings don't draw as part
92 // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
93 // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
94 // See the NSButtonCell section under
95 // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
96 if ([aCell showsFirstResponder]) {
97 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
98 CGContextSaveGState(cgContext);
100 // It's important to set the focus ring style before we enter the
101 // transparency layer so that the transparency layer only contains
102 // the normal button mask without the focus ring, and the conversion
103 // to the focus ring shape happens only when the transparency layer is
104 // ended.
105 NSSetFocusRingStyle(NSFocusRingOnly);
107 // We need to draw the whole button into a transparency layer because
108 // many button types are composed of multiple parts, and if these parts
109 // were drawn while the focus ring style was active, each individual part
110 // would produce a focus ring for itself. But we only want one focus ring
111 // for the whole button. The transparency layer is a way to merge the
112 // individual button parts together before the focus ring shape is
113 // calculated.
114 CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
115 [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
116 CGContextEndTransparencyLayer(cgContext);
118 CGContextRestoreGState(cgContext);
119 }
120 #endif
121 }
123 /**
124 * NSProgressBarCell is used to draw progress bars of any size.
125 */
126 @interface NSProgressBarCell : NSCell
127 {
128 /*All instance variables are private*/
129 double mValue;
130 double mMax;
131 bool mIsIndeterminate;
132 bool mIsHorizontal;
133 }
135 - (void)setValue:(double)value;
136 - (double)value;
137 - (void)setMax:(double)max;
138 - (double)max;
139 - (void)setIndeterminate:(bool)aIndeterminate;
140 - (bool)isIndeterminate;
141 - (void)setHorizontal:(bool)aIsHorizontal;
142 - (bool)isHorizontal;
143 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
144 @end
146 @implementation NSProgressBarCell
148 - (void)setMax:(double)aMax
149 {
150 mMax = aMax;
151 }
153 - (double)max
154 {
155 return mMax;
156 }
158 - (void)setValue:(double)aValue
159 {
160 mValue = aValue;
161 }
163 - (double)value
164 {
165 return mValue;
166 }
168 - (void)setIndeterminate:(bool)aIndeterminate
169 {
170 mIsIndeterminate = aIndeterminate;
171 }
173 - (bool)isIndeterminate
174 {
175 return mIsIndeterminate;
176 }
178 - (void)setHorizontal:(bool)aIsHorizontal
179 {
180 mIsHorizontal = aIsHorizontal;
181 }
183 - (bool)isHorizontal
184 {
185 return mIsHorizontal;
186 }
188 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
189 {
190 CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
192 HIThemeTrackDrawInfo tdi;
194 tdi.version = 0;
195 tdi.min = 0;
197 tdi.value = INT32_MAX * (mValue / mMax);
198 tdi.max = INT32_MAX;
199 tdi.bounds = NSRectToCGRect(cellFrame);
200 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
201 tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
202 : kThemeTrackActive;
204 NSControlSize size = [self controlSize];
205 if (size == NSRegularControlSize) {
206 tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
207 : kThemeLargeProgressBar;
208 } else {
209 NS_ASSERTION(size == NSSmallControlSize,
210 "We shouldn't have another size than small and regular for the moment");
211 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
212 : kThemeMediumProgressBar;
213 }
215 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
216 int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
217 tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) /
218 milliSecondsPerStep);
220 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
221 }
223 @end
225 @interface ContextAwareSearchFieldCell : NSSearchFieldCell
226 {
227 nsIFrame* mContext;
228 }
230 // setContext: stores the searchfield nsIFrame so that it can be consulted
231 // during painting. Please reset this by calling setContext:nullptr as soon as
232 // you're done with painting because we don't want to keep a dangling pointer.
233 - (void)setContext:(nsIFrame*)aContext;
234 @end
236 @implementation ContextAwareSearchFieldCell
238 - (id)initTextCell:(NSString*)aString
239 {
240 if ((self = [super initTextCell:aString])) {
241 mContext = nullptr;
242 }
243 return self;
244 }
246 - (void)setContext:(nsIFrame*)aContext
247 {
248 mContext = aContext;
249 }
251 static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
252 {
253 nsIContent* content = aFrame->GetContent();
254 if (!content)
255 return NO;
257 if (content->Tag() == nsGkAtoms::toolbar ||
258 content->Tag() == nsGkAtoms::toolbox ||
259 content->Tag() == nsGkAtoms::statusbar)
260 return YES;
262 switch (aFrame->StyleDisplay()->mAppearance) {
263 case NS_THEME_TOOLBAR:
264 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
265 case NS_THEME_STATUSBAR:
266 return YES;
267 default:
268 return NO;
269 }
270 }
272 - (BOOL)_isToolbarMode
273 {
274 // On 10.7, searchfields have two different styles, depending on whether
275 // the searchfield is on top of of window chrome. This function is called on
276 // 10.7 during drawing in order to determine which style to use.
277 for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
278 if (IsToolbarStyleContainer(frame)) {
279 return YES;
280 }
281 }
282 return NO;
283 }
285 @end
287 // Workaround for Bug 542048
288 // On 64-bit, NSSearchFieldCells don't draw focus rings.
289 #if defined(__x86_64__)
291 static void DrawFocusRing(NSRect rect, float radius)
292 {
293 NSSetFocusRingStyle(NSFocusRingOnly);
294 NSBezierPath* path = [NSBezierPath bezierPath];
295 rect = NSInsetRect(rect, radius, radius);
296 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
297 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
298 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle: 0.0 endAngle: 90.0];
299 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0];
300 [path closePath];
301 [path fill];
302 }
304 @interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
306 @implementation SearchFieldCellWithFocusRing
308 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
309 {
310 [super drawWithFrame:rect inView:controlView];
311 if ([self showsFirstResponder]) {
312 DrawFocusRing(rect, NSHeight(rect) / 2);
313 }
314 }
316 @end
318 #endif
320 // Copied from nsLookAndFeel.h
321 // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
322 static const int kThemeScrollBarArrowsBoth = 2;
324 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
325 #define MAX_FOCUS_RING_WIDTH 4
327 // These enums are for indexing into the margin array.
328 enum {
329 leopardOS = 0
330 };
332 enum {
333 miniControlSize,
334 smallControlSize,
335 regularControlSize
336 };
338 enum {
339 leftMargin,
340 topMargin,
341 rightMargin,
342 bottomMargin
343 };
345 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
346 if (cocoaControlSize == NSMiniControlSize)
347 return miniControlSize;
348 else if (cocoaControlSize == NSSmallControlSize)
349 return smallControlSize;
350 else
351 return regularControlSize;
352 }
354 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
355 if (enumControlSize == miniControlSize)
356 return NSMiniControlSize;
357 else if (enumControlSize == smallControlSize)
358 return NSSmallControlSize;
359 else
360 return NSRegularControlSize;
361 }
363 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
364 {
365 if (aControlSize == NSRegularControlSize)
366 return @"regular";
367 else if (aControlSize == NSSmallControlSize)
368 return @"small";
369 else
370 return @"mini";
371 }
373 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
374 {
375 if (!marginSet)
376 return;
378 static int osIndex = leopardOS;
379 int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
380 const float* buttonMargins = marginSet[osIndex][controlSize];
381 rect->origin.x -= buttonMargins[leftMargin];
382 rect->origin.y -= buttonMargins[bottomMargin];
383 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
384 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
385 }
387 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
388 nsIWidget** aTopLevelWidget = NULL)
389 {
390 if (!aFrame)
391 return nil;
393 nsIWidget* widget = aFrame->GetNearestWidget();
394 if (!widget)
395 return nil;
397 nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
398 if (aTopLevelWidget)
399 *aTopLevelWidget = topLevelWidget;
401 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
402 }
404 static NSSize
405 WindowButtonsSize(nsIFrame* aFrame)
406 {
407 NSWindow* window = NativeWindowForFrame(aFrame);
408 if (!window) {
409 // Return fallback values.
410 if (!nsCocoaFeatures::OnLionOrLater())
411 return NSMakeSize(57, 16);
412 return NSMakeSize(54, 16);
413 }
415 NSRect buttonBox = NSZeroRect;
416 NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
417 if (closeButton) {
418 buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
419 }
420 NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
421 if (minimizeButton) {
422 buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
423 }
424 NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
425 if (zoomButton) {
426 buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
427 }
428 return buttonBox.size;
429 }
431 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
432 {
433 nsIWidget* topLevelWidget = NULL;
434 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
435 if (!topLevelWidget || !win)
436 return YES;
438 // XUL popups, e.g. the toolbar customization popup, can't become key windows,
439 // but controls in these windows should still get the active look.
440 if (topLevelWidget->WindowType() == eWindowType_popup)
441 return YES;
442 if ([win isSheet])
443 return [win isKeyWindow];
444 return [win isMainWindow] && ![win attachedSheet];
445 }
447 // Toolbar controls and content controls respond to different window
448 // activeness states.
449 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
450 {
451 if (aIsToolbarControl)
452 return [NativeWindowForFrame(aFrame) isMainWindow];
453 return FrameIsInActiveWindow(aFrame);
454 }
456 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
459 nsNativeThemeCocoa::nsNativeThemeCocoa()
460 {
461 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
463 // provide a local autorelease pool, as this is called during startup
464 // before the main event-loop pool is in place
465 nsAutoreleasePool pool;
467 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:nil];
468 [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
469 [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
470 [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
472 mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
473 [mPushButtonCell setButtonType:NSMomentaryPushInButton];
474 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
476 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
477 [mRadioButtonCell setButtonType:NSRadioButton];
479 mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
480 [mCheckboxCell setButtonType:NSSwitchButton];
481 [mCheckboxCell setAllowsMixedState:YES];
483 #if defined(__x86_64__)
484 mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
485 #else
486 mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
487 #endif
488 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
489 [mSearchFieldCell setBezeled:YES];
490 [mSearchFieldCell setEditable:YES];
491 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
493 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
495 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
496 [mComboBoxCell setBezeled:YES];
497 [mComboBoxCell setEditable:YES];
498 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
500 mProgressBarCell = [[NSProgressBarCell alloc] init];
502 mMeterBarCell = [[NSLevelIndicatorCell alloc]
503 initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
505 mCellDrawView = [[CellDrawView alloc] init];
507 NS_OBJC_END_TRY_ABORT_BLOCK;
508 }
510 nsNativeThemeCocoa::~nsNativeThemeCocoa()
511 {
512 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
514 [mMeterBarCell release];
515 [mProgressBarCell release];
516 [mHelpButtonCell release];
517 [mPushButtonCell release];
518 [mRadioButtonCell release];
519 [mCheckboxCell release];
520 [mSearchFieldCell release];
521 [mDropdownCell release];
522 [mComboBoxCell release];
523 [mCellDrawView release];
525 NS_OBJC_END_TRY_ABORT_BLOCK;
526 }
528 // Limit on the area of the target rect (in pixels^2) in
529 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
530 // don't draw the object into a bitmap buffer. This is to avoid crashes in
531 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
532 // CGContextDrawImage(), and also to avoid very poor drawing performance in
533 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
534 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
535 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
536 // different amounts of RAM.
537 #define BITMAP_MAX_AREA 500000
539 static int
540 GetBackingScaleFactorForRendering(CGContextRef cgContext)
541 {
542 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
543 CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
544 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
545 fabs(transformedUserSpacePixel.size.height));
546 return maxScale > 1.0 ? 2 : 1;
547 }
549 /*
550 * Draw the given NSCell into the given cgContext.
551 *
552 * destRect - the size and position of the resulting control rectangle
553 * controlSize - the NSControlSize which will be given to the NSCell before
554 * asking it to render
555 * naturalSize - The natural dimensions of this control.
556 * If the control rect size is not equal to either of these, a scale
557 * will be applied to the context so that rendering the control at the
558 * natural size will result in it filling the destRect space.
559 * If a control has no natural dimensions in either/both axes, pass 0.0f.
560 * minimumSize - The minimum dimensions of this control.
561 * If the control rect size is less than the minimum for a given axis,
562 * a scale will be applied to the context so that the minimum is used
563 * for drawing. If a control has no minimum dimensions in either/both
564 * axes, pass 0.0f.
565 * marginSet - an array of margins; a multidimensional array of [2][3][4],
566 * with the first dimension being the OS version (Tiger or Leopard),
567 * the second being the control size (mini, small, regular), and the third
568 * being the 4 margin values (left, top, right, bottom).
569 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
570 * matter if this is really the right view; it just has to return YES when
571 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
572 * mirrorHorizontal - whether to mirror the cell horizontally
573 */
574 static void DrawCellWithScaling(NSCell *cell,
575 CGContextRef cgContext,
576 const HIRect& destRect,
577 NSControlSize controlSize,
578 NSSize naturalSize,
579 NSSize minimumSize,
580 const float marginSet[][3][4],
581 NSView* view,
582 BOOL mirrorHorizontal)
583 {
584 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
586 NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
588 if (naturalSize.width != 0.0f)
589 drawRect.size.width = naturalSize.width;
590 if (naturalSize.height != 0.0f)
591 drawRect.size.height = naturalSize.height;
593 // Keep aspect ratio when scaling if one dimension is free.
594 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
595 drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
596 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
597 drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
599 // Honor minimum sizes.
600 if (drawRect.size.width < minimumSize.width)
601 drawRect.size.width = minimumSize.width;
602 if (drawRect.size.height < minimumSize.height)
603 drawRect.size.height = minimumSize.height;
605 [NSGraphicsContext saveGraphicsState];
607 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
608 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
609 // Inflate the rect Gecko gave us by the margin for the control.
610 InflateControlRect(&drawRect, controlSize, marginSet);
612 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
613 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
615 DrawCellIncludingFocusRing(cell, drawRect, view);
617 [NSGraphicsContext setCurrentContext:savedContext];
618 }
619 else {
620 float w = ceil(drawRect.size.width);
621 float h = ceil(drawRect.size.height);
622 NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
624 // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
625 InflateControlRect(&tmpRect, controlSize, marginSet);
627 // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
628 w += MAX_FOCUS_RING_WIDTH * 2.0;
629 h += MAX_FOCUS_RING_WIDTH * 2.0;
631 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
632 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
633 CGContextRef ctx = CGBitmapContextCreate(NULL,
634 (int) w * backingScaleFactor, (int) h * backingScaleFactor,
635 8, (int) w * backingScaleFactor * 4,
636 rgb, kCGImageAlphaPremultipliedFirst);
637 CGColorSpaceRelease(rgb);
639 // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
640 // This is the first flip transform, applied to cgContext.
641 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
642 CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
643 if (mirrorHorizontal) {
644 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
645 CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
646 }
648 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
649 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
651 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
653 // This is the second flip transform, applied to ctx.
654 CGContextScaleCTM(ctx, 1.0f, -1.0f);
655 CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
657 DrawCellIncludingFocusRing(cell, tmpRect, view);
659 [NSGraphicsContext setCurrentContext:savedContext];
661 CGImageRef img = CGBitmapContextCreateImage(ctx);
663 // Drop the image into the original destination rectangle, scaling to fit
664 // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
665 // doesn't extend beyond the overflow rect
666 float xscale = destRect.size.width / drawRect.size.width;
667 float yscale = destRect.size.height / drawRect.size.height;
668 float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
669 float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
670 CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
671 destRect.origin.y - scaledFocusRingY,
672 destRect.size.width + scaledFocusRingX * 2,
673 destRect.size.height + scaledFocusRingY * 2),
674 img);
676 CGImageRelease(img);
677 CGContextRelease(ctx);
678 }
680 [NSGraphicsContext restoreGraphicsState];
682 #if DRAW_IN_FRAME_DEBUG
683 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
684 CGContextFillRect(cgContext, destRect);
685 #endif
687 NS_OBJC_END_TRY_ABORT_BLOCK;
688 }
690 struct CellRenderSettings {
691 // The natural dimensions of the control.
692 // If a control has no natural dimensions in either/both axes, set to 0.0f.
693 NSSize naturalSizes[3];
695 // The minimum dimensions of the control.
696 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
697 NSSize minimumSizes[3];
699 // A three-dimensional array,
700 // with the first dimension being the OS version (only Leopard for the moment),
701 // the second being the control size (mini, small, regular), and the third
702 // being the 4 margin values (left, top, right, bottom).
703 float margins[1][3][4];
704 };
706 /*
707 * This is a helper method that returns the required NSControlSize given a size
708 * and the size of the three controls plus a tolerance.
709 * size - The width or the height of the element to draw.
710 * sizes - An array with the all the width/height of the element for its
711 * different sizes.
712 * tolerance - The tolerance as passed to DrawCellWithSnapping.
713 * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
714 */
715 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
716 {
717 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
718 if (sizes[i] == 0) {
719 continue;
720 }
722 CGFloat next = 0;
723 // Find next value.
724 for (uint32_t j = i+1; j <= regularControlSize; ++j) {
725 if (sizes[j] != 0) {
726 next = sizes[j];
727 break;
728 }
729 }
731 // If it's the latest value, we pick it.
732 if (next == 0) {
733 return CocoaSizeForEnum(i);
734 }
736 if (size <= sizes[i] + tolerance && size < next) {
737 return CocoaSizeForEnum(i);
738 }
739 }
741 // If we are here, that means sizes[] was an array with only empty values
742 // or the algorithm above is wrong.
743 // The former can happen but the later would be wrong.
744 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
745 "We found no control! We shouldn't be there!");
746 return CocoaSizeForEnum(regularControlSize);
747 }
749 /*
750 * Draw the given NSCell into the given cgContext with a nice control size.
751 *
752 * This function is similar to DrawCellWithScaling, but it decides what
753 * control size to use based on the destRect's size.
754 * Scaling is only applied when the difference between the destRect's size
755 * and the next smaller natural size is greater than snapTolerance. Otherwise
756 * it snaps to the next smaller control size without scaling because unscaled
757 * controls look nicer.
758 */
759 static void DrawCellWithSnapping(NSCell *cell,
760 CGContextRef cgContext,
761 const HIRect& destRect,
762 const CellRenderSettings settings,
763 float verticalAlignFactor,
764 NSView* view,
765 BOOL mirrorHorizontal,
766 float snapTolerance = 2.0f)
767 {
768 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
770 const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
771 const NSSize *sizes = settings.naturalSizes;
772 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
773 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
774 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
776 HIRect drawRect = destRect;
778 CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
779 NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
780 CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
781 NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
783 NSControlSize controlSize = NSRegularControlSize;
784 int sizeIndex = 0;
786 // At some sizes, don't scale but snap.
787 const NSControlSize smallerControlSize =
788 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
789 controlSizeX : controlSizeY;
790 const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
791 const NSSize size = sizes[smallerControlSizeIndex];
792 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
793 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
794 if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
795 diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
796 // Snap to the smaller control size.
797 controlSize = smallerControlSize;
798 sizeIndex = smallerControlSizeIndex;
799 // Resize and center the drawRect.
800 if (sizes[sizeIndex].width) {
801 drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
802 drawRect.size.width = sizes[sizeIndex].width;
803 }
804 if (sizes[sizeIndex].height) {
805 drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
806 drawRect.size.height = sizes[sizeIndex].height;
807 }
808 } else {
809 // Use the larger control size.
810 controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
811 controlSizeX : controlSizeY;
812 sizeIndex = EnumSizeForCocoaSize(controlSize);
813 }
815 [cell setControlSize:controlSize];
817 NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
818 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
819 minimumSize, settings.margins, view, mirrorHorizontal);
821 NS_OBJC_END_TRY_ABORT_BLOCK;
822 }
824 static float VerticalAlignFactor(nsIFrame *aFrame)
825 {
826 if (!aFrame)
827 return 0.5f; // default: center
829 const nsStyleCoord& va = aFrame->StyleTextReset()->mVerticalAlign;
830 uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
831 ? va.GetIntValue()
832 : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
833 switch (intval) {
834 case NS_STYLE_VERTICAL_ALIGN_TOP:
835 case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
836 return 0.0f;
838 case NS_STYLE_VERTICAL_ALIGN_SUB:
839 case NS_STYLE_VERTICAL_ALIGN_SUPER:
840 case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
841 case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
842 return 0.5f;
844 case NS_STYLE_VERTICAL_ALIGN_BASELINE:
845 case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
846 case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
847 return 1.0f;
849 default:
850 NS_NOTREACHED("invalid vertical-align");
851 return 0.5f;
852 }
853 }
855 // These are the sizes that Gecko needs to request to draw if it wants
856 // to get a standard-sized Aqua radio button drawn. Note that the rects
857 // that draw these are actually a little bigger.
858 static const CellRenderSettings radioSettings = {
859 {
860 NSMakeSize(11, 11), // mini
861 NSMakeSize(13, 13), // small
862 NSMakeSize(16, 16) // regular
863 },
864 {
865 NSZeroSize, NSZeroSize, NSZeroSize
866 },
867 {
868 { // Leopard
869 {0, 0, 0, 0}, // mini
870 {0, 1, 1, 1}, // small
871 {0, 0, 0, 0} // regular
872 }
873 }
874 };
876 static const CellRenderSettings checkboxSettings = {
877 {
878 NSMakeSize(11, 11), // mini
879 NSMakeSize(13, 13), // small
880 NSMakeSize(16, 16) // regular
881 },
882 {
883 NSZeroSize, NSZeroSize, NSZeroSize
884 },
885 {
886 { // Leopard
887 {0, 1, 0, 0}, // mini
888 {0, 1, 0, 1}, // small
889 {0, 1, 0, 1} // regular
890 }
891 }
892 };
894 void
895 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
896 const HIRect& inBoxRect, bool inSelected,
897 EventStates inState, nsIFrame* aFrame)
898 {
899 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
901 NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
902 NSCellStateValue state = inSelected ? NSOnState : NSOffState;
904 // Check if we have an indeterminate checkbox
905 if (inCheckbox && GetIndeterminate(aFrame))
906 state = NSMixedState;
908 [cell setEnabled:!IsDisabled(aFrame, inState)];
909 [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
910 [cell setState:state];
911 [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
912 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
914 // Ensure that the control is square.
915 float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
916 HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
917 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
918 length, length);
920 DrawCellWithSnapping(cell, cgContext, drawRect,
921 inCheckbox ? checkboxSettings : radioSettings,
922 VerticalAlignFactor(aFrame), mCellDrawView, NO);
924 NS_OBJC_END_TRY_ABORT_BLOCK;
925 }
927 static const CellRenderSettings searchFieldSettings = {
928 {
929 NSMakeSize(0, 16), // mini
930 NSMakeSize(0, 19), // small
931 NSMakeSize(0, 22) // regular
932 },
933 {
934 NSMakeSize(32, 0), // mini
935 NSMakeSize(38, 0), // small
936 NSMakeSize(44, 0) // regular
937 },
938 {
939 { // Leopard
940 {0, 0, 0, 0}, // mini
941 {0, 0, 0, 0}, // small
942 {0, 0, 0, 0} // regular
943 }
944 }
945 };
947 void
948 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
949 nsIFrame* aFrame, EventStates inState)
950 {
951 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
953 ContextAwareSearchFieldCell* cell = mSearchFieldCell;
954 [cell setContext:aFrame];
955 [cell setEnabled:!IsDisabled(aFrame, inState)];
956 // NOTE: this could probably use inState
957 [cell setShowsFirstResponder:IsFocused(aFrame)];
959 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
960 VerticalAlignFactor(aFrame), mCellDrawView,
961 IsFrameRTL(aFrame));
963 [cell setContext:nullptr];
965 NS_OBJC_END_TRY_ABORT_BLOCK;
966 }
968 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
970 static const CellRenderSettings pushButtonSettings = {
971 {
972 NSMakeSize(0, 16), // mini
973 NSMakeSize(0, 19), // small
974 NSMakeSize(0, 22) // regular
975 },
976 {
977 NSMakeSize(18, 0), // mini
978 NSMakeSize(26, 0), // small
979 NSMakeSize(30, 0) // regular
980 },
981 {
982 { // Leopard
983 {0, 0, 0, 0}, // mini
984 {4, 0, 4, 1}, // small
985 {5, 0, 5, 2} // regular
986 }
987 }
988 };
990 // The height at which we start doing square buttons instead of rounded buttons
991 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
992 // we switch over to doing square buttons which looks fine at any size.
993 #define DO_SQUARE_BUTTON_HEIGHT 26
995 void
996 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
997 EventStates inState, uint8_t aWidgetType,
998 nsIFrame* aFrame)
999 {
1000 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1002 BOOL isActive = FrameIsInActiveWindow(aFrame);
1003 BOOL isDisabled = IsDisabled(aFrame, inState);
1005 NSButtonCell* cell = (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) ? mHelpButtonCell : mPushButtonCell;
1006 [cell setEnabled:!isDisabled];
1007 [cell setHighlighted:isActive &&
1008 inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
1009 [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
1011 if (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) {
1012 DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
1013 NSZeroSize, kHelpButtonSize, NULL, mCellDrawView,
1014 false); // Don't mirror icon in RTL.
1015 } else {
1016 // If the button is tall enough, draw the square button style so that
1017 // buttons with non-standard content look good. Otherwise draw normal
1018 // rounded aqua buttons.
1019 if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
1020 [cell setBezelStyle:NSShadowlessSquareBezelStyle];
1021 DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
1022 NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView,
1023 IsFrameRTL(aFrame));
1024 } else {
1025 [cell setBezelStyle:NSRoundedBezelStyle];
1026 DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1027 mCellDrawView, IsFrameRTL(aFrame), 1.0f);
1028 }
1029 }
1031 #if DRAW_IN_FRAME_DEBUG
1032 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1033 CGContextFillRect(cgContext, inBoxRect);
1034 #endif
1036 NS_OBJC_END_TRY_ABORT_BLOCK;
1037 }
1039 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
1041 static void
1042 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1043 RenderHIThemeControlFunction aFunc, void* aData,
1044 BOOL mirrorHorizontally = NO)
1045 {
1046 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1047 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1049 bool drawDirect;
1050 HIRect drawRect = aRect;
1051 drawRect.origin = CGPointZero;
1053 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1054 savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1055 drawDirect = TRUE;
1056 } else {
1057 drawDirect = FALSE;
1058 }
1060 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1061 // is too large.
1062 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1063 aFunc(aCGContext, drawRect, aData);
1064 } else {
1065 // Inflate the buffer to capture focus rings.
1066 int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
1067 int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
1069 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1070 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1071 CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
1072 w * backingScaleFactor,
1073 h * backingScaleFactor,
1074 8,
1075 w * backingScaleFactor * 4,
1076 colorSpace,
1077 kCGImageAlphaPremultipliedFirst);
1078 CGColorSpaceRelease(colorSpace);
1080 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1081 CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
1083 // HITheme always wants to draw into a flipped context, or things
1084 // get confused.
1085 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1086 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1088 aFunc(bitmapctx, drawRect, aData);
1090 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1092 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1094 // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1095 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1096 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1098 if (mirrorHorizontally) {
1099 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1100 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1101 }
1103 HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
1104 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1106 CGContextSetCTM(aCGContext, ctm);
1108 CGImageRelease(bitmap);
1109 CGContextRelease(bitmapctx);
1110 }
1112 CGContextSetCTM(aCGContext, savedCTM);
1113 }
1115 static void
1116 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1117 {
1118 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1119 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1120 }
1122 void
1123 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
1124 const HIRect& inBoxRect, bool inIsDefault,
1125 ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
1126 EventStates inState, nsIFrame* aFrame)
1127 {
1128 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1130 BOOL isActive = FrameIsInActiveWindow(aFrame);
1131 BOOL isDisabled = IsDisabled(aFrame, inState);
1133 HIThemeButtonDrawInfo bdi;
1134 bdi.version = 0;
1135 bdi.kind = inKind;
1136 bdi.value = inValue;
1137 bdi.adornment = inAdornment;
1139 if (isDisabled) {
1140 bdi.state = kThemeStateUnavailable;
1141 }
1142 else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
1143 bdi.state = kThemeStatePressed;
1144 }
1145 else {
1146 if (inKind == kThemeArrowButton)
1147 bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
1148 else if (!isActive && inKind == kThemeListHeaderButton)
1149 bdi.state = kThemeStateInactive;
1150 else
1151 bdi.state = kThemeStateActive;
1152 }
1154 if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
1155 bdi.adornment |= kThemeAdornmentFocus;
1157 if (inIsDefault && !isDisabled && isActive &&
1158 !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
1159 bdi.adornment |= kThemeAdornmentDefault;
1160 bdi.animation.time.start = 0;
1161 bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1162 }
1164 HIRect drawFrame = inBoxRect;
1166 if (inKind == kThemePushButton) {
1167 drawFrame.size.height -= 2;
1168 if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
1169 bdi.kind = kThemePushButtonMini;
1170 }
1171 else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
1172 bdi.kind = kThemePushButtonSmall;
1173 drawFrame.origin.y -= 1;
1174 drawFrame.origin.x += 1;
1175 drawFrame.size.width -= 2;
1176 }
1177 }
1178 else if (inKind == kThemeListHeaderButton) {
1179 CGContextClipToRect(cgContext, inBoxRect);
1180 // Always remove the top border.
1181 drawFrame.origin.y -= 1;
1182 drawFrame.size.height += 1;
1183 // Remove the left border in LTR mode and the right border in RTL mode.
1184 drawFrame.size.width += 1;
1185 bool isLast = IsLastTreeHeaderCell(aFrame);
1186 if (isLast)
1187 drawFrame.size.width += 1; // Also remove the other border.
1188 if (!IsFrameRTL(aFrame) || isLast)
1189 drawFrame.origin.x -= 1;
1190 }
1192 RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1193 IsFrameRTL(aFrame));
1195 #if DRAW_IN_FRAME_DEBUG
1196 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1197 CGContextFillRect(cgContext, inBoxRect);
1198 #endif
1200 NS_OBJC_END_TRY_ABORT_BLOCK;
1201 }
1203 static const CellRenderSettings dropdownSettings = {
1204 {
1205 NSMakeSize(0, 16), // mini
1206 NSMakeSize(0, 19), // small
1207 NSMakeSize(0, 22) // regular
1208 },
1209 {
1210 NSMakeSize(18, 0), // mini
1211 NSMakeSize(38, 0), // small
1212 NSMakeSize(44, 0) // regular
1213 },
1214 {
1215 { // Leopard
1216 {1, 1, 2, 1}, // mini
1217 {3, 0, 3, 1}, // small
1218 {3, 0, 3, 0} // regular
1219 }
1220 }
1221 };
1223 static const CellRenderSettings editableMenulistSettings = {
1224 {
1225 NSMakeSize(0, 15), // mini
1226 NSMakeSize(0, 18), // small
1227 NSMakeSize(0, 21) // regular
1228 },
1229 {
1230 NSMakeSize(18, 0), // mini
1231 NSMakeSize(38, 0), // small
1232 NSMakeSize(44, 0) // regular
1233 },
1234 {
1235 { // Leopard
1236 {0, 0, 2, 2}, // mini
1237 {0, 0, 3, 2}, // small
1238 {0, 1, 3, 3} // regular
1239 }
1240 }
1241 };
1243 void
1244 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1245 EventStates inState, uint8_t aWidgetType,
1246 nsIFrame* aFrame)
1247 {
1248 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1250 [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
1252 BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
1253 NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1255 [cell setEnabled:!IsDisabled(aFrame, inState)];
1256 [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
1257 [cell setHighlighted:IsOpenButton(aFrame)];
1258 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
1260 const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
1261 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
1262 0.5f, mCellDrawView, IsFrameRTL(aFrame));
1264 NS_OBJC_END_TRY_ABORT_BLOCK;
1265 }
1267 static const CellRenderSettings spinnerSettings = {
1268 {
1269 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1270 NSMakeSize(15, 22), // small
1271 NSMakeSize(19, 27) // regular
1272 },
1273 {
1274 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1275 NSMakeSize(15, 22), // small
1276 NSMakeSize(19, 27) // regular
1277 },
1278 {
1279 { // Leopard
1280 {0, 0, 0, 0}, // mini
1281 {0, 0, 0, 0}, // small
1282 {0, 0, 0, 0} // regular
1283 }
1284 }
1285 };
1287 void
1288 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
1289 const HIRect& inBoxRect, ThemeDrawState inDrawState,
1290 ThemeButtonAdornment inAdornment,
1291 EventStates inState, nsIFrame* aFrame)
1292 {
1293 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1295 HIThemeButtonDrawInfo bdi;
1296 bdi.version = 0;
1297 bdi.kind = inKind;
1298 bdi.value = kThemeButtonOff;
1299 bdi.adornment = inAdornment;
1301 if (IsDisabled(aFrame, inState))
1302 bdi.state = kThemeStateUnavailable;
1303 else
1304 bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1306 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1308 NS_OBJC_END_TRY_ABORT_BLOCK;
1309 }
1311 void
1312 nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1313 ThemeButtonKind inKind,
1314 const HIRect& inBoxRect,
1315 ThemeDrawState inDrawState,
1316 ThemeButtonAdornment inAdornment,
1317 EventStates inState,
1318 nsIFrame* aFrame,
1319 uint8_t aWidgetType)
1320 {
1321 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1323 MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
1324 aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON);
1326 HIThemeButtonDrawInfo bdi;
1327 bdi.version = 0;
1328 bdi.kind = inKind;
1329 bdi.value = kThemeButtonOff;
1330 bdi.adornment = inAdornment;
1332 if (IsDisabled(aFrame, inState))
1333 bdi.state = kThemeStateUnavailable;
1334 else
1335 bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1337 // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1338 // together as a single unit (presumably because when one button is active,
1339 // the appearance of both changes (in different ways)). Here we have to paint
1340 // both buttons, using clip to hide the one we don't want to paint.
1341 HIRect drawRect = inBoxRect;
1342 drawRect.size.height *= 2;
1343 if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
1344 drawRect.origin.y -= inBoxRect.size.height;
1345 }
1347 // Shift the drawing a little to the left, since cocoa paints with more
1348 // blank space around the visual buttons than we'd like:
1349 drawRect.origin.x -= 1;
1351 CGContextSaveGState(cgContext);
1352 CGContextClipToRect(cgContext, inBoxRect);
1354 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1356 CGContextRestoreGState(cgContext);
1358 NS_OBJC_END_TRY_ABORT_BLOCK;
1359 }
1361 void
1362 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
1363 const HIRect& inBoxRect, bool inDisabled,
1364 EventStates inState)
1365 {
1366 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1368 HIThemeFrameDrawInfo fdi;
1369 fdi.version = 0;
1370 fdi.kind = inKind;
1372 // We don't ever set an inactive state for this because it doesn't
1373 // look right (see other apps).
1374 fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
1376 // for some reason focus rings on listboxes draw incorrectly
1377 if (inKind == kHIThemeFrameListBox)
1378 fdi.isFocused = 0;
1379 else
1380 fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1382 // HIThemeDrawFrame takes the rect for the content area of the frame, not
1383 // the bounding rect for the frame. Here we reduce the size of the rect we
1384 // will pass to make it the size of the content.
1385 HIRect drawRect = inBoxRect;
1386 if (inKind == kHIThemeFrameTextFieldSquare) {
1387 SInt32 frameOutset = 0;
1388 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1389 drawRect.origin.x += frameOutset;
1390 drawRect.origin.y += frameOutset;
1391 drawRect.size.width -= frameOutset * 2;
1392 drawRect.size.height -= frameOutset * 2;
1393 }
1394 else if (inKind == kHIThemeFrameListBox) {
1395 SInt32 frameOutset = 0;
1396 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1397 drawRect.origin.x += frameOutset;
1398 drawRect.origin.y += frameOutset;
1399 drawRect.size.width -= frameOutset * 2;
1400 drawRect.size.height -= frameOutset * 2;
1401 }
1403 #if DRAW_IN_FRAME_DEBUG
1404 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1405 CGContextFillRect(cgContext, inBoxRect);
1406 #endif
1408 HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1410 NS_OBJC_END_TRY_ABORT_BLOCK;
1411 }
1413 static const CellRenderSettings progressSettings[2][2] = {
1414 // Vertical progress bar.
1415 {
1416 // Determined settings.
1417 {
1418 {
1419 NSZeroSize, // mini
1420 NSMakeSize(10, 0), // small
1421 NSMakeSize(16, 0) // regular
1422 },
1423 {
1424 NSZeroSize, NSZeroSize, NSZeroSize
1425 },
1426 {
1427 { // Leopard
1428 {0, 0, 0, 0}, // mini
1429 {1, 1, 1, 1}, // small
1430 {1, 1, 1, 1} // regular
1431 }
1432 }
1433 },
1434 // There is no horizontal margin in regular undetermined size.
1435 {
1436 {
1437 NSZeroSize, // mini
1438 NSMakeSize(10, 0), // small
1439 NSMakeSize(16, 0) // regular
1440 },
1441 {
1442 NSZeroSize, NSZeroSize, NSZeroSize
1443 },
1444 {
1445 { // Leopard
1446 {0, 0, 0, 0}, // mini
1447 {1, 1, 1, 1}, // small
1448 {1, 0, 1, 0} // regular
1449 }
1450 }
1451 }
1452 },
1453 // Horizontal progress bar.
1454 {
1455 // Determined settings.
1456 {
1457 {
1458 NSZeroSize, // mini
1459 NSMakeSize(0, 10), // small
1460 NSMakeSize(0, 16) // regular
1461 },
1462 {
1463 NSZeroSize, NSZeroSize, NSZeroSize
1464 },
1465 {
1466 { // Leopard
1467 {0, 0, 0, 0}, // mini
1468 {1, 1, 1, 1}, // small
1469 {1, 1, 1, 1} // regular
1470 }
1471 }
1472 },
1473 // There is no horizontal margin in regular undetermined size.
1474 {
1475 {
1476 NSZeroSize, // mini
1477 NSMakeSize(0, 10), // small
1478 NSMakeSize(0, 16) // regular
1479 },
1480 {
1481 NSZeroSize, NSZeroSize, NSZeroSize
1482 },
1483 {
1484 { // Leopard
1485 {0, 0, 0, 0}, // mini
1486 {1, 1, 1, 1}, // small
1487 {0, 1, 0, 1} // regular
1488 }
1489 }
1490 }
1491 }
1492 };
1494 void
1495 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1496 bool inIsIndeterminate, bool inIsHorizontal,
1497 double inValue, double inMaxValue,
1498 nsIFrame* aFrame)
1499 {
1500 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1502 NSProgressBarCell* cell = mProgressBarCell;
1504 [cell setValue:inValue];
1505 [cell setMax:inMaxValue];
1506 [cell setIndeterminate:inIsIndeterminate];
1507 [cell setHorizontal:inIsHorizontal];
1508 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
1509 : NSClearControlTint)];
1511 DrawCellWithSnapping(cell, cgContext, inBoxRect,
1512 progressSettings[inIsHorizontal][inIsIndeterminate],
1513 VerticalAlignFactor(aFrame), mCellDrawView,
1514 IsFrameRTL(aFrame));
1516 NS_OBJC_END_TRY_ABORT_BLOCK;
1517 }
1519 static const CellRenderSettings meterSetting = {
1520 {
1521 NSMakeSize(0, 16), // mini
1522 NSMakeSize(0, 16), // small
1523 NSMakeSize(0, 16) // regular
1524 },
1525 {
1526 NSZeroSize, NSZeroSize, NSZeroSize
1527 },
1528 {
1529 { // Leopard
1530 {1, 1, 1, 1}, // mini
1531 {1, 1, 1, 1}, // small
1532 {1, 1, 1, 1} // regular
1533 }
1534 }
1535 };
1537 void
1538 nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1539 nsIFrame* aFrame)
1540 {
1541 NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1543 NS_PRECONDITION(aFrame, "aFrame should not be null here!");
1545 // When using -moz-meterbar on an non meter element, we will not be able to
1546 // get all the needed information so we just draw an empty meter.
1547 nsIContent* content = aFrame->GetContent();
1548 if (!(content && content->IsHTML(nsGkAtoms::meter))) {
1549 DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
1550 meterSetting, VerticalAlignFactor(aFrame),
1551 mCellDrawView, IsFrameRTL(aFrame));
1552 return;
1553 }
1555 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1556 double value = meterElement->Value();
1557 double min = meterElement->Min();
1558 double max = meterElement->Max();
1560 NSLevelIndicatorCell* cell = mMeterBarCell;
1562 [cell setMinValue:min];
1563 [cell setMaxValue:max];
1564 [cell setDoubleValue:value];
1566 /**
1567 * The way HTML and Cocoa defines the meter/indicator widget are different.
1568 * So, we are going to use a trick to get the Cocoa widget showing what we
1569 * are expecting: we set the warningValue or criticalValue to the current
1570 * value when we want to have the widget to be in the warning or critical
1571 * state.
1572 */
1573 EventStates states = aFrame->GetContent()->AsElement()->State();
1575 // Reset previously set warning and critical values.
1576 [cell setWarningValue:max+1];
1577 [cell setCriticalValue:max+1];
1579 if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1580 [cell setWarningValue:value];
1581 } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1582 [cell setCriticalValue:value];
1583 }
1585 HIRect rect = CGRectStandardize(inBoxRect);
1586 BOOL vertical = IsVerticalMeter(aFrame);
1588 CGContextSaveGState(cgContext);
1590 if (vertical) {
1591 /**
1592 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1593 * show a rotated horizontal meter bar.
1594 * Given that we want to show a vertical meter bar, we assume that the rect
1595 * has vertical dimensions but we can't correctly draw a meter widget inside
1596 * such a rectangle so we need to inverse width and height (and re-position)
1597 * to get a rectangle with horizontal dimensions.
1598 * Finally, we want to show a vertical meter so we want to rotate the result
1599 * so it is vertical. We do that by changing the context.
1600 */
1601 CGFloat tmp = rect.size.width;
1602 rect.size.width = rect.size.height;
1603 rect.size.height = tmp;
1604 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1605 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1607 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1608 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1609 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1610 }
1612 DrawCellWithSnapping(cell, cgContext, rect,
1613 meterSetting, VerticalAlignFactor(aFrame),
1614 mCellDrawView, !vertical && IsFrameRTL(aFrame));
1616 CGContextRestoreGState(cgContext);
1618 NS_OBJC_END_TRY_ABORT_BLOCK
1619 }
1621 void
1622 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1623 nsIFrame* aFrame)
1624 {
1625 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1627 HIThemeTabPaneDrawInfo tpdi;
1629 tpdi.version = 1;
1630 tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1631 tpdi.direction = kThemeTabNorth;
1632 tpdi.size = kHIThemeTabSizeNormal;
1633 tpdi.kind = kHIThemeTabKindNormal;
1635 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1637 NS_OBJC_END_TRY_ABORT_BLOCK;
1638 }
1640 void
1641 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1642 EventStates inState, bool inIsVertical,
1643 bool inIsReverse, int32_t inCurrentValue,
1644 int32_t inMinValue, int32_t inMaxValue,
1645 nsIFrame* aFrame)
1646 {
1647 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1649 HIThemeTrackDrawInfo tdi;
1651 tdi.version = 0;
1652 tdi.kind = kThemeMediumSlider;
1653 tdi.bounds = inBoxRect;
1654 tdi.min = inMinValue;
1655 tdi.max = inMaxValue;
1656 tdi.value = inCurrentValue;
1657 tdi.attributes = kThemeTrackShowThumb;
1658 if (!inIsVertical)
1659 tdi.attributes |= kThemeTrackHorizontal;
1660 if (inIsReverse)
1661 tdi.attributes |= kThemeTrackRightToLeft;
1662 if (inState.HasState(NS_EVENT_STATE_FOCUS))
1663 tdi.attributes |= kThemeTrackHasFocus;
1664 if (IsDisabled(aFrame, inState))
1665 tdi.enableState = kThemeTrackDisabled;
1666 else
1667 tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1668 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1669 tdi.trackInfo.slider.pressState = 0;
1671 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1673 NS_OBJC_END_TRY_ABORT_BLOCK;
1674 }
1676 nsIFrame*
1677 nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
1678 {
1679 // Usually a separator is drawn by the segment to the right of the
1680 // separator, but pressed and selected segments have higher priority.
1681 if (!aBefore || !aAfter)
1682 return nullptr;
1683 if (IsSelectedButton(aAfter))
1684 return aAfter;
1685 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
1686 return aBefore;
1687 return aAfter;
1688 }
1690 CGRect
1691 nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
1692 nsIFrame* aCurrent, nsIFrame* aRight)
1693 {
1694 // A separator between two segments should always be located in the leftmost
1695 // pixel column of the segment to the right of the separator, regardless of
1696 // who ends up drawing it.
1697 // CoreUI draws the separators inside the drawing rect.
1698 if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
1699 // The left button draws the separator, so we need to make room for it.
1700 aRect.origin.x += 1;
1701 aRect.size.width -= 1;
1702 }
1703 if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
1704 // We draw the right separator, so we need to extend the draw rect into the
1705 // segment to our right.
1706 aRect.size.width += 1;
1707 }
1708 return aRect;
1709 }
1711 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
1712 {
1713 if (aIsFirst) {
1714 if (aIsLast)
1715 return @"kCUISegmentPositionOnly";
1716 return @"kCUISegmentPositionFirst";
1717 }
1718 if (aIsLast)
1719 return @"kCUISegmentPositionLast";
1720 return @"kCUISegmentPositionMiddle";
1721 }
1723 struct SegmentedControlRenderSettings {
1724 const CGFloat* heights;
1725 const NSString* widgetName;
1726 const BOOL ignoresPressedWhenSelected;
1727 const BOOL isToolbarControl;
1728 };
1730 static const CGFloat tabHeights[3] = { 17, 20, 23 };
1732 static const SegmentedControlRenderSettings tabRenderSettings = {
1733 tabHeights, @"tab", YES, NO
1734 };
1736 static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
1738 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
1739 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
1740 };
1742 void
1743 nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
1744 EventStates inState, nsIFrame* aFrame,
1745 const SegmentedControlRenderSettings& aSettings)
1746 {
1747 BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
1748 BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1749 BOOL isSelected = IsSelectedButton(aFrame);
1750 BOOL isPressed = IsPressedButton(aFrame);
1751 if (isSelected && aSettings.ignoresPressedWhenSelected) {
1752 isPressed = NO;
1753 }
1755 BOOL isRTL = IsFrameRTL(aFrame);
1756 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
1757 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
1758 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
1759 if (drawRect.size.width * drawRect.size.height > CUIDRAW_MAX_AREA) {
1760 return;
1761 }
1762 BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
1763 BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
1764 NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
1766 CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1767 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1768 aSettings.widgetName, @"widget",
1769 ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
1770 [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
1771 [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
1772 [NSNumber numberWithBool:isSelected], @"value",
1773 (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
1774 [NSNumber numberWithBool:isFocused], @"focus",
1775 CUIControlSizeForCocoaSize(controlSize), @"size",
1776 [NSNumber numberWithBool:YES], @"is.flipped",
1777 @"up", @"direction",
1778 nil],
1779 nil);
1780 }
1782 static inline UInt8
1783 ConvertToPressState(EventStates aButtonState, UInt8 aPressState)
1784 {
1785 // If the button is pressed, return the press state passed in. Otherwise, return 0.
1786 return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0;
1787 }
1789 void
1790 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame,
1791 EventStates aButtonStates[])
1792 {
1793 static nsIContent::AttrValuesArray attributeValues[] = {
1794 &nsGkAtoms::scrollbarUpTop,
1795 &nsGkAtoms::scrollbarDownTop,
1796 &nsGkAtoms::scrollbarUpBottom,
1797 &nsGkAtoms::scrollbarDownBottom,
1798 nullptr
1799 };
1801 // Get the state of any scrollbar buttons in our child frames
1802 for (nsIFrame *childFrame = aFrame->GetFirstPrincipalChild();
1803 childFrame;
1804 childFrame = childFrame->GetNextSibling()) {
1806 nsIContent *childContent = childFrame->GetContent();
1807 if (!childContent) continue;
1808 int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr,
1809 attributeValues, eCaseMatters);
1810 if (attrIndex < 0) continue;
1812 aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
1813 }
1814 }
1816 // Both of the following sets of numbers were derived by loading the testcase in
1817 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1818 // These magic numbers are the minimum sizes we can draw a scrollbar and still
1819 // have room for everything to display, including the thumb
1820 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1821 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1822 // And these are the minimum sizes if we don't draw the thumb
1823 #define MIN_SCROLLBAR_SIZE 56
1824 #define MIN_SMALL_SCROLLBAR_SIZE 46
1826 void
1827 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame,
1828 const CGSize& aSize, bool aShouldGetButtonStates)
1829 {
1830 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1832 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1833 int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
1834 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
1835 int32_t thumbSize = CheckIntAttr(aFrame, nsGkAtoms::pageincrement, 10);
1837 bool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
1838 nsGkAtoms::horizontal, eCaseMatters);
1839 bool isSmall = aFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1841 aTdi.version = 0;
1842 aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1843 aTdi.bounds.origin = CGPointZero;
1844 aTdi.bounds.size = aSize;
1845 aTdi.min = minpos;
1846 aTdi.max = maxpos;
1847 aTdi.value = curpos;
1848 aTdi.attributes = 0;
1849 aTdi.enableState = kThemeTrackActive;
1850 if (isHorizontal)
1851 aTdi.attributes |= kThemeTrackHorizontal;
1853 aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
1855 // This should be done early on so things like "kThemeTrackNothingToScroll" can
1856 // override the active enable state.
1857 aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1859 /* Only display features if we have enough room for them.
1860 * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
1861 */
1862 int32_t longSideLength = (int32_t)(isHorizontal ? (aSize.width) : (aSize.height));
1863 if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
1864 aTdi.attributes |= kThemeTrackShowThumb;
1865 }
1866 else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
1867 aTdi.enableState = kThemeTrackNothingToScroll;
1868 return;
1869 }
1871 aTdi.trackInfo.scrollbar.pressState = 0;
1873 // Only go get these scrollbar button states if we need it. For example,
1874 // there's no reason to look up scrollbar button states when we're only
1875 // creating a TrackDrawInfo to determine the size of the thumb. There's
1876 // also no reason to do this on Lion or later, whose scrollbars have no
1877 // arrow buttons.
1878 if (aShouldGetButtonStates && !nsCocoaFeatures::OnLionOrLater()) {
1879 EventStates buttonStates[4];
1880 GetScrollbarPressStates(aFrame, buttonStates);
1881 NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
1882 // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
1883 // the up scrollbar button.
1884 if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
1885 aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1886 ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
1887 ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
1888 ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1889 } else {
1890 aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1891 ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
1892 ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
1893 ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1894 }
1895 }
1897 NS_OBJC_END_TRY_ABORT_BLOCK;
1898 }
1900 static void
1901 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1902 {
1903 HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1904 HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
1905 }
1907 void
1908 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
1909 {
1910 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1912 HIThemeTrackDrawInfo tdi;
1913 GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, true); // True means we want the press states
1914 RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
1916 NS_OBJC_END_TRY_ABORT_BLOCK;
1917 }
1919 nsIFrame*
1920 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
1921 {
1922 // Walk our parents to find a scrollbar frame
1923 nsIFrame *scrollbarFrame = aFrame;
1924 do {
1925 if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
1926 } while ((scrollbarFrame = scrollbarFrame->GetParent()));
1928 // We return null if we can't find a parent scrollbar frame
1929 return scrollbarFrame;
1930 }
1932 static bool
1933 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
1934 {
1935 if (![aWindow isKindOfClass:[ToolbarWindow class]])
1936 return false;
1938 ToolbarWindow* win = (ToolbarWindow*)aWindow;
1939 float unifiedToolbarHeight = [win unifiedToolbarHeight];
1940 return inBoxRect.origin.x == 0 &&
1941 inBoxRect.size.width >= [win frame].size.width &&
1942 CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight;
1943 }
1945 // By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
1946 // upper corners. Depending on the context type, it fills the background in
1947 // the corners with black or leaves it transparent. Unfortunately, this corner
1948 // rounding interacts poorly with the window corner masking we apply during
1949 // titlebar drawing and results in small remnants of the corner background
1950 // appearing at the rounded edge.
1951 // So we draw square corners.
1952 static void
1953 DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
1954 CGFloat aUnifiedHeight, BOOL aIsMain)
1955 {
1956 // We extend the draw rect horizontally and clip away the rounded corners.
1957 const CGFloat extendHorizontal = 10;
1958 CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
1959 if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
1960 CGContextSaveGState(aContext);
1961 CGContextClipToRect(aContext, aRect);
1963 CUIDraw([NSWindow coreUIRenderer], drawRect, aContext,
1964 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1965 @"kCUIWidgetWindowFrame", @"widget",
1966 @"regularwin", @"windowtype",
1967 (aIsMain ? @"normal" : @"inactive"), @"state",
1968 [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
1969 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
1970 [NSNumber numberWithBool:YES], @"is.flipped",
1971 nil],
1972 nil);
1974 CGContextRestoreGState(aContext);
1975 }
1976 }
1978 void
1979 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
1980 NSWindow* aWindow)
1981 {
1982 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1984 CGContextSaveGState(cgContext);
1985 CGContextClipToRect(cgContext, inBoxRect);
1987 CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight],
1988 inBoxRect.size.height);
1989 BOOL isMain = [aWindow isMainWindow];
1990 CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height;
1991 CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
1992 inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
1993 DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain);
1995 CGContextRestoreGState(cgContext);
1997 NS_OBJC_END_TRY_ABORT_BLOCK;
1998 }
2000 void
2001 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2002 nsIFrame *aFrame)
2003 {
2004 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2006 if (inBoxRect.size.height < 2.0f)
2007 return;
2009 CGContextSaveGState(cgContext);
2010 CGContextClipToRect(cgContext, inBoxRect);
2012 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2013 // and bottom bar. We only want the bottom bar, so we extend the draw rect
2014 // upwards to make space for the title bar, and then we clip it away.
2015 CGRect drawRect = inBoxRect;
2016 const int extendUpwards = 40;
2017 drawRect.origin.y -= extendUpwards;
2018 drawRect.size.height += extendUpwards;
2019 if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
2020 CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
2021 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
2022 @"kCUIWidgetWindowFrame", @"widget",
2023 @"regularwin", @"windowtype",
2024 (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
2025 [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
2026 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2027 [NSNumber numberWithBool:YES], @"is.flipped",
2028 nil],
2029 nil);
2030 }
2032 CGContextRestoreGState(cgContext);
2034 NS_OBJC_END_TRY_ABORT_BLOCK;
2035 }
2037 void
2038 nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2039 CGFloat aUnifiedHeight, BOOL aIsMain)
2040 {
2041 CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
2042 DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain);
2043 }
2045 static void
2046 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
2047 {
2048 HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2049 HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2050 }
2052 void
2053 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
2054 nsIFrame *aFrame)
2055 {
2056 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2058 HIThemeGrowBoxDrawInfo drawInfo;
2059 drawInfo.version = 0;
2060 drawInfo.state = kThemeStateActive;
2061 drawInfo.kind = kHIThemeGrowBoxKindNormal;
2062 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2063 drawInfo.size = kHIThemeGrowBoxSizeNormal;
2065 RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
2066 IsFrameRTL(aFrame));
2068 NS_OBJC_END_TRY_ABORT_BLOCK;
2069 }
2071 static bool
2072 IsHiDPIContext(nsDeviceContext* aContext)
2073 {
2074 return nsPresContext::AppUnitsPerCSSPixel() >=
2075 2 * aContext->UnscaledAppUnitsPerDevPixel();
2076 }
2078 NS_IMETHODIMP
2079 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
2080 nsIFrame* aFrame,
2081 uint8_t aWidgetType,
2082 const nsRect& aRect,
2083 const nsRect& aDirtyRect)
2084 {
2085 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2087 // setup to draw into the correct port
2088 int32_t p2a = aContext->AppUnitsPerDevPixel();
2090 gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y,
2091 aDirtyRect.width, aDirtyRect.height);
2092 gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2093 nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
2094 nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
2095 nativeWidgetRect.Round();
2096 if (nativeWidgetRect.IsEmpty())
2097 return NS_OK; // Don't attempt to draw invisible widgets.
2099 gfxContext* thebesCtx = aContext->ThebesContext();
2100 if (!thebesCtx)
2101 return NS_ERROR_FAILURE;
2103 gfxContextMatrixAutoSaveRestore save(thebesCtx);
2105 bool hidpi = IsHiDPIContext(aContext->DeviceContext());
2106 if (hidpi) {
2107 // Use high-resolution drawing.
2108 nativeWidgetRect.ScaleInverse(2.0f);
2109 nativeDirtyRect.ScaleInverse(2.0f);
2110 thebesCtx->Scale(2.0f, 2.0f);
2111 }
2113 gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect,
2114 hidpi ? 2.0f : 1.0f);
2116 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2117 if (cgContext == nullptr) {
2118 // The Quartz surface handles 0x0 surfaces by internally
2119 // making all operations no-ops; there's no cgcontext created for them.
2120 // Unfortunately, this means that callers that want to render
2121 // directly to the CGContext need to be aware of this quirk.
2122 return NS_OK;
2123 }
2125 #if 0
2126 if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
2127 fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
2128 aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
2129 fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
2130 mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
2131 fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
2132 mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
2133 CGAffineTransform mm = CGContextGetCTM(cgContext);
2134 fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
2135 mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
2136 }
2137 #endif
2139 CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
2140 nativeWidgetRect.Width(), nativeWidgetRect.Height());
2142 #if 0
2143 fprintf(stderr, " --> macRect %f %f %f %f\n",
2144 macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
2145 CGRect bounds = CGContextGetClipBoundingBox(cgContext);
2146 fprintf(stderr, " --> clip bounds: %f %f %f %f\n",
2147 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
2149 //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
2150 //CGContextFillRect(cgContext, bounds);
2151 #endif
2153 EventStates eventState = GetContentState(aFrame, aWidgetType);
2155 switch (aWidgetType) {
2156 case NS_THEME_DIALOG: {
2157 HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
2158 CGContextFillRect(cgContext, macRect);
2159 }
2160 break;
2162 case NS_THEME_MENUPOPUP: {
2163 HIThemeMenuDrawInfo mdi;
2164 memset(&mdi, 0, sizeof(mdi));
2165 mdi.version = 0;
2166 mdi.menuType = IsDisabled(aFrame, eventState) ?
2167 static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
2168 static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
2170 bool isLeftOfParent = false;
2171 if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
2172 mdi.menuType = kThemeMenuTypeHierarchical;
2173 }
2175 // The rounded corners draw outside the frame.
2176 CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
2177 macRect.size.width, macRect.size.height - 8);
2178 HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
2179 }
2180 break;
2182 case NS_THEME_MENUITEM: {
2183 bool isTransparent;
2184 if (thebesCtx->IsCairo()) {
2185 isTransparent = thebesCtx->OriginalSurface()->GetContentType() == gfxContentType::COLOR_ALPHA;
2186 } else {
2187 SurfaceFormat format = thebesCtx->GetDrawTarget()->GetFormat();
2188 isTransparent = (format == SurfaceFormat::R8G8B8A8) ||
2189 (format == SurfaceFormat::B8G8R8A8);
2190 }
2191 if (isTransparent) {
2192 // Clear the background to get correct transparency.
2193 CGContextClearRect(cgContext, macRect);
2194 }
2196 // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
2197 HIThemeMenuItemDrawInfo drawInfo;
2198 memset(&drawInfo, 0, sizeof(drawInfo));
2199 drawInfo.version = 0;
2200 drawInfo.itemType = kThemeMenuItemPlain;
2201 drawInfo.state = (IsDisabled(aFrame, eventState) ?
2202 static_cast<ThemeMenuState>(kThemeMenuDisabled) :
2203 CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2204 static_cast<ThemeMenuState>(kThemeMenuSelected) :
2205 static_cast<ThemeMenuState>(kThemeMenuActive));
2207 // XXX pass in the menu rect instead of always using the item rect
2208 HIRect ignored;
2209 HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
2210 }
2211 break;
2213 case NS_THEME_MENUSEPARATOR: {
2214 ThemeMenuState menuState;
2215 if (IsDisabled(aFrame, eventState)) {
2216 menuState = kThemeMenuDisabled;
2217 }
2218 else {
2219 menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2220 kThemeMenuSelected : kThemeMenuActive;
2221 }
2223 HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
2224 HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
2225 }
2226 break;
2228 case NS_THEME_TOOLTIP:
2229 CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
2230 CGContextFillRect(cgContext, macRect);
2231 break;
2233 case NS_THEME_CHECKBOX:
2234 case NS_THEME_RADIO: {
2235 bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
2236 DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
2237 eventState, aFrame);
2238 }
2239 break;
2241 case NS_THEME_BUTTON:
2242 if (IsDefaultButton(aFrame)) {
2243 if (!IsDisabled(aFrame, eventState) && FrameIsInActiveWindow(aFrame) &&
2244 !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
2245 NS_WARNING("Unable to animate button!");
2246 }
2247 DrawButton(cgContext, kThemePushButton, macRect, true,
2248 kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
2249 } else if (IsButtonTypeMenu(aFrame)) {
2250 DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2251 } else {
2252 DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame);
2253 }
2254 break;
2256 case NS_THEME_MOZ_MAC_HELP_BUTTON:
2257 DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame);
2258 break;
2260 case NS_THEME_BUTTON_BEVEL:
2261 DrawButton(cgContext, kThemeMediumBevelButton, macRect,
2262 IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
2263 eventState, aFrame);
2264 break;
2266 case NS_THEME_SPINNER: {
2267 nsIContent* content = aFrame->GetContent();
2268 if (content->IsHTML()) {
2269 // In HTML the theming for the spin buttons is drawn individually into
2270 // their own backgrounds instead of being drawn into the background of
2271 // their spinner parent as it is for XUL.
2272 break;
2273 }
2274 ThemeDrawState state = kThemeStateActive;
2275 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2276 NS_LITERAL_STRING("up"), eCaseMatters)) {
2277 state = kThemeStatePressedUp;
2278 }
2279 else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2280 NS_LITERAL_STRING("down"), eCaseMatters)) {
2281 state = kThemeStatePressedDown;
2282 }
2284 DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
2285 kThemeAdornmentNone, eventState, aFrame);
2286 }
2287 break;
2289 case NS_THEME_SPINNER_UP_BUTTON:
2290 case NS_THEME_SPINNER_DOWN_BUTTON: {
2291 nsNumberControlFrame* numberControlFrame =
2292 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2293 if (numberControlFrame) {
2294 ThemeDrawState state = kThemeStateActive;
2295 if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2296 state = kThemeStatePressedUp;
2297 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2298 state = kThemeStatePressedDown;
2299 }
2300 DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
2301 kThemeAdornmentNone, eventState, aFrame, aWidgetType);
2302 }
2303 }
2304 break;
2306 case NS_THEME_TOOLBAR_BUTTON:
2307 DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
2308 break;
2310 case NS_THEME_TOOLBAR_SEPARATOR: {
2311 HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
2312 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2313 }
2314 break;
2316 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2317 case NS_THEME_TOOLBAR: {
2318 NSWindow* win = NativeWindowForFrame(aFrame);
2319 if (ToolbarCanBeUnified(cgContext, macRect, win)) {
2320 DrawUnifiedToolbar(cgContext, macRect, win);
2321 break;
2322 }
2323 BOOL isMain = [win isMainWindow];
2324 CGRect drawRect = macRect;
2326 // top border
2327 drawRect.size.height = 1.0f;
2328 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
2330 // background
2331 drawRect.origin.y += drawRect.size.height;
2332 drawRect.size.height = macRect.size.height - 2.0f;
2333 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
2335 // bottom border
2336 drawRect.origin.y += drawRect.size.height;
2337 drawRect.size.height = 1.0f;
2338 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
2339 }
2340 break;
2342 case NS_THEME_WINDOW_TITLEBAR: {
2343 NSWindow* win = NativeWindowForFrame(aFrame);
2344 BOOL isMain = [win isMainWindow];
2345 float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ?
2346 [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height;
2347 DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain);
2348 }
2349 break;
2351 case NS_THEME_TOOLBOX: {
2352 HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
2353 HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
2354 }
2355 break;
2357 case NS_THEME_STATUSBAR:
2358 DrawStatusBar(cgContext, macRect, aFrame);
2359 break;
2361 case NS_THEME_DROPDOWN:
2362 case NS_THEME_DROPDOWN_TEXTFIELD:
2363 DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2364 break;
2366 case NS_THEME_DROPDOWN_BUTTON:
2367 DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
2368 kThemeAdornmentArrowDownArrow, eventState, aFrame);
2369 break;
2371 case NS_THEME_GROUPBOX: {
2372 HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
2373 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2374 break;
2375 }
2377 case NS_THEME_TEXTFIELD:
2378 case NS_THEME_NUMBER_INPUT:
2379 // HIThemeSetFill is not available on 10.3
2380 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2381 CGContextFillRect(cgContext, macRect);
2383 // XUL textboxes set the native appearance on the containing box, while
2384 // concrete focus is set on the html:input element within it. We can
2385 // though, check the focused attribute of xul textboxes in this case.
2386 // On Mac, focus rings are always shown for textboxes, so we do not need
2387 // to check the window's focus ring state here
2388 if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
2389 eventState |= NS_EVENT_STATE_FOCUS;
2390 }
2392 DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
2393 IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
2394 break;
2396 case NS_THEME_SEARCHFIELD:
2397 DrawSearchField(cgContext, macRect, aFrame, eventState);
2398 break;
2400 case NS_THEME_PROGRESSBAR:
2401 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2402 NS_WARNING("Unable to animate progressbar!");
2403 }
2404 DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2405 aFrame->StyleDisplay()->mOrient != NS_STYLE_ORIENT_VERTICAL,
2406 GetProgressValue(aFrame), GetProgressMaxValue(aFrame), aFrame);
2407 break;
2409 case NS_THEME_PROGRESSBAR_VERTICAL:
2410 DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2411 false, GetProgressValue(aFrame),
2412 GetProgressMaxValue(aFrame), aFrame);
2413 break;
2415 case NS_THEME_METERBAR:
2416 DrawMeter(cgContext, macRect, aFrame);
2417 break;
2419 case NS_THEME_PROGRESSBAR_CHUNK:
2420 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2421 case NS_THEME_METERBAR_CHUNK:
2422 // Do nothing: progress and meter bars cases will draw chunks.
2423 break;
2425 case NS_THEME_TREEVIEW_TWISTY:
2426 DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2427 kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
2428 break;
2430 case NS_THEME_TREEVIEW_TWISTY_OPEN:
2431 DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2432 kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
2433 break;
2435 case NS_THEME_TREEVIEW_HEADER_CELL: {
2436 TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
2437 DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
2438 sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
2439 sortDirection == eTreeSortDirection_Ascending ?
2440 kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
2441 }
2442 break;
2444 case NS_THEME_TREEVIEW_TREEITEM:
2445 case NS_THEME_TREEVIEW:
2446 // HIThemeSetFill is not available on 10.3
2447 // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
2448 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2449 CGContextFillRect(cgContext, macRect);
2450 break;
2452 case NS_THEME_TREEVIEW_HEADER:
2453 // do nothing, taken care of by individual header cells
2454 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2455 // do nothing, taken care of by treeview header
2456 case NS_THEME_TREEVIEW_LINE:
2457 // do nothing, these lines don't exist on macos
2458 break;
2460 case NS_THEME_SCALE_HORIZONTAL:
2461 case NS_THEME_SCALE_VERTICAL: {
2462 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
2463 int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
2464 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
2465 if (!maxpos)
2466 maxpos = 100;
2468 bool reverse = aFrame->GetContent()->
2469 AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
2470 NS_LITERAL_STRING("reverse"), eCaseMatters);
2471 DrawScale(cgContext, macRect, eventState,
2472 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
2473 curpos, minpos, maxpos, aFrame);
2474 }
2475 break;
2477 case NS_THEME_SCALE_THUMB_HORIZONTAL:
2478 case NS_THEME_SCALE_THUMB_VERTICAL:
2479 // do nothing, drawn by scale
2480 break;
2482 case NS_THEME_RANGE: {
2483 nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
2484 if (!rangeFrame) {
2485 break;
2486 }
2487 // DrawScale requires integer min, max and value. This is purely for
2488 // drawing, so we normalize to a range 0-1000 here.
2489 int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
2490 int32_t min = 0;
2491 int32_t max = 1000;
2492 bool isVertical = !IsRangeHorizontal(aFrame);
2493 bool reverseDir =
2494 isVertical ||
2495 rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
2496 DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
2497 value, min, max, aFrame);
2498 break;
2499 }
2501 case NS_THEME_SCROLLBAR_SMALL:
2502 case NS_THEME_SCROLLBAR:
2503 if (!nsLookAndFeel::UseOverlayScrollbars()) {
2504 DrawScrollbar(cgContext, macRect, aFrame);
2505 }
2506 break;
2507 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2508 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2509 if (nsLookAndFeel::UseOverlayScrollbars()) {
2510 BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2511 BOOL isRolledOver = CheckBooleanAttr(GetParentScrollbarFrame(aFrame),
2512 nsGkAtoms::hover);
2513 if (!nsCocoaFeatures::OnMountainLionOrLater() || !isRolledOver) {
2514 if (isHorizontal) {
2515 macRect.origin.y += 4;
2516 macRect.size.height -= 4;
2517 } else {
2518 if (aFrame->StyleVisibility()->mDirection !=
2519 NS_STYLE_DIRECTION_RTL) {
2520 macRect.origin.x += 4;
2521 }
2522 macRect.size.width -= 4;
2523 }
2524 }
2525 const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
2526 CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
2527 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
2528 @"kCUIWidgetOverlayScrollBar", @"widget",
2529 @"regular", @"size",
2530 (isRolledOver ? @"rollover" : @""), @"state",
2531 (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
2532 (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
2533 [NSNumber numberWithBool:YES], @"indiconly",
2534 [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
2535 [NSNumber numberWithBool:YES], @"is.flipped",
2536 nil],
2537 nil);
2538 }
2539 break;
2540 case NS_THEME_SCROLLBAR_BUTTON_UP:
2541 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2542 #if SCROLLBARS_VISUAL_DEBUG
2543 CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
2544 CGContextFillRect(cgContext, macRect);
2545 #endif
2546 break;
2547 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2548 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2549 #if SCROLLBARS_VISUAL_DEBUG
2550 CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
2551 CGContextFillRect(cgContext, macRect);
2552 #endif
2553 break;
2554 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2555 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2556 if (nsLookAndFeel::UseOverlayScrollbars() &&
2557 CheckBooleanAttr(GetParentScrollbarFrame(aFrame), nsGkAtoms::hover)) {
2558 BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2559 if (!nsCocoaFeatures::OnMountainLionOrLater()) {
2560 // On OSX 10.7, scrollbars don't grow when hovered. The adjustments
2561 // below were obtained by trial and error.
2562 if (isHorizontal) {
2563 macRect.origin.y += 2.0;
2564 } else {
2565 if (aFrame->StyleVisibility()->mDirection !=
2566 NS_STYLE_DIRECTION_RTL) {
2567 macRect.origin.x += 3.0;
2568 } else {
2569 macRect.origin.x -= 1.0;
2570 }
2571 }
2572 }
2573 const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
2574 CUIDraw([NSWindow coreUIRenderer], macRect, cgContext,
2575 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
2576 @"kCUIWidgetOverlayScrollBar", @"widget",
2577 @"regular", @"size",
2578 (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
2579 (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
2580 [NSNumber numberWithBool:YES], @"noindicator",
2581 [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
2582 [NSNumber numberWithBool:YES], @"is.flipped",
2583 nil],
2584 nil);
2585 }
2586 break;
2588 case NS_THEME_TEXTFIELD_MULTILINE: {
2589 // we have to draw this by hand because there is no HITheme value for it
2590 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2592 CGContextFillRect(cgContext, macRect);
2594 CGContextSetLineWidth(cgContext, 1.0);
2595 CGContextSetShouldAntialias(cgContext, false);
2597 // stroke everything but the top line of the text area
2598 CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
2599 CGContextBeginPath(cgContext);
2600 CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2601 CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
2602 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
2603 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2604 CGContextStrokePath(cgContext);
2606 // stroke the line across the top of the text area
2607 CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
2608 CGContextBeginPath(cgContext);
2609 CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2610 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2611 CGContextStrokePath(cgContext);
2613 // draw a focus ring
2614 if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
2615 // We need to bring the rectangle in by 1 pixel on each side.
2616 CGRect cgr = CGRectMake(macRect.origin.x + 1,
2617 macRect.origin.y + 1,
2618 macRect.size.width - 2,
2619 macRect.size.height - 2);
2620 HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
2621 }
2622 }
2623 break;
2625 case NS_THEME_LISTBOX: {
2626 // We have to draw this by hand because kHIThemeFrameListBox drawing
2627 // is buggy on 10.5, see bug 579259.
2628 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2629 CGContextFillRect(cgContext, macRect);
2631 // #8E8E8E for the top border, #BEBEBE for the rest.
2632 float x = macRect.origin.x, y = macRect.origin.y;
2633 float w = macRect.size.width, h = macRect.size.height;
2634 CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
2635 CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2636 CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
2637 CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2638 CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2639 CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2640 }
2641 break;
2643 case NS_THEME_TAB:
2644 DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
2645 break;
2647 case NS_THEME_TAB_PANELS:
2648 DrawTabPanel(cgContext, macRect, aFrame);
2649 break;
2651 case NS_THEME_RESIZER:
2652 DrawResizer(cgContext, macRect, aFrame);
2653 break;
2654 }
2656 nativeDrawing.EndNativeDrawing();
2658 return NS_OK;
2660 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2661 }
2663 nsIntMargin
2664 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
2665 {
2666 if (IsFrameRTL(aFrame)) {
2667 // Return a copy of aMargin w/ right & left reversed:
2668 return nsIntMargin(aMargin.top, aMargin.left,
2669 aMargin.bottom, aMargin.right);
2670 }
2672 return aMargin;
2673 }
2675 static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2676 static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2677 static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2679 NS_IMETHODIMP
2680 nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
2681 nsIFrame* aFrame,
2682 uint8_t aWidgetType,
2683 nsIntMargin* aResult)
2684 {
2685 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2687 aResult->SizeTo(0, 0, 0, 0);
2689 switch (aWidgetType) {
2690 case NS_THEME_BUTTON:
2691 {
2692 if (IsButtonTypeMenu(aFrame)) {
2693 *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2694 } else {
2695 aResult->SizeTo(1, 7, 3, 7);
2696 }
2697 break;
2698 }
2700 case NS_THEME_TOOLBAR_BUTTON:
2701 {
2702 aResult->SizeTo(1, 4, 1, 4);
2703 break;
2704 }
2706 case NS_THEME_CHECKBOX:
2707 case NS_THEME_RADIO:
2708 {
2709 // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
2710 // assume a border width of 2px.
2711 aResult->SizeTo(2, 2, 2, 2);
2712 break;
2713 }
2715 case NS_THEME_DROPDOWN:
2716 case NS_THEME_DROPDOWN_BUTTON:
2717 *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2718 break;
2720 case NS_THEME_DROPDOWN_TEXTFIELD:
2721 *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
2722 break;
2724 case NS_THEME_NUMBER_INPUT:
2725 case NS_THEME_TEXTFIELD:
2726 {
2727 SInt32 frameOutset = 0;
2728 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2730 SInt32 textPadding = 0;
2731 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2733 frameOutset += textPadding;
2735 aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2736 break;
2737 }
2739 case NS_THEME_TEXTFIELD_MULTILINE:
2740 aResult->SizeTo(1, 1, 1, 1);
2741 break;
2743 case NS_THEME_SEARCHFIELD:
2744 *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
2745 break;
2747 case NS_THEME_LISTBOX:
2748 {
2749 SInt32 frameOutset = 0;
2750 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2751 aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2752 break;
2753 }
2755 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2756 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2757 {
2758 bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2760 // On Lion and later, scrollbars have no arrows.
2761 if (!nsCocoaFeatures::OnLionOrLater()) {
2762 // There's only an endcap to worry about when both arrows are on the bottom
2763 NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2764 if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2765 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2766 if (!scrollbarFrame) return NS_ERROR_FAILURE;
2767 bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2769 // There isn't a metric for this, so just hardcode a best guess at the value.
2770 // This value is even less exact due to the fact that the endcap is partially concave.
2771 int32_t endcapSize = isSmall ? 5 : 6;
2773 if (isHorizontal)
2774 aResult->SizeTo(0, 0, 0, endcapSize);
2775 else
2776 aResult->SizeTo(endcapSize, 0, 0, 0);
2777 }
2778 }
2780 if (nsLookAndFeel::UseOverlayScrollbars()) {
2781 if (isHorizontal) {
2782 aResult->SizeTo(2, 1, 1, 1);
2783 } else {
2784 aResult->SizeTo(1, 1, 1, 2);
2785 }
2786 }
2788 break;
2789 }
2791 case NS_THEME_STATUSBAR:
2792 aResult->SizeTo(1, 0, 0, 0);
2793 break;
2794 }
2796 if (IsHiDPIContext(aContext)) {
2797 *aResult = *aResult + *aResult; // doubled
2798 }
2800 return NS_OK;
2802 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2803 }
2805 // Return false here to indicate that CSS padding values should be used. There is
2806 // no reason to make a distinction between padding and border values, just specify
2807 // whatever values you want in GetWidgetBorder and only use this to return true
2808 // if you want to override CSS padding values.
2809 bool
2810 nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2811 nsIFrame* aFrame,
2812 uint8_t aWidgetType,
2813 nsIntMargin* aResult)
2814 {
2815 // We don't want CSS padding being used for certain widgets.
2816 // See bug 381639 for an example of why.
2817 switch (aWidgetType) {
2818 case NS_THEME_BUTTON:
2819 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2820 // and have a meaningful baseline, so they can't have
2821 // author-specified padding.
2822 case NS_THEME_CHECKBOX:
2823 case NS_THEME_RADIO:
2824 aResult->SizeTo(0, 0, 0, 0);
2825 return true;
2826 }
2827 return false;
2828 }
2830 bool
2831 nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
2832 uint8_t aWidgetType, nsRect* aOverflowRect)
2833 {
2834 int32_t p2a = aContext->AppUnitsPerDevPixel();
2835 switch (aWidgetType) {
2836 case NS_THEME_BUTTON:
2837 case NS_THEME_MOZ_MAC_HELP_BUTTON:
2838 case NS_THEME_TOOLBAR_BUTTON:
2839 case NS_THEME_NUMBER_INPUT:
2840 case NS_THEME_TEXTFIELD:
2841 case NS_THEME_TEXTFIELD_MULTILINE:
2842 case NS_THEME_SEARCHFIELD:
2843 case NS_THEME_LISTBOX:
2844 case NS_THEME_DROPDOWN:
2845 case NS_THEME_DROPDOWN_BUTTON:
2846 case NS_THEME_DROPDOWN_TEXTFIELD:
2847 case NS_THEME_CHECKBOX:
2848 case NS_THEME_RADIO:
2849 case NS_THEME_TAB:
2850 {
2851 // We assume that the above widgets can draw a focus ring that will be less than
2852 // or equal to 4 pixels thick.
2853 nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH,
2854 MAX_FOCUS_RING_WIDTH,
2855 MAX_FOCUS_RING_WIDTH,
2856 MAX_FOCUS_RING_WIDTH);
2857 nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
2858 NSIntPixelsToAppUnits(extraSize.right, p2a),
2859 NSIntPixelsToAppUnits(extraSize.bottom, p2a),
2860 NSIntPixelsToAppUnits(extraSize.left, p2a));
2861 aOverflowRect->Inflate(m);
2862 return true;
2863 }
2864 case NS_THEME_PROGRESSBAR:
2865 {
2866 // Progress bars draw a 2 pixel white shadow under their progress indicators
2867 nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
2868 aOverflowRect->Inflate(m);
2869 return true;
2870 }
2871 }
2873 return false;
2874 }
2876 static const int32_t kRegularScrollbarThumbMinSize = 26;
2877 static const int32_t kSmallScrollbarThumbMinSize = 26;
2879 NS_IMETHODIMP
2880 nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext,
2881 nsIFrame* aFrame,
2882 uint8_t aWidgetType,
2883 nsIntSize* aResult,
2884 bool* aIsOverridable)
2885 {
2886 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2888 aResult->SizeTo(0,0);
2889 *aIsOverridable = true;
2891 switch (aWidgetType) {
2892 case NS_THEME_BUTTON:
2893 {
2894 aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2895 pushButtonSettings.naturalSizes[miniControlSize].height);
2896 break;
2897 }
2899 case NS_THEME_MOZ_MAC_HELP_BUTTON:
2900 {
2901 aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
2902 *aIsOverridable = false;
2903 break;
2904 }
2906 case NS_THEME_TOOLBAR_BUTTON:
2907 {
2908 aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
2909 break;
2910 }
2912 case NS_THEME_SPINNER:
2913 case NS_THEME_SPINNER_UP_BUTTON:
2914 case NS_THEME_SPINNER_DOWN_BUTTON:
2915 {
2916 SInt32 buttonHeight = 0, buttonWidth = 0;
2917 if (aFrame->GetContent()->IsXUL()) {
2918 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2919 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2920 } else {
2921 NSSize size =
2922 spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
2923 buttonWidth = size.width;
2924 buttonHeight = size.height;
2925 if (aWidgetType != NS_THEME_SPINNER) {
2926 // the buttons are half the height of the spinner
2927 buttonHeight /= 2;
2928 }
2929 }
2930 aResult->SizeTo(buttonWidth, buttonHeight);
2931 *aIsOverridable = true;
2932 break;
2933 }
2935 case NS_THEME_DROPDOWN:
2936 case NS_THEME_DROPDOWN_BUTTON:
2937 {
2938 SInt32 popupHeight = 0;
2939 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2940 aResult->SizeTo(0, popupHeight);
2941 break;
2942 }
2944 case NS_THEME_NUMBER_INPUT:
2945 case NS_THEME_TEXTFIELD:
2946 case NS_THEME_TEXTFIELD_MULTILINE:
2947 case NS_THEME_SEARCHFIELD:
2948 {
2949 // at minimum, we should be tall enough for 9pt text.
2950 // I'm using hardcoded values here because the appearance manager
2951 // values for the frame size are incorrect.
2952 aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2953 break;
2954 }
2956 case NS_THEME_WINDOW_BUTTON_BOX: {
2957 NSSize size = WindowButtonsSize(aFrame);
2958 aResult->SizeTo(size.width, size.height);
2959 *aIsOverridable = false;
2960 break;
2961 }
2963 case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON: {
2964 if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)]) {
2965 // This value is hardcoded because it's needed before we can measure the
2966 // position and size of the fullscreen button.
2967 aResult->SizeTo(16, 17);
2968 }
2969 *aIsOverridable = false;
2970 break;
2971 }
2973 case NS_THEME_PROGRESSBAR:
2974 {
2975 SInt32 barHeight = 0;
2976 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2977 aResult->SizeTo(0, barHeight);
2978 break;
2979 }
2981 case NS_THEME_TREEVIEW_TWISTY:
2982 case NS_THEME_TREEVIEW_TWISTY_OPEN:
2983 {
2984 SInt32 twistyHeight = 0, twistyWidth = 0;
2985 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2986 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2987 aResult->SizeTo(twistyWidth, twistyHeight);
2988 *aIsOverridable = false;
2989 break;
2990 }
2992 case NS_THEME_TREEVIEW_HEADER:
2993 case NS_THEME_TREEVIEW_HEADER_CELL:
2994 {
2995 SInt32 headerHeight = 0;
2996 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2997 aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
2998 break;
2999 }
3001 case NS_THEME_TAB:
3002 {
3003 aResult->SizeTo(0, tabHeights[miniControlSize]);
3004 break;
3005 }
3007 case NS_THEME_RANGE:
3008 {
3009 // The Mac Appearance Manager API (the old API we're currently using)
3010 // doesn't define constants to obtain a minimum size for sliders. We use
3011 // the "thickness" of a slider that has default dimensions for both the
3012 // minimum width and height to get something sane and so that paint
3013 // invalidation works.
3014 SInt32 size = 0;
3015 if (IsRangeHorizontal(aFrame)) {
3016 ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
3017 } else {
3018 ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
3019 }
3020 aResult->SizeTo(size, size);
3021 *aIsOverridable = true;
3022 break;
3023 }
3025 case NS_THEME_RANGE_THUMB:
3026 {
3027 SInt32 width = 0;
3028 SInt32 height = 0;
3029 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3030 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3031 aResult->SizeTo(width, height);
3032 *aIsOverridable = false;
3033 break;
3034 }
3036 case NS_THEME_SCALE_HORIZONTAL:
3037 {
3038 SInt32 scaleHeight = 0;
3039 ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
3040 aResult->SizeTo(scaleHeight, scaleHeight);
3041 *aIsOverridable = false;
3042 break;
3043 }
3045 case NS_THEME_SCALE_VERTICAL:
3046 {
3047 SInt32 scaleWidth = 0;
3048 ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
3049 aResult->SizeTo(scaleWidth, scaleWidth);
3050 *aIsOverridable = false;
3051 break;
3052 }
3054 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
3055 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
3056 {
3057 // Find our parent scrollbar frame in order to find out whether we're in
3058 // a small or a large scrollbar.
3059 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3060 if (!scrollbarFrame)
3061 return NS_ERROR_FAILURE;
3063 bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
3064 bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
3065 int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
3066 minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
3067 break;
3068 }
3070 case NS_THEME_SCROLLBAR:
3071 case NS_THEME_SCROLLBAR_SMALL:
3072 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
3073 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
3074 {
3075 *aIsOverridable = false;
3077 if (nsLookAndFeel::UseOverlayScrollbars()) {
3078 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
3079 if (scrollbarFrame &&
3080 scrollbarFrame->StyleDisplay()->mAppearance ==
3081 NS_THEME_SCROLLBAR_SMALL) {
3082 aResult->SizeTo(14, 14);
3083 }
3084 else {
3085 aResult->SizeTo(16, 16);
3086 }
3087 break;
3088 }
3090 // yeah, i know i'm cheating a little here, but i figure that it
3091 // really doesn't matter if the scrollbar is vertical or horizontal
3092 // and the width metric is a really good metric for every piece
3093 // of the scrollbar.
3095 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3096 if (!scrollbarFrame) return NS_ERROR_FAILURE;
3098 int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
3099 kThemeMetricSmallScrollBarWidth :
3100 kThemeMetricScrollBarWidth;
3101 SInt32 scrollbarWidth = 0;
3102 ::GetThemeMetric(themeMetric, &scrollbarWidth);
3103 aResult->SizeTo(scrollbarWidth, scrollbarWidth);
3104 break;
3105 }
3107 case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
3108 {
3109 int32_t themeMetric = kThemeMetricScrollBarWidth;
3111 if (aFrame) {
3112 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
3113 if (scrollbarFrame &&
3114 scrollbarFrame->StyleDisplay()->mAppearance ==
3115 NS_THEME_SCROLLBAR_SMALL) {
3116 // XXX We're interested in the width of non-disappearing scrollbars
3117 // to leave enough space for a dropmarker in non-native styled
3118 // comboboxes (bug 869314). It isn't clear to me if comboboxes can
3119 // ever have small scrollbars.
3120 themeMetric = kThemeMetricSmallScrollBarWidth;
3121 }
3122 }
3124 SInt32 scrollbarWidth = 0;
3125 ::GetThemeMetric(themeMetric, &scrollbarWidth);
3126 aResult->SizeTo(scrollbarWidth, scrollbarWidth);
3127 break;
3128 }
3130 case NS_THEME_SCROLLBAR_BUTTON_UP:
3131 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
3132 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
3133 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
3134 {
3135 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
3136 if (!scrollbarFrame) return NS_ERROR_FAILURE;
3138 // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
3139 int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
3140 kThemeMetricSmallScrollBarWidth :
3141 kThemeMetricScrollBarWidth;
3142 SInt32 scrollbarWidth = 0;
3143 ::GetThemeMetric(themeMetric, &scrollbarWidth);
3145 // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
3146 if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
3147 aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
3148 else
3149 aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
3151 *aIsOverridable = false;
3152 break;
3153 }
3154 case NS_THEME_RESIZER:
3155 {
3156 HIThemeGrowBoxDrawInfo drawInfo;
3157 drawInfo.version = 0;
3158 drawInfo.state = kThemeStateActive;
3159 drawInfo.kind = kHIThemeGrowBoxKindNormal;
3160 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3161 drawInfo.size = kHIThemeGrowBoxSizeNormal;
3162 HIPoint pnt = { 0, 0 };
3163 HIRect bounds;
3164 HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3165 aResult->SizeTo(bounds.size.width, bounds.size.height);
3166 *aIsOverridable = false;
3167 }
3168 }
3170 if (IsHiDPIContext(aContext->DeviceContext())) {
3171 *aResult = *aResult * 2;
3172 }
3174 return NS_OK;
3176 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3177 }
3179 NS_IMETHODIMP
3180 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
3181 nsIAtom* aAttribute, bool* aShouldRepaint)
3182 {
3183 // Some widget types just never change state.
3184 switch (aWidgetType) {
3185 case NS_THEME_WINDOW_TITLEBAR:
3186 case NS_THEME_TOOLBOX:
3187 case NS_THEME_TOOLBAR:
3188 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3189 case NS_THEME_STATUSBAR:
3190 case NS_THEME_STATUSBAR_PANEL:
3191 case NS_THEME_STATUSBAR_RESIZER_PANEL:
3192 case NS_THEME_TOOLTIP:
3193 case NS_THEME_TAB_PANELS:
3194 case NS_THEME_TAB_PANEL:
3195 case NS_THEME_DIALOG:
3196 case NS_THEME_MENUPOPUP:
3197 case NS_THEME_GROUPBOX:
3198 case NS_THEME_PROGRESSBAR_CHUNK:
3199 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
3200 case NS_THEME_PROGRESSBAR:
3201 case NS_THEME_PROGRESSBAR_VERTICAL:
3202 case NS_THEME_METERBAR:
3203 case NS_THEME_METERBAR_CHUNK:
3204 *aShouldRepaint = false;
3205 return NS_OK;
3206 }
3208 // XXXdwh Not sure what can really be done here. Can at least guess for
3209 // specific widgets that they're highly unlikely to have certain states.
3210 // For example, a toolbar doesn't care about any states.
3211 if (!aAttribute) {
3212 // Hover/focus/active changed. Always repaint.
3213 *aShouldRepaint = true;
3214 } else {
3215 // Check the attribute to see if it's relevant.
3216 // disabled, checked, dlgtype, default, etc.
3217 *aShouldRepaint = false;
3218 if (aAttribute == nsGkAtoms::disabled ||
3219 aAttribute == nsGkAtoms::checked ||
3220 aAttribute == nsGkAtoms::selected ||
3221 aAttribute == nsGkAtoms::menuactive ||
3222 aAttribute == nsGkAtoms::sortDirection ||
3223 aAttribute == nsGkAtoms::focused ||
3224 aAttribute == nsGkAtoms::_default ||
3225 aAttribute == nsGkAtoms::open ||
3226 aAttribute == nsGkAtoms::hover)
3227 *aShouldRepaint = true;
3228 }
3230 return NS_OK;
3231 }
3233 NS_IMETHODIMP
3234 nsNativeThemeCocoa::ThemeChanged()
3235 {
3236 // This is unimplemented because we don't care if gecko changes its theme
3237 // and Mac OS X doesn't have themes.
3238 return NS_OK;
3239 }
3241 bool
3242 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3243 uint8_t aWidgetType)
3244 {
3245 // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
3246 // render natively even if native theme support is disabled.
3247 if (aWidgetType != NS_THEME_SCROLLBAR &&
3248 aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
3249 return false;
3251 // if this is a dropdown button in a combobox the answer is always no
3252 if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
3253 nsIFrame* parentFrame = aFrame->GetParent();
3254 if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
3255 return false;
3256 }
3258 switch (aWidgetType) {
3259 case NS_THEME_LISTBOX:
3261 case NS_THEME_DIALOG:
3262 case NS_THEME_WINDOW:
3263 case NS_THEME_WINDOW_BUTTON_BOX:
3264 case NS_THEME_WINDOW_TITLEBAR:
3265 case NS_THEME_MENUPOPUP:
3266 case NS_THEME_MENUITEM:
3267 case NS_THEME_MENUSEPARATOR:
3268 case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON:
3269 case NS_THEME_TOOLTIP:
3271 case NS_THEME_CHECKBOX:
3272 case NS_THEME_CHECKBOX_CONTAINER:
3273 case NS_THEME_RADIO:
3274 case NS_THEME_RADIO_CONTAINER:
3275 case NS_THEME_GROUPBOX:
3276 case NS_THEME_MOZ_MAC_HELP_BUTTON:
3277 case NS_THEME_BUTTON:
3278 case NS_THEME_BUTTON_BEVEL:
3279 case NS_THEME_TOOLBAR_BUTTON:
3280 case NS_THEME_SPINNER:
3281 case NS_THEME_SPINNER_UP_BUTTON:
3282 case NS_THEME_SPINNER_DOWN_BUTTON:
3283 case NS_THEME_TOOLBAR:
3284 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3285 case NS_THEME_STATUSBAR:
3286 case NS_THEME_NUMBER_INPUT:
3287 case NS_THEME_TEXTFIELD:
3288 case NS_THEME_TEXTFIELD_MULTILINE:
3289 case NS_THEME_SEARCHFIELD:
3290 //case NS_THEME_TOOLBOX:
3291 //case NS_THEME_TOOLBAR_BUTTON:
3292 case NS_THEME_PROGRESSBAR:
3293 case NS_THEME_PROGRESSBAR_VERTICAL:
3294 case NS_THEME_PROGRESSBAR_CHUNK:
3295 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
3296 case NS_THEME_METERBAR:
3297 case NS_THEME_METERBAR_CHUNK:
3298 case NS_THEME_TOOLBAR_SEPARATOR:
3300 case NS_THEME_TAB_PANELS:
3301 case NS_THEME_TAB:
3303 case NS_THEME_TREEVIEW_TWISTY:
3304 case NS_THEME_TREEVIEW_TWISTY_OPEN:
3305 case NS_THEME_TREEVIEW:
3306 case NS_THEME_TREEVIEW_HEADER:
3307 case NS_THEME_TREEVIEW_HEADER_CELL:
3308 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
3309 case NS_THEME_TREEVIEW_TREEITEM:
3310 case NS_THEME_TREEVIEW_LINE:
3312 case NS_THEME_RANGE:
3314 case NS_THEME_SCALE_HORIZONTAL:
3315 case NS_THEME_SCALE_THUMB_HORIZONTAL:
3316 case NS_THEME_SCALE_VERTICAL:
3317 case NS_THEME_SCALE_THUMB_VERTICAL:
3319 case NS_THEME_SCROLLBAR:
3320 case NS_THEME_SCROLLBAR_SMALL:
3321 case NS_THEME_SCROLLBAR_BUTTON_UP:
3322 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
3323 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
3324 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
3325 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
3326 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
3327 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
3328 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
3329 case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
3331 case NS_THEME_DROPDOWN:
3332 case NS_THEME_DROPDOWN_BUTTON:
3333 case NS_THEME_DROPDOWN_TEXT:
3334 case NS_THEME_DROPDOWN_TEXTFIELD:
3335 return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
3336 break;
3338 case NS_THEME_RESIZER:
3339 {
3340 nsIFrame* parentFrame = aFrame->GetParent();
3341 if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
3342 return true;
3344 // Note that IsWidgetStyled is not called for resizers on Mac. This is
3345 // because for scrollable containers, the native resizer looks better
3346 // when (non-overlay) scrollbars are present even when the style is
3347 // overriden, and the custom transparent resizer looks better when
3348 // scrollbars are not present.
3349 nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3350 return (!nsLookAndFeel::UseOverlayScrollbars() &&
3351 scrollFrame && scrollFrame->GetScrollbarVisibility());
3352 break;
3353 }
3354 }
3356 return false;
3357 }
3359 bool
3360 nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
3361 {
3362 // flesh this out at some point
3363 switch (aWidgetType) {
3364 case NS_THEME_DROPDOWN_BUTTON:
3365 case NS_THEME_RADIO:
3366 case NS_THEME_CHECKBOX:
3367 case NS_THEME_PROGRESSBAR:
3368 case NS_THEME_METERBAR:
3369 case NS_THEME_RANGE:
3370 case NS_THEME_MOZ_MAC_HELP_BUTTON:
3371 return false;
3372 break;
3373 }
3374 return true;
3375 }
3377 bool
3378 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
3379 {
3380 if (aWidgetType == NS_THEME_DROPDOWN ||
3381 aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
3382 aWidgetType == NS_THEME_BUTTON ||
3383 aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON ||
3384 aWidgetType == NS_THEME_RADIO ||
3385 aWidgetType == NS_THEME_RANGE ||
3386 aWidgetType == NS_THEME_CHECKBOX)
3387 return true;
3389 return false;
3390 }
3392 bool
3393 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
3394 {
3395 return false;
3396 }
3398 bool
3399 nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
3400 {
3401 switch (aWidgetType) {
3402 case NS_THEME_DIALOG:
3403 case NS_THEME_GROUPBOX:
3404 case NS_THEME_TAB_PANELS:
3405 case NS_THEME_MENUPOPUP:
3406 case NS_THEME_MENUITEM:
3407 case NS_THEME_MENUSEPARATOR:
3408 case NS_THEME_TOOLTIP:
3409 case NS_THEME_SPINNER:
3410 case NS_THEME_SPINNER_UP_BUTTON:
3411 case NS_THEME_SPINNER_DOWN_BUTTON:
3412 case NS_THEME_TOOLBAR_SEPARATOR:
3413 case NS_THEME_TOOLBOX:
3414 case NS_THEME_NUMBER_INPUT:
3415 case NS_THEME_TEXTFIELD:
3416 case NS_THEME_TREEVIEW:
3417 case NS_THEME_TREEVIEW_LINE:
3418 case NS_THEME_TEXTFIELD_MULTILINE:
3419 case NS_THEME_LISTBOX:
3420 case NS_THEME_RESIZER:
3421 return false;
3422 default:
3423 return true;
3424 }
3425 }
3427 nsITheme::Transparency
3428 nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
3429 {
3430 switch (aWidgetType) {
3431 case NS_THEME_MENUPOPUP:
3432 case NS_THEME_TOOLTIP:
3433 return eTransparent;
3435 case NS_THEME_SCROLLBAR_SMALL:
3436 case NS_THEME_SCROLLBAR:
3437 return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
3439 case NS_THEME_STATUSBAR:
3440 // Knowing that scrollbars and statusbars are opaque improves
3441 // performance, because we create layers for them.
3442 return eOpaque;
3444 case NS_THEME_TOOLBAR:
3445 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
3446 return eOpaque;
3448 default:
3449 return eUnknownTransparency;
3450 }
3451 }