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 <objc/NSObjCRuntime.h>
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/Helpers.h"
11 #include "mozilla/gfx/PathHelpers.h"
12 #include "nsChildView.h"
13 #include "nsDeviceContext.h"
14 #include "nsLayoutUtils.h"
15 #include "nsObjCExceptions.h"
16 #include "nsNumberControlFrame.h"
17 #include "nsRangeFrame.h"
20 #include "nsStyleConsts.h"
21 #include "nsPresContext.h"
22 #include "nsIContent.h"
23 #include "mozilla/dom/Document.h"
26 #include "nsNameSpaceManager.h"
27 #include "nsPresContext.h"
28 #include "nsGkAtoms.h"
29 #include "nsCocoaFeatures.h"
30 #include "nsCocoaWindow.h"
31 #include "nsNativeThemeColors.h"
32 #include "nsIScrollableFrame.h"
33 #include "mozilla/ClearOnShutdown.h"
34 #include "mozilla/Range.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/HTMLMeterElement.h"
37 #include "mozilla/layers/StackingContextHelper.h"
38 #include "mozilla/StaticPrefs_layout.h"
39 #include "mozilla/StaticPrefs_widget.h"
40 #include "nsLookAndFeel.h"
41 #include "MacThemeGeometryType.h"
42 #include "VibrancyManager.h"
44 #include "gfxContext.h"
45 #include "gfxQuartzSurface.h"
46 #include "gfxQuartzNativeDrawing.h"
47 #include "gfxUtils.h" // for ToDeviceColor
50 using namespace mozilla;
51 using namespace mozilla::gfx;
52 using mozilla::dom::HTMLMeterElement;
54 #define DRAW_IN_FRAME_DEBUG 0
55 #define SCROLLBARS_VISUAL_DEBUG 0
57 // private Quartz routines needed here
59 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
60 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
61 typedef CFTypeRef CUIRendererRef;
62 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx,
63 CFDictionaryRef options, CFDictionaryRef* result);
66 // Workaround for NSCell control tint drawing
67 // Without this workaround, NSCells are always drawn with the clear control tint
68 // as long as they're not attached to an NSControl which is a subview of an
70 // XXXmstange Why doesn't Webkit need this?
71 @implementation NSCell (ControlTintWorkaround)
72 - (int)_realControlTint {
73 return [self controlTint];
77 // This is the window for our MOZCellDrawView. When an NSCell is drawn, some
78 // NSCell implementations look at the draw view's window to determine whether
79 // the cell should draw with the active look.
80 @interface MOZCellDrawWindow : NSWindow
81 @property BOOL cellsShouldLookActive;
84 @implementation MOZCellDrawWindow
86 // Override three different methods, for good measure. The NSCell implementation
87 // could call any one of them.
88 - (BOOL)_hasActiveAppearance {
89 return self.cellsShouldLookActive;
91 - (BOOL)hasKeyAppearance {
92 return self.cellsShouldLookActive;
94 - (BOOL)_hasKeyAppearance {
95 return self.cellsShouldLookActive;
100 // The purpose of this class is to provide objects that can be used when drawing
101 // NSCells using drawWithFrame:inView: without causing any harm. Only a small
102 // number of methods are called on the draw view, among those "isFlipped" and
103 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
104 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
105 // NSView) will be called when drawing search fields, and we only provide it in
106 // order to prevent "unrecognized selector" exceptions.
107 // There's no need to pass the actual NSView that we're drawing into to
108 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
109 // invalidations as soon as we draw a focusring!
110 // This class needs to be an NSControl so that NSTextFieldCell (and
111 // NSSearchFieldCell, which is a subclass of NSTextFieldCell) draws a focus
113 @interface MOZCellDrawView : NSControl
114 // Called by NSTreeHeaderCell during drawing.
115 @property BOOL _drawingEndSeparator;
118 @implementation MOZCellDrawView
124 - (NSText*)currentEditor {
130 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame,
132 if ([aCell showsFirstResponder]) {
133 CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];
134 CGContextSaveGState(cgContext);
136 // It's important to set the focus ring style before we enter the
137 // transparency layer so that the transparency layer only contains
138 // the normal button mask without the focus ring, and the conversion
139 // to the focus ring shape happens only when the transparency layer is
141 NSSetFocusRingStyle(NSFocusRingOnly);
143 // We need to draw the whole button into a transparency layer because
144 // many button types are composed of multiple parts, and if these parts
145 // were drawn while the focus ring style was active, each individual part
146 // would produce a focus ring for itself. But we only want one focus ring
147 // for the whole button. The transparency layer is a way to merge the
148 // individual button parts together before the focus ring shape is
150 CGContextBeginTransparencyLayerWithRect(cgContext,
151 NSRectToCGRect(aWithFrame), 0);
152 [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
153 CGContextEndTransparencyLayer(cgContext);
155 CGContextRestoreGState(cgContext);
159 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame,
161 [aCell drawWithFrame:aWithFrame inView:aInView];
162 DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
166 * NSProgressBarCell is used to draw progress bars of any size.
168 @interface NSProgressBarCell : NSCell {
169 /*All instance variables are private*/
172 bool mIsIndeterminate;
176 - (void)setValue:(double)value;
178 - (void)setMax:(double)max;
180 - (void)setIndeterminate:(bool)aIndeterminate;
181 - (bool)isIndeterminate;
182 - (void)setHorizontal:(bool)aIsHorizontal;
183 - (bool)isHorizontal;
184 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
187 @implementation NSProgressBarCell
189 - (void)setMax:(double)aMax {
197 - (void)setValue:(double)aValue {
205 - (void)setIndeterminate:(bool)aIndeterminate {
206 mIsIndeterminate = aIndeterminate;
209 - (bool)isIndeterminate {
210 return mIsIndeterminate;
213 - (void)setHorizontal:(bool)aIsHorizontal {
214 mIsHorizontal = aIsHorizontal;
217 - (bool)isHorizontal {
218 return mIsHorizontal;
221 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
222 CGContext* cgContext = [[NSGraphicsContext currentContext] CGContext];
224 HIThemeTrackDrawInfo tdi;
229 tdi.value = INT32_MAX * (mValue / mMax);
231 tdi.bounds = NSRectToCGRect(cellFrame);
232 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
233 tdi.enableState = [self controlTint] == NSClearControlTint
234 ? kThemeTrackInactive
237 NSControlSize size = [self controlSize];
238 if (size == NSControlSizeRegular) {
240 mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
243 size == NSControlSizeSmall,
244 "We shouldn't have another size than small and regular for the moment");
245 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
246 : kThemeMediumProgressBar;
249 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
250 int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
251 tdi.trackInfo.progress.phase = uint8_t(
252 PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
254 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
259 @interface MOZSearchFieldCell : NSSearchFieldCell
260 @property BOOL shouldUseToolbarStyle;
263 @implementation MOZSearchFieldCell
265 - (instancetype)init {
266 // We would like to render a search field which has the magnifying glass icon
267 // at the start of the search field, and no cancel button. On 10.12 and 10.13,
268 // empty search fields render the magnifying glass icon in the middle of the
269 // field. So in order to get the icon to show at the start of the field, we
270 // need to give the field some content. We achieve this with a single space
272 self = [super initTextCell:@" "];
274 // However, because the field is now non-empty, by default it shows a cancel
275 // button. To hide the cancel button, override it with a custom NSButtonCell
276 // which renders nothing.
277 NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil];
278 invisibleCell.bezeled = NO;
279 invisibleCell.bordered = NO;
280 self.cancelButtonCell = invisibleCell;
281 [invisibleCell release];
286 - (BOOL)_isToolbarMode {
287 return self.shouldUseToolbarStyle;
292 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
294 static CGFloat kMaxFocusRingWidth =
295 0; // initialized by the nsNativeThemeCocoa constructor
297 // These enums are for indexing into the margin array.
299 leopardOSorlater = 0, // 10.6 - 10.9
300 yosemiteOSorlater = 1 // 10.10+
303 enum { miniControlSize, smallControlSize, regularControlSize };
305 enum { leftMargin, topMargin, rightMargin, bottomMargin };
307 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
308 if (cocoaControlSize == NSControlSizeMini)
309 return miniControlSize;
310 else if (cocoaControlSize == NSControlSizeSmall)
311 return smallControlSize;
313 return regularControlSize;
316 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
317 if (enumControlSize == miniControlSize)
318 return NSControlSizeMini;
319 else if (enumControlSize == smallControlSize)
320 return NSControlSizeSmall;
322 return NSControlSizeRegular;
325 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
326 if (aControlSize == NSControlSizeRegular)
328 else if (aControlSize == NSControlSizeSmall)
334 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
335 const float marginSet[][3][4]) {
336 if (!marginSet) return;
338 static int osIndex = yosemiteOSorlater;
339 size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
340 const float* buttonMargins = marginSet[osIndex][controlSize];
341 rect->origin.x -= buttonMargins[leftMargin];
342 rect->origin.y -= buttonMargins[bottomMargin];
343 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
344 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
347 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
348 nsIWidget** aTopLevelWidget = NULL) {
349 if (!aFrame) return nil;
351 nsIWidget* widget = aFrame->GetNearestWidget();
352 if (!widget) return nil;
354 nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
355 if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
357 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
360 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
361 NSWindow* window = NativeWindowForFrame(aFrame);
363 // Return fallback values.
364 return NSMakeSize(54, 16);
367 NSRect buttonBox = NSZeroRect;
368 NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
370 buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
372 NSButton* minimizeButton =
373 [window standardWindowButton:NSWindowMiniaturizeButton];
374 if (minimizeButton) {
375 buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
377 NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
379 buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
381 return buttonBox.size;
384 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
385 nsIWidget* topLevelWidget = NULL;
386 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
387 if (!topLevelWidget || !win) return YES;
389 // XUL popups, e.g. the toolbar customization popup, can't become key windows,
390 // but controls in these windows should still get the active look.
391 if (topLevelWidget->GetWindowType() == widget::WindowType::Popup) {
395 return [win isKeyWindow];
397 return [win isMainWindow] && ![win attachedSheet];
400 // Toolbar controls and content controls respond to different window
401 // activeness states.
402 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
403 if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
404 return FrameIsInActiveWindow(aFrame);
407 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
409 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
410 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
412 kMaxFocusRingWidth = 7;
414 // provide a local autorelease pool, as this is called during startup
415 // before the main event-loop pool is in place
416 nsAutoreleasePool pool;
418 mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
419 [mDisclosureButtonCell setBezelStyle:NSBezelStyleRoundedDisclosure];
420 [mDisclosureButtonCell setButtonType:NSButtonTypePushOnPushOff];
421 [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
423 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
424 [mHelpButtonCell setBezelStyle:NSBezelStyleHelpButton];
425 [mHelpButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
426 [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
428 mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
429 [mPushButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
430 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
432 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
433 [mRadioButtonCell setButtonType:NSButtonTypeRadio];
435 mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
436 [mCheckboxCell setButtonType:NSButtonTypeSwitch];
437 [mCheckboxCell setAllowsMixedState:YES];
439 mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
440 [mTextFieldCell setBezeled:YES];
441 [mTextFieldCell setEditable:YES];
442 [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
444 mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
445 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
446 [mSearchFieldCell setBezeled:YES];
447 [mSearchFieldCell setEditable:YES];
448 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
450 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
452 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
453 [mComboBoxCell setBezeled:YES];
454 [mComboBoxCell setEditable:YES];
455 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
457 mProgressBarCell = [[NSProgressBarCell alloc] init];
459 mMeterBarCell = [[NSLevelIndicatorCell alloc]
460 initWithLevelIndicatorStyle:NSLevelIndicatorStyleContinuousCapacity];
462 mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
464 mCellDrawView = [[MOZCellDrawView alloc] init];
466 if (XRE_IsParentProcess()) {
467 // Put the cell draw view into a window that is never shown.
468 // This allows us to convince some NSCell implementations (such as
469 // NSButtonCell for default buttons) to draw with the active appearance.
470 // Another benefit of putting the draw view in a window is the fact that it
471 // lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit the
472 // current NSApplication effectiveAppearance automatically, so the field
473 // adapts to Dark Mode correctly. We don't create this window when the
474 // native theme is used in the content process because NSWindow creation
475 // runs into the sandbox and because we never run default buttons in content
477 mCellDrawWindow = [[MOZCellDrawWindow alloc]
478 initWithContentRect:NSZeroRect
479 styleMask:NSWindowStyleMaskBorderless
480 backing:NSBackingStoreBuffered
482 [mCellDrawWindow.contentView addSubview:mCellDrawView];
485 NS_OBJC_END_TRY_IGNORE_BLOCK;
488 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
489 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
491 [mMeterBarCell release];
492 [mProgressBarCell release];
493 [mDisclosureButtonCell release];
494 [mHelpButtonCell release];
495 [mPushButtonCell release];
496 [mRadioButtonCell release];
497 [mCheckboxCell release];
498 [mTextFieldCell release];
499 [mSearchFieldCell release];
500 [mDropdownCell release];
501 [mComboBoxCell release];
502 [mTreeHeaderCell release];
503 [mCellDrawWindow release];
504 [mCellDrawView release];
506 NS_OBJC_END_TRY_IGNORE_BLOCK;
509 // Limit on the area of the target rect (in pixels^2) in
510 // DrawCellWithScaling() and DrawButton() and above which we
511 // don't draw the object into a bitmap buffer. This is to avoid crashes in
512 // [NSGraphicsContext graphicsContextWithCGContext:flipped:] and
513 // CGContextDrawImage(), and also to avoid very poor drawing performance in
514 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
515 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
516 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
517 // different amounts of RAM.
518 #define BITMAP_MAX_AREA 500000
520 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
521 CGAffineTransform ctm =
522 CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
523 CGRect transformedUserSpacePixel =
524 CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
525 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
526 fabs(transformedUserSpacePixel.size.height));
527 return maxScale > 1.0 ? 2 : 1;
531 * Draw the given NSCell into the given cgContext.
533 * destRect - the size and position of the resulting control rectangle
534 * controlSize - the NSControlSize which will be given to the NSCell before
535 * asking it to render
536 * naturalSize - The natural dimensions of this control.
537 * If the control rect size is not equal to either of these, a scale
538 * will be applied to the context so that rendering the control at the
539 * natural size will result in it filling the destRect space.
540 * If a control has no natural dimensions in either/both axes, pass 0.0f.
541 * minimumSize - The minimum dimensions of this control.
542 * If the control rect size is less than the minimum for a given axis,
543 * a scale will be applied to the context so that the minimum is used
544 * for drawing. If a control has no minimum dimensions in either/both
546 * marginSet - an array of margins; a multidimensional array of [2][3][4],
547 * with the first dimension being the OS version (Tiger or Leopard),
548 * the second being the control size (mini, small, regular), and the third
549 * being the 4 margin values (left, top, right, bottom).
550 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
551 * matter if this is really the right view; it just has to return YES when
552 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
553 * mirrorHorizontal - whether to mirror the cell horizontally
555 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext,
556 const HIRect& destRect,
557 NSControlSize controlSize, NSSize naturalSize,
559 const float marginSet[][3][4], NSView* view,
560 BOOL mirrorHorizontal) {
561 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
563 NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y,
564 destRect.size.width, destRect.size.height);
566 if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
567 if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
569 // Keep aspect ratio when scaling if one dimension is free.
570 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
571 drawRect.size.width =
572 destRect.size.width * naturalSize.height / destRect.size.height;
573 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
574 drawRect.size.height =
575 destRect.size.height * naturalSize.width / destRect.size.width;
577 // Honor minimum sizes.
578 if (drawRect.size.width < minimumSize.width)
579 drawRect.size.width = minimumSize.width;
580 if (drawRect.size.height < minimumSize.height)
581 drawRect.size.height = minimumSize.height;
583 [NSGraphicsContext saveGraphicsState];
585 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
586 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
587 // Inflate the rect Gecko gave us by the margin for the control.
588 InflateControlRect(&drawRect, controlSize, marginSet);
590 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
592 setCurrentContext:[NSGraphicsContext
593 graphicsContextWithCGContext:cgContext
596 DrawCellIncludingFocusRing(cell, drawRect, view);
598 [NSGraphicsContext setCurrentContext:savedContext];
600 float w = ceil(drawRect.size.width);
601 float h = ceil(drawRect.size.height);
602 NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
604 // inflate to figure out the frame we need to tell NSCell to draw in, to get
605 // something that's 0,0,w,h
606 InflateControlRect(&tmpRect, controlSize, marginSet);
608 // and then, expand by kMaxFocusRingWidth size to make sure we can capture
610 w += kMaxFocusRingWidth * 2.0;
611 h += kMaxFocusRingWidth * 2.0;
613 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
614 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
615 CGContextRef ctx = CGBitmapContextCreate(
616 NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
617 (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
618 CGColorSpaceRelease(rgb);
620 // We need to flip the image twice in order to avoid drawing bugs on 10.4,
621 // see bug 465069. This is the first flip transform, applied to cgContext.
622 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
623 CGContextTranslateCTM(cgContext, 0.0f,
624 -(2.0 * destRect.origin.y + destRect.size.height));
625 if (mirrorHorizontal) {
626 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
627 CGContextTranslateCTM(
628 cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
631 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
633 setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
636 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
638 // Set the context's "base transform" to in order to get correctly-sized
640 CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor,
641 backingScaleFactor));
643 // This is the second flip transform, applied to ctx.
644 CGContextScaleCTM(ctx, 1.0f, -1.0f);
645 CGContextTranslateCTM(ctx, 0.0f,
646 -(2.0 * tmpRect.origin.y + tmpRect.size.height));
648 DrawCellIncludingFocusRing(cell, tmpRect, view);
650 [NSGraphicsContext setCurrentContext:savedContext];
652 CGImageRef img = CGBitmapContextCreateImage(ctx);
654 // Drop the image into the original destination rectangle, scaling to fit
655 // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
656 // doesn't extend beyond the overflow rect
657 float xscale = destRect.size.width / drawRect.size.width;
658 float yscale = destRect.size.height / drawRect.size.height;
659 float scaledFocusRingX =
660 xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
661 float scaledFocusRingY =
662 yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
663 CGContextDrawImage(cgContext,
664 CGRectMake(destRect.origin.x - scaledFocusRingX,
665 destRect.origin.y - scaledFocusRingY,
666 destRect.size.width + scaledFocusRingX * 2,
667 destRect.size.height + scaledFocusRingY * 2),
671 CGContextRelease(ctx);
674 [NSGraphicsContext restoreGraphicsState];
676 #if DRAW_IN_FRAME_DEBUG
677 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
678 CGContextFillRect(cgContext, destRect);
681 NS_OBJC_END_TRY_IGNORE_BLOCK;
684 struct CellRenderSettings {
685 // The natural dimensions of the control.
686 // If a control has no natural dimensions in either/both axes, set to 0.0f.
687 NSSize naturalSizes[3];
689 // The minimum dimensions of the control.
690 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
691 NSSize minimumSizes[3];
693 // A three-dimensional array,
694 // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and
695 // above), the second being the control size (mini, small, regular), and the
696 // third being the 4 margin values (left, top, right, bottom).
697 float margins[2][3][4];
701 * This is a helper method that returns the required NSControlSize given a size
702 * and the size of the three controls plus a tolerance.
703 * size - The width or the height of the element to draw.
704 * sizes - An array with the all the width/height of the element for its
706 * tolerance - The tolerance as passed to DrawCellWithSnapping.
707 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
709 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes,
711 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
718 for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
725 // If it's the latest value, we pick it.
727 return CocoaSizeForEnum(i);
730 if (size <= sizes[i] + tolerance && size < next) {
731 return CocoaSizeForEnum(i);
735 // If we are here, that means sizes[] was an array with only empty values
736 // or the algorithm above is wrong.
737 // The former can happen but the later would be wrong.
738 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
739 "We found no control! We shouldn't be there!");
740 return CocoaSizeForEnum(regularControlSize);
744 * Draw the given NSCell into the given cgContext with a nice control size.
746 * This function is similar to DrawCellWithScaling, but it decides what
747 * control size to use based on the destRect's size.
748 * Scaling is only applied when the difference between the destRect's size
749 * and the next smaller natural size is greater than snapTolerance. Otherwise
750 * it snaps to the next smaller control size without scaling because unscaled
751 * controls look nicer.
753 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext,
754 const HIRect& destRect,
755 const CellRenderSettings settings,
756 float verticalAlignFactor, NSView* view,
757 BOOL mirrorHorizontal,
758 float snapTolerance = 2.0f) {
759 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
761 const float rectWidth = destRect.size.width,
762 rectHeight = destRect.size.height;
763 const NSSize* sizes = settings.naturalSizes;
764 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
765 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
766 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
768 HIRect drawRect = destRect;
770 CGFloat controlWidths[3] = {miniSize.width, smallSize.width,
772 NSControlSize controlSizeX =
773 FindControlSize(rectWidth, controlWidths, snapTolerance);
774 CGFloat controlHeights[3] = {miniSize.height, smallSize.height,
776 NSControlSize controlSizeY =
777 FindControlSize(rectHeight, controlHeights, snapTolerance);
779 NSControlSize controlSize = NSControlSizeRegular;
780 size_t sizeIndex = 0;
782 // At some sizes, don't scale but snap.
783 const NSControlSize smallerControlSize =
784 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY)
787 const size_t smallerControlSizeIndex =
788 EnumSizeForCocoaSize(smallerControlSize);
789 const NSSize size = sizes[smallerControlSizeIndex];
790 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
791 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
792 if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
793 diffHeight <= snapTolerance) {
794 // Snap to the smaller control size.
795 controlSize = smallerControlSize;
796 sizeIndex = smallerControlSizeIndex;
797 MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
799 // Resize and center the drawRect.
800 if (sizes[sizeIndex].width) {
802 ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
803 drawRect.size.width = sizes[sizeIndex].width;
805 if (sizes[sizeIndex].height) {
807 floor((destRect.size.height - sizes[sizeIndex].height) *
808 verticalAlignFactor);
809 drawRect.size.height = sizes[sizeIndex].height;
812 // Use the larger control size.
814 EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
817 sizeIndex = EnumSizeForCocoaSize(controlSize);
820 [cell setControlSize:controlSize];
822 MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
823 const NSSize minimumSize = settings.minimumSizes[sizeIndex];
824 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
825 minimumSize, settings.margins, view, mirrorHorizontal);
827 NS_OBJC_END_TRY_IGNORE_BLOCK;
830 @interface NSWindow (CoreUIRendererPrivate)
831 + (CUIRendererRef)coreUIRenderer;
834 @interface NSObject (NSAppearanceCoreUIRendering)
835 - (void)_drawInRect:(CGRect)rect
836 context:(CGContextRef)cgContext
840 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext,
841 NSDictionary* aOptions,
842 bool aSkipAreaCheck = false) {
843 if (!aSkipAreaCheck &&
844 aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
848 NSAppearance* appearance = NSAppearance.currentAppearance;
850 [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
851 // Render through NSAppearance on Mac OS 10.10 and up. This will call
852 // CUIDraw with a CoreUI renderer that will give us the correct 10.10
853 // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
854 // renders 10.9-style widgets on 10.10.
855 [appearance _drawInRect:aRect context:cgContext options:aOptions];
858 CUIRendererRef renderer =
859 [NSWindow respondsToSelector:@selector(coreUIRenderer)]
860 ? [NSWindow coreUIRenderer]
862 CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
866 static float VerticalAlignFactor(nsIFrame* aFrame) {
867 if (!aFrame) return 0.5f; // default: center
869 const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
870 auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
872 case StyleVerticalAlignKeyword::Top:
873 case StyleVerticalAlignKeyword::TextTop:
876 case StyleVerticalAlignKeyword::Sub:
877 case StyleVerticalAlignKeyword::Super:
878 case StyleVerticalAlignKeyword::Middle:
879 case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
882 case StyleVerticalAlignKeyword::Baseline:
883 case StyleVerticalAlignKeyword::Bottom:
884 case StyleVerticalAlignKeyword::TextBottom:
888 MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
893 static void ApplyControlParamsToNSCell(
894 nsNativeThemeCocoa::ControlParams aControlParams, NSCell* aCell) {
895 [aCell setEnabled:!aControlParams.disabled];
896 [aCell setShowsFirstResponder:(aControlParams.focused &&
897 !aControlParams.disabled &&
898 aControlParams.insideActiveWindow)];
899 [aCell setHighlighted:aControlParams.pressed];
902 // These are the sizes that Gecko needs to request to draw if it wants
903 // to get a standard-sized Aqua radio button drawn. Note that the rects
904 // that draw these are actually a little bigger.
905 static const CellRenderSettings radioSettings = {
907 NSMakeSize(11, 11), // mini
908 NSMakeSize(13, 13), // small
909 NSMakeSize(16, 16) // regular
911 {NSZeroSize, NSZeroSize, NSZeroSize},
914 {0, 0, 0, 0}, // mini
915 {0, 1, 1, 1}, // small
916 {0, 0, 0, 0} // regular
920 {0, 0, 0, 0}, // mini
921 {1, 1, 1, 2}, // small
922 {0, 0, 0, 0} // regular
925 static const CellRenderSettings checkboxSettings = {
927 NSMakeSize(11, 11), // mini
928 NSMakeSize(13, 13), // small
929 NSMakeSize(16, 16) // regular
931 {NSZeroSize, NSZeroSize, NSZeroSize},
934 {0, 1, 0, 0}, // mini
935 {0, 1, 0, 1}, // small
936 {0, 1, 0, 1} // regular
940 {0, 1, 0, 0}, // mini
941 {0, 1, 0, 1}, // small
942 {0, 1, 0, 1} // regular
945 static NSControlStateValue CellStateForCheckboxOrRadioState(
946 nsNativeThemeCocoa::CheckboxOrRadioState aState) {
948 case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
949 return NSControlStateValueOff;
950 case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
951 return NSControlStateValueOn;
952 case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
953 return NSControlStateValueMixed;
957 void nsNativeThemeCocoa::DrawCheckboxOrRadio(
958 CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
959 const CheckboxOrRadioParams& aParams) {
960 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
962 NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
963 ApplyControlParamsToNSCell(aParams.controlParams, cell);
965 [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
966 [cell setControlTint:(aParams.controlParams.insideActiveWindow
967 ? [NSColor currentControlTint]
968 : NSClearControlTint)];
970 // Ensure that the control is square.
971 float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
972 HIRect drawRect = CGRectMake(
973 inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
974 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
977 if (mCellDrawWindow) {
978 mCellDrawWindow.cellsShouldLookActive =
979 aParams.controlParams.insideActiveWindow;
981 DrawCellWithSnapping(cell, cgContext, drawRect,
982 inCheckbox ? checkboxSettings : radioSettings,
983 aParams.verticalAlignFactor, mCellDrawView, NO);
985 NS_OBJC_END_TRY_IGNORE_BLOCK;
988 static const CellRenderSettings searchFieldSettings = {
990 NSMakeSize(0, 16), // mini
991 NSMakeSize(0, 19), // small
992 NSMakeSize(0, 22) // regular
995 NSMakeSize(32, 0), // mini
996 NSMakeSize(38, 0), // small
997 NSMakeSize(44, 0) // regular
1001 {0, 0, 0, 0}, // mini
1002 {0, 0, 0, 0}, // small
1003 {0, 0, 0, 0} // regular
1007 {0, 0, 0, 0}, // mini
1008 {0, 0, 0, 0}, // small
1009 {0, 0, 0, 0} // regular
1012 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
1013 nsIContent* content = aFrame->GetContent();
1018 if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox,
1019 nsGkAtoms::statusbar)) {
1023 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1024 case StyleAppearance::Toolbar:
1025 case StyleAppearance::Statusbar:
1032 static bool IsInsideToolbar(nsIFrame* aFrame) {
1033 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
1034 if (IsToolbarStyleContainer(frame)) {
1041 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
1042 nsIFrame* aFrame, ElementState aEventState) {
1043 TextFieldParams params;
1044 params.insideToolbar = IsInsideToolbar(aFrame);
1045 params.disabled = aEventState.HasState(ElementState::DISABLED);
1047 // See ShouldUnconditionallyDrawFocusRingIfFocused.
1048 params.focused = aEventState.HasState(ElementState::FOCUS);
1050 params.rtl = IsFrameRTL(aFrame);
1051 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1055 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext,
1056 const HIRect& inBoxRect,
1057 const TextFieldParams& aParams) {
1058 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1060 NSTextFieldCell* cell = mTextFieldCell;
1061 [cell setEnabled:!aParams.disabled];
1062 [cell setShowsFirstResponder:aParams.focused];
1064 if (mCellDrawWindow) {
1065 mCellDrawWindow.cellsShouldLookActive =
1066 YES; // TODO: propagate correct activeness state
1068 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
1069 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1071 NS_OBJC_END_TRY_IGNORE_BLOCK;
1074 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext,
1075 const HIRect& inBoxRect,
1076 const TextFieldParams& aParams) {
1077 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1079 mSearchFieldCell.enabled = !aParams.disabled;
1080 mSearchFieldCell.showsFirstResponder = aParams.focused;
1081 mSearchFieldCell.placeholderString = @"";
1082 mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1084 if (mCellDrawWindow) {
1085 mCellDrawWindow.cellsShouldLookActive =
1086 YES; // TODO: propagate correct activeness state
1088 DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect,
1089 searchFieldSettings, aParams.verticalAlignFactor,
1090 mCellDrawView, aParams.rtl);
1092 NS_OBJC_END_TRY_IGNORE_BLOCK;
1095 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1096 // Mac always draws focus rings for textboxes and lists.
1097 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1098 case StyleAppearance::NumberInput:
1099 case StyleAppearance::Textfield:
1100 case StyleAppearance::Textarea:
1101 case StyleAppearance::Searchfield:
1102 case StyleAppearance::Listbox:
1109 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1110 nsIFrame* aFrame, ElementState aEventState) {
1111 ControlParams params;
1112 params.disabled = aEventState.HasState(ElementState::DISABLED);
1113 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1115 aEventState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER);
1116 params.focused = aEventState.HasState(ElementState::FOCUS) &&
1117 (aEventState.HasState(ElementState::FOCUSRING) ||
1118 ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1119 params.rtl = IsFrameRTL(aFrame);
1123 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1124 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1126 static const CellRenderSettings pushButtonSettings = {
1128 NSMakeSize(0, 16), // mini
1129 NSMakeSize(0, 19), // small
1130 NSMakeSize(0, 22) // regular
1133 NSMakeSize(18, 0), // mini
1134 NSMakeSize(26, 0), // small
1135 NSMakeSize(30, 0) // regular
1139 {0, 0, 0, 0}, // mini
1140 {4, 0, 4, 1}, // small
1141 {5, 0, 5, 2} // regular
1145 {0, 0, 0, 0}, // mini
1146 {4, 0, 4, 1}, // small
1147 {5, 0, 5, 2} // regular
1150 // The height at which we start doing square buttons instead of rounded buttons
1151 // Rounded buttons look bad if drawn at a height greater than 26, so at that
1152 // point we switch over to doing square buttons which looks fine at any size.
1153 #define DO_SQUARE_BUTTON_HEIGHT 26
1155 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext,
1156 const HIRect& inBoxRect,
1157 ButtonType aButtonType,
1158 ControlParams aControlParams) {
1159 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1161 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1162 [mPushButtonCell setBezelStyle:NSBezelStyleRounded];
1163 mPushButtonCell.keyEquivalent =
1164 aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1166 if (mCellDrawWindow) {
1167 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1169 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect,
1170 pushButtonSettings, 0.5f, mCellDrawView,
1171 aControlParams.rtl, 1.0f);
1173 NS_OBJC_END_TRY_IGNORE_BLOCK;
1176 void nsNativeThemeCocoa::DrawSquareBezelPushButton(
1177 CGContextRef cgContext, const HIRect& inBoxRect,
1178 ControlParams aControlParams) {
1179 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1181 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1182 [mPushButtonCell setBezelStyle:NSBezelStyleShadowlessSquare];
1184 if (mCellDrawWindow) {
1185 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1187 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect,
1188 NSControlSizeRegular, NSZeroSize, NSMakeSize(14, 0), NULL,
1189 mCellDrawView, aControlParams.rtl);
1191 NS_OBJC_END_TRY_IGNORE_BLOCK;
1194 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext,
1195 const HIRect& inBoxRect,
1196 ControlParams aControlParams) {
1197 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1199 ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1201 if (mCellDrawWindow) {
1202 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1204 DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect,
1205 NSControlSizeRegular, NSZeroSize, kHelpButtonSize, NULL,
1207 false); // Don't mirror icon in RTL.
1209 NS_OBJC_END_TRY_IGNORE_BLOCK;
1212 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext,
1213 const HIRect& inBoxRect,
1214 ControlParams aControlParams,
1215 NSControlStateValue aCellState) {
1216 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1218 ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1219 [mDisclosureButtonCell setState:aCellState];
1221 if (mCellDrawWindow) {
1222 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1224 DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect,
1225 NSControlSizeRegular, NSZeroSize, kDisclosureButtonSize,
1226 NULL, mCellDrawView,
1227 false); // Don't mirror icon in RTL.
1229 NS_OBJC_END_TRY_IGNORE_BLOCK;
1232 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext,
1233 const HIRect& aRenderRect,
1236 static void RenderTransformedHIThemeControl(CGContextRef aCGContext,
1237 const HIRect& aRect,
1238 RenderHIThemeControlFunction aFunc,
1240 BOOL mirrorHorizontally = NO) {
1241 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1242 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1245 HIRect drawRect = aRect;
1246 drawRect.origin = CGPointZero;
1248 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1249 savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1255 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1257 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1258 aFunc(aCGContext, drawRect, aData);
1260 // Inflate the buffer to capture focus rings.
1261 int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1262 int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1264 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1265 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1266 CGContextRef bitmapctx = CGBitmapContextCreate(
1267 NULL, w * backingScaleFactor, h * backingScaleFactor, 8,
1268 w * backingScaleFactor * 4, colorSpace,
1269 kCGImageAlphaPremultipliedFirst);
1270 CGColorSpaceRelease(colorSpace);
1272 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1273 CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1275 // Set the context's "base transform" to in order to get correctly-sized
1277 CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(
1278 backingScaleFactor, backingScaleFactor));
1280 // HITheme always wants to draw into a flipped context, or things
1282 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1283 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1285 aFunc(bitmapctx, drawRect, aData);
1287 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1289 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1291 // We need to unflip, so that we can do a DrawImage without getting a
1293 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1294 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1296 if (mirrorHorizontally) {
1297 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1298 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1301 HIRect inflatedDrawRect =
1302 CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1303 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1305 CGContextSetCTM(aCGContext, ctm);
1307 CGImageRelease(bitmap);
1308 CGContextRelease(bitmapctx);
1311 CGContextSetCTM(aCGContext, savedCTM);
1314 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect,
1316 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1317 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal,
1321 static ThemeDrawState ToThemeDrawState(
1322 const nsNativeThemeCocoa::ControlParams& aParams) {
1323 if (aParams.disabled) {
1324 return kThemeStateUnavailable;
1326 if (aParams.pressed) {
1327 return kThemeStatePressed;
1329 return kThemeStateActive;
1332 void nsNativeThemeCocoa::DrawHIThemeButton(
1333 CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
1334 ThemeButtonValue aValue, ThemeDrawState aState,
1335 ThemeButtonAdornment aAdornment, const ControlParams& aParams) {
1336 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1338 HIThemeButtonDrawInfo bdi;
1343 bdi.adornment = aAdornment;
1345 if (aParams.focused && aParams.insideActiveWindow) {
1346 bdi.adornment |= kThemeAdornmentFocus;
1349 RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi,
1352 #if DRAW_IN_FRAME_DEBUG
1353 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1354 CGContextFillRect(cgContext, inBoxRect);
1357 NS_OBJC_END_TRY_IGNORE_BLOCK;
1360 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext,
1361 const HIRect& inBoxRect,
1362 const ButtonParams& aParams) {
1363 ControlParams controlParams = aParams.controlParams;
1365 switch (aParams.button) {
1366 case ButtonType::eRegularPushButton:
1367 case ButtonType::eDefaultPushButton:
1368 DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1370 case ButtonType::eSquareBezelPushButton:
1371 DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1373 case ButtonType::eArrowButton:
1374 DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1375 kThemeStateUnavailable, kThemeAdornmentArrowDownArrow,
1378 case ButtonType::eHelpButton:
1379 DrawHelpButton(cgContext, inBoxRect, controlParams);
1381 case ButtonType::eTreeTwistyPointingRight:
1382 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1383 kThemeDisclosureRight, ToThemeDrawState(controlParams),
1384 kThemeAdornmentNone, controlParams);
1386 case ButtonType::eTreeTwistyPointingDown:
1387 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1388 kThemeDisclosureDown, ToThemeDrawState(controlParams),
1389 kThemeAdornmentNone, controlParams);
1391 case ButtonType::eDisclosureButtonClosed:
1392 DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1393 NSControlStateValueOff);
1395 case ButtonType::eDisclosureButtonOpen:
1396 DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1397 NSControlStateValueOn);
1402 nsNativeThemeCocoa::TreeHeaderCellParams
1403 nsNativeThemeCocoa::ComputeTreeHeaderCellParams(nsIFrame* aFrame,
1404 ElementState aEventState) {
1405 TreeHeaderCellParams params;
1406 params.controlParams = ComputeControlParams(aFrame, aEventState);
1407 params.sortDirection = GetTreeSortDirection(aFrame);
1408 params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1412 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1413 // This method has been present in the same form since at least macOS 10.4.
1414 - (void)_setSortable:(BOOL)arg1
1415 showSortIndicator:(BOOL)arg2
1416 ascending:(BOOL)arg3
1417 priority:(NSInteger)arg4
1418 highlightForSort:(BOOL)arg5;
1421 void nsNativeThemeCocoa::DrawTreeHeaderCell(
1422 CGContextRef cgContext, const HIRect& inBoxRect,
1423 const TreeHeaderCellParams& aParams) {
1424 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1426 // Without clearing the cell's title, it takes on a default value of "Field",
1427 // which is displayed underneath the title set in the front-end.
1428 NSCell* cell = (NSCell*)mTreeHeaderCell;
1431 if ([mTreeHeaderCell
1432 respondsToSelector:@selector
1434 showSortIndicator:ascending:priority:highlightForSort:)]) {
1435 switch (aParams.sortDirection) {
1436 case eTreeSortDirection_Ascending:
1437 [mTreeHeaderCell _setSortable:YES
1438 showSortIndicator:YES
1441 highlightForSort:YES];
1443 case eTreeSortDirection_Descending:
1444 [mTreeHeaderCell _setSortable:YES
1445 showSortIndicator:YES
1448 highlightForSort:YES];
1451 // eTreeSortDirection_Natural
1452 [mTreeHeaderCell _setSortable:YES
1453 showSortIndicator:NO
1456 highlightForSort:NO];
1461 mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1462 mTreeHeaderCell.state =
1463 (mTreeHeaderCell.enabled && aParams.controlParams.pressed)
1464 ? NSControlStateValueOn
1465 : NSControlStateValueOff;
1467 mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1469 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1470 NSGraphicsContext.currentContext =
1471 [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
1472 DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1473 NSGraphicsContext.currentContext = savedContext;
1475 #if DRAW_IN_FRAME_DEBUG
1476 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1477 CGContextFillRect(cgContext, inBoxRect);
1480 NS_OBJC_END_TRY_IGNORE_BLOCK;
1483 static const CellRenderSettings dropdownSettings = {
1485 NSMakeSize(0, 16), // mini
1486 NSMakeSize(0, 19), // small
1487 NSMakeSize(0, 22) // regular
1490 NSMakeSize(18, 0), // mini
1491 NSMakeSize(38, 0), // small
1492 NSMakeSize(44, 0) // regular
1496 {1, 1, 2, 1}, // mini
1497 {3, 0, 3, 1}, // small
1498 {3, 0, 3, 0} // regular
1502 {1, 1, 2, 1}, // mini
1503 {3, 0, 3, 1}, // small
1504 {3, 0, 3, 0} // regular
1507 static const CellRenderSettings editableMenulistSettings = {
1509 NSMakeSize(0, 15), // mini
1510 NSMakeSize(0, 18), // small
1511 NSMakeSize(0, 21) // regular
1514 NSMakeSize(18, 0), // mini
1515 NSMakeSize(38, 0), // small
1516 NSMakeSize(44, 0) // regular
1520 {0, 0, 2, 2}, // mini
1521 {0, 0, 3, 2}, // small
1522 {0, 1, 3, 3} // regular
1526 {0, 0, 2, 2}, // mini
1527 {0, 0, 3, 2}, // small
1528 {0, 1, 3, 3} // regular
1531 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext,
1532 const HIRect& inBoxRect,
1533 const DropdownParams& aParams) {
1534 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1536 [mDropdownCell setPullsDown:aParams.pullsDown];
1538 aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1540 ApplyControlParamsToNSCell(aParams.controlParams, cell);
1542 if (aParams.controlParams.insideActiveWindow) {
1543 [cell setControlTint:[NSColor currentControlTint]];
1545 [cell setControlTint:NSClearControlTint];
1548 const CellRenderSettings& settings =
1549 aParams.editable ? editableMenulistSettings : dropdownSettings;
1551 if (mCellDrawWindow) {
1552 mCellDrawWindow.cellsShouldLookActive =
1553 aParams.controlParams.insideActiveWindow;
1555 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f,
1556 mCellDrawView, aParams.controlParams.rtl);
1558 NS_OBJC_END_TRY_IGNORE_BLOCK;
1561 static const CellRenderSettings spinnerSettings = {
1564 16), // mini (width trimmed by 2px to reduce blank border)
1565 NSMakeSize(15, 22), // small
1566 NSMakeSize(19, 27) // regular
1570 16), // mini (width trimmed by 2px to reduce blank border)
1571 NSMakeSize(15, 22), // small
1572 NSMakeSize(19, 27) // regular
1576 {0, 0, 0, 0}, // mini
1577 {0, 0, 0, 0}, // small
1578 {0, 0, 0, 0} // regular
1582 {0, 0, 0, 0}, // mini
1583 {0, 0, 0, 0}, // small
1584 {0, 0, 0, 0} // regular
1587 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(
1588 ThemeButtonKind aKind, const SpinButtonParams& aParams) {
1589 HIThemeButtonDrawInfo bdi;
1592 bdi.value = kThemeButtonOff;
1593 bdi.adornment = kThemeAdornmentNone;
1595 if (aParams.disabled) {
1596 bdi.state = kThemeStateUnavailable;
1597 } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1598 if (*aParams.pressedButton == SpinButton::eUp) {
1599 bdi.state = kThemeStatePressedUp;
1601 bdi.state = kThemeStatePressedDown;
1604 bdi.state = kThemeStateActive;
1610 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext,
1611 const HIRect& inBoxRect,
1612 const SpinButtonParams& aParams) {
1613 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1615 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1616 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1618 NS_OBJC_END_TRY_IGNORE_BLOCK;
1621 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1622 const HIRect& inBoxRect,
1623 SpinButton aDrawnButton,
1624 const SpinButtonParams& aParams) {
1625 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1627 HIThemeButtonDrawInfo bdi =
1628 SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1630 // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1631 // together as a single unit (presumably because when one button is active,
1632 // the appearance of both changes (in different ways)). Here we have to paint
1633 // both buttons, using clip to hide the one we don't want to paint.
1634 HIRect drawRect = inBoxRect;
1635 drawRect.size.height *= 2;
1636 if (aDrawnButton == SpinButton::eDown) {
1637 drawRect.origin.y -= inBoxRect.size.height;
1640 // Shift the drawing a little to the left, since cocoa paints with more
1641 // blank space around the visual buttons than we'd like:
1642 drawRect.origin.x -= 1;
1644 CGContextSaveGState(cgContext);
1645 CGContextClipToRect(cgContext, inBoxRect);
1647 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1649 CGContextRestoreGState(cgContext);
1651 NS_OBJC_END_TRY_IGNORE_BLOCK;
1654 static const CellRenderSettings progressSettings[2][2] = {
1655 // Vertical progress bar.
1656 {// Determined settings.
1659 NSMakeSize(10, 0), // small
1660 NSMakeSize(16, 0) // regular
1662 {NSZeroSize, NSZeroSize, NSZeroSize},
1665 {0, 0, 0, 0}, // mini
1666 {1, 1, 1, 1}, // small
1667 {1, 1, 1, 1} // regular
1669 // There is no horizontal margin in regular undetermined size.
1672 NSMakeSize(10, 0), // small
1673 NSMakeSize(16, 0) // regular
1675 {NSZeroSize, NSZeroSize, NSZeroSize},
1678 {0, 0, 0, 0}, // mini
1679 {1, 1, 1, 1}, // small
1680 {1, 0, 1, 0} // regular
1684 {0, 0, 0, 0}, // mini
1685 {1, 1, 1, 1}, // small
1686 {1, 0, 1, 0} // regular
1688 // Horizontal progress bar.
1689 {// Determined settings.
1692 NSMakeSize(0, 10), // small
1693 NSMakeSize(0, 16) // regular
1695 {NSZeroSize, NSZeroSize, NSZeroSize},
1698 {0, 0, 0, 0}, // mini
1699 {1, 1, 1, 1}, // small
1700 {1, 1, 1, 1} // regular
1704 {0, 0, 0, 0}, // mini
1705 {1, 1, 1, 1}, // small
1706 {1, 1, 1, 1} // regular
1708 // There is no horizontal margin in regular undetermined size.
1711 NSMakeSize(0, 10), // small
1712 NSMakeSize(0, 16) // regular
1714 {NSZeroSize, NSZeroSize, NSZeroSize},
1717 {0, 0, 0, 0}, // mini
1718 {1, 1, 1, 1}, // small
1719 {0, 1, 0, 1} // regular
1723 {0, 0, 0, 0}, // mini
1724 {1, 1, 1, 1}, // small
1725 {0, 1, 0, 1} // regular
1728 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1729 nsIFrame* aFrame, ElementState aEventState, bool aIsHorizontal) {
1730 ProgressParams params;
1731 params.value = GetProgressValue(aFrame);
1732 params.max = GetProgressMaxValue(aFrame);
1733 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1734 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1735 params.indeterminate = aEventState.HasState(ElementState::INDETERMINATE);
1736 params.horizontal = aIsHorizontal;
1737 params.rtl = IsFrameRTL(aFrame);
1741 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext,
1742 const HIRect& inBoxRect,
1743 const ProgressParams& aParams) {
1744 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1746 NSProgressBarCell* cell = mProgressBarCell;
1748 [cell setValue:aParams.value];
1749 [cell setMax:aParams.max];
1750 [cell setIndeterminate:aParams.indeterminate];
1751 [cell setHorizontal:aParams.horizontal];
1753 setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1754 : NSClearControlTint)];
1756 if (mCellDrawWindow) {
1757 mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1759 DrawCellWithSnapping(
1760 cell, cgContext, inBoxRect,
1761 progressSettings[aParams.horizontal][aParams.indeterminate],
1762 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1764 NS_OBJC_END_TRY_IGNORE_BLOCK;
1767 static const CellRenderSettings meterSetting = {
1769 NSMakeSize(0, 16), // mini
1770 NSMakeSize(0, 16), // small
1771 NSMakeSize(0, 16) // regular
1773 {NSZeroSize, NSZeroSize, NSZeroSize},
1776 {1, 1, 1, 1}, // mini
1777 {1, 1, 1, 1}, // small
1778 {1, 1, 1, 1} // regular
1782 {1, 1, 1, 1}, // mini
1783 {1, 1, 1, 1}, // small
1784 {1, 1, 1, 1} // regular
1787 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(
1789 nsIContent* content = aFrame->GetContent();
1790 if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1791 return MeterParams();
1794 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1796 params.value = meterElement->Value();
1797 params.min = meterElement->Min();
1798 params.max = meterElement->Max();
1799 ElementState states = meterElement->State();
1800 if (states.HasState(ElementState::SUB_OPTIMUM)) {
1801 params.optimumState = OptimumState::eSubOptimum;
1802 } else if (states.HasState(ElementState::SUB_SUB_OPTIMUM)) {
1803 params.optimumState = OptimumState::eSubSubOptimum;
1805 params.horizontal = !IsVerticalMeter(aFrame);
1806 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1807 params.rtl = IsFrameRTL(aFrame);
1812 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext,
1813 const HIRect& inBoxRect,
1814 const MeterParams& aParams) {
1815 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1817 NSLevelIndicatorCell* cell = mMeterBarCell;
1819 [cell setMinValue:aParams.min];
1820 [cell setMaxValue:aParams.max];
1821 [cell setDoubleValue:aParams.value];
1824 * The way HTML and Cocoa defines the meter/indicator widget are different.
1825 * So, we are going to use a trick to get the Cocoa widget showing what we
1826 * are expecting: we set the warningValue or criticalValue to the current
1827 * value when we want to have the widget to be in the warning or critical
1830 switch (aParams.optimumState) {
1831 case OptimumState::eOptimum:
1832 [cell setWarningValue:aParams.max + 1];
1833 [cell setCriticalValue:aParams.max + 1];
1835 case OptimumState::eSubOptimum:
1836 [cell setWarningValue:aParams.value];
1837 [cell setCriticalValue:aParams.max + 1];
1839 case OptimumState::eSubSubOptimum:
1840 [cell setWarningValue:aParams.max + 1];
1841 [cell setCriticalValue:aParams.value];
1845 HIRect rect = CGRectStandardize(inBoxRect);
1846 BOOL vertical = !aParams.horizontal;
1848 CGContextSaveGState(cgContext);
1852 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1853 * show a rotated horizontal meter bar.
1854 * Given that we want to show a vertical meter bar, we assume that the rect
1855 * has vertical dimensions but we can't correctly draw a meter widget inside
1856 * such a rectangle so we need to inverse width and height (and re-position)
1857 * to get a rectangle with horizontal dimensions.
1858 * Finally, we want to show a vertical meter so we want to rotate the result
1859 * so it is vertical. We do that by changing the context.
1861 CGFloat tmp = rect.size.width;
1862 rect.size.width = rect.size.height;
1863 rect.size.height = tmp;
1864 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1865 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1867 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1868 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1869 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect),
1870 -CGRectGetMidY(rect));
1873 if (mCellDrawWindow) {
1874 mCellDrawWindow.cellsShouldLookActive =
1875 YES; // TODO: propagate correct activeness state
1877 DrawCellWithSnapping(cell, cgContext, rect, meterSetting,
1878 aParams.verticalAlignFactor, mCellDrawView,
1879 !vertical && aParams.rtl);
1881 CGContextRestoreGState(cgContext);
1883 NS_OBJC_END_TRY_IGNORE_BLOCK
1886 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext,
1887 const HIRect& inBoxRect,
1888 bool aIsInsideActiveWindow) {
1889 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1891 HIThemeTabPaneDrawInfo tpdi;
1894 tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1895 tpdi.direction = kThemeTabNorth;
1896 tpdi.size = kHIThemeTabSizeNormal;
1897 tpdi.kind = kHIThemeTabKindNormal;
1899 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1901 NS_OBJC_END_TRY_IGNORE_BLOCK;
1904 Maybe<nsNativeThemeCocoa::ScaleParams>
1905 nsNativeThemeCocoa::ComputeHTMLScaleParams(nsIFrame* aFrame,
1906 ElementState aEventState) {
1907 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1912 bool isHorizontal = IsRangeHorizontal(aFrame);
1914 // ScaleParams requires integer min, max and value. This is purely for
1915 // drawing, so we normalize to a range 0-1000 here.
1917 params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1920 params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1921 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1922 params.focused = aEventState.HasState(ElementState::FOCUSRING);
1923 params.disabled = aEventState.HasState(ElementState::DISABLED);
1924 params.horizontal = isHorizontal;
1925 return Some(params);
1928 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext,
1929 const HIRect& inBoxRect,
1930 const ScaleParams& aParams) {
1931 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1933 HIThemeTrackDrawInfo tdi;
1936 tdi.kind = kThemeMediumSlider;
1937 tdi.bounds = inBoxRect;
1938 tdi.min = aParams.min;
1939 tdi.max = aParams.max;
1940 tdi.value = aParams.value;
1941 tdi.attributes = kThemeTrackShowThumb;
1942 if (aParams.horizontal) {
1943 tdi.attributes |= kThemeTrackHorizontal;
1945 if (aParams.reverse) {
1946 tdi.attributes |= kThemeTrackRightToLeft;
1948 if (aParams.focused) {
1949 tdi.attributes |= kThemeTrackHasFocus;
1951 if (aParams.disabled) {
1952 tdi.enableState = kThemeTrackDisabled;
1955 aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1957 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1958 tdi.trackInfo.slider.pressState = 0;
1960 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1962 NS_OBJC_END_TRY_IGNORE_BLOCK;
1965 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore,
1967 // Usually a separator is drawn by the segment to the right of the
1968 // separator, but pressed and selected segments have higher priority.
1969 if (!aBefore || !aAfter) return nullptr;
1970 if (IsSelectedButton(aAfter)) return aAfter;
1971 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
1975 static CGRect SeparatorAdjustedRect(CGRect aRect,
1976 nsNativeThemeCocoa::SegmentParams aParams) {
1977 // A separator between two segments should always be located in the leftmost
1978 // pixel column of the segment to the right of the separator, regardless of
1979 // who ends up drawing it.
1980 // CoreUI draws the separators inside the drawing rect.
1981 if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
1982 // The segment to the left of us draws the separator, so we need to make
1984 aRect.origin.x += 1;
1985 aRect.size.width -= 1;
1987 if (aParams.drawsRightSeparator) {
1988 // We draw the right separator, so we need to extend the draw rect into the
1989 // segment to our right.
1990 aRect.size.width += 1;
1995 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
1997 if (aIsLast) return @"kCUISegmentPositionOnly";
1998 return @"kCUISegmentPositionFirst";
2000 if (aIsLast) return @"kCUISegmentPositionLast";
2001 return @"kCUISegmentPositionMiddle";
2004 struct SegmentedControlRenderSettings {
2005 const CGFloat* heights;
2006 const NSString* widgetName;
2009 static const CGFloat tabHeights[3] = {17, 20, 23};
2011 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights,
2014 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2016 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2017 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2019 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2020 nsIFrame* aFrame, ElementState aEventState, SegmentType aSegmentType) {
2021 SegmentParams params;
2022 params.segmentType = aSegmentType;
2023 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2024 params.pressed = IsPressedButton(aFrame);
2025 params.selected = IsSelectedButton(aFrame);
2026 params.focused = aEventState.HasState(ElementState::FOCUSRING);
2027 bool isRTL = IsFrameRTL(aFrame);
2028 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2029 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2030 params.atLeftEnd = !left;
2031 params.atRightEnd = !right;
2032 params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2033 params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2038 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2039 nsNativeThemeCocoa::SegmentType aSegmentType) {
2040 switch (aSegmentType) {
2041 case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2042 return toolbarButtonRenderSettings;
2043 case nsNativeThemeCocoa::SegmentType::eTab:
2044 return tabRenderSettings;
2048 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext,
2049 const HIRect& inBoxRect,
2050 const SegmentParams& aParams) {
2051 SegmentedControlRenderSettings renderSettings =
2052 RenderSettingsForSegmentType(aParams.segmentType);
2053 NSControlSize controlSize =
2054 FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2055 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2057 NSDictionary* dict = @{
2058 @"widget" : renderSettings.widgetName,
2059 @"kCUIPresentationStateKey" :
2060 (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2061 : @"kCUIPresentationStateInactive"),
2062 @"kCUIPositionKey" :
2063 ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2064 @"kCUISegmentLeadingSeparatorKey" :
2065 [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2066 @"kCUISegmentTrailingSeparatorKey" :
2067 [NSNumber numberWithBool:aParams.drawsRightSeparator],
2068 @"value" : [NSNumber numberWithBool:aParams.selected],
2069 @"state" : (aParams.pressed
2071 : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2072 @"focus" : [NSNumber numberWithBool:aParams.focused],
2073 @"size" : CUIControlSizeForCocoaSize(controlSize),
2074 @"is.flipped" : [NSNumber numberWithBool:YES],
2075 @"direction" : @"up"
2078 RenderWithCoreUI(drawRect, cgContext, dict);
2081 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext,
2082 const CGRect& inBoxRect, bool aIsMain) {
2083 CGRect drawRect = inBoxRect;
2086 drawRect.size.height = 1.0f;
2087 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2090 drawRect.origin.y += drawRect.size.height;
2091 drawRect.size.height = inBoxRect.size.height - 2.0f;
2092 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2095 drawRect.origin.y += drawRect.size.height;
2096 drawRect.size.height = 1.0f;
2097 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect,
2101 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2102 if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2104 ToolbarWindow* win = (ToolbarWindow*)aWindow;
2105 float unifiedToolbarHeight = [win unifiedToolbarHeight];
2106 return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2107 aRect.YMost() <= unifiedToolbarHeight;
2110 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext,
2111 const HIRect& inBoxRect, bool aIsMain) {
2112 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2114 if (inBoxRect.size.height < 2.0f) return;
2116 CGContextSaveGState(cgContext);
2117 CGContextClipToRect(cgContext, inBoxRect);
2119 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2120 // and bottom bar. We only want the bottom bar, so we extend the draw rect
2121 // upwards to make space for the title bar, and then we clip it away.
2122 CGRect drawRect = inBoxRect;
2123 const int extendUpwards = 40;
2124 drawRect.origin.y -= extendUpwards;
2125 drawRect.size.height += extendUpwards;
2127 drawRect, cgContext,
2128 [NSDictionary dictionaryWithObjectsAndKeys:
2129 @"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2130 @"windowtype", (aIsMain ? @"normal" : @"inactive"),
2132 [NSNumber numberWithInt:inBoxRect.size.height],
2133 @"kCUIWindowFrameBottomBarHeightKey",
2134 [NSNumber numberWithBool:YES],
2135 @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2136 [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2138 CGContextRestoreGState(cgContext);
2140 NS_OBJC_END_TRY_IGNORE_BLOCK;
2143 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext,
2144 const CGRect& inBoxRect,
2146 mTextFieldCell.enabled = YES;
2147 mTextFieldCell.showsFirstResponder = aIsFocused;
2149 if (mCellDrawWindow) {
2150 mCellDrawWindow.cellsShouldLookActive = YES;
2153 // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do
2154 // the usual save+restore dance.
2155 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2156 NSGraphicsContext.currentContext =
2157 [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
2158 DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2159 NSGraphicsContext.currentContext = savedContext;
2162 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2163 return AppUnitsPerCSSPixel() >=
2164 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2167 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2168 nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2169 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2171 // setup to draw into the correct port
2172 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2174 gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2175 nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2176 float originalHeight = nativeWidgetRect.Height();
2177 nativeWidgetRect.Round();
2178 if (nativeWidgetRect.IsEmpty()) {
2179 return Nothing(); // Don't attempt to draw invisible widgets.
2182 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2184 // Use high-resolution drawing.
2185 nativeWidgetRect.Scale(0.5f);
2186 originalHeight *= 0.5f;
2189 ElementState elementState = GetContentState(aFrame, aAppearance);
2191 switch (aAppearance) {
2192 case StyleAppearance::Menupopup:
2195 case StyleAppearance::Tooltip:
2198 case StyleAppearance::Checkbox:
2199 case StyleAppearance::Radio: {
2200 bool isCheckbox = aAppearance == StyleAppearance::Checkbox;
2202 CheckboxOrRadioParams params;
2203 params.state = CheckboxOrRadioState::eOff;
2204 if (isCheckbox && elementState.HasState(ElementState::INDETERMINATE)) {
2205 params.state = CheckboxOrRadioState::eIndeterminate;
2206 } else if (elementState.HasState(ElementState::CHECKED)) {
2207 params.state = CheckboxOrRadioState::eOn;
2209 params.controlParams = ComputeControlParams(aFrame, elementState);
2210 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2212 return Some(WidgetInfo::Checkbox(params));
2214 return Some(WidgetInfo::Radio(params));
2217 case StyleAppearance::Button:
2218 if (IsDefaultButton(aFrame)) {
2219 // Check whether the default button is in a document that does not
2220 // match the :-moz-window-inactive pseudoclass. This activeness check
2221 // is different from the other "active window" checks in this file
2222 // because we absolutely need the button's default button appearance to
2223 // be in sync with its text color, and the text color is changed by
2224 // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2225 // default buttons in active windows have blue background and white
2226 // text, and default buttons in inactive windows have white background
2228 DocumentState docState = aFrame->PresContext()->Document()->State();
2229 ControlParams params = ComputeControlParams(aFrame, elementState);
2230 params.insideActiveWindow =
2231 !docState.HasState(DocumentState::WINDOW_INACTIVE);
2232 return Some(WidgetInfo::Button(
2233 ButtonParams{params, ButtonType::eDefaultPushButton}));
2235 if (IsButtonTypeMenu(aFrame)) {
2236 ControlParams controlParams =
2237 ComputeControlParams(aFrame, elementState);
2238 controlParams.pressed = IsOpenButton(aFrame);
2239 DropdownParams params;
2240 params.controlParams = controlParams;
2241 params.pullsDown = true;
2242 params.editable = false;
2243 return Some(WidgetInfo::Dropdown(params));
2245 if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2246 // If the button is tall enough, draw the square button style so that
2247 // buttons with non-standard content look good. Otherwise draw normal
2248 // rounded aqua buttons.
2249 // This comparison is done based on the height that is calculated
2250 // without the top, because the snapped height can be affected by the
2251 // top of the rect and that may result in different height depending on
2253 return Some(WidgetInfo::Button(
2254 ButtonParams{ComputeControlParams(aFrame, elementState),
2255 ButtonType::eSquareBezelPushButton}));
2257 return Some(WidgetInfo::Button(
2258 ButtonParams{ComputeControlParams(aFrame, elementState),
2259 ButtonType::eRegularPushButton}));
2261 case StyleAppearance::MozMacHelpButton:
2262 return Some(WidgetInfo::Button(
2263 ButtonParams{ComputeControlParams(aFrame, elementState),
2264 ButtonType::eHelpButton}));
2266 case StyleAppearance::MozMacDisclosureButtonOpen:
2267 case StyleAppearance::MozMacDisclosureButtonClosed: {
2268 ButtonType buttonType =
2269 (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2270 ? ButtonType::eDisclosureButtonClosed
2271 : ButtonType::eDisclosureButtonOpen;
2272 return Some(WidgetInfo::Button(ButtonParams{
2273 ComputeControlParams(aFrame, elementState), buttonType}));
2276 case StyleAppearance::Spinner: {
2277 bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2278 nsIContent* content = aFrame->GetContent();
2279 if (isSpinner && content->IsHTMLElement()) {
2280 // In HTML the theming for the spin buttons is drawn individually into
2281 // their own backgrounds instead of being drawn into the background of
2282 // their spinner parent as it is for XUL.
2285 SpinButtonParams params;
2286 if (content->IsElement()) {
2287 if (content->AsElement()->AttrValueIs(
2288 kNameSpaceID_None, nsGkAtoms::state, u"up"_ns, eCaseMatters)) {
2289 params.pressedButton = Some(SpinButton::eUp);
2290 } else if (content->AsElement()->AttrValueIs(
2291 kNameSpaceID_None, nsGkAtoms::state, u"down"_ns,
2293 params.pressedButton = Some(SpinButton::eDown);
2296 params.disabled = elementState.HasState(ElementState::DISABLED);
2297 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2299 return Some(WidgetInfo::SpinButtons(params));
2302 case StyleAppearance::SpinnerUpbutton:
2303 case StyleAppearance::SpinnerDownbutton: {
2304 nsNumberControlFrame* numberControlFrame =
2305 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2306 if (numberControlFrame) {
2307 SpinButtonParams params;
2308 if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2309 params.pressedButton = Some(SpinButton::eUp);
2310 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2311 params.pressedButton = Some(SpinButton::eDown);
2313 params.disabled = elementState.HasState(ElementState::DISABLED);
2314 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2315 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2316 return Some(WidgetInfo::SpinButtonUp(params));
2318 return Some(WidgetInfo::SpinButtonDown(params));
2322 case StyleAppearance::Toolbarbutton: {
2323 SegmentParams params = ComputeSegmentParams(aFrame, elementState,
2324 SegmentType::eToolbarButton);
2325 params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2326 return Some(WidgetInfo::Segment(params));
2329 case StyleAppearance::Separator:
2330 return Some(WidgetInfo::Separator());
2332 case StyleAppearance::Toolbar: {
2333 NSWindow* win = NativeWindowForFrame(aFrame);
2334 bool isMain = [win isMainWindow];
2335 if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2336 // Unified toolbars are drawn similar to vibrancy; we communicate their
2337 // extents via the theme geometry mechanism and then place native views
2338 // under Gecko's rendering. So Gecko just needs to be transparent in the
2339 // place where the toolbar should be visible.
2342 return Some(WidgetInfo::Toolbar(isMain));
2345 case StyleAppearance::MozWindowTitlebar: {
2349 case StyleAppearance::Statusbar:
2350 return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2352 case StyleAppearance::MenulistButton:
2353 case StyleAppearance::Menulist: {
2354 ControlParams controlParams = ComputeControlParams(aFrame, elementState);
2355 controlParams.pressed = IsOpenButton(aFrame);
2356 DropdownParams params;
2357 params.controlParams = controlParams;
2358 params.pullsDown = false;
2359 params.editable = false;
2360 return Some(WidgetInfo::Dropdown(params));
2363 case StyleAppearance::MozMenulistArrowButton:
2364 return Some(WidgetInfo::Button(
2365 ButtonParams{ComputeControlParams(aFrame, elementState),
2366 ButtonType::eArrowButton}));
2368 case StyleAppearance::Textfield:
2369 case StyleAppearance::NumberInput:
2371 WidgetInfo::TextField(ComputeTextFieldParams(aFrame, elementState)));
2373 case StyleAppearance::Searchfield:
2374 return Some(WidgetInfo::SearchField(
2375 ComputeTextFieldParams(aFrame, elementState)));
2377 case StyleAppearance::ProgressBar: {
2378 if (elementState.HasState(ElementState::INDETERMINATE)) {
2379 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2380 NS_WARNING("Unable to animate progressbar!");
2383 return Some(WidgetInfo::ProgressBar(ComputeProgressParams(
2384 aFrame, elementState, !IsVerticalProgress(aFrame))));
2387 case StyleAppearance::Meter:
2388 return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2390 case StyleAppearance::Progresschunk:
2391 case StyleAppearance::Meterchunk:
2392 // Do nothing: progress and meter bars cases will draw chunks.
2395 case StyleAppearance::Treetwisty:
2396 return Some(WidgetInfo::Button(
2397 ButtonParams{ComputeControlParams(aFrame, elementState),
2398 ButtonType::eTreeTwistyPointingRight}));
2400 case StyleAppearance::Treetwistyopen:
2401 return Some(WidgetInfo::Button(
2402 ButtonParams{ComputeControlParams(aFrame, elementState),
2403 ButtonType::eTreeTwistyPointingDown}));
2405 case StyleAppearance::Treeheadercell:
2406 return Some(WidgetInfo::TreeHeaderCell(
2407 ComputeTreeHeaderCellParams(aFrame, elementState)));
2409 case StyleAppearance::Treeitem:
2410 case StyleAppearance::Treeview:
2411 return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2413 case StyleAppearance::Treeheader:
2414 // do nothing, taken care of by individual header cells
2415 case StyleAppearance::Treeline:
2416 // do nothing, these lines don't exist on macos
2419 case StyleAppearance::Range: {
2420 Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, elementState);
2422 return Some(WidgetInfo::Scale(*params));
2427 case StyleAppearance::Textarea:
2428 return Some(WidgetInfo::MultilineTextField(
2429 elementState.HasState(ElementState::FOCUS)));
2431 case StyleAppearance::Listbox:
2432 return Some(WidgetInfo::ListBox());
2434 case StyleAppearance::Tab: {
2435 SegmentParams params =
2436 ComputeSegmentParams(aFrame, elementState, SegmentType::eTab);
2437 params.pressed = params.pressed && !params.selected;
2438 return Some(WidgetInfo::Segment(params));
2441 case StyleAppearance::Tabpanels:
2442 return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2450 NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2454 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2455 StyleAppearance aAppearance,
2456 const nsRect& aRect,
2457 const nsRect& aDirtyRect,
2458 DrawOverflow aDrawOverflow) {
2459 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2461 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2462 return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance,
2463 aRect, aDirtyRect, aDrawOverflow);
2466 Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2472 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2474 gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2475 nativeWidgetRect.Round();
2477 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2479 auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2481 RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(),
2482 nativeWidgetRect, NSRectToRect(aDirtyRect, p2a),
2483 hidpi ? 2.0f : 1.0f);
2487 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2490 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2491 LookAndFeel::ColorScheme aScheme,
2492 DrawTarget& aDrawTarget,
2493 const gfx::Rect& aWidgetRect,
2494 const gfx::Rect& aDirtyRect,
2496 // Some of the drawing below uses NSAppearance.currentAppearance behind the
2497 // scenes. Set it to the appearance we want, the same way as
2498 // nsLookAndFeel::NativeGetColor.
2499 NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2501 // Also set the cell draw window's appearance; this is respected by
2502 // NSTextFieldCell (and its subclass NSSearchFieldCell).
2503 if (mCellDrawWindow) {
2504 mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2507 const Widget widget = aWidgetInfo.Widget();
2509 // Some widgets render using DrawTarget, and some using CGContext.
2511 case Widget::eColorFill: {
2512 sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2513 aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2517 AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2518 gfx::Rect widgetRect = aWidgetRect;
2519 gfx::Rect dirtyRect = aDirtyRect;
2521 dirtyRect.Scale(1.0f / aScale);
2522 widgetRect.Scale(1.0f / aScale);
2523 aDrawTarget.SetTransform(
2524 aDrawTarget.GetTransform().PreScale(aScale, aScale));
2526 // The remaining widgets require a CGContext.
2527 CGRect macRect = CGRectMake(widgetRect.X(), widgetRect.Y(),
2528 widgetRect.Width(), widgetRect.Height());
2530 gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2532 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2533 if (cgContext == nullptr) {
2534 // The Quartz surface handles 0x0 surfaces by internally
2535 // making all operations no-ops; there's no cgcontext created for them.
2536 // Unfortunately, this means that callers that want to render
2537 // directly to the CGContext need to be aware of this quirk.
2541 // Set the context's "base transform" to in order to get correctly-sized
2543 CGContextSetBaseCTM(cgContext,
2544 CGAffineTransformMakeScale(aScale, aScale));
2547 case Widget::eColorFill:
2548 MOZ_CRASH("already handled in outer switch");
2550 case Widget::eCheckbox: {
2551 CheckboxOrRadioParams params =
2552 aWidgetInfo.Params<CheckboxOrRadioParams>();
2553 DrawCheckboxOrRadio(cgContext, true, macRect, params);
2556 case Widget::eRadio: {
2557 CheckboxOrRadioParams params =
2558 aWidgetInfo.Params<CheckboxOrRadioParams>();
2559 DrawCheckboxOrRadio(cgContext, false, macRect, params);
2562 case Widget::eButton: {
2563 ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2564 DrawButton(cgContext, macRect, params);
2567 case Widget::eDropdown: {
2568 DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2569 DrawDropdown(cgContext, macRect, params);
2572 case Widget::eSpinButtons: {
2573 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2574 DrawSpinButtons(cgContext, macRect, params);
2577 case Widget::eSpinButtonUp: {
2578 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2579 DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2582 case Widget::eSpinButtonDown: {
2583 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2584 DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2587 case Widget::eSegment: {
2588 SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2589 DrawSegment(cgContext, macRect, params);
2592 case Widget::eSeparator: {
2593 HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2594 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2597 case Widget::eToolbar: {
2598 bool isMain = aWidgetInfo.Params<bool>();
2599 DrawToolbar(cgContext, macRect, isMain);
2602 case Widget::eStatusBar: {
2603 bool isMain = aWidgetInfo.Params<bool>();
2604 DrawStatusBar(cgContext, macRect, isMain);
2607 case Widget::eGroupBox: {
2608 HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive,
2609 kHIThemeGroupBoxKindPrimary};
2610 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2613 case Widget::eTextField: {
2614 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2615 DrawTextField(cgContext, macRect, params);
2618 case Widget::eSearchField: {
2619 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2620 DrawSearchField(cgContext, macRect, params);
2623 case Widget::eProgressBar: {
2624 ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2625 DrawProgress(cgContext, macRect, params);
2628 case Widget::eMeter: {
2629 MeterParams params = aWidgetInfo.Params<MeterParams>();
2630 DrawMeter(cgContext, macRect, params);
2633 case Widget::eTreeHeaderCell: {
2634 TreeHeaderCellParams params =
2635 aWidgetInfo.Params<TreeHeaderCellParams>();
2636 DrawTreeHeaderCell(cgContext, macRect, params);
2639 case Widget::eScale: {
2640 ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2641 DrawScale(cgContext, macRect, params);
2644 case Widget::eMultilineTextField: {
2645 bool isFocused = aWidgetInfo.Params<bool>();
2646 DrawMultilineTextField(cgContext, macRect, isFocused);
2649 case Widget::eListBox: {
2650 // Fill the content with the control background color.
2651 CGContextSetFillColorWithColor(
2652 cgContext, [NSColor.controlBackgroundColor CGColor]);
2653 CGContextFillRect(cgContext, macRect);
2654 // Draw the frame using kCUIWidgetScrollViewFrame. This is what
2655 // NSScrollView uses in
2656 // -[NSScrollView drawRect:] if you give it a borderType of
2659 macRect, cgContext, @{
2660 @"widget" : @"kCUIWidgetScrollViewFrame",
2661 @"kCUIIsFlippedKey" : @YES,
2662 @"kCUIVariantMetal" : @NO,
2666 case Widget::eTabPanel: {
2667 bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2668 DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2673 // Reset the base CTM.
2674 CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2676 nativeDrawing.EndNativeDrawing();
2681 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2682 mozilla::wr::DisplayListBuilder& aBuilder,
2683 mozilla::wr::IpcResourceUpdateQueue& aResources,
2684 const mozilla::layers::StackingContextHelper& aSc,
2685 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2686 StyleAppearance aAppearance, const nsRect& aRect) {
2687 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2688 return ThemeCocoa::CreateWebRenderCommandsForWidget(
2689 aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
2692 // This list needs to stay consistent with the list in DrawWidgetBackground.
2693 // For every switch case in DrawWidgetBackground, there are three choices:
2694 // - If the case in DrawWidgetBackground draws nothing for the given widget
2695 // type, then don't list it here. We will hit the "default: return true;"
2697 // - If the case in DrawWidgetBackground draws something simple for the given
2698 // widget type, imitate that drawing using WebRender commands.
2699 // - If the case in DrawWidgetBackground draws something complicated for the
2700 // given widget type, return false here.
2701 switch (aAppearance) {
2702 case StyleAppearance::Checkbox:
2703 case StyleAppearance::Radio:
2704 case StyleAppearance::Button:
2705 case StyleAppearance::MozMacHelpButton:
2706 case StyleAppearance::MozMacDisclosureButtonOpen:
2707 case StyleAppearance::MozMacDisclosureButtonClosed:
2708 case StyleAppearance::Spinner:
2709 case StyleAppearance::SpinnerUpbutton:
2710 case StyleAppearance::SpinnerDownbutton:
2711 case StyleAppearance::Toolbarbutton:
2712 case StyleAppearance::Separator:
2713 case StyleAppearance::Toolbar:
2714 case StyleAppearance::MozWindowTitlebar:
2715 case StyleAppearance::Statusbar:
2716 case StyleAppearance::Menulist:
2717 case StyleAppearance::MenulistButton:
2718 case StyleAppearance::MozMenulistArrowButton:
2719 case StyleAppearance::Textfield:
2720 case StyleAppearance::NumberInput:
2721 case StyleAppearance::Searchfield:
2722 case StyleAppearance::ProgressBar:
2723 case StyleAppearance::Meter:
2724 case StyleAppearance::Treeheadercell:
2725 case StyleAppearance::Treetwisty:
2726 case StyleAppearance::Treetwistyopen:
2727 case StyleAppearance::Treeitem:
2728 case StyleAppearance::Treeview:
2729 case StyleAppearance::Range:
2732 case StyleAppearance::Textarea:
2733 case StyleAppearance::Listbox:
2734 case StyleAppearance::Tab:
2735 case StyleAppearance::Tabpanels:
2743 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(
2744 const LayoutDeviceIntMargin& aMargin, nsIFrame* aFrame) {
2745 // Assuming aMargin was originally specified for a horizontal LTR context,
2746 // reinterpret the values as logical, and then map to physical coords
2747 // according to aFrame's actual writing mode.
2748 WritingMode wm = aFrame->GetWritingMode();
2749 nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
2751 .GetPhysicalMargin(wm);
2752 return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2755 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2756 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2757 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2758 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2760 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(
2761 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
2762 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2763 return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
2766 LayoutDeviceIntMargin result;
2768 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2769 switch (aAppearance) {
2770 case StyleAppearance::Button: {
2771 if (IsButtonTypeMenu(aFrame)) {
2772 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2775 DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2780 case StyleAppearance::Toolbarbutton: {
2781 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2785 case StyleAppearance::Checkbox:
2786 case StyleAppearance::Radio: {
2787 // nsCheckboxRadioFrame::GetIntrinsicWidth and
2788 // nsCheckboxRadioFrame::GetIntrinsicHeight assume a border width of 2px.
2789 result.SizeTo(2, 2, 2, 2);
2793 case StyleAppearance::Menulist:
2794 case StyleAppearance::MenulistButton:
2795 case StyleAppearance::MozMenulistArrowButton:
2796 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2799 case StyleAppearance::NumberInput:
2800 case StyleAppearance::Textfield: {
2801 SInt32 frameOutset = 0;
2802 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2804 SInt32 textPadding = 0;
2805 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2807 frameOutset += textPadding;
2809 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2813 case StyleAppearance::Textarea:
2814 result.SizeTo(1, 1, 1, 1);
2817 case StyleAppearance::Searchfield: {
2818 auto border = nsCocoaFeatures::OnBigSurOrLater()
2819 ? kAquaSearchfieldBorderBigSur
2820 : kAquaSearchfieldBorder;
2821 result = DirectionAwareMargin(border, aFrame);
2825 case StyleAppearance::Listbox: {
2826 SInt32 frameOutset = 0;
2827 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2828 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2832 case StyleAppearance::Statusbar:
2833 result.SizeTo(1, 0, 0, 0);
2840 if (IsHiDPIContext(aContext)) {
2841 result = result + result; // doubled
2844 NS_OBJC_END_TRY_BLOCK_RETURN(result);
2847 // Return false here to indicate that CSS padding values should be used. There
2848 // is no reason to make a distinction between padding and border values, just
2849 // specify whatever values you want in GetWidgetBorder and only use this to
2850 // return true if you want to override CSS padding values.
2851 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2853 StyleAppearance aAppearance,
2854 LayoutDeviceIntMargin* aResult) {
2855 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2856 return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
2859 // We don't want CSS padding being used for certain widgets.
2860 // See bug 381639 for an example of why.
2861 switch (aAppearance) {
2862 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2863 // and have a meaningful baseline, so they can't have
2864 // author-specified padding.
2865 case StyleAppearance::Checkbox:
2866 case StyleAppearance::Radio:
2867 aResult->SizeTo(0, 0, 0, 0);
2870 case StyleAppearance::Searchfield:
2871 if (nsCocoaFeatures::OnBigSurOrLater()) {
2882 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext,
2884 StyleAppearance aAppearance,
2885 nsRect* aOverflowRect) {
2886 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2887 return ThemeCocoa::GetWidgetOverflow(aContext, aFrame, aAppearance,
2890 nsIntMargin overflow;
2891 switch (aAppearance) {
2892 case StyleAppearance::Button:
2893 case StyleAppearance::MozMacDisclosureButtonOpen:
2894 case StyleAppearance::MozMacDisclosureButtonClosed:
2895 case StyleAppearance::MozMacHelpButton:
2896 case StyleAppearance::Toolbarbutton:
2897 case StyleAppearance::NumberInput:
2898 case StyleAppearance::Textfield:
2899 case StyleAppearance::Textarea:
2900 case StyleAppearance::Searchfield:
2901 case StyleAppearance::Listbox:
2902 case StyleAppearance::Menulist:
2903 case StyleAppearance::MenulistButton:
2904 case StyleAppearance::MozMenulistArrowButton:
2905 case StyleAppearance::Checkbox:
2906 case StyleAppearance::Radio:
2907 case StyleAppearance::Tab: {
2908 overflow.SizeTo(static_cast<int32_t>(kMaxFocusRingWidth),
2909 static_cast<int32_t>(kMaxFocusRingWidth),
2910 static_cast<int32_t>(kMaxFocusRingWidth),
2911 static_cast<int32_t>(kMaxFocusRingWidth));
2914 case StyleAppearance::ProgressBar: {
2915 // Progress bars draw a 2 pixel white shadow under their progress
2917 overflow.bottom = 2;
2920 case StyleAppearance::Meter: {
2921 // Meter bars overflow their boxes by about 2 pixels.
2922 overflow.SizeTo(2, 2, 2, 2);
2929 if (IsHiDPIContext(aContext)) {
2930 // Double the number of device pixels.
2931 overflow += overflow;
2934 if (overflow != nsIntMargin()) {
2935 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2936 aOverflowRect->Inflate(nsMargin(NSIntPixelsToAppUnits(overflow.top, p2a),
2937 NSIntPixelsToAppUnits(overflow.right, p2a),
2938 NSIntPixelsToAppUnits(overflow.bottom, p2a),
2939 NSIntPixelsToAppUnits(overflow.left, p2a)));
2946 LayoutDeviceIntSize nsNativeThemeCocoa::GetMinimumWidgetSize(
2947 nsPresContext* aPresContext, nsIFrame* aFrame,
2948 StyleAppearance aAppearance) {
2949 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2951 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2952 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
2955 LayoutDeviceIntSize result;
2956 switch (aAppearance) {
2957 case StyleAppearance::Button: {
2958 result.SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2959 pushButtonSettings.naturalSizes[miniControlSize].height);
2963 case StyleAppearance::MozMacDisclosureButtonOpen:
2964 case StyleAppearance::MozMacDisclosureButtonClosed: {
2965 result.SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
2969 case StyleAppearance::MozMacHelpButton: {
2970 result.SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
2974 case StyleAppearance::Toolbarbutton: {
2975 result.SizeTo(0, toolbarButtonHeights[miniControlSize]);
2979 case StyleAppearance::Spinner:
2980 case StyleAppearance::SpinnerUpbutton:
2981 case StyleAppearance::SpinnerDownbutton: {
2982 SInt32 buttonHeight = 0, buttonWidth = 0;
2983 if (aFrame->GetContent()->IsXULElement()) {
2984 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2985 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2989 .minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
2990 buttonWidth = size.width;
2991 buttonHeight = size.height;
2992 if (aAppearance != StyleAppearance::Spinner) {
2993 // the buttons are half the height of the spinner
2997 result.SizeTo(buttonWidth, buttonHeight);
3001 case StyleAppearance::Menulist:
3002 case StyleAppearance::MenulistButton: {
3003 SInt32 popupHeight = 0;
3004 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3005 result.SizeTo(0, popupHeight);
3009 case StyleAppearance::NumberInput:
3010 case StyleAppearance::Textfield:
3011 case StyleAppearance::Textarea:
3012 case StyleAppearance::Searchfield: {
3013 // at minimum, we should be tall enough for 9pt text.
3014 // I'm using hardcoded values here because the appearance manager
3015 // values for the frame size are incorrect.
3016 result.SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3020 case StyleAppearance::MozWindowButtonBox: {
3021 NSSize size = WindowButtonsSize(aFrame);
3022 result.SizeTo(size.width, size.height);
3026 case StyleAppearance::ProgressBar: {
3027 SInt32 barHeight = 0;
3028 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3029 result.SizeTo(0, barHeight);
3033 case StyleAppearance::Separator: {
3034 result.SizeTo(1, 1);
3038 case StyleAppearance::Treetwisty:
3039 case StyleAppearance::Treetwistyopen: {
3040 SInt32 twistyHeight = 0, twistyWidth = 0;
3041 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3042 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3043 result.SizeTo(twistyWidth, twistyHeight);
3047 case StyleAppearance::Treeheader:
3048 case StyleAppearance::Treeheadercell: {
3049 SInt32 headerHeight = 0;
3050 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3051 result.SizeTo(0, headerHeight);
3055 case StyleAppearance::Tab: {
3056 result.SizeTo(0, tabHeights[miniControlSize]);
3060 case StyleAppearance::RangeThumb: {
3063 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3064 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3065 result.SizeTo(width, height);
3069 case StyleAppearance::MozMenulistArrowButton:
3070 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame,
3077 if (IsHiDPIContext(aPresContext->DeviceContext())) {
3078 result = result * 2;
3083 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntSize());
3087 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame,
3088 StyleAppearance aAppearance,
3089 nsAtom* aAttribute, bool* aShouldRepaint,
3090 const nsAttrValue* aOldValue) {
3091 // Some widget types just never change state.
3092 switch (aAppearance) {
3093 case StyleAppearance::MozWindowTitlebar:
3094 case StyleAppearance::Toolbox:
3095 case StyleAppearance::Toolbar:
3096 case StyleAppearance::Statusbar:
3097 case StyleAppearance::Tooltip:
3098 case StyleAppearance::Tabpanels:
3099 case StyleAppearance::Tabpanel:
3100 case StyleAppearance::Menupopup:
3101 case StyleAppearance::Progresschunk:
3102 case StyleAppearance::ProgressBar:
3103 case StyleAppearance::Meter:
3104 case StyleAppearance::Meterchunk:
3105 *aShouldRepaint = false;
3111 // XXXdwh Not sure what can really be done here. Can at least guess for
3112 // specific widgets that they're highly unlikely to have certain states.
3113 // For example, a toolbar doesn't care about any states.
3115 // Hover/focus/active changed. Always repaint.
3116 *aShouldRepaint = true;
3118 // Check the attribute to see if it's relevant.
3119 // disabled, checked, dlgtype, default, etc.
3120 *aShouldRepaint = false;
3121 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3122 aAttribute == nsGkAtoms::selected ||
3123 aAttribute == nsGkAtoms::visuallyselected ||
3124 aAttribute == nsGkAtoms::menuactive ||
3125 aAttribute == nsGkAtoms::sortDirection ||
3126 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3127 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3128 *aShouldRepaint = true;
3135 nsNativeThemeCocoa::ThemeChanged() {
3136 // This is unimplemented because we don't care if gecko changes its theme
3137 // and macOS system appearance changes are handled by
3138 // nsLookAndFeel::SystemWantsDarkTheme.
3142 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext,
3144 StyleAppearance aAppearance) {
3145 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
3146 return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3148 // if this is a dropdown button in a combobox the answer is always no
3149 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3150 nsIFrame* parentFrame = aFrame->GetParent();
3151 if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3154 switch (aAppearance) {
3155 // Combobox dropdowns don't support native theming in vertical mode.
3156 case StyleAppearance::Menulist:
3157 case StyleAppearance::MenulistButton:
3158 case StyleAppearance::MozMenulistArrowButton:
3159 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3164 case StyleAppearance::Listbox:
3165 case StyleAppearance::MozWindowButtonBox:
3166 case StyleAppearance::MozWindowTitlebar:
3167 case StyleAppearance::Menupopup:
3168 case StyleAppearance::Tooltip:
3170 case StyleAppearance::Checkbox:
3171 case StyleAppearance::Radio:
3172 case StyleAppearance::MozMacHelpButton:
3173 case StyleAppearance::MozMacDisclosureButtonOpen:
3174 case StyleAppearance::MozMacDisclosureButtonClosed:
3175 case StyleAppearance::MozMacUnifiedToolbarWindow:
3176 case StyleAppearance::Button:
3177 case StyleAppearance::Toolbarbutton:
3178 case StyleAppearance::Spinner:
3179 case StyleAppearance::SpinnerUpbutton:
3180 case StyleAppearance::SpinnerDownbutton:
3181 case StyleAppearance::Toolbar:
3182 case StyleAppearance::Statusbar:
3183 case StyleAppearance::NumberInput:
3184 case StyleAppearance::Textfield:
3185 case StyleAppearance::Textarea:
3186 case StyleAppearance::Searchfield:
3187 case StyleAppearance::Toolbox:
3188 case StyleAppearance::ProgressBar:
3189 case StyleAppearance::Progresschunk:
3190 case StyleAppearance::Meter:
3191 case StyleAppearance::Meterchunk:
3192 case StyleAppearance::Separator:
3194 case StyleAppearance::Tabpanels:
3195 case StyleAppearance::Tab:
3197 case StyleAppearance::Treetwisty:
3198 case StyleAppearance::Treetwistyopen:
3199 case StyleAppearance::Treeview:
3200 case StyleAppearance::Treeheader:
3201 case StyleAppearance::Treeheadercell:
3202 case StyleAppearance::Treeitem:
3203 case StyleAppearance::Treeline:
3205 case StyleAppearance::Range:
3206 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3215 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3216 // flesh this out at some point
3217 switch (aAppearance) {
3218 case StyleAppearance::MozMenulistArrowButton:
3219 case StyleAppearance::Radio:
3220 case StyleAppearance::Checkbox:
3221 case StyleAppearance::ProgressBar:
3222 case StyleAppearance::Meter:
3223 case StyleAppearance::Range:
3224 case StyleAppearance::MozMacHelpButton:
3225 case StyleAppearance::MozMacDisclosureButtonOpen:
3226 case StyleAppearance::MozMacDisclosureButtonClosed:
3234 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*,
3235 StyleAppearance aAppearance) {
3236 switch (aAppearance) {
3237 case StyleAppearance::Textarea:
3238 case StyleAppearance::Textfield:
3239 case StyleAppearance::Searchfield:
3240 case StyleAppearance::NumberInput:
3241 case StyleAppearance::Menulist:
3242 case StyleAppearance::MenulistButton:
3243 case StyleAppearance::Button:
3244 case StyleAppearance::MozMacHelpButton:
3245 case StyleAppearance::MozMacDisclosureButtonOpen:
3246 case StyleAppearance::MozMacDisclosureButtonClosed:
3247 case StyleAppearance::Radio:
3248 case StyleAppearance::Range:
3249 case StyleAppearance::Checkbox:
3256 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3258 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(
3259 StyleAppearance aAppearance) {
3260 switch (aAppearance) {
3261 case StyleAppearance::Tabpanels:
3262 case StyleAppearance::Menupopup:
3263 case StyleAppearance::Tooltip:
3264 case StyleAppearance::Spinner:
3265 case StyleAppearance::SpinnerUpbutton:
3266 case StyleAppearance::SpinnerDownbutton:
3267 case StyleAppearance::Separator:
3268 case StyleAppearance::Toolbox:
3269 case StyleAppearance::NumberInput:
3270 case StyleAppearance::Textfield:
3271 case StyleAppearance::Treeview:
3272 case StyleAppearance::Treeline:
3273 case StyleAppearance::Textarea:
3274 case StyleAppearance::Listbox:
3281 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3282 nsIFrame* aFrame, StyleAppearance aAppearance) {
3283 switch (aAppearance) {
3284 case StyleAppearance::MozWindowTitlebar:
3285 return eThemeGeometryTypeTitlebar;
3286 case StyleAppearance::Toolbar:
3287 return eThemeGeometryTypeToolbar;
3288 case StyleAppearance::Toolbox:
3289 return eThemeGeometryTypeToolbox;
3290 case StyleAppearance::MozWindowButtonBox:
3291 return eThemeGeometryTypeWindowButtons;
3292 case StyleAppearance::Tooltip:
3293 return eThemeGeometryTypeTooltip;
3294 case StyleAppearance::Menupopup:
3295 return eThemeGeometryTypeMenu;
3297 return eThemeGeometryTypeUnknown;
3301 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(
3302 nsIFrame* aFrame, StyleAppearance aAppearance) {
3303 if (IsWidgetScrollbarPart(aAppearance)) {
3304 return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3307 switch (aAppearance) {
3308 case StyleAppearance::Menupopup:
3309 case StyleAppearance::Tooltip:
3310 case StyleAppearance::Toolbar:
3311 return eTransparent;
3312 case StyleAppearance::MozMacUnifiedToolbarWindow:
3313 // We want these to be treated as opaque by Gecko. We ensure there's an
3314 // appropriate OS-level clear color to make sure that's the case.
3316 case StyleAppearance::Statusbar:
3317 // Knowing that scrollbars and statusbars are opaque improves
3318 // performance, because we create layers for them.
3322 return eUnknownTransparency;
3326 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3327 return do_AddRef(new nsNativeThemeCocoa());