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 IsActiveToolbarControl(nsIFrame* aFrame) {
403 return NativeWindowForFrame(aFrame).isMainWindow;
406 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
408 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
409 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
411 kMaxFocusRingWidth = 7;
413 // provide a local autorelease pool, as this is called during startup
414 // before the main event-loop pool is in place
415 nsAutoreleasePool pool;
417 mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
418 [mDisclosureButtonCell setBezelStyle:NSBezelStyleRoundedDisclosure];
419 [mDisclosureButtonCell setButtonType:NSButtonTypePushOnPushOff];
420 [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
422 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
423 [mHelpButtonCell setBezelStyle:NSBezelStyleHelpButton];
424 [mHelpButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
425 [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
427 mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
428 [mPushButtonCell setButtonType:NSButtonTypeMomentaryPushIn];
429 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
431 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
432 [mRadioButtonCell setButtonType:NSButtonTypeRadio];
434 mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
435 [mCheckboxCell setButtonType:NSButtonTypeSwitch];
436 [mCheckboxCell setAllowsMixedState:YES];
438 mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
439 [mTextFieldCell setBezeled:YES];
440 [mTextFieldCell setEditable:YES];
441 [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
443 mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
444 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
445 [mSearchFieldCell setBezeled:YES];
446 [mSearchFieldCell setEditable:YES];
447 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
449 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
451 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
452 [mComboBoxCell setBezeled:YES];
453 [mComboBoxCell setEditable:YES];
454 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
456 mProgressBarCell = [[NSProgressBarCell alloc] init];
458 mMeterBarCell = [[NSLevelIndicatorCell alloc]
459 initWithLevelIndicatorStyle:NSLevelIndicatorStyleContinuousCapacity];
461 mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
463 mCellDrawView = [[MOZCellDrawView alloc] init];
465 if (XRE_IsParentProcess()) {
466 // Put the cell draw view into a window that is never shown.
467 // This allows us to convince some NSCell implementations (such as
468 // NSButtonCell for default buttons) to draw with the active appearance.
469 // Another benefit of putting the draw view in a window is the fact that it
470 // lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit the
471 // current NSApplication effectiveAppearance automatically, so the field
472 // adapts to Dark Mode correctly. We don't create this window when the
473 // native theme is used in the content process because NSWindow creation
474 // runs into the sandbox and because we never run default buttons in content
476 mCellDrawWindow = [[MOZCellDrawWindow alloc]
477 initWithContentRect:NSZeroRect
478 styleMask:NSWindowStyleMaskBorderless
479 backing:NSBackingStoreBuffered
481 [mCellDrawWindow.contentView addSubview:mCellDrawView];
484 NS_OBJC_END_TRY_IGNORE_BLOCK;
487 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
488 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
490 [mMeterBarCell release];
491 [mProgressBarCell release];
492 [mDisclosureButtonCell release];
493 [mHelpButtonCell release];
494 [mPushButtonCell release];
495 [mRadioButtonCell release];
496 [mCheckboxCell release];
497 [mTextFieldCell release];
498 [mSearchFieldCell release];
499 [mDropdownCell release];
500 [mComboBoxCell release];
501 [mTreeHeaderCell release];
502 [mCellDrawWindow release];
503 [mCellDrawView release];
505 NS_OBJC_END_TRY_IGNORE_BLOCK;
508 // Limit on the area of the target rect (in pixels^2) in
509 // DrawCellWithScaling() and DrawButton() and above which we
510 // don't draw the object into a bitmap buffer. This is to avoid crashes in
511 // [NSGraphicsContext graphicsContextWithCGContext:flipped:] and
512 // CGContextDrawImage(), and also to avoid very poor drawing performance in
513 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
514 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
515 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
516 // different amounts of RAM.
517 #define BITMAP_MAX_AREA 500000
519 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
520 CGAffineTransform ctm =
521 CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
522 CGRect transformedUserSpacePixel =
523 CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
524 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
525 fabs(transformedUserSpacePixel.size.height));
526 return maxScale > 1.0 ? 2 : 1;
530 * Draw the given NSCell into the given cgContext.
532 * destRect - the size and position of the resulting control rectangle
533 * controlSize - the NSControlSize which will be given to the NSCell before
534 * asking it to render
535 * naturalSize - The natural dimensions of this control.
536 * If the control rect size is not equal to either of these, a scale
537 * will be applied to the context so that rendering the control at the
538 * natural size will result in it filling the destRect space.
539 * If a control has no natural dimensions in either/both axes, pass 0.0f.
540 * minimumSize - The minimum dimensions of this control.
541 * If the control rect size is less than the minimum for a given axis,
542 * a scale will be applied to the context so that the minimum is used
543 * for drawing. If a control has no minimum dimensions in either/both
545 * marginSet - an array of margins; a multidimensional array of [2][3][4],
546 * with the first dimension being the OS version (Tiger or Leopard),
547 * the second being the control size (mini, small, regular), and the third
548 * being the 4 margin values (left, top, right, bottom).
549 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
550 * matter if this is really the right view; it just has to return YES when
551 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
552 * mirrorHorizontal - whether to mirror the cell horizontally
554 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext,
555 const HIRect& destRect,
556 NSControlSize controlSize, NSSize naturalSize,
558 const float marginSet[][3][4], NSView* view,
559 BOOL mirrorHorizontal) {
560 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
562 NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y,
563 destRect.size.width, destRect.size.height);
565 if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
566 if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
568 // Keep aspect ratio when scaling if one dimension is free.
569 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
570 drawRect.size.width =
571 destRect.size.width * naturalSize.height / destRect.size.height;
572 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
573 drawRect.size.height =
574 destRect.size.height * naturalSize.width / destRect.size.width;
576 // Honor minimum sizes.
577 if (drawRect.size.width < minimumSize.width)
578 drawRect.size.width = minimumSize.width;
579 if (drawRect.size.height < minimumSize.height)
580 drawRect.size.height = minimumSize.height;
582 [NSGraphicsContext saveGraphicsState];
584 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
585 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
586 // Inflate the rect Gecko gave us by the margin for the control.
587 InflateControlRect(&drawRect, controlSize, marginSet);
589 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
591 setCurrentContext:[NSGraphicsContext
592 graphicsContextWithCGContext:cgContext
595 DrawCellIncludingFocusRing(cell, drawRect, view);
597 [NSGraphicsContext setCurrentContext:savedContext];
599 float w = ceil(drawRect.size.width);
600 float h = ceil(drawRect.size.height);
601 NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
603 // inflate to figure out the frame we need to tell NSCell to draw in, to get
604 // something that's 0,0,w,h
605 InflateControlRect(&tmpRect, controlSize, marginSet);
607 // and then, expand by kMaxFocusRingWidth size to make sure we can capture
609 w += kMaxFocusRingWidth * 2.0;
610 h += kMaxFocusRingWidth * 2.0;
612 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
613 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
614 CGContextRef ctx = CGBitmapContextCreate(
615 NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
616 (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
617 CGColorSpaceRelease(rgb);
619 // We need to flip the image twice in order to avoid drawing bugs on 10.4,
620 // see bug 465069. This is the first flip transform, applied to cgContext.
621 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
622 CGContextTranslateCTM(cgContext, 0.0f,
623 -(2.0 * destRect.origin.y + destRect.size.height));
624 if (mirrorHorizontal) {
625 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
626 CGContextTranslateCTM(
627 cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
630 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
632 setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
635 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
637 // Set the context's "base transform" to in order to get correctly-sized
639 CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor,
640 backingScaleFactor));
642 // This is the second flip transform, applied to ctx.
643 CGContextScaleCTM(ctx, 1.0f, -1.0f);
644 CGContextTranslateCTM(ctx, 0.0f,
645 -(2.0 * tmpRect.origin.y + tmpRect.size.height));
647 DrawCellIncludingFocusRing(cell, tmpRect, view);
649 [NSGraphicsContext setCurrentContext:savedContext];
651 CGImageRef img = CGBitmapContextCreateImage(ctx);
653 // Drop the image into the original destination rectangle, scaling to fit
654 // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
655 // doesn't extend beyond the overflow rect
656 float xscale = destRect.size.width / drawRect.size.width;
657 float yscale = destRect.size.height / drawRect.size.height;
658 float scaledFocusRingX =
659 xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
660 float scaledFocusRingY =
661 yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
662 CGContextDrawImage(cgContext,
663 CGRectMake(destRect.origin.x - scaledFocusRingX,
664 destRect.origin.y - scaledFocusRingY,
665 destRect.size.width + scaledFocusRingX * 2,
666 destRect.size.height + scaledFocusRingY * 2),
670 CGContextRelease(ctx);
673 [NSGraphicsContext restoreGraphicsState];
675 #if DRAW_IN_FRAME_DEBUG
676 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
677 CGContextFillRect(cgContext, destRect);
680 NS_OBJC_END_TRY_IGNORE_BLOCK;
683 struct CellRenderSettings {
684 // The natural dimensions of the control.
685 // If a control has no natural dimensions in either/both axes, set to 0.0f.
686 NSSize naturalSizes[3];
688 // The minimum dimensions of the control.
689 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
690 NSSize minimumSizes[3];
692 // A three-dimensional array,
693 // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and
694 // above), the second being the control size (mini, small, regular), and the
695 // third being the 4 margin values (left, top, right, bottom).
696 float margins[2][3][4];
700 * This is a helper method that returns the required NSControlSize given a size
701 * and the size of the three controls plus a tolerance.
702 * size - The width or the height of the element to draw.
703 * sizes - An array with the all the width/height of the element for its
705 * tolerance - The tolerance as passed to DrawCellWithSnapping.
706 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
708 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes,
710 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
717 for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
724 // If it's the latest value, we pick it.
726 return CocoaSizeForEnum(i);
729 if (size <= sizes[i] + tolerance && size < next) {
730 return CocoaSizeForEnum(i);
734 // If we are here, that means sizes[] was an array with only empty values
735 // or the algorithm above is wrong.
736 // The former can happen but the later would be wrong.
737 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
738 "We found no control! We shouldn't be there!");
739 return CocoaSizeForEnum(regularControlSize);
743 * Draw the given NSCell into the given cgContext with a nice control size.
745 * This function is similar to DrawCellWithScaling, but it decides what
746 * control size to use based on the destRect's size.
747 * Scaling is only applied when the difference between the destRect's size
748 * and the next smaller natural size is greater than snapTolerance. Otherwise
749 * it snaps to the next smaller control size without scaling because unscaled
750 * controls look nicer.
752 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext,
753 const HIRect& destRect,
754 const CellRenderSettings settings,
755 float verticalAlignFactor, NSView* view,
756 BOOL mirrorHorizontal,
757 float snapTolerance = 2.0f) {
758 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
760 const float rectWidth = destRect.size.width,
761 rectHeight = destRect.size.height;
762 const NSSize* sizes = settings.naturalSizes;
763 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
764 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
765 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
767 HIRect drawRect = destRect;
769 CGFloat controlWidths[3] = {miniSize.width, smallSize.width,
771 NSControlSize controlSizeX =
772 FindControlSize(rectWidth, controlWidths, snapTolerance);
773 CGFloat controlHeights[3] = {miniSize.height, smallSize.height,
775 NSControlSize controlSizeY =
776 FindControlSize(rectHeight, controlHeights, snapTolerance);
778 NSControlSize controlSize = NSControlSizeRegular;
779 size_t sizeIndex = 0;
781 // At some sizes, don't scale but snap.
782 const NSControlSize smallerControlSize =
783 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY)
786 const size_t smallerControlSizeIndex =
787 EnumSizeForCocoaSize(smallerControlSize);
788 const NSSize size = sizes[smallerControlSizeIndex];
789 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
790 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
791 if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
792 diffHeight <= snapTolerance) {
793 // Snap to the smaller control size.
794 controlSize = smallerControlSize;
795 sizeIndex = smallerControlSizeIndex;
796 MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
798 // Resize and center the drawRect.
799 if (sizes[sizeIndex].width) {
801 ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
802 drawRect.size.width = sizes[sizeIndex].width;
804 if (sizes[sizeIndex].height) {
806 floor((destRect.size.height - sizes[sizeIndex].height) *
807 verticalAlignFactor);
808 drawRect.size.height = sizes[sizeIndex].height;
811 // Use the larger control size.
813 EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
816 sizeIndex = EnumSizeForCocoaSize(controlSize);
819 [cell setControlSize:controlSize];
821 MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
822 const NSSize minimumSize = settings.minimumSizes[sizeIndex];
823 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
824 minimumSize, settings.margins, view, mirrorHorizontal);
826 NS_OBJC_END_TRY_IGNORE_BLOCK;
829 @interface NSWindow (CoreUIRendererPrivate)
830 + (CUIRendererRef)coreUIRenderer;
833 @interface NSObject (NSAppearanceCoreUIRendering)
834 - (void)_drawInRect:(CGRect)rect
835 context:(CGContextRef)cgContext
839 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext,
840 NSDictionary* aOptions,
841 bool aSkipAreaCheck = false) {
842 if (!aSkipAreaCheck &&
843 aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
847 NSAppearance* appearance = NSAppearance.currentAppearance;
849 [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
850 // Render through NSAppearance on Mac OS 10.10 and up. This will call
851 // CUIDraw with a CoreUI renderer that will give us the correct 10.10
852 // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
853 // renders 10.9-style widgets on 10.10.
854 [appearance _drawInRect:aRect context:cgContext options:aOptions];
857 CUIRendererRef renderer =
858 [NSWindow respondsToSelector:@selector(coreUIRenderer)]
859 ? [NSWindow coreUIRenderer]
861 CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
865 static float VerticalAlignFactor(nsIFrame* aFrame) {
866 if (!aFrame) return 0.5f; // default: center
868 const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
869 auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
871 case StyleVerticalAlignKeyword::Top:
872 case StyleVerticalAlignKeyword::TextTop:
875 case StyleVerticalAlignKeyword::Sub:
876 case StyleVerticalAlignKeyword::Super:
877 case StyleVerticalAlignKeyword::Middle:
878 case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
881 case StyleVerticalAlignKeyword::Baseline:
882 case StyleVerticalAlignKeyword::Bottom:
883 case StyleVerticalAlignKeyword::TextBottom:
887 MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
892 static void ApplyControlParamsToNSCell(
893 nsNativeThemeCocoa::ControlParams aControlParams, NSCell* aCell) {
894 [aCell setEnabled:!aControlParams.disabled];
895 [aCell setShowsFirstResponder:(aControlParams.focused &&
896 !aControlParams.disabled &&
897 aControlParams.insideActiveWindow)];
898 [aCell setHighlighted:aControlParams.pressed];
901 // These are the sizes that Gecko needs to request to draw if it wants
902 // to get a standard-sized Aqua radio button drawn. Note that the rects
903 // that draw these are actually a little bigger.
904 static const CellRenderSettings radioSettings = {
906 NSMakeSize(11, 11), // mini
907 NSMakeSize(13, 13), // small
908 NSMakeSize(16, 16) // regular
910 {NSZeroSize, NSZeroSize, NSZeroSize},
913 {0, 0, 0, 0}, // mini
914 {0, 1, 1, 1}, // small
915 {0, 0, 0, 0} // regular
919 {0, 0, 0, 0}, // mini
920 {1, 1, 1, 2}, // small
921 {0, 0, 0, 0} // regular
924 static const CellRenderSettings checkboxSettings = {
926 NSMakeSize(11, 11), // mini
927 NSMakeSize(13, 13), // small
928 NSMakeSize(16, 16) // regular
930 {NSZeroSize, NSZeroSize, NSZeroSize},
933 {0, 1, 0, 0}, // mini
934 {0, 1, 0, 1}, // small
935 {0, 1, 0, 1} // regular
939 {0, 1, 0, 0}, // mini
940 {0, 1, 0, 1}, // small
941 {0, 1, 0, 1} // regular
944 static NSControlStateValue CellStateForCheckboxOrRadioState(
945 nsNativeThemeCocoa::CheckboxOrRadioState aState) {
947 case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
948 return NSControlStateValueOff;
949 case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
950 return NSControlStateValueOn;
951 case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
952 return NSControlStateValueMixed;
956 void nsNativeThemeCocoa::DrawCheckboxOrRadio(
957 CGContextRef cgContext, bool inCheckbox, const HIRect& inBoxRect,
958 const CheckboxOrRadioParams& aParams) {
959 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
961 NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
962 ApplyControlParamsToNSCell(aParams.controlParams, cell);
964 [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
965 [cell setControlTint:(aParams.controlParams.insideActiveWindow
966 ? [NSColor currentControlTint]
967 : NSClearControlTint)];
969 // Ensure that the control is square.
970 float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
971 HIRect drawRect = CGRectMake(
972 inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
973 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
976 if (mCellDrawWindow) {
977 mCellDrawWindow.cellsShouldLookActive =
978 aParams.controlParams.insideActiveWindow;
980 DrawCellWithSnapping(cell, cgContext, drawRect,
981 inCheckbox ? checkboxSettings : radioSettings,
982 aParams.verticalAlignFactor, mCellDrawView, NO);
984 NS_OBJC_END_TRY_IGNORE_BLOCK;
987 static const CellRenderSettings searchFieldSettings = {
989 NSMakeSize(0, 16), // mini
990 NSMakeSize(0, 19), // small
991 NSMakeSize(0, 22) // regular
994 NSMakeSize(32, 0), // mini
995 NSMakeSize(38, 0), // small
996 NSMakeSize(44, 0) // regular
1000 {0, 0, 0, 0}, // mini
1001 {0, 0, 0, 0}, // small
1002 {0, 0, 0, 0} // regular
1006 {0, 0, 0, 0}, // mini
1007 {0, 0, 0, 0}, // small
1008 {0, 0, 0, 0} // regular
1011 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
1012 nsIContent* content = aFrame->GetContent();
1017 if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox,
1018 nsGkAtoms::statusbar)) {
1022 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1023 case StyleAppearance::Statusbar:
1030 static bool IsInsideToolbar(nsIFrame* aFrame) {
1031 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
1032 if (IsToolbarStyleContainer(frame)) {
1039 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
1040 nsIFrame* aFrame, ElementState aEventState) {
1041 TextFieldParams params;
1042 params.insideToolbar = IsInsideToolbar(aFrame);
1043 params.disabled = aEventState.HasState(ElementState::DISABLED);
1045 // See ShouldUnconditionallyDrawFocusRingIfFocused.
1046 params.focused = aEventState.HasState(ElementState::FOCUS);
1048 params.rtl = IsFrameRTL(aFrame);
1049 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1053 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext,
1054 const HIRect& inBoxRect,
1055 const TextFieldParams& aParams) {
1056 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1058 NSTextFieldCell* cell = mTextFieldCell;
1059 [cell setEnabled:!aParams.disabled];
1060 [cell setShowsFirstResponder:aParams.focused];
1062 if (mCellDrawWindow) {
1063 mCellDrawWindow.cellsShouldLookActive =
1064 YES; // TODO: propagate correct activeness state
1066 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
1067 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1069 NS_OBJC_END_TRY_IGNORE_BLOCK;
1072 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext,
1073 const HIRect& inBoxRect,
1074 const TextFieldParams& aParams) {
1075 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1077 mSearchFieldCell.enabled = !aParams.disabled;
1078 mSearchFieldCell.showsFirstResponder = aParams.focused;
1079 mSearchFieldCell.placeholderString = @"";
1080 mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1082 if (mCellDrawWindow) {
1083 mCellDrawWindow.cellsShouldLookActive =
1084 YES; // TODO: propagate correct activeness state
1086 DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect,
1087 searchFieldSettings, aParams.verticalAlignFactor,
1088 mCellDrawView, aParams.rtl);
1090 NS_OBJC_END_TRY_IGNORE_BLOCK;
1093 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1094 // Mac always draws focus rings for textboxes and lists.
1095 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1096 case StyleAppearance::NumberInput:
1097 case StyleAppearance::Textfield:
1098 case StyleAppearance::Textarea:
1099 case StyleAppearance::Searchfield:
1100 case StyleAppearance::Listbox:
1107 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1108 nsIFrame* aFrame, ElementState aEventState) {
1109 ControlParams params;
1110 params.disabled = aEventState.HasState(ElementState::DISABLED);
1111 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1113 aEventState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER);
1114 params.focused = aEventState.HasState(ElementState::FOCUS) &&
1115 (aEventState.HasState(ElementState::FOCUSRING) ||
1116 ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1117 params.rtl = IsFrameRTL(aFrame);
1121 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1122 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1124 static const CellRenderSettings pushButtonSettings = {
1126 NSMakeSize(0, 16), // mini
1127 NSMakeSize(0, 19), // small
1128 NSMakeSize(0, 22) // regular
1131 NSMakeSize(18, 0), // mini
1132 NSMakeSize(26, 0), // small
1133 NSMakeSize(30, 0) // regular
1137 {0, 0, 0, 0}, // mini
1138 {4, 0, 4, 1}, // small
1139 {5, 0, 5, 2} // regular
1143 {0, 0, 0, 0}, // mini
1144 {4, 0, 4, 1}, // small
1145 {5, 0, 5, 2} // regular
1148 // The height at which we start doing square buttons instead of rounded buttons
1149 // Rounded buttons look bad if drawn at a height greater than 26, so at that
1150 // point we switch over to doing square buttons which looks fine at any size.
1151 #define DO_SQUARE_BUTTON_HEIGHT 26
1153 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext,
1154 const HIRect& inBoxRect,
1155 ButtonType aButtonType,
1156 ControlParams aControlParams) {
1157 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1159 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1160 [mPushButtonCell setBezelStyle:NSBezelStyleRounded];
1161 mPushButtonCell.keyEquivalent =
1162 aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1164 if (mCellDrawWindow) {
1165 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1167 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect,
1168 pushButtonSettings, 0.5f, mCellDrawView,
1169 aControlParams.rtl, 1.0f);
1171 NS_OBJC_END_TRY_IGNORE_BLOCK;
1174 void nsNativeThemeCocoa::DrawSquareBezelPushButton(
1175 CGContextRef cgContext, const HIRect& inBoxRect,
1176 ControlParams aControlParams) {
1177 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1179 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1180 [mPushButtonCell setBezelStyle:NSBezelStyleShadowlessSquare];
1182 if (mCellDrawWindow) {
1183 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1185 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect,
1186 NSControlSizeRegular, NSZeroSize, NSMakeSize(14, 0), NULL,
1187 mCellDrawView, aControlParams.rtl);
1189 NS_OBJC_END_TRY_IGNORE_BLOCK;
1192 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext,
1193 const HIRect& inBoxRect,
1194 ControlParams aControlParams) {
1195 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1197 ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1199 if (mCellDrawWindow) {
1200 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1202 DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect,
1203 NSControlSizeRegular, NSZeroSize, kHelpButtonSize, NULL,
1205 false); // Don't mirror icon in RTL.
1207 NS_OBJC_END_TRY_IGNORE_BLOCK;
1210 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext,
1211 const HIRect& inBoxRect,
1212 ControlParams aControlParams,
1213 NSControlStateValue aCellState) {
1214 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1216 ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1217 [mDisclosureButtonCell setState:aCellState];
1219 if (mCellDrawWindow) {
1220 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1222 DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect,
1223 NSControlSizeRegular, NSZeroSize, kDisclosureButtonSize,
1224 NULL, mCellDrawView,
1225 false); // Don't mirror icon in RTL.
1227 NS_OBJC_END_TRY_IGNORE_BLOCK;
1230 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext,
1231 const HIRect& aRenderRect,
1234 static void RenderTransformedHIThemeControl(CGContextRef aCGContext,
1235 const HIRect& aRect,
1236 RenderHIThemeControlFunction aFunc,
1238 BOOL mirrorHorizontally = NO) {
1239 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1240 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1243 HIRect drawRect = aRect;
1244 drawRect.origin = CGPointZero;
1246 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
1247 savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1253 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1255 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1256 aFunc(aCGContext, drawRect, aData);
1258 // Inflate the buffer to capture focus rings.
1259 int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1260 int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1262 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1263 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1264 CGContextRef bitmapctx = CGBitmapContextCreate(
1265 NULL, w * backingScaleFactor, h * backingScaleFactor, 8,
1266 w * backingScaleFactor * 4, colorSpace,
1267 kCGImageAlphaPremultipliedFirst);
1268 CGColorSpaceRelease(colorSpace);
1270 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1271 CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1273 // Set the context's "base transform" to in order to get correctly-sized
1275 CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(
1276 backingScaleFactor, backingScaleFactor));
1278 // HITheme always wants to draw into a flipped context, or things
1280 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1281 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1283 aFunc(bitmapctx, drawRect, aData);
1285 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1287 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1289 // We need to unflip, so that we can do a DrawImage without getting a
1291 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1292 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1294 if (mirrorHorizontally) {
1295 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1296 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1299 HIRect inflatedDrawRect =
1300 CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1301 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1303 CGContextSetCTM(aCGContext, ctm);
1305 CGImageRelease(bitmap);
1306 CGContextRelease(bitmapctx);
1309 CGContextSetCTM(aCGContext, savedCTM);
1312 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect,
1314 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1315 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal,
1319 static ThemeDrawState ToThemeDrawState(
1320 const nsNativeThemeCocoa::ControlParams& aParams) {
1321 if (aParams.disabled) {
1322 return kThemeStateUnavailable;
1324 if (aParams.pressed) {
1325 return kThemeStatePressed;
1327 return kThemeStateActive;
1330 void nsNativeThemeCocoa::DrawHIThemeButton(
1331 CGContextRef cgContext, const HIRect& aRect, ThemeButtonKind aKind,
1332 ThemeButtonValue aValue, ThemeDrawState aState,
1333 ThemeButtonAdornment aAdornment, const ControlParams& aParams) {
1334 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1336 HIThemeButtonDrawInfo bdi;
1341 bdi.adornment = aAdornment;
1343 if (aParams.focused && aParams.insideActiveWindow) {
1344 bdi.adornment |= kThemeAdornmentFocus;
1347 RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi,
1350 #if DRAW_IN_FRAME_DEBUG
1351 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1352 CGContextFillRect(cgContext, inBoxRect);
1355 NS_OBJC_END_TRY_IGNORE_BLOCK;
1358 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext,
1359 const HIRect& inBoxRect,
1360 const ButtonParams& aParams) {
1361 ControlParams controlParams = aParams.controlParams;
1363 switch (aParams.button) {
1364 case ButtonType::eRegularPushButton:
1365 case ButtonType::eDefaultPushButton:
1366 DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1368 case ButtonType::eSquareBezelPushButton:
1369 DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1371 case ButtonType::eArrowButton:
1372 DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1373 kThemeStateUnavailable, kThemeAdornmentArrowDownArrow,
1376 case ButtonType::eHelpButton:
1377 DrawHelpButton(cgContext, inBoxRect, controlParams);
1379 case ButtonType::eTreeTwistyPointingRight:
1380 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1381 kThemeDisclosureRight, ToThemeDrawState(controlParams),
1382 kThemeAdornmentNone, controlParams);
1384 case ButtonType::eTreeTwistyPointingDown:
1385 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton,
1386 kThemeDisclosureDown, ToThemeDrawState(controlParams),
1387 kThemeAdornmentNone, controlParams);
1389 case ButtonType::eDisclosureButtonClosed:
1390 DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1391 NSControlStateValueOff);
1393 case ButtonType::eDisclosureButtonOpen:
1394 DrawDisclosureButton(cgContext, inBoxRect, controlParams,
1395 NSControlStateValueOn);
1400 nsNativeThemeCocoa::TreeHeaderCellParams
1401 nsNativeThemeCocoa::ComputeTreeHeaderCellParams(nsIFrame* aFrame,
1402 ElementState aEventState) {
1403 TreeHeaderCellParams params;
1404 params.controlParams = ComputeControlParams(aFrame, aEventState);
1405 params.sortDirection = GetTreeSortDirection(aFrame);
1406 params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1410 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1411 // This method has been present in the same form since at least macOS 10.4.
1412 - (void)_setSortable:(BOOL)arg1
1413 showSortIndicator:(BOOL)arg2
1414 ascending:(BOOL)arg3
1415 priority:(NSInteger)arg4
1416 highlightForSort:(BOOL)arg5;
1419 void nsNativeThemeCocoa::DrawTreeHeaderCell(
1420 CGContextRef cgContext, const HIRect& inBoxRect,
1421 const TreeHeaderCellParams& aParams) {
1422 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1424 // Without clearing the cell's title, it takes on a default value of "Field",
1425 // which is displayed underneath the title set in the front-end.
1426 NSCell* cell = (NSCell*)mTreeHeaderCell;
1429 if ([mTreeHeaderCell
1430 respondsToSelector:@selector
1432 showSortIndicator:ascending:priority:highlightForSort:)]) {
1433 switch (aParams.sortDirection) {
1434 case eTreeSortDirection_Ascending:
1435 [mTreeHeaderCell _setSortable:YES
1436 showSortIndicator:YES
1439 highlightForSort:YES];
1441 case eTreeSortDirection_Descending:
1442 [mTreeHeaderCell _setSortable:YES
1443 showSortIndicator:YES
1446 highlightForSort:YES];
1449 // eTreeSortDirection_Natural
1450 [mTreeHeaderCell _setSortable:YES
1451 showSortIndicator:NO
1454 highlightForSort:NO];
1459 mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1460 mTreeHeaderCell.state =
1461 (mTreeHeaderCell.enabled && aParams.controlParams.pressed)
1462 ? NSControlStateValueOn
1463 : NSControlStateValueOff;
1465 mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1467 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1468 NSGraphicsContext.currentContext =
1469 [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
1470 DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1471 NSGraphicsContext.currentContext = savedContext;
1473 #if DRAW_IN_FRAME_DEBUG
1474 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1475 CGContextFillRect(cgContext, inBoxRect);
1478 NS_OBJC_END_TRY_IGNORE_BLOCK;
1481 static const CellRenderSettings dropdownSettings = {
1483 NSMakeSize(0, 16), // mini
1484 NSMakeSize(0, 19), // small
1485 NSMakeSize(0, 22) // regular
1488 NSMakeSize(18, 0), // mini
1489 NSMakeSize(38, 0), // small
1490 NSMakeSize(44, 0) // regular
1494 {1, 1, 2, 1}, // mini
1495 {3, 0, 3, 1}, // small
1496 {3, 0, 3, 0} // regular
1500 {1, 1, 2, 1}, // mini
1501 {3, 0, 3, 1}, // small
1502 {3, 0, 3, 0} // regular
1505 static const CellRenderSettings editableMenulistSettings = {
1507 NSMakeSize(0, 15), // mini
1508 NSMakeSize(0, 18), // small
1509 NSMakeSize(0, 21) // regular
1512 NSMakeSize(18, 0), // mini
1513 NSMakeSize(38, 0), // small
1514 NSMakeSize(44, 0) // regular
1518 {0, 0, 2, 2}, // mini
1519 {0, 0, 3, 2}, // small
1520 {0, 1, 3, 3} // regular
1524 {0, 0, 2, 2}, // mini
1525 {0, 0, 3, 2}, // small
1526 {0, 1, 3, 3} // regular
1529 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext,
1530 const HIRect& inBoxRect,
1531 const DropdownParams& aParams) {
1532 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1534 [mDropdownCell setPullsDown:aParams.pullsDown];
1536 aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1538 ApplyControlParamsToNSCell(aParams.controlParams, cell);
1540 if (aParams.controlParams.insideActiveWindow) {
1541 [cell setControlTint:[NSColor currentControlTint]];
1543 [cell setControlTint:NSClearControlTint];
1546 const CellRenderSettings& settings =
1547 aParams.editable ? editableMenulistSettings : dropdownSettings;
1549 if (mCellDrawWindow) {
1550 mCellDrawWindow.cellsShouldLookActive =
1551 aParams.controlParams.insideActiveWindow;
1553 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f,
1554 mCellDrawView, aParams.controlParams.rtl);
1556 NS_OBJC_END_TRY_IGNORE_BLOCK;
1559 static const CellRenderSettings spinnerSettings = {
1562 16), // mini (width trimmed by 2px to reduce blank border)
1563 NSMakeSize(15, 22), // small
1564 NSMakeSize(19, 27) // regular
1568 16), // mini (width trimmed by 2px to reduce blank border)
1569 NSMakeSize(15, 22), // small
1570 NSMakeSize(19, 27) // regular
1574 {0, 0, 0, 0}, // mini
1575 {0, 0, 0, 0}, // small
1576 {0, 0, 0, 0} // regular
1580 {0, 0, 0, 0}, // mini
1581 {0, 0, 0, 0}, // small
1582 {0, 0, 0, 0} // regular
1585 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(
1586 ThemeButtonKind aKind, const SpinButtonParams& aParams) {
1587 HIThemeButtonDrawInfo bdi;
1590 bdi.value = kThemeButtonOff;
1591 bdi.adornment = kThemeAdornmentNone;
1593 if (aParams.disabled) {
1594 bdi.state = kThemeStateUnavailable;
1595 } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1596 if (*aParams.pressedButton == SpinButton::eUp) {
1597 bdi.state = kThemeStatePressedUp;
1599 bdi.state = kThemeStatePressedDown;
1602 bdi.state = kThemeStateActive;
1608 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext,
1609 const HIRect& inBoxRect,
1610 const SpinButtonParams& aParams) {
1611 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1613 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1614 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1616 NS_OBJC_END_TRY_IGNORE_BLOCK;
1619 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
1620 const HIRect& inBoxRect,
1621 SpinButton aDrawnButton,
1622 const SpinButtonParams& aParams) {
1623 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1625 HIThemeButtonDrawInfo bdi =
1626 SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1628 // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1629 // together as a single unit (presumably because when one button is active,
1630 // the appearance of both changes (in different ways)). Here we have to paint
1631 // both buttons, using clip to hide the one we don't want to paint.
1632 HIRect drawRect = inBoxRect;
1633 drawRect.size.height *= 2;
1634 if (aDrawnButton == SpinButton::eDown) {
1635 drawRect.origin.y -= inBoxRect.size.height;
1638 // Shift the drawing a little to the left, since cocoa paints with more
1639 // blank space around the visual buttons than we'd like:
1640 drawRect.origin.x -= 1;
1642 CGContextSaveGState(cgContext);
1643 CGContextClipToRect(cgContext, inBoxRect);
1645 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1647 CGContextRestoreGState(cgContext);
1649 NS_OBJC_END_TRY_IGNORE_BLOCK;
1652 static const CellRenderSettings progressSettings[2][2] = {
1653 // Vertical progress bar.
1654 {// Determined settings.
1657 NSMakeSize(10, 0), // small
1658 NSMakeSize(16, 0) // regular
1660 {NSZeroSize, NSZeroSize, NSZeroSize},
1663 {0, 0, 0, 0}, // mini
1664 {1, 1, 1, 1}, // small
1665 {1, 1, 1, 1} // regular
1667 // There is no horizontal margin in regular undetermined size.
1670 NSMakeSize(10, 0), // small
1671 NSMakeSize(16, 0) // regular
1673 {NSZeroSize, NSZeroSize, NSZeroSize},
1676 {0, 0, 0, 0}, // mini
1677 {1, 1, 1, 1}, // small
1678 {1, 0, 1, 0} // regular
1682 {0, 0, 0, 0}, // mini
1683 {1, 1, 1, 1}, // small
1684 {1, 0, 1, 0} // regular
1686 // Horizontal progress bar.
1687 {// Determined settings.
1690 NSMakeSize(0, 10), // small
1691 NSMakeSize(0, 16) // regular
1693 {NSZeroSize, NSZeroSize, NSZeroSize},
1696 {0, 0, 0, 0}, // mini
1697 {1, 1, 1, 1}, // small
1698 {1, 1, 1, 1} // regular
1702 {0, 0, 0, 0}, // mini
1703 {1, 1, 1, 1}, // small
1704 {1, 1, 1, 1} // regular
1706 // There is no horizontal margin in regular undetermined size.
1709 NSMakeSize(0, 10), // small
1710 NSMakeSize(0, 16) // regular
1712 {NSZeroSize, NSZeroSize, NSZeroSize},
1715 {0, 0, 0, 0}, // mini
1716 {1, 1, 1, 1}, // small
1717 {0, 1, 0, 1} // regular
1721 {0, 0, 0, 0}, // mini
1722 {1, 1, 1, 1}, // small
1723 {0, 1, 0, 1} // regular
1726 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1727 nsIFrame* aFrame, ElementState aEventState, bool aIsHorizontal) {
1728 ProgressParams params;
1729 params.value = GetProgressValue(aFrame);
1730 params.max = GetProgressMaxValue(aFrame);
1731 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1732 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1733 params.indeterminate = aEventState.HasState(ElementState::INDETERMINATE);
1734 params.horizontal = aIsHorizontal;
1735 params.rtl = IsFrameRTL(aFrame);
1739 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext,
1740 const HIRect& inBoxRect,
1741 const ProgressParams& aParams) {
1742 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1744 NSProgressBarCell* cell = mProgressBarCell;
1746 [cell setValue:aParams.value];
1747 [cell setMax:aParams.max];
1748 [cell setIndeterminate:aParams.indeterminate];
1749 [cell setHorizontal:aParams.horizontal];
1751 setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1752 : NSClearControlTint)];
1754 if (mCellDrawWindow) {
1755 mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1757 DrawCellWithSnapping(
1758 cell, cgContext, inBoxRect,
1759 progressSettings[aParams.horizontal][aParams.indeterminate],
1760 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1762 NS_OBJC_END_TRY_IGNORE_BLOCK;
1765 static const CellRenderSettings meterSetting = {
1767 NSMakeSize(0, 16), // mini
1768 NSMakeSize(0, 16), // small
1769 NSMakeSize(0, 16) // regular
1771 {NSZeroSize, NSZeroSize, NSZeroSize},
1774 {1, 1, 1, 1}, // mini
1775 {1, 1, 1, 1}, // small
1776 {1, 1, 1, 1} // regular
1780 {1, 1, 1, 1}, // mini
1781 {1, 1, 1, 1}, // small
1782 {1, 1, 1, 1} // regular
1785 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(
1787 nsIContent* content = aFrame->GetContent();
1788 if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1789 return MeterParams();
1792 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1794 params.value = meterElement->Value();
1795 params.min = meterElement->Min();
1796 params.max = meterElement->Max();
1797 ElementState states = meterElement->State();
1798 if (states.HasState(ElementState::SUB_OPTIMUM)) {
1799 params.optimumState = OptimumState::eSubOptimum;
1800 } else if (states.HasState(ElementState::SUB_SUB_OPTIMUM)) {
1801 params.optimumState = OptimumState::eSubSubOptimum;
1803 params.horizontal = !IsVerticalMeter(aFrame);
1804 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1805 params.rtl = IsFrameRTL(aFrame);
1810 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext,
1811 const HIRect& inBoxRect,
1812 const MeterParams& aParams) {
1813 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1815 NSLevelIndicatorCell* cell = mMeterBarCell;
1817 [cell setMinValue:aParams.min];
1818 [cell setMaxValue:aParams.max];
1819 [cell setDoubleValue:aParams.value];
1822 * The way HTML and Cocoa defines the meter/indicator widget are different.
1823 * So, we are going to use a trick to get the Cocoa widget showing what we
1824 * are expecting: we set the warningValue or criticalValue to the current
1825 * value when we want to have the widget to be in the warning or critical
1828 switch (aParams.optimumState) {
1829 case OptimumState::eOptimum:
1830 [cell setWarningValue:aParams.max + 1];
1831 [cell setCriticalValue:aParams.max + 1];
1833 case OptimumState::eSubOptimum:
1834 [cell setWarningValue:aParams.value];
1835 [cell setCriticalValue:aParams.max + 1];
1837 case OptimumState::eSubSubOptimum:
1838 [cell setWarningValue:aParams.max + 1];
1839 [cell setCriticalValue:aParams.value];
1843 HIRect rect = CGRectStandardize(inBoxRect);
1844 BOOL vertical = !aParams.horizontal;
1846 CGContextSaveGState(cgContext);
1850 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1851 * show a rotated horizontal meter bar.
1852 * Given that we want to show a vertical meter bar, we assume that the rect
1853 * has vertical dimensions but we can't correctly draw a meter widget inside
1854 * such a rectangle so we need to inverse width and height (and re-position)
1855 * to get a rectangle with horizontal dimensions.
1856 * Finally, we want to show a vertical meter so we want to rotate the result
1857 * so it is vertical. We do that by changing the context.
1859 CGFloat tmp = rect.size.width;
1860 rect.size.width = rect.size.height;
1861 rect.size.height = tmp;
1862 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1863 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1865 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1866 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1867 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect),
1868 -CGRectGetMidY(rect));
1871 if (mCellDrawWindow) {
1872 mCellDrawWindow.cellsShouldLookActive =
1873 YES; // TODO: propagate correct activeness state
1875 DrawCellWithSnapping(cell, cgContext, rect, meterSetting,
1876 aParams.verticalAlignFactor, mCellDrawView,
1877 !vertical && aParams.rtl);
1879 CGContextRestoreGState(cgContext);
1881 NS_OBJC_END_TRY_IGNORE_BLOCK
1884 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext,
1885 const HIRect& inBoxRect,
1886 bool aIsInsideActiveWindow) {
1887 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1889 HIThemeTabPaneDrawInfo tpdi;
1892 tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1893 tpdi.direction = kThemeTabNorth;
1894 tpdi.size = kHIThemeTabSizeNormal;
1895 tpdi.kind = kHIThemeTabKindNormal;
1897 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1899 NS_OBJC_END_TRY_IGNORE_BLOCK;
1902 Maybe<nsNativeThemeCocoa::ScaleParams>
1903 nsNativeThemeCocoa::ComputeHTMLScaleParams(nsIFrame* aFrame,
1904 ElementState aEventState) {
1905 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1910 bool isHorizontal = IsRangeHorizontal(aFrame);
1912 // ScaleParams requires integer min, max and value. This is purely for
1913 // drawing, so we normalize to a range 0-1000 here.
1915 params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1918 params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1919 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1920 params.focused = aEventState.HasState(ElementState::FOCUSRING);
1921 params.disabled = aEventState.HasState(ElementState::DISABLED);
1922 params.horizontal = isHorizontal;
1923 return Some(params);
1926 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext,
1927 const HIRect& inBoxRect,
1928 const ScaleParams& aParams) {
1929 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1931 HIThemeTrackDrawInfo tdi;
1934 tdi.kind = kThemeMediumSlider;
1935 tdi.bounds = inBoxRect;
1936 tdi.min = aParams.min;
1937 tdi.max = aParams.max;
1938 tdi.value = aParams.value;
1939 tdi.attributes = kThemeTrackShowThumb;
1940 if (aParams.horizontal) {
1941 tdi.attributes |= kThemeTrackHorizontal;
1943 if (aParams.reverse) {
1944 tdi.attributes |= kThemeTrackRightToLeft;
1946 if (aParams.focused) {
1947 tdi.attributes |= kThemeTrackHasFocus;
1949 if (aParams.disabled) {
1950 tdi.enableState = kThemeTrackDisabled;
1953 aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1955 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1956 tdi.trackInfo.slider.pressState = 0;
1958 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1960 NS_OBJC_END_TRY_IGNORE_BLOCK;
1963 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore,
1965 // Usually a separator is drawn by the segment to the right of the
1966 // separator, but pressed and selected segments have higher priority.
1967 if (!aBefore || !aAfter) return nullptr;
1968 if (IsSelectedButton(aAfter)) return aAfter;
1969 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
1973 static CGRect SeparatorAdjustedRect(CGRect aRect,
1974 nsNativeThemeCocoa::SegmentParams aParams) {
1975 // A separator between two segments should always be located in the leftmost
1976 // pixel column of the segment to the right of the separator, regardless of
1977 // who ends up drawing it.
1978 // CoreUI draws the separators inside the drawing rect.
1979 if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
1980 // The segment to the left of us draws the separator, so we need to make
1982 aRect.origin.x += 1;
1983 aRect.size.width -= 1;
1985 if (aParams.drawsRightSeparator) {
1986 // We draw the right separator, so we need to extend the draw rect into the
1987 // segment to our right.
1988 aRect.size.width += 1;
1993 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
1995 if (aIsLast) return @"kCUISegmentPositionOnly";
1996 return @"kCUISegmentPositionFirst";
1998 if (aIsLast) return @"kCUISegmentPositionLast";
1999 return @"kCUISegmentPositionMiddle";
2002 struct SegmentedControlRenderSettings {
2003 const CGFloat* heights;
2004 const NSString* widgetName;
2007 static const CGFloat tabHeights[3] = {17, 20, 23};
2009 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights,
2012 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2014 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2015 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2017 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2018 nsIFrame* aFrame, ElementState aEventState, SegmentType aSegmentType) {
2019 SegmentParams params;
2020 params.segmentType = aSegmentType;
2021 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2022 params.pressed = IsPressedButton(aFrame);
2023 params.selected = IsSelectedButton(aFrame);
2024 params.focused = aEventState.HasState(ElementState::FOCUSRING);
2025 bool isRTL = IsFrameRTL(aFrame);
2026 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2027 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2028 params.atLeftEnd = !left;
2029 params.atRightEnd = !right;
2030 params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2031 params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2036 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2037 nsNativeThemeCocoa::SegmentType aSegmentType) {
2038 switch (aSegmentType) {
2039 case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2040 return toolbarButtonRenderSettings;
2041 case nsNativeThemeCocoa::SegmentType::eTab:
2042 return tabRenderSettings;
2046 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext,
2047 const HIRect& inBoxRect,
2048 const SegmentParams& aParams) {
2049 SegmentedControlRenderSettings renderSettings =
2050 RenderSettingsForSegmentType(aParams.segmentType);
2051 NSControlSize controlSize =
2052 FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2053 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2055 NSDictionary* dict = @{
2056 @"widget" : renderSettings.widgetName,
2057 @"kCUIPresentationStateKey" :
2058 (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2059 : @"kCUIPresentationStateInactive"),
2060 @"kCUIPositionKey" :
2061 ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2062 @"kCUISegmentLeadingSeparatorKey" :
2063 [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2064 @"kCUISegmentTrailingSeparatorKey" :
2065 [NSNumber numberWithBool:aParams.drawsRightSeparator],
2066 @"value" : [NSNumber numberWithBool:aParams.selected],
2067 @"state" : (aParams.pressed
2069 : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2070 @"focus" : [NSNumber numberWithBool:aParams.focused],
2071 @"size" : CUIControlSizeForCocoaSize(controlSize),
2072 @"is.flipped" : [NSNumber numberWithBool:YES],
2073 @"direction" : @"up"
2076 RenderWithCoreUI(drawRect, cgContext, dict);
2079 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext,
2080 const HIRect& inBoxRect, bool aIsMain) {
2081 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2083 if (inBoxRect.size.height < 2.0f) return;
2085 CGContextSaveGState(cgContext);
2086 CGContextClipToRect(cgContext, inBoxRect);
2088 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2089 // and bottom bar. We only want the bottom bar, so we extend the draw rect
2090 // upwards to make space for the title bar, and then we clip it away.
2091 CGRect drawRect = inBoxRect;
2092 const int extendUpwards = 40;
2093 drawRect.origin.y -= extendUpwards;
2094 drawRect.size.height += extendUpwards;
2096 drawRect, cgContext,
2097 [NSDictionary dictionaryWithObjectsAndKeys:
2098 @"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2099 @"windowtype", (aIsMain ? @"normal" : @"inactive"),
2101 [NSNumber numberWithInt:inBoxRect.size.height],
2102 @"kCUIWindowFrameBottomBarHeightKey",
2103 [NSNumber numberWithBool:YES],
2104 @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2105 [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2107 CGContextRestoreGState(cgContext);
2109 NS_OBJC_END_TRY_IGNORE_BLOCK;
2112 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext,
2113 const CGRect& inBoxRect,
2115 mTextFieldCell.enabled = YES;
2116 mTextFieldCell.showsFirstResponder = aIsFocused;
2118 if (mCellDrawWindow) {
2119 mCellDrawWindow.cellsShouldLookActive = YES;
2122 // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do
2123 // the usual save+restore dance.
2124 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2125 NSGraphicsContext.currentContext =
2126 [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:YES];
2127 DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2128 NSGraphicsContext.currentContext = savedContext;
2131 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2132 return AppUnitsPerCSSPixel() >=
2133 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2136 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2137 nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2138 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2140 // setup to draw into the correct port
2141 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2143 gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2144 nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2145 float originalHeight = nativeWidgetRect.Height();
2146 nativeWidgetRect.Round();
2147 if (nativeWidgetRect.IsEmpty()) {
2148 return Nothing(); // Don't attempt to draw invisible widgets.
2151 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2153 // Use high-resolution drawing.
2154 nativeWidgetRect.Scale(0.5f);
2155 originalHeight *= 0.5f;
2158 ElementState elementState = GetContentState(aFrame, aAppearance);
2160 switch (aAppearance) {
2161 case StyleAppearance::Menupopup:
2162 case StyleAppearance::Tooltip:
2165 case StyleAppearance::Checkbox:
2166 case StyleAppearance::Radio: {
2167 bool isCheckbox = aAppearance == StyleAppearance::Checkbox;
2169 CheckboxOrRadioParams params;
2170 params.state = CheckboxOrRadioState::eOff;
2171 if (isCheckbox && elementState.HasState(ElementState::INDETERMINATE)) {
2172 params.state = CheckboxOrRadioState::eIndeterminate;
2173 } else if (elementState.HasState(ElementState::CHECKED)) {
2174 params.state = CheckboxOrRadioState::eOn;
2176 params.controlParams = ComputeControlParams(aFrame, elementState);
2177 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2179 return Some(WidgetInfo::Checkbox(params));
2181 return Some(WidgetInfo::Radio(params));
2184 case StyleAppearance::Button:
2185 if (IsDefaultButton(aFrame)) {
2186 // Check whether the default button is in a document that does not
2187 // match the :-moz-window-inactive pseudoclass. This activeness check
2188 // is different from the other "active window" checks in this file
2189 // because we absolutely need the button's default button appearance to
2190 // be in sync with its text color, and the text color is changed by
2191 // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2192 // default buttons in active windows have blue background and white
2193 // text, and default buttons in inactive windows have white background
2195 DocumentState docState = aFrame->PresContext()->Document()->State();
2196 ControlParams params = ComputeControlParams(aFrame, elementState);
2197 params.insideActiveWindow =
2198 !docState.HasState(DocumentState::WINDOW_INACTIVE);
2199 return Some(WidgetInfo::Button(
2200 ButtonParams{params, ButtonType::eDefaultPushButton}));
2202 if (IsButtonTypeMenu(aFrame)) {
2203 ControlParams controlParams =
2204 ComputeControlParams(aFrame, elementState);
2205 controlParams.pressed = IsOpenButton(aFrame);
2206 DropdownParams params;
2207 params.controlParams = controlParams;
2208 params.pullsDown = true;
2209 params.editable = false;
2210 return Some(WidgetInfo::Dropdown(params));
2212 if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2213 // If the button is tall enough, draw the square button style so that
2214 // buttons with non-standard content look good. Otherwise draw normal
2215 // rounded aqua buttons.
2216 // This comparison is done based on the height that is calculated
2217 // without the top, because the snapped height can be affected by the
2218 // top of the rect and that may result in different height depending on
2220 return Some(WidgetInfo::Button(
2221 ButtonParams{ComputeControlParams(aFrame, elementState),
2222 ButtonType::eSquareBezelPushButton}));
2224 return Some(WidgetInfo::Button(
2225 ButtonParams{ComputeControlParams(aFrame, elementState),
2226 ButtonType::eRegularPushButton}));
2228 case StyleAppearance::MozMacHelpButton:
2229 return Some(WidgetInfo::Button(
2230 ButtonParams{ComputeControlParams(aFrame, elementState),
2231 ButtonType::eHelpButton}));
2233 case StyleAppearance::MozMacDisclosureButtonOpen:
2234 case StyleAppearance::MozMacDisclosureButtonClosed: {
2235 ButtonType buttonType =
2236 (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2237 ? ButtonType::eDisclosureButtonClosed
2238 : ButtonType::eDisclosureButtonOpen;
2239 return Some(WidgetInfo::Button(ButtonParams{
2240 ComputeControlParams(aFrame, elementState), buttonType}));
2243 case StyleAppearance::Spinner: {
2244 bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2245 nsIContent* content = aFrame->GetContent();
2246 if (isSpinner && content->IsHTMLElement()) {
2247 // In HTML the theming for the spin buttons is drawn individually into
2248 // their own backgrounds instead of being drawn into the background of
2249 // their spinner parent as it is for XUL.
2252 SpinButtonParams params;
2253 if (content->IsElement()) {
2254 if (content->AsElement()->AttrValueIs(
2255 kNameSpaceID_None, nsGkAtoms::state, u"up"_ns, eCaseMatters)) {
2256 params.pressedButton = Some(SpinButton::eUp);
2257 } else if (content->AsElement()->AttrValueIs(
2258 kNameSpaceID_None, nsGkAtoms::state, u"down"_ns,
2260 params.pressedButton = Some(SpinButton::eDown);
2263 params.disabled = elementState.HasState(ElementState::DISABLED);
2264 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2266 return Some(WidgetInfo::SpinButtons(params));
2269 case StyleAppearance::SpinnerUpbutton:
2270 case StyleAppearance::SpinnerDownbutton: {
2271 nsNumberControlFrame* numberControlFrame =
2272 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2273 if (numberControlFrame) {
2274 SpinButtonParams params;
2275 if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2276 params.pressedButton = Some(SpinButton::eUp);
2277 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2278 params.pressedButton = Some(SpinButton::eDown);
2280 params.disabled = elementState.HasState(ElementState::DISABLED);
2281 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2282 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2283 return Some(WidgetInfo::SpinButtonUp(params));
2285 return Some(WidgetInfo::SpinButtonDown(params));
2289 case StyleAppearance::Toolbarbutton: {
2290 SegmentParams params = ComputeSegmentParams(aFrame, elementState,
2291 SegmentType::eToolbarButton);
2292 params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2293 return Some(WidgetInfo::Segment(params));
2296 case StyleAppearance::Separator:
2297 return Some(WidgetInfo::Separator());
2299 case StyleAppearance::MozWindowTitlebar: {
2303 case StyleAppearance::Statusbar:
2304 return Some(WidgetInfo::StatusBar(IsActiveToolbarControl(aFrame)));
2306 case StyleAppearance::MenulistButton:
2307 case StyleAppearance::Menulist: {
2308 ControlParams controlParams = ComputeControlParams(aFrame, elementState);
2309 controlParams.pressed = IsOpenButton(aFrame);
2310 DropdownParams params;
2311 params.controlParams = controlParams;
2312 params.pullsDown = false;
2313 params.editable = false;
2314 return Some(WidgetInfo::Dropdown(params));
2317 case StyleAppearance::MozMenulistArrowButton:
2318 return Some(WidgetInfo::Button(
2319 ButtonParams{ComputeControlParams(aFrame, elementState),
2320 ButtonType::eArrowButton}));
2322 case StyleAppearance::Textfield:
2323 case StyleAppearance::NumberInput:
2325 WidgetInfo::TextField(ComputeTextFieldParams(aFrame, elementState)));
2327 case StyleAppearance::Searchfield:
2328 return Some(WidgetInfo::SearchField(
2329 ComputeTextFieldParams(aFrame, elementState)));
2331 case StyleAppearance::ProgressBar: {
2332 if (elementState.HasState(ElementState::INDETERMINATE)) {
2333 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2334 NS_WARNING("Unable to animate progressbar!");
2337 return Some(WidgetInfo::ProgressBar(ComputeProgressParams(
2338 aFrame, elementState, !IsVerticalProgress(aFrame))));
2341 case StyleAppearance::Meter:
2342 return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2344 case StyleAppearance::Progresschunk:
2345 case StyleAppearance::Meterchunk:
2346 // Do nothing: progress and meter bars cases will draw chunks.
2349 case StyleAppearance::Treetwisty:
2350 return Some(WidgetInfo::Button(
2351 ButtonParams{ComputeControlParams(aFrame, elementState),
2352 ButtonType::eTreeTwistyPointingRight}));
2354 case StyleAppearance::Treetwistyopen:
2355 return Some(WidgetInfo::Button(
2356 ButtonParams{ComputeControlParams(aFrame, elementState),
2357 ButtonType::eTreeTwistyPointingDown}));
2359 case StyleAppearance::Treeheadercell:
2360 return Some(WidgetInfo::TreeHeaderCell(
2361 ComputeTreeHeaderCellParams(aFrame, elementState)));
2363 case StyleAppearance::Treeitem:
2364 case StyleAppearance::Treeview:
2365 return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2367 case StyleAppearance::Treeheader:
2368 // do nothing, taken care of by individual header cells
2369 case StyleAppearance::Treeline:
2370 // do nothing, these lines don't exist on macos
2373 case StyleAppearance::Range: {
2374 Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, elementState);
2376 return Some(WidgetInfo::Scale(*params));
2381 case StyleAppearance::Textarea:
2382 return Some(WidgetInfo::MultilineTextField(
2383 elementState.HasState(ElementState::FOCUS)));
2385 case StyleAppearance::Listbox:
2386 return Some(WidgetInfo::ListBox());
2388 case StyleAppearance::Tab: {
2389 SegmentParams params =
2390 ComputeSegmentParams(aFrame, elementState, SegmentType::eTab);
2391 params.pressed = params.pressed && !params.selected;
2392 return Some(WidgetInfo::Segment(params));
2395 case StyleAppearance::Tabpanels:
2396 return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2404 NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2408 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2409 StyleAppearance aAppearance,
2410 const nsRect& aRect,
2411 const nsRect& aDirtyRect,
2412 DrawOverflow aDrawOverflow) {
2413 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2415 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2416 return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance,
2417 aRect, aDirtyRect, aDrawOverflow);
2420 Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2426 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2428 gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2429 nativeWidgetRect.Round();
2431 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2433 auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2435 RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(),
2436 nativeWidgetRect, NSRectToRect(aDirtyRect, p2a),
2437 hidpi ? 2.0f : 1.0f);
2441 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2444 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2445 LookAndFeel::ColorScheme aScheme,
2446 DrawTarget& aDrawTarget,
2447 const gfx::Rect& aWidgetRect,
2448 const gfx::Rect& aDirtyRect,
2450 // Some of the drawing below uses NSAppearance.currentAppearance behind the
2451 // scenes. Set it to the appearance we want, the same way as
2452 // nsLookAndFeel::NativeGetColor.
2453 NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2455 // Also set the cell draw window's appearance; this is respected by
2456 // NSTextFieldCell (and its subclass NSSearchFieldCell).
2457 if (mCellDrawWindow) {
2458 mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2461 const Widget widget = aWidgetInfo.Widget();
2463 // Some widgets render using DrawTarget, and some using CGContext.
2465 case Widget::eColorFill: {
2466 sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2467 aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2471 AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2472 gfx::Rect widgetRect = aWidgetRect;
2473 gfx::Rect dirtyRect = aDirtyRect;
2475 dirtyRect.Scale(1.0f / aScale);
2476 widgetRect.Scale(1.0f / aScale);
2477 aDrawTarget.SetTransform(
2478 aDrawTarget.GetTransform().PreScale(aScale, aScale));
2480 // The remaining widgets require a CGContext.
2481 CGRect macRect = CGRectMake(widgetRect.X(), widgetRect.Y(),
2482 widgetRect.Width(), widgetRect.Height());
2484 gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2486 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2487 if (cgContext == nullptr) {
2488 // The Quartz surface handles 0x0 surfaces by internally
2489 // making all operations no-ops; there's no cgcontext created for them.
2490 // Unfortunately, this means that callers that want to render
2491 // directly to the CGContext need to be aware of this quirk.
2495 // Set the context's "base transform" to in order to get correctly-sized
2497 CGContextSetBaseCTM(cgContext,
2498 CGAffineTransformMakeScale(aScale, aScale));
2501 case Widget::eColorFill:
2502 MOZ_CRASH("already handled in outer switch");
2504 case Widget::eCheckbox: {
2505 CheckboxOrRadioParams params =
2506 aWidgetInfo.Params<CheckboxOrRadioParams>();
2507 DrawCheckboxOrRadio(cgContext, true, macRect, params);
2510 case Widget::eRadio: {
2511 CheckboxOrRadioParams params =
2512 aWidgetInfo.Params<CheckboxOrRadioParams>();
2513 DrawCheckboxOrRadio(cgContext, false, macRect, params);
2516 case Widget::eButton: {
2517 ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2518 DrawButton(cgContext, macRect, params);
2521 case Widget::eDropdown: {
2522 DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2523 DrawDropdown(cgContext, macRect, params);
2526 case Widget::eSpinButtons: {
2527 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2528 DrawSpinButtons(cgContext, macRect, params);
2531 case Widget::eSpinButtonUp: {
2532 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2533 DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2536 case Widget::eSpinButtonDown: {
2537 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2538 DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2541 case Widget::eSegment: {
2542 SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2543 DrawSegment(cgContext, macRect, params);
2546 case Widget::eSeparator: {
2547 HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2548 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2551 case Widget::eStatusBar: {
2552 bool isMain = aWidgetInfo.Params<bool>();
2553 DrawStatusBar(cgContext, macRect, isMain);
2556 case Widget::eGroupBox: {
2557 HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive,
2558 kHIThemeGroupBoxKindPrimary};
2559 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2562 case Widget::eTextField: {
2563 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2564 DrawTextField(cgContext, macRect, params);
2567 case Widget::eSearchField: {
2568 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2569 DrawSearchField(cgContext, macRect, params);
2572 case Widget::eProgressBar: {
2573 ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2574 DrawProgress(cgContext, macRect, params);
2577 case Widget::eMeter: {
2578 MeterParams params = aWidgetInfo.Params<MeterParams>();
2579 DrawMeter(cgContext, macRect, params);
2582 case Widget::eTreeHeaderCell: {
2583 TreeHeaderCellParams params =
2584 aWidgetInfo.Params<TreeHeaderCellParams>();
2585 DrawTreeHeaderCell(cgContext, macRect, params);
2588 case Widget::eScale: {
2589 ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2590 DrawScale(cgContext, macRect, params);
2593 case Widget::eMultilineTextField: {
2594 bool isFocused = aWidgetInfo.Params<bool>();
2595 DrawMultilineTextField(cgContext, macRect, isFocused);
2598 case Widget::eListBox: {
2599 // Fill the content with the control background color.
2600 CGContextSetFillColorWithColor(
2601 cgContext, [NSColor.controlBackgroundColor CGColor]);
2602 CGContextFillRect(cgContext, macRect);
2603 // Draw the frame using kCUIWidgetScrollViewFrame. This is what
2604 // NSScrollView uses in
2605 // -[NSScrollView drawRect:] if you give it a borderType of
2608 macRect, cgContext, @{
2609 @"widget" : @"kCUIWidgetScrollViewFrame",
2610 @"kCUIIsFlippedKey" : @YES,
2611 @"kCUIVariantMetal" : @NO,
2615 case Widget::eTabPanel: {
2616 bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2617 DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2622 // Reset the base CTM.
2623 CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2625 nativeDrawing.EndNativeDrawing();
2630 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2631 mozilla::wr::DisplayListBuilder& aBuilder,
2632 mozilla::wr::IpcResourceUpdateQueue& aResources,
2633 const mozilla::layers::StackingContextHelper& aSc,
2634 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2635 StyleAppearance aAppearance, const nsRect& aRect) {
2636 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2637 return ThemeCocoa::CreateWebRenderCommandsForWidget(
2638 aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
2641 // This list needs to stay consistent with the list in DrawWidgetBackground.
2642 // For every switch case in DrawWidgetBackground, there are three choices:
2643 // - If the case in DrawWidgetBackground draws nothing for the given widget
2644 // type, then don't list it here. We will hit the "default: return true;"
2646 // - If the case in DrawWidgetBackground draws something simple for the given
2647 // widget type, imitate that drawing using WebRender commands.
2648 // - If the case in DrawWidgetBackground draws something complicated for the
2649 // given widget type, return false here.
2650 switch (aAppearance) {
2651 case StyleAppearance::Checkbox:
2652 case StyleAppearance::Radio:
2653 case StyleAppearance::Button:
2654 case StyleAppearance::MozMacHelpButton:
2655 case StyleAppearance::MozMacDisclosureButtonOpen:
2656 case StyleAppearance::MozMacDisclosureButtonClosed:
2657 case StyleAppearance::Spinner:
2658 case StyleAppearance::SpinnerUpbutton:
2659 case StyleAppearance::SpinnerDownbutton:
2660 case StyleAppearance::Toolbarbutton:
2661 case StyleAppearance::Separator:
2662 case StyleAppearance::MozWindowTitlebar:
2663 case StyleAppearance::Statusbar:
2664 case StyleAppearance::Menulist:
2665 case StyleAppearance::MenulistButton:
2666 case StyleAppearance::MozMenulistArrowButton:
2667 case StyleAppearance::Textfield:
2668 case StyleAppearance::NumberInput:
2669 case StyleAppearance::Searchfield:
2670 case StyleAppearance::ProgressBar:
2671 case StyleAppearance::Meter:
2672 case StyleAppearance::Treeheadercell:
2673 case StyleAppearance::Treetwisty:
2674 case StyleAppearance::Treetwistyopen:
2675 case StyleAppearance::Treeitem:
2676 case StyleAppearance::Treeview:
2677 case StyleAppearance::Range:
2680 case StyleAppearance::Textarea:
2681 case StyleAppearance::Listbox:
2682 case StyleAppearance::Tab:
2683 case StyleAppearance::Tabpanels:
2691 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(
2692 const LayoutDeviceIntMargin& aMargin, nsIFrame* aFrame) {
2693 // Assuming aMargin was originally specified for a horizontal LTR context,
2694 // reinterpret the values as logical, and then map to physical coords
2695 // according to aFrame's actual writing mode.
2696 WritingMode wm = aFrame->GetWritingMode();
2697 nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
2699 .GetPhysicalMargin(wm);
2700 return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2703 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2704 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2705 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2706 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2708 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(
2709 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
2710 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2711 return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
2714 LayoutDeviceIntMargin result;
2716 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2717 switch (aAppearance) {
2718 case StyleAppearance::Button: {
2719 if (IsButtonTypeMenu(aFrame)) {
2720 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2723 DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2728 case StyleAppearance::Toolbarbutton: {
2729 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2733 case StyleAppearance::Checkbox:
2734 case StyleAppearance::Radio: {
2735 // nsCheckboxRadioFrame::GetIntrinsicWidth and
2736 // nsCheckboxRadioFrame::GetIntrinsicHeight assume a border width of 2px.
2737 result.SizeTo(2, 2, 2, 2);
2741 case StyleAppearance::Menulist:
2742 case StyleAppearance::MenulistButton:
2743 case StyleAppearance::MozMenulistArrowButton:
2744 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2747 case StyleAppearance::NumberInput:
2748 case StyleAppearance::Textfield: {
2749 SInt32 frameOutset = 0;
2750 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2752 SInt32 textPadding = 0;
2753 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2755 frameOutset += textPadding;
2757 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2761 case StyleAppearance::Textarea:
2762 result.SizeTo(1, 1, 1, 1);
2765 case StyleAppearance::Searchfield: {
2766 auto border = nsCocoaFeatures::OnBigSurOrLater()
2767 ? kAquaSearchfieldBorderBigSur
2768 : kAquaSearchfieldBorder;
2769 result = DirectionAwareMargin(border, aFrame);
2773 case StyleAppearance::Listbox: {
2774 SInt32 frameOutset = 0;
2775 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2776 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2780 case StyleAppearance::Statusbar:
2781 result.SizeTo(1, 0, 0, 0);
2788 if (IsHiDPIContext(aContext)) {
2789 result = result + result; // doubled
2792 NS_OBJC_END_TRY_BLOCK_RETURN(result);
2795 // Return false here to indicate that CSS padding values should be used. There
2796 // is no reason to make a distinction between padding and border values, just
2797 // specify whatever values you want in GetWidgetBorder and only use this to
2798 // return true if you want to override CSS padding values.
2799 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2801 StyleAppearance aAppearance,
2802 LayoutDeviceIntMargin* aResult) {
2803 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2804 return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
2807 // We don't want CSS padding being used for certain widgets.
2808 // See bug 381639 for an example of why.
2809 switch (aAppearance) {
2810 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2811 // and have a meaningful baseline, so they can't have
2812 // author-specified padding.
2813 case StyleAppearance::Checkbox:
2814 case StyleAppearance::Radio:
2815 aResult->SizeTo(0, 0, 0, 0);
2818 case StyleAppearance::Searchfield:
2819 if (nsCocoaFeatures::OnBigSurOrLater()) {
2830 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext,
2832 StyleAppearance aAppearance,
2833 nsRect* aOverflowRect) {
2834 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2835 return ThemeCocoa::GetWidgetOverflow(aContext, aFrame, aAppearance,
2838 nsIntMargin overflow;
2839 switch (aAppearance) {
2840 case StyleAppearance::Button:
2841 case StyleAppearance::MozMacDisclosureButtonOpen:
2842 case StyleAppearance::MozMacDisclosureButtonClosed:
2843 case StyleAppearance::MozMacHelpButton:
2844 case StyleAppearance::Toolbarbutton:
2845 case StyleAppearance::NumberInput:
2846 case StyleAppearance::Textfield:
2847 case StyleAppearance::Textarea:
2848 case StyleAppearance::Searchfield:
2849 case StyleAppearance::Listbox:
2850 case StyleAppearance::Menulist:
2851 case StyleAppearance::MenulistButton:
2852 case StyleAppearance::MozMenulistArrowButton:
2853 case StyleAppearance::Checkbox:
2854 case StyleAppearance::Radio:
2855 case StyleAppearance::Tab: {
2856 overflow.SizeTo(static_cast<int32_t>(kMaxFocusRingWidth),
2857 static_cast<int32_t>(kMaxFocusRingWidth),
2858 static_cast<int32_t>(kMaxFocusRingWidth),
2859 static_cast<int32_t>(kMaxFocusRingWidth));
2862 case StyleAppearance::ProgressBar: {
2863 // Progress bars draw a 2 pixel white shadow under their progress
2865 overflow.bottom = 2;
2868 case StyleAppearance::Meter: {
2869 // Meter bars overflow their boxes by about 2 pixels.
2870 overflow.SizeTo(2, 2, 2, 2);
2877 if (IsHiDPIContext(aContext)) {
2878 // Double the number of device pixels.
2879 overflow += overflow;
2882 if (overflow != nsIntMargin()) {
2883 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2884 aOverflowRect->Inflate(nsMargin(NSIntPixelsToAppUnits(overflow.top, p2a),
2885 NSIntPixelsToAppUnits(overflow.right, p2a),
2886 NSIntPixelsToAppUnits(overflow.bottom, p2a),
2887 NSIntPixelsToAppUnits(overflow.left, p2a)));
2894 LayoutDeviceIntSize nsNativeThemeCocoa::GetMinimumWidgetSize(
2895 nsPresContext* aPresContext, nsIFrame* aFrame,
2896 StyleAppearance aAppearance) {
2897 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2899 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
2900 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
2903 LayoutDeviceIntSize result;
2904 switch (aAppearance) {
2905 case StyleAppearance::Button: {
2906 result.SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2907 pushButtonSettings.naturalSizes[miniControlSize].height);
2911 case StyleAppearance::MozMacDisclosureButtonOpen:
2912 case StyleAppearance::MozMacDisclosureButtonClosed: {
2913 result.SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
2917 case StyleAppearance::MozMacHelpButton: {
2918 result.SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
2922 case StyleAppearance::Toolbarbutton: {
2923 result.SizeTo(0, toolbarButtonHeights[miniControlSize]);
2927 case StyleAppearance::Spinner:
2928 case StyleAppearance::SpinnerUpbutton:
2929 case StyleAppearance::SpinnerDownbutton: {
2930 SInt32 buttonHeight = 0, buttonWidth = 0;
2931 if (aFrame->GetContent()->IsXULElement()) {
2932 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2933 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2937 .minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
2938 buttonWidth = size.width;
2939 buttonHeight = size.height;
2940 if (aAppearance != StyleAppearance::Spinner) {
2941 // the buttons are half the height of the spinner
2945 result.SizeTo(buttonWidth, buttonHeight);
2949 case StyleAppearance::Menulist:
2950 case StyleAppearance::MenulistButton: {
2951 SInt32 popupHeight = 0;
2952 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2953 result.SizeTo(0, popupHeight);
2957 case StyleAppearance::NumberInput:
2958 case StyleAppearance::Textfield:
2959 case StyleAppearance::Textarea:
2960 case StyleAppearance::Searchfield: {
2961 // at minimum, we should be tall enough for 9pt text.
2962 // I'm using hardcoded values here because the appearance manager
2963 // values for the frame size are incorrect.
2964 result.SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2968 case StyleAppearance::MozWindowButtonBox: {
2969 NSSize size = WindowButtonsSize(aFrame);
2970 result.SizeTo(size.width, size.height);
2974 case StyleAppearance::ProgressBar: {
2975 SInt32 barHeight = 0;
2976 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2977 result.SizeTo(0, barHeight);
2981 case StyleAppearance::Separator: {
2982 result.SizeTo(1, 1);
2986 case StyleAppearance::Treetwisty:
2987 case StyleAppearance::Treetwistyopen: {
2988 SInt32 twistyHeight = 0, twistyWidth = 0;
2989 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2990 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2991 result.SizeTo(twistyWidth, twistyHeight);
2995 case StyleAppearance::Treeheader:
2996 case StyleAppearance::Treeheadercell: {
2997 SInt32 headerHeight = 0;
2998 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2999 result.SizeTo(0, headerHeight);
3003 case StyleAppearance::Tab: {
3004 result.SizeTo(0, tabHeights[miniControlSize]);
3008 case StyleAppearance::RangeThumb: {
3011 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3012 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3013 result.SizeTo(width, height);
3017 case StyleAppearance::MozMenulistArrowButton:
3018 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame,
3025 if (IsHiDPIContext(aPresContext->DeviceContext())) {
3026 result = result * 2;
3031 NS_OBJC_END_TRY_BLOCK_RETURN(LayoutDeviceIntSize());
3035 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame,
3036 StyleAppearance aAppearance,
3037 nsAtom* aAttribute, bool* aShouldRepaint,
3038 const nsAttrValue* aOldValue) {
3039 // Some widget types just never change state.
3040 switch (aAppearance) {
3041 case StyleAppearance::MozWindowTitlebar:
3042 case StyleAppearance::Statusbar:
3043 case StyleAppearance::Tooltip:
3044 case StyleAppearance::Tabpanels:
3045 case StyleAppearance::Tabpanel:
3046 case StyleAppearance::Menupopup:
3047 case StyleAppearance::Progresschunk:
3048 case StyleAppearance::ProgressBar:
3049 case StyleAppearance::Meter:
3050 case StyleAppearance::Meterchunk:
3051 *aShouldRepaint = false;
3057 // XXXdwh Not sure what can really be done here. Can at least guess for
3058 // specific widgets that they're highly unlikely to have certain states.
3059 // For example, a toolbar doesn't care about any states.
3061 // Hover/focus/active changed. Always repaint.
3062 *aShouldRepaint = true;
3064 // Check the attribute to see if it's relevant.
3065 // disabled, checked, dlgtype, default, etc.
3066 *aShouldRepaint = false;
3067 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3068 aAttribute == nsGkAtoms::selected ||
3069 aAttribute == nsGkAtoms::visuallyselected ||
3070 aAttribute == nsGkAtoms::menuactive ||
3071 aAttribute == nsGkAtoms::sortDirection ||
3072 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3073 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3074 *aShouldRepaint = true;
3081 nsNativeThemeCocoa::ThemeChanged() {
3082 // This is unimplemented because we don't care if gecko changes its theme
3083 // and macOS system appearance changes are handled by
3084 // nsLookAndFeel::SystemWantsDarkTheme.
3088 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext,
3090 StyleAppearance aAppearance) {
3091 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
3092 return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3094 // if this is a dropdown button in a combobox the answer is always no
3095 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3096 nsIFrame* parentFrame = aFrame->GetParent();
3097 if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3100 switch (aAppearance) {
3101 // Combobox dropdowns don't support native theming in vertical mode.
3102 case StyleAppearance::Menulist:
3103 case StyleAppearance::MenulistButton:
3104 case StyleAppearance::MozMenulistArrowButton:
3105 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3110 case StyleAppearance::Listbox:
3111 case StyleAppearance::MozWindowButtonBox:
3112 case StyleAppearance::MozWindowTitlebar:
3113 case StyleAppearance::Menupopup:
3114 case StyleAppearance::Tooltip:
3116 case StyleAppearance::Checkbox:
3117 case StyleAppearance::Radio:
3118 case StyleAppearance::MozMacHelpButton:
3119 case StyleAppearance::MozMacDisclosureButtonOpen:
3120 case StyleAppearance::MozMacDisclosureButtonClosed:
3121 case StyleAppearance::MozMacUnifiedToolbarWindow:
3122 case StyleAppearance::Button:
3123 case StyleAppearance::Toolbarbutton:
3124 case StyleAppearance::Spinner:
3125 case StyleAppearance::SpinnerUpbutton:
3126 case StyleAppearance::SpinnerDownbutton:
3127 case StyleAppearance::Statusbar:
3128 case StyleAppearance::NumberInput:
3129 case StyleAppearance::Textfield:
3130 case StyleAppearance::Textarea:
3131 case StyleAppearance::Searchfield:
3132 case StyleAppearance::ProgressBar:
3133 case StyleAppearance::Progresschunk:
3134 case StyleAppearance::Meter:
3135 case StyleAppearance::Meterchunk:
3136 case StyleAppearance::Separator:
3138 case StyleAppearance::Tabpanels:
3139 case StyleAppearance::Tab:
3141 case StyleAppearance::Treetwisty:
3142 case StyleAppearance::Treetwistyopen:
3143 case StyleAppearance::Treeview:
3144 case StyleAppearance::Treeheader:
3145 case StyleAppearance::Treeheadercell:
3146 case StyleAppearance::Treeitem:
3147 case StyleAppearance::Treeline:
3149 case StyleAppearance::Range:
3150 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3159 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3160 // flesh this out at some point
3161 switch (aAppearance) {
3162 case StyleAppearance::MozMenulistArrowButton:
3163 case StyleAppearance::Radio:
3164 case StyleAppearance::Checkbox:
3165 case StyleAppearance::ProgressBar:
3166 case StyleAppearance::Meter:
3167 case StyleAppearance::Range:
3168 case StyleAppearance::MozMacHelpButton:
3169 case StyleAppearance::MozMacDisclosureButtonOpen:
3170 case StyleAppearance::MozMacDisclosureButtonClosed:
3178 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*,
3179 StyleAppearance aAppearance) {
3180 switch (aAppearance) {
3181 case StyleAppearance::Textarea:
3182 case StyleAppearance::Textfield:
3183 case StyleAppearance::Searchfield:
3184 case StyleAppearance::NumberInput:
3185 case StyleAppearance::Menulist:
3186 case StyleAppearance::MenulistButton:
3187 case StyleAppearance::Button:
3188 case StyleAppearance::MozMacHelpButton:
3189 case StyleAppearance::MozMacDisclosureButtonOpen:
3190 case StyleAppearance::MozMacDisclosureButtonClosed:
3191 case StyleAppearance::Radio:
3192 case StyleAppearance::Range:
3193 case StyleAppearance::Checkbox:
3200 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3202 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(
3203 StyleAppearance aAppearance) {
3204 switch (aAppearance) {
3205 case StyleAppearance::Tabpanels:
3206 case StyleAppearance::Menupopup:
3207 case StyleAppearance::Tooltip:
3208 case StyleAppearance::Spinner:
3209 case StyleAppearance::SpinnerUpbutton:
3210 case StyleAppearance::SpinnerDownbutton:
3211 case StyleAppearance::Separator:
3212 case StyleAppearance::NumberInput:
3213 case StyleAppearance::Textfield:
3214 case StyleAppearance::Treeview:
3215 case StyleAppearance::Treeline:
3216 case StyleAppearance::Textarea:
3217 case StyleAppearance::Listbox:
3224 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3225 nsIFrame* aFrame, StyleAppearance aAppearance) {
3226 switch (aAppearance) {
3227 case StyleAppearance::MozWindowTitlebar:
3228 return eThemeGeometryTypeTitlebar;
3229 case StyleAppearance::MozWindowButtonBox:
3230 return eThemeGeometryTypeWindowButtons;
3232 return eThemeGeometryTypeUnknown;
3236 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(
3237 nsIFrame* aFrame, StyleAppearance aAppearance) {
3238 if (IsWidgetScrollbarPart(aAppearance)) {
3239 return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3242 switch (aAppearance) {
3243 case StyleAppearance::Menupopup:
3244 case StyleAppearance::Tooltip:
3245 return eTransparent;
3246 case StyleAppearance::MozMacUnifiedToolbarWindow:
3247 // We want these to be treated as opaque by Gecko. We ensure there's an
3248 // appropriate OS-level clear color to make sure that's the case.
3250 case StyleAppearance::Statusbar:
3251 // Knowing that scrollbars and statusbars are opaque improves
3252 // performance, because we create layers for them.
3256 return eUnknownTransparency;
3260 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3261 return do_AddRef(new nsNativeThemeCocoa());