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/EventStates.h"
35 #include "mozilla/Range.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/HTMLMeterElement.h"
38 #include "mozilla/layers/StackingContextHelper.h"
39 #include "mozilla/StaticPrefs_layout.h"
40 #include "mozilla/StaticPrefs_widget.h"
41 #include "nsLookAndFeel.h"
42 #include "MacThemeGeometryType.h"
43 #include "SDKDeclarations.h"
44 #include "VibrancyManager.h"
46 #include "gfxContext.h"
47 #include "gfxQuartzSurface.h"
48 #include "gfxQuartzNativeDrawing.h"
49 #include "gfxUtils.h" // for ToDeviceColor
52 using namespace mozilla;
53 using namespace mozilla::gfx;
54 using mozilla::dom::HTMLMeterElement;
56 #define DRAW_IN_FRAME_DEBUG 0
57 #define SCROLLBARS_VISUAL_DEBUG 0
59 // private Quartz routines needed here
61 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
62 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
63 typedef CFTypeRef CUIRendererRef;
64 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
65 CFDictionaryRef* result);
68 // Workaround for NSCell control tint drawing
69 // Without this workaround, NSCells are always drawn with the clear control tint
70 // as long as they're not attached to an NSControl which is a subview of an active window.
71 // XXXmstange Why doesn't Webkit need this?
72 @implementation NSCell (ControlTintWorkaround)
73 - (int)_realControlTint {
74 return [self controlTint];
78 // This is the window for our MOZCellDrawView. When an NSCell is drawn, some NSCell implementations
79 // look at the draw view's window to determine whether 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 could call any one
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 ring.
112 @interface MOZCellDrawView : NSControl
113 // Called by NSTreeHeaderCell during drawing.
114 @property BOOL _drawingEndSeparator;
117 @implementation MOZCellDrawView
123 - (NSText*)currentEditor {
129 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
130 if ([aCell showsFirstResponder]) {
131 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
132 CGContextSaveGState(cgContext);
134 // It's important to set the focus ring style before we enter the
135 // transparency layer so that the transparency layer only contains
136 // the normal button mask without the focus ring, and the conversion
137 // to the focus ring shape happens only when the transparency layer is
139 NSSetFocusRingStyle(NSFocusRingOnly);
141 // We need to draw the whole button into a transparency layer because
142 // many button types are composed of multiple parts, and if these parts
143 // were drawn while the focus ring style was active, each individual part
144 // would produce a focus ring for itself. But we only want one focus ring
145 // for the whole button. The transparency layer is a way to merge the
146 // individual button parts together before the focus ring shape is
148 CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
149 [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
150 CGContextEndTransparencyLayer(cgContext);
152 CGContextRestoreGState(cgContext);
156 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
157 [aCell drawWithFrame:aWithFrame inView:aInView];
158 DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
162 * NSProgressBarCell is used to draw progress bars of any size.
164 @interface NSProgressBarCell : NSCell {
165 /*All instance variables are private*/
168 bool mIsIndeterminate;
172 - (void)setValue:(double)value;
174 - (void)setMax:(double)max;
176 - (void)setIndeterminate:(bool)aIndeterminate;
177 - (bool)isIndeterminate;
178 - (void)setHorizontal:(bool)aIsHorizontal;
179 - (bool)isHorizontal;
180 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
183 @implementation NSProgressBarCell
185 - (void)setMax:(double)aMax {
193 - (void)setValue:(double)aValue {
201 - (void)setIndeterminate:(bool)aIndeterminate {
202 mIsIndeterminate = aIndeterminate;
205 - (bool)isIndeterminate {
206 return mIsIndeterminate;
209 - (void)setHorizontal:(bool)aIsHorizontal {
210 mIsHorizontal = aIsHorizontal;
213 - (bool)isHorizontal {
214 return mIsHorizontal;
217 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
218 CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
220 HIThemeTrackDrawInfo tdi;
225 tdi.value = INT32_MAX * (mValue / mMax);
227 tdi.bounds = NSRectToCGRect(cellFrame);
228 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
230 [self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive;
232 NSControlSize size = [self controlSize];
233 if (size == NSControlSizeRegular) {
234 tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
236 NS_ASSERTION(size == NSControlSizeSmall,
237 "We shouldn't have another size than small and regular for the moment");
238 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
241 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
242 int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
243 tdi.trackInfo.progress.phase =
244 uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
246 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
251 @interface MOZSearchFieldCell : NSSearchFieldCell
252 @property BOOL shouldUseToolbarStyle;
255 @implementation MOZSearchFieldCell
257 - (instancetype)init {
258 // We would like to render a search field which has the magnifying glass icon at the start of the
259 // search field, and no cancel button.
260 // On 10.12 and 10.13, empty search fields render the magnifying glass icon in the middle of the
261 // field. So in order to get the icon to show at the start of the field, we need to give the field
262 // some content. We achieve this with a single space character.
263 self = [super initTextCell:@" "];
265 // However, because the field is now non-empty, by default it shows a cancel button. To hide the
266 // cancel button, override it with a custom NSButtonCell which renders nothing.
267 NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil];
268 invisibleCell.bezeled = NO;
269 invisibleCell.bordered = NO;
270 self.cancelButtonCell = invisibleCell;
271 [invisibleCell release];
276 - (BOOL)_isToolbarMode {
277 return self.shouldUseToolbarStyle;
282 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
284 static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
286 // These enums are for indexing into the margin array.
288 leopardOSorlater = 0, // 10.6 - 10.9
289 yosemiteOSorlater = 1 // 10.10+
292 enum { miniControlSize, smallControlSize, regularControlSize };
294 enum { leftMargin, topMargin, rightMargin, bottomMargin };
296 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
297 if (cocoaControlSize == NSControlSizeMini)
298 return miniControlSize;
299 else if (cocoaControlSize == NSControlSizeSmall)
300 return smallControlSize;
302 return regularControlSize;
305 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
306 if (enumControlSize == miniControlSize)
307 return NSControlSizeMini;
308 else if (enumControlSize == smallControlSize)
309 return NSControlSizeSmall;
311 return NSControlSizeRegular;
314 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
315 if (aControlSize == NSControlSizeRegular)
317 else if (aControlSize == NSControlSizeSmall)
323 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
324 const float marginSet[][3][4]) {
325 if (!marginSet) return;
327 static int osIndex = yosemiteOSorlater;
328 size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
329 const float* buttonMargins = marginSet[osIndex][controlSize];
330 rect->origin.x -= buttonMargins[leftMargin];
331 rect->origin.y -= buttonMargins[bottomMargin];
332 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
333 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
336 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) {
337 if (!aFrame) return nil;
339 nsIWidget* widget = aFrame->GetNearestWidget();
340 if (!widget) return nil;
342 nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
343 if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
345 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
348 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
349 NSWindow* window = NativeWindowForFrame(aFrame);
351 // Return fallback values.
352 return NSMakeSize(54, 16);
355 NSRect buttonBox = NSZeroRect;
356 NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
358 buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
360 NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
361 if (minimizeButton) {
362 buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
364 NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
366 buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
368 return buttonBox.size;
371 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
372 nsIWidget* topLevelWidget = NULL;
373 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
374 if (!topLevelWidget || !win) return YES;
376 // XUL popups, e.g. the toolbar customization popup, can't become key windows,
377 // but controls in these windows should still get the active look.
378 if (topLevelWidget->WindowType() == eWindowType_popup) return YES;
379 if ([win isSheet]) return [win isKeyWindow];
380 return [win isMainWindow] && ![win attachedSheet];
383 // Toolbar controls and content controls respond to different window
384 // activeness states.
385 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
386 if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
387 return FrameIsInActiveWindow(aFrame);
390 static bool IsInSourceList(nsIFrame* aFrame) {
391 for (nsIFrame* frame = aFrame->GetParent(); frame;
392 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) {
393 if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) {
400 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
402 nsNativeThemeCocoa::nsNativeThemeCocoa() : ThemeCocoa(ScrollbarStyle()) {
403 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
405 kMaxFocusRingWidth = 7;
407 // provide a local autorelease pool, as this is called during startup
408 // before the main event-loop pool is in place
409 nsAutoreleasePool pool;
411 mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
412 [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
413 [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
414 [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
416 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
417 [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
418 [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
419 [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
421 mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
422 [mPushButtonCell setButtonType:NSMomentaryPushInButton];
423 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
425 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""];
426 [mRadioButtonCell setButtonType:NSRadioButton];
428 mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""];
429 [mCheckboxCell setButtonType:NSSwitchButton];
430 [mCheckboxCell setAllowsMixedState:YES];
432 mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
433 [mTextFieldCell setBezeled:YES];
434 [mTextFieldCell setEditable:YES];
435 [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior];
437 mSearchFieldCell = [[MOZSearchFieldCell alloc] init];
438 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
439 [mSearchFieldCell setBezeled:YES];
440 [mSearchFieldCell setEditable:YES];
441 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
443 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
445 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
446 [mComboBoxCell setBezeled:YES];
447 [mComboBoxCell setEditable:YES];
448 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
450 mProgressBarCell = [[NSProgressBarCell alloc] init];
452 mMeterBarCell = [[NSLevelIndicatorCell alloc]
453 initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
455 mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
457 mCellDrawView = [[MOZCellDrawView alloc] init];
459 if (XRE_IsParentProcess()) {
460 // Put the cell draw view into a window that is never shown.
461 // This allows us to convince some NSCell implementations (such as NSButtonCell for default
462 // buttons) to draw with the active appearance. Another benefit of putting the draw view in a
463 // window is the fact that it lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit
464 // the current NSApplication effectiveAppearance automatically, so the field adapts to Dark Mode
466 // We don't create this window when the native theme is used in the content process because
467 // NSWindow creation runs into the sandbox and because we never run default buttons in content
469 mCellDrawWindow = [[MOZCellDrawWindow alloc] initWithContentRect:NSZeroRect
470 styleMask:NSWindowStyleMaskBorderless
471 backing:NSBackingStoreBuffered
473 [mCellDrawWindow.contentView addSubview:mCellDrawView];
476 NS_OBJC_END_TRY_IGNORE_BLOCK;
479 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
480 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
482 [mMeterBarCell release];
483 [mProgressBarCell release];
484 [mDisclosureButtonCell release];
485 [mHelpButtonCell release];
486 [mPushButtonCell release];
487 [mRadioButtonCell release];
488 [mCheckboxCell release];
489 [mTextFieldCell release];
490 [mSearchFieldCell release];
491 [mDropdownCell release];
492 [mComboBoxCell release];
493 [mTreeHeaderCell release];
494 [mCellDrawWindow release];
495 [mCellDrawView release];
497 NS_OBJC_END_TRY_IGNORE_BLOCK;
500 // Limit on the area of the target rect (in pixels^2) in
501 // DrawCellWithScaling() and DrawButton() and above which we
502 // don't draw the object into a bitmap buffer. This is to avoid crashes in
503 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
504 // CGContextDrawImage(), and also to avoid very poor drawing performance in
505 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
506 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
507 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
508 // different amounts of RAM.
509 #define BITMAP_MAX_AREA 500000
511 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
512 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
513 CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
514 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
515 fabs(transformedUserSpacePixel.size.height));
516 return maxScale > 1.0 ? 2 : 1;
520 * Draw the given NSCell into the given cgContext.
522 * destRect - the size and position of the resulting control rectangle
523 * controlSize - the NSControlSize which will be given to the NSCell before
524 * asking it to render
525 * naturalSize - The natural dimensions of this control.
526 * If the control rect size is not equal to either of these, a scale
527 * will be applied to the context so that rendering the control at the
528 * natural size will result in it filling the destRect space.
529 * If a control has no natural dimensions in either/both axes, pass 0.0f.
530 * minimumSize - The minimum dimensions of this control.
531 * If the control rect size is less than the minimum for a given axis,
532 * a scale will be applied to the context so that the minimum is used
533 * for drawing. If a control has no minimum dimensions in either/both
535 * marginSet - an array of margins; a multidimensional array of [2][3][4],
536 * with the first dimension being the OS version (Tiger or Leopard),
537 * the second being the control size (mini, small, regular), and the third
538 * being the 4 margin values (left, top, right, bottom).
539 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
540 * matter if this is really the right view; it just has to return YES when
541 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
542 * mirrorHorizontal - whether to mirror the cell horizontally
544 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
545 NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize,
546 const float marginSet[][3][4], NSView* view,
547 BOOL mirrorHorizontal) {
548 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
551 NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
553 if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
554 if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
556 // Keep aspect ratio when scaling if one dimension is free.
557 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
558 drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
559 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
560 drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
562 // Honor minimum sizes.
563 if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width;
564 if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height;
566 [NSGraphicsContext saveGraphicsState];
568 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
569 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
570 // Inflate the rect Gecko gave us by the margin for the control.
571 InflateControlRect(&drawRect, controlSize, marginSet);
573 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
575 setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
578 DrawCellIncludingFocusRing(cell, drawRect, view);
580 [NSGraphicsContext setCurrentContext:savedContext];
582 float w = ceil(drawRect.size.width);
583 float h = ceil(drawRect.size.height);
584 NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
586 // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's
588 InflateControlRect(&tmpRect, controlSize, marginSet);
590 // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
591 w += kMaxFocusRingWidth * 2.0;
592 h += kMaxFocusRingWidth * 2.0;
594 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
595 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
596 CGContextRef ctx = CGBitmapContextCreate(
597 NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
598 (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
599 CGColorSpaceRelease(rgb);
601 // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
602 // This is the first flip transform, applied to cgContext.
603 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
604 CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
605 if (mirrorHorizontal) {
606 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
607 CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
610 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
611 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx
614 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
616 // Set the context's "base transform" to in order to get correctly-sized focus rings.
617 CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
619 // This is the second flip transform, applied to ctx.
620 CGContextScaleCTM(ctx, 1.0f, -1.0f);
621 CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
623 DrawCellIncludingFocusRing(cell, tmpRect, view);
625 [NSGraphicsContext setCurrentContext:savedContext];
627 CGImageRef img = CGBitmapContextCreateImage(ctx);
629 // Drop the image into the original destination rectangle, scaling to fit
630 // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
631 // doesn't extend beyond the overflow rect
632 float xscale = destRect.size.width / drawRect.size.width;
633 float yscale = destRect.size.height / drawRect.size.height;
634 float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
635 float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
638 CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY,
639 destRect.size.width + scaledFocusRingX * 2,
640 destRect.size.height + scaledFocusRingY * 2),
644 CGContextRelease(ctx);
647 [NSGraphicsContext restoreGraphicsState];
649 #if DRAW_IN_FRAME_DEBUG
650 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
651 CGContextFillRect(cgContext, destRect);
654 NS_OBJC_END_TRY_IGNORE_BLOCK;
657 struct CellRenderSettings {
658 // The natural dimensions of the control.
659 // If a control has no natural dimensions in either/both axes, set to 0.0f.
660 NSSize naturalSizes[3];
662 // The minimum dimensions of the control.
663 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
664 NSSize minimumSizes[3];
666 // A three-dimensional array,
667 // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
668 // the second being the control size (mini, small, regular), and the third
669 // being the 4 margin values (left, top, right, bottom).
670 float margins[2][3][4];
674 * This is a helper method that returns the required NSControlSize given a size
675 * and the size of the three controls plus a tolerance.
676 * size - The width or the height of the element to draw.
677 * sizes - An array with the all the width/height of the element for its
679 * tolerance - The tolerance as passed to DrawCellWithSnapping.
680 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
682 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) {
683 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
690 for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
697 // If it's the latest value, we pick it.
699 return CocoaSizeForEnum(i);
702 if (size <= sizes[i] + tolerance && size < next) {
703 return CocoaSizeForEnum(i);
707 // If we are here, that means sizes[] was an array with only empty values
708 // or the algorithm above is wrong.
709 // The former can happen but the later would be wrong.
710 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
711 "We found no control! We shouldn't be there!");
712 return CocoaSizeForEnum(regularControlSize);
716 * Draw the given NSCell into the given cgContext with a nice control size.
718 * This function is similar to DrawCellWithScaling, but it decides what
719 * control size to use based on the destRect's size.
720 * Scaling is only applied when the difference between the destRect's size
721 * and the next smaller natural size is greater than snapTolerance. Otherwise
722 * it snaps to the next smaller control size without scaling because unscaled
723 * controls look nicer.
725 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
726 const CellRenderSettings settings, float verticalAlignFactor,
727 NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) {
728 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
730 const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
731 const NSSize* sizes = settings.naturalSizes;
732 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
733 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
734 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
736 HIRect drawRect = destRect;
738 CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width};
739 NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
740 CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height};
741 NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
743 NSControlSize controlSize = NSControlSizeRegular;
744 size_t sizeIndex = 0;
746 // At some sizes, don't scale but snap.
747 const NSControlSize smallerControlSize =
748 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX
750 const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
751 const NSSize size = sizes[smallerControlSizeIndex];
752 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
753 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
754 if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
755 diffHeight <= snapTolerance) {
756 // Snap to the smaller control size.
757 controlSize = smallerControlSize;
758 sizeIndex = smallerControlSizeIndex;
759 MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
761 // Resize and center the drawRect.
762 if (sizes[sizeIndex].width) {
763 drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
764 drawRect.size.width = sizes[sizeIndex].width;
766 if (sizes[sizeIndex].height) {
768 floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
769 drawRect.size.height = sizes[sizeIndex].height;
772 // Use the larger control size.
773 controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
776 sizeIndex = EnumSizeForCocoaSize(controlSize);
779 [cell setControlSize:controlSize];
781 MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
782 const NSSize minimumSize = settings.minimumSizes[sizeIndex];
783 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize,
784 settings.margins, view, mirrorHorizontal);
786 NS_OBJC_END_TRY_IGNORE_BLOCK;
789 @interface NSWindow (CoreUIRendererPrivate)
790 + (CUIRendererRef)coreUIRenderer;
793 @interface NSObject (NSAppearanceCoreUIRendering)
794 - (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
797 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions,
798 bool aSkipAreaCheck = false) {
799 if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
803 NSAppearance* appearance = NSAppearance.currentAppearance;
804 if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
805 // Render through NSAppearance on Mac OS 10.10 and up. This will call
806 // CUIDraw with a CoreUI renderer that will give us the correct 10.10
807 // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
808 // renders 10.9-style widgets on 10.10.
809 [appearance _drawInRect:aRect context:cgContext options:aOptions];
812 CUIRendererRef renderer =
813 [NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil;
814 CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
818 static float VerticalAlignFactor(nsIFrame* aFrame) {
819 if (!aFrame) return 0.5f; // default: center
821 const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
822 auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
824 case StyleVerticalAlignKeyword::Top:
825 case StyleVerticalAlignKeyword::TextTop:
828 case StyleVerticalAlignKeyword::Sub:
829 case StyleVerticalAlignKeyword::Super:
830 case StyleVerticalAlignKeyword::Middle:
831 case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
834 case StyleVerticalAlignKeyword::Baseline:
835 case StyleVerticalAlignKeyword::Bottom:
836 case StyleVerticalAlignKeyword::TextBottom:
840 MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
845 static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
847 [aCell setEnabled:!aControlParams.disabled];
848 [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
849 aControlParams.insideActiveWindow)];
850 [aCell setHighlighted:aControlParams.pressed];
853 // These are the sizes that Gecko needs to request to draw if it wants
854 // to get a standard-sized Aqua radio button drawn. Note that the rects
855 // that draw these are actually a little bigger.
856 static const CellRenderSettings radioSettings = {{
857 NSMakeSize(11, 11), // mini
858 NSMakeSize(13, 13), // small
859 NSMakeSize(16, 16) // regular
861 {NSZeroSize, NSZeroSize, NSZeroSize},
864 {0, 0, 0, 0}, // mini
865 {0, 1, 1, 1}, // small
866 {0, 0, 0, 0} // regular
870 {0, 0, 0, 0}, // mini
871 {1, 1, 1, 2}, // small
872 {0, 0, 0, 0} // regular
875 static const CellRenderSettings checkboxSettings = {{
876 NSMakeSize(11, 11), // mini
877 NSMakeSize(13, 13), // small
878 NSMakeSize(16, 16) // regular
880 {NSZeroSize, NSZeroSize, NSZeroSize},
883 {0, 1, 0, 0}, // mini
884 {0, 1, 0, 1}, // small
885 {0, 1, 0, 1} // regular
889 {0, 1, 0, 0}, // mini
890 {0, 1, 0, 1}, // small
891 {0, 1, 0, 1} // regular
894 static NSCellStateValue CellStateForCheckboxOrRadioState(
895 nsNativeThemeCocoa::CheckboxOrRadioState aState) {
897 case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
899 case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
901 case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
906 void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
907 const HIRect& inBoxRect,
908 const CheckboxOrRadioParams& aParams) {
909 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
911 NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
912 ApplyControlParamsToNSCell(aParams.controlParams, cell);
914 [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
915 [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint]
916 : NSClearControlTint)];
918 // Ensure that the control is square.
919 float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
920 HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
921 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
924 if (mCellDrawWindow) {
925 mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow;
927 DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings,
928 aParams.verticalAlignFactor, mCellDrawView, NO);
930 NS_OBJC_END_TRY_IGNORE_BLOCK;
933 static const CellRenderSettings searchFieldSettings = {{
934 NSMakeSize(0, 16), // mini
935 NSMakeSize(0, 19), // small
936 NSMakeSize(0, 22) // regular
939 NSMakeSize(32, 0), // mini
940 NSMakeSize(38, 0), // small
941 NSMakeSize(44, 0) // regular
945 {0, 0, 0, 0}, // mini
946 {0, 0, 0, 0}, // small
947 {0, 0, 0, 0} // regular
951 {0, 0, 0, 0}, // mini
952 {0, 0, 0, 0}, // small
953 {0, 0, 0, 0} // regular
956 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
957 nsIContent* content = aFrame->GetContent();
962 if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
966 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
967 case StyleAppearance::Toolbar:
968 case StyleAppearance::Statusbar:
975 static bool IsInsideToolbar(nsIFrame* aFrame) {
976 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
977 if (IsToolbarStyleContainer(frame)) {
984 nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams(
985 nsIFrame* aFrame, EventStates aEventState) {
986 TextFieldParams params;
987 params.insideToolbar = IsInsideToolbar(aFrame);
988 params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
990 // See ShouldUnconditionallyDrawFocusRingIfFocused.
991 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
993 params.rtl = IsFrameRTL(aFrame);
994 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
998 void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext, const HIRect& inBoxRect,
999 const TextFieldParams& aParams) {
1000 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1002 NSTextFieldCell* cell = mTextFieldCell;
1003 [cell setEnabled:!aParams.disabled];
1004 [cell setShowsFirstResponder:aParams.focused];
1006 if (mCellDrawWindow) {
1007 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state
1009 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor,
1010 mCellDrawView, aParams.rtl);
1012 NS_OBJC_END_TRY_IGNORE_BLOCK;
1015 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
1016 const TextFieldParams& aParams) {
1017 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1019 mSearchFieldCell.enabled = !aParams.disabled;
1020 mSearchFieldCell.showsFirstResponder = aParams.focused;
1021 mSearchFieldCell.placeholderString = @"";
1022 mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar;
1024 if (mCellDrawWindow) {
1025 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state
1027 DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect, searchFieldSettings,
1028 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1030 NS_OBJC_END_TRY_IGNORE_BLOCK;
1033 static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
1034 static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
1035 static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
1036 static NSString* kCheckmarkImage = @"MenuOnState";
1037 static NSString* kMenuarrowRightImage = @"MenuSubmenu";
1038 static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
1039 static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
1040 static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
1041 static const CGFloat kMenuIconIndent = 6.0f;
1043 NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
1044 switch (aParams.icon) {
1045 case MenuIcon::eCheckmark:
1046 return kCheckmarkImage;
1047 case MenuIcon::eMenuArrow:
1048 return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
1049 case MenuIcon::eMenuDownScrollArrow:
1050 return kMenuDownScrollArrowImage;
1051 case MenuIcon::eMenuUpScrollArrow:
1052 return kMenuUpScrollArrowImage;
1056 NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
1058 case MenuIcon::eCheckmark:
1059 return kCheckmarkSize;
1060 case MenuIcon::eMenuArrow:
1061 return kMenuarrowSize;
1062 case MenuIcon::eMenuDownScrollArrow:
1063 case MenuIcon::eMenuUpScrollArrow:
1064 return kMenuScrollArrowSize;
1068 nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
1069 nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
1070 bool isDisabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1072 MenuIconParams params;
1073 params.icon = aIcon;
1074 params.disabled = isDisabled;
1075 params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1076 params.centerHorizontally = true;
1077 params.rtl = IsFrameRTL(aFrame);
1081 void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
1082 const MenuIconParams& aParams) {
1083 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1085 NSSize size = GetMenuIconSize(aParams.icon);
1087 // Adjust size and position of our drawRect.
1088 CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
1089 CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
1090 CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
1091 CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
1092 CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
1093 : aParams.rtl ? paddingEndX
1095 aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
1098 aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
1100 NSString* imageName = GetMenuIconName(aParams);
1103 drawRect, cgContext,
1104 [NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
1105 imageName, @"imageNameKey", state, @"state",
1106 @"image", @"widget", [NSNumber numberWithBool:YES],
1107 @"is.flipped", nil]);
1109 #if DRAW_IN_FRAME_DEBUG
1110 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1111 CGContextFillRect(cgContext, drawRect);
1114 NS_OBJC_END_TRY_IGNORE_BLOCK;
1117 nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
1118 nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
1119 bool isDisabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1121 MenuItemParams params;
1122 params.checked = aIsChecked;
1123 params.disabled = isDisabled;
1124 params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1125 params.rtl = IsFrameRTL(aFrame);
1129 void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
1130 const MenuItemParams& aParams) {
1131 if (aParams.checked) {
1132 MenuIconParams params;
1133 params.disabled = aParams.disabled;
1134 params.insideActiveMenuItem = aParams.selected;
1135 params.rtl = aParams.rtl;
1136 params.icon = MenuIcon::eCheckmark;
1137 DrawMenuIcon(cgContext, inBoxRect, params);
1141 void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
1142 const MenuItemParams& aParams) {
1143 // Workaround for visual artifacts issues with
1144 // HIThemeDrawMenuSeparator on macOS Big Sur.
1145 if (nsCocoaFeatures::OnBigSurOrLater()) {
1146 CGRect separatorRect = inBoxRect;
1147 separatorRect.size.height = 1;
1148 separatorRect.size.width -= 42;
1149 separatorRect.origin.x += 21;
1150 // Use transparent black with an alpha similar to the native separator.
1151 // The values 231 (menu background) and 205 (separator color) have been
1152 // sampled from a window screenshot of a native context menu.
1153 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0);
1154 CGContextFillRect(cgContext, separatorRect);
1158 ThemeMenuState menuState;
1159 if (aParams.disabled) {
1160 menuState = kThemeMenuDisabled;
1162 menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
1165 HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
1166 HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
1169 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1170 // Mac always draws focus rings for textboxes and lists.
1171 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1172 case StyleAppearance::NumberInput:
1173 case StyleAppearance::Textfield:
1174 case StyleAppearance::Textarea:
1175 case StyleAppearance::Searchfield:
1176 case StyleAppearance::Listbox:
1183 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1184 nsIFrame* aFrame, EventStates aEventState) {
1185 ControlParams params;
1186 params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1187 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1188 params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
1189 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) &&
1190 (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
1191 ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1192 params.rtl = IsFrameRTL(aFrame);
1196 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1197 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1199 static const CellRenderSettings pushButtonSettings = {{
1200 NSMakeSize(0, 16), // mini
1201 NSMakeSize(0, 19), // small
1202 NSMakeSize(0, 22) // regular
1205 NSMakeSize(18, 0), // mini
1206 NSMakeSize(26, 0), // small
1207 NSMakeSize(30, 0) // regular
1211 {0, 0, 0, 0}, // mini
1212 {4, 0, 4, 1}, // small
1213 {5, 0, 5, 2} // regular
1217 {0, 0, 0, 0}, // mini
1218 {4, 0, 4, 1}, // small
1219 {5, 0, 5, 2} // regular
1222 // The height at which we start doing square buttons instead of rounded buttons
1223 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
1224 // we switch over to doing square buttons which looks fine at any size.
1225 #define DO_SQUARE_BUTTON_HEIGHT 26
1227 void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1228 ButtonType aButtonType, ControlParams aControlParams) {
1229 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1231 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1232 [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
1233 mPushButtonCell.keyEquivalent = aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @"";
1235 if (mCellDrawWindow) {
1236 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1238 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1239 mCellDrawView, aControlParams.rtl, 1.0f);
1241 NS_OBJC_END_TRY_IGNORE_BLOCK;
1244 void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1245 ControlParams aControlParams) {
1246 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1248 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1249 [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
1251 if (mCellDrawWindow) {
1252 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1254 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1255 NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
1257 NS_OBJC_END_TRY_IGNORE_BLOCK;
1260 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
1261 ControlParams aControlParams) {
1262 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1264 ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1266 if (mCellDrawWindow) {
1267 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1269 DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1270 kHelpButtonSize, NULL, mCellDrawView,
1271 false); // Don't mirror icon in RTL.
1273 NS_OBJC_END_TRY_IGNORE_BLOCK;
1276 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
1277 ControlParams aControlParams,
1278 NSCellStateValue aCellState) {
1279 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1281 ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1282 [mDisclosureButtonCell setState:aCellState];
1284 if (mCellDrawWindow) {
1285 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow;
1287 DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1288 kDisclosureButtonSize, NULL, mCellDrawView,
1289 false); // Don't mirror icon in RTL.
1291 NS_OBJC_END_TRY_IGNORE_BLOCK;
1294 void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
1295 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1296 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
1297 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
1299 CGContextSaveGState(cgContext);
1300 NSSetFocusRingStyle(NSFocusRingOnly);
1301 NSRectFill(NSRectFromCGRect(inBoxRect));
1302 CGContextRestoreGState(cgContext);
1303 [NSGraphicsContext setCurrentContext:savedContext];
1305 NS_OBJC_END_TRY_IGNORE_BLOCK;
1308 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect,
1311 static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1312 RenderHIThemeControlFunction aFunc, void* aData,
1313 BOOL mirrorHorizontally = NO) {
1314 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1315 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1318 HIRect drawRect = aRect;
1319 drawRect.origin = CGPointZero;
1321 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f &&
1322 (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1328 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1330 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1331 aFunc(aCGContext, drawRect, aData);
1333 // Inflate the buffer to capture focus rings.
1334 int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1335 int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1337 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1338 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1339 CGContextRef bitmapctx = CGBitmapContextCreate(
1340 NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4,
1341 colorSpace, kCGImageAlphaPremultipliedFirst);
1342 CGColorSpaceRelease(colorSpace);
1344 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1345 CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1347 // Set the context's "base transform" to in order to get correctly-sized focus rings.
1348 CGContextSetBaseCTM(bitmapctx,
1349 CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
1351 // HITheme always wants to draw into a flipped context, or things
1353 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1354 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1356 aFunc(bitmapctx, drawRect, aData);
1358 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1360 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1362 // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1363 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1364 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1366 if (mirrorHorizontally) {
1367 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1368 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1371 HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1372 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1374 CGContextSetCTM(aCGContext, ctm);
1376 CGImageRelease(bitmap);
1377 CGContextRelease(bitmapctx);
1380 CGContextSetCTM(aCGContext, savedCTM);
1383 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
1384 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1385 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1388 static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
1389 if (aParams.disabled) {
1390 return kThemeStateUnavailable;
1392 if (aParams.pressed) {
1393 return kThemeStatePressed;
1395 return kThemeStateActive;
1398 void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
1399 ThemeButtonKind aKind, ThemeButtonValue aValue,
1400 ThemeDrawState aState, ThemeButtonAdornment aAdornment,
1401 const ControlParams& aParams) {
1402 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1404 HIThemeButtonDrawInfo bdi;
1409 bdi.adornment = aAdornment;
1411 if (aParams.focused && aParams.insideActiveWindow) {
1412 bdi.adornment |= kThemeAdornmentFocus;
1415 RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
1417 #if DRAW_IN_FRAME_DEBUG
1418 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1419 CGContextFillRect(cgContext, inBoxRect);
1422 NS_OBJC_END_TRY_IGNORE_BLOCK;
1425 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
1426 const ButtonParams& aParams) {
1427 ControlParams controlParams = aParams.controlParams;
1429 switch (aParams.button) {
1430 case ButtonType::eRegularPushButton:
1431 case ButtonType::eDefaultPushButton:
1432 DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams);
1434 case ButtonType::eSquareBezelPushButton:
1435 DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1437 case ButtonType::eArrowButton:
1438 DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1439 kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
1441 case ButtonType::eHelpButton:
1442 DrawHelpButton(cgContext, inBoxRect, controlParams);
1444 case ButtonType::eTreeTwistyPointingRight:
1445 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
1446 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1448 case ButtonType::eTreeTwistyPointingDown:
1449 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
1450 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1452 case ButtonType::eDisclosureButtonClosed:
1453 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
1455 case ButtonType::eDisclosureButtonOpen:
1456 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
1461 nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
1462 nsIFrame* aFrame, EventStates aEventState) {
1463 TreeHeaderCellParams params;
1464 params.controlParams = ComputeControlParams(aFrame, aEventState);
1465 params.sortDirection = GetTreeSortDirection(aFrame);
1466 params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1470 @interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
1471 // This method has been present in the same form since at least macOS 10.4.
1472 - (void)_setSortable:(BOOL)arg1
1473 showSortIndicator:(BOOL)arg2
1474 ascending:(BOOL)arg3
1475 priority:(NSInteger)arg4
1476 highlightForSort:(BOOL)arg5;
1479 void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
1480 const TreeHeaderCellParams& aParams) {
1481 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1483 // Without clearing the cell's title, it takes on a default value of "Field",
1484 // which is displayed underneath the title set in the front-end.
1485 NSCell* cell = (NSCell*)mTreeHeaderCell;
1488 if ([mTreeHeaderCell respondsToSelector:@selector
1489 (_setSortable:showSortIndicator:ascending:priority:highlightForSort:)]) {
1490 switch (aParams.sortDirection) {
1491 case eTreeSortDirection_Ascending:
1492 [mTreeHeaderCell _setSortable:YES
1493 showSortIndicator:YES
1496 highlightForSort:YES];
1498 case eTreeSortDirection_Descending:
1499 [mTreeHeaderCell _setSortable:YES
1500 showSortIndicator:YES
1503 highlightForSort:YES];
1506 // eTreeSortDirection_Natural
1507 [mTreeHeaderCell _setSortable:YES
1508 showSortIndicator:NO
1511 highlightForSort:NO];
1516 mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
1517 mTreeHeaderCell.state =
1518 (mTreeHeaderCell.enabled && aParams.controlParams.pressed) ? NSOnState : NSOffState;
1520 mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
1522 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
1523 NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext
1525 DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
1526 NSGraphicsContext.currentContext = savedContext;
1528 #if DRAW_IN_FRAME_DEBUG
1529 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1530 CGContextFillRect(cgContext, inBoxRect);
1533 NS_OBJC_END_TRY_IGNORE_BLOCK;
1536 static const CellRenderSettings dropdownSettings = {{
1537 NSMakeSize(0, 16), // mini
1538 NSMakeSize(0, 19), // small
1539 NSMakeSize(0, 22) // regular
1542 NSMakeSize(18, 0), // mini
1543 NSMakeSize(38, 0), // small
1544 NSMakeSize(44, 0) // regular
1548 {1, 1, 2, 1}, // mini
1549 {3, 0, 3, 1}, // small
1550 {3, 0, 3, 0} // regular
1554 {1, 1, 2, 1}, // mini
1555 {3, 0, 3, 1}, // small
1556 {3, 0, 3, 0} // regular
1559 static const CellRenderSettings editableMenulistSettings = {{
1560 NSMakeSize(0, 15), // mini
1561 NSMakeSize(0, 18), // small
1562 NSMakeSize(0, 21) // regular
1565 NSMakeSize(18, 0), // mini
1566 NSMakeSize(38, 0), // small
1567 NSMakeSize(44, 0) // regular
1571 {0, 0, 2, 2}, // mini
1572 {0, 0, 3, 2}, // small
1573 {0, 1, 3, 3} // regular
1577 {0, 0, 2, 2}, // mini
1578 {0, 0, 3, 2}, // small
1579 {0, 1, 3, 3} // regular
1582 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1583 const DropdownParams& aParams) {
1584 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1586 [mDropdownCell setPullsDown:aParams.pullsDown];
1587 NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1589 ApplyControlParamsToNSCell(aParams.controlParams, cell);
1591 if (aParams.controlParams.insideActiveWindow) {
1592 [cell setControlTint:[NSColor currentControlTint]];
1594 [cell setControlTint:NSClearControlTint];
1597 const CellRenderSettings& settings =
1598 aParams.editable ? editableMenulistSettings : dropdownSettings;
1600 if (mCellDrawWindow) {
1601 mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow;
1603 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
1604 aParams.controlParams.rtl);
1606 NS_OBJC_END_TRY_IGNORE_BLOCK;
1609 static const CellRenderSettings spinnerSettings = {
1611 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1612 NSMakeSize(15, 22), // small
1613 NSMakeSize(19, 27) // regular
1616 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1617 NSMakeSize(15, 22), // small
1618 NSMakeSize(19, 27) // regular
1622 {0, 0, 0, 0}, // mini
1623 {0, 0, 0, 0}, // small
1624 {0, 0, 0, 0} // regular
1628 {0, 0, 0, 0}, // mini
1629 {0, 0, 0, 0}, // small
1630 {0, 0, 0, 0} // regular
1633 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
1634 const SpinButtonParams& aParams) {
1635 HIThemeButtonDrawInfo bdi;
1638 bdi.value = kThemeButtonOff;
1639 bdi.adornment = kThemeAdornmentNone;
1641 if (aParams.disabled) {
1642 bdi.state = kThemeStateUnavailable;
1643 } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1644 if (*aParams.pressedButton == SpinButton::eUp) {
1645 bdi.state = kThemeStatePressedUp;
1647 bdi.state = kThemeStatePressedDown;
1650 bdi.state = kThemeStateActive;
1656 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
1657 const SpinButtonParams& aParams) {
1658 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1660 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1661 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1663 NS_OBJC_END_TRY_IGNORE_BLOCK;
1666 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
1667 SpinButton aDrawnButton, const SpinButtonParams& aParams) {
1668 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1670 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1672 // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1673 // together as a single unit (presumably because when one button is active,
1674 // the appearance of both changes (in different ways)). Here we have to paint
1675 // both buttons, using clip to hide the one we don't want to paint.
1676 HIRect drawRect = inBoxRect;
1677 drawRect.size.height *= 2;
1678 if (aDrawnButton == SpinButton::eDown) {
1679 drawRect.origin.y -= inBoxRect.size.height;
1682 // Shift the drawing a little to the left, since cocoa paints with more
1683 // blank space around the visual buttons than we'd like:
1684 drawRect.origin.x -= 1;
1686 CGContextSaveGState(cgContext);
1687 CGContextClipToRect(cgContext, inBoxRect);
1689 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1691 CGContextRestoreGState(cgContext);
1693 NS_OBJC_END_TRY_IGNORE_BLOCK;
1696 static const CellRenderSettings progressSettings[2][2] = {
1697 // Vertical progress bar.
1698 {// Determined settings.
1701 NSMakeSize(10, 0), // small
1702 NSMakeSize(16, 0) // regular
1704 {NSZeroSize, NSZeroSize, NSZeroSize},
1707 {0, 0, 0, 0}, // mini
1708 {1, 1, 1, 1}, // small
1709 {1, 1, 1, 1} // regular
1711 // There is no horizontal margin in regular undetermined size.
1714 NSMakeSize(10, 0), // small
1715 NSMakeSize(16, 0) // regular
1717 {NSZeroSize, NSZeroSize, NSZeroSize},
1720 {0, 0, 0, 0}, // mini
1721 {1, 1, 1, 1}, // small
1722 {1, 0, 1, 0} // regular
1726 {0, 0, 0, 0}, // mini
1727 {1, 1, 1, 1}, // small
1728 {1, 0, 1, 0} // regular
1730 // Horizontal progress bar.
1731 {// Determined settings.
1734 NSMakeSize(0, 10), // small
1735 NSMakeSize(0, 16) // regular
1737 {NSZeroSize, NSZeroSize, NSZeroSize},
1740 {0, 0, 0, 0}, // mini
1741 {1, 1, 1, 1}, // small
1742 {1, 1, 1, 1} // regular
1746 {0, 0, 0, 0}, // mini
1747 {1, 1, 1, 1}, // small
1748 {1, 1, 1, 1} // regular
1750 // There is no horizontal margin in regular undetermined size.
1753 NSMakeSize(0, 10), // small
1754 NSMakeSize(0, 16) // regular
1756 {NSZeroSize, NSZeroSize, NSZeroSize},
1759 {0, 0, 0, 0}, // mini
1760 {1, 1, 1, 1}, // small
1761 {0, 1, 0, 1} // regular
1765 {0, 0, 0, 0}, // mini
1766 {1, 1, 1, 1}, // small
1767 {0, 1, 0, 1} // regular
1770 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1771 nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
1772 ProgressParams params;
1773 params.value = GetProgressValue(aFrame);
1774 params.max = GetProgressMaxValue(aFrame);
1775 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1776 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1777 params.indeterminate = aEventState.HasState(NS_EVENT_STATE_INDETERMINATE);
1778 params.horizontal = aIsHorizontal;
1779 params.rtl = IsFrameRTL(aFrame);
1783 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1784 const ProgressParams& aParams) {
1785 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1787 NSProgressBarCell* cell = mProgressBarCell;
1789 [cell setValue:aParams.value];
1790 [cell setMax:aParams.max];
1791 [cell setIndeterminate:aParams.indeterminate];
1792 [cell setHorizontal:aParams.horizontal];
1793 [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1794 : NSClearControlTint)];
1796 if (mCellDrawWindow) {
1797 mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow;
1799 DrawCellWithSnapping(cell, cgContext, inBoxRect,
1800 progressSettings[aParams.horizontal][aParams.indeterminate],
1801 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1803 NS_OBJC_END_TRY_IGNORE_BLOCK;
1806 static const CellRenderSettings meterSetting = {{
1807 NSMakeSize(0, 16), // mini
1808 NSMakeSize(0, 16), // small
1809 NSMakeSize(0, 16) // regular
1811 {NSZeroSize, NSZeroSize, NSZeroSize},
1814 {1, 1, 1, 1}, // mini
1815 {1, 1, 1, 1}, // small
1816 {1, 1, 1, 1} // regular
1820 {1, 1, 1, 1}, // mini
1821 {1, 1, 1, 1}, // small
1822 {1, 1, 1, 1} // regular
1825 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
1826 nsIContent* content = aFrame->GetContent();
1827 if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1828 return MeterParams();
1831 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1833 params.value = meterElement->Value();
1834 params.min = meterElement->Min();
1835 params.max = meterElement->Max();
1836 EventStates states = meterElement->State();
1837 if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1838 params.optimumState = OptimumState::eSubOptimum;
1839 } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1840 params.optimumState = OptimumState::eSubSubOptimum;
1842 params.horizontal = !IsVerticalMeter(aFrame);
1843 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1844 params.rtl = IsFrameRTL(aFrame);
1849 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1850 const MeterParams& aParams) {
1851 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
1853 NSLevelIndicatorCell* cell = mMeterBarCell;
1855 [cell setMinValue:aParams.min];
1856 [cell setMaxValue:aParams.max];
1857 [cell setDoubleValue:aParams.value];
1860 * The way HTML and Cocoa defines the meter/indicator widget are different.
1861 * So, we are going to use a trick to get the Cocoa widget showing what we
1862 * are expecting: we set the warningValue or criticalValue to the current
1863 * value when we want to have the widget to be in the warning or critical
1866 switch (aParams.optimumState) {
1867 case OptimumState::eOptimum:
1868 [cell setWarningValue:aParams.max + 1];
1869 [cell setCriticalValue:aParams.max + 1];
1871 case OptimumState::eSubOptimum:
1872 [cell setWarningValue:aParams.value];
1873 [cell setCriticalValue:aParams.max + 1];
1875 case OptimumState::eSubSubOptimum:
1876 [cell setWarningValue:aParams.max + 1];
1877 [cell setCriticalValue:aParams.value];
1881 HIRect rect = CGRectStandardize(inBoxRect);
1882 BOOL vertical = !aParams.horizontal;
1884 CGContextSaveGState(cgContext);
1888 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1889 * show a rotated horizontal meter bar.
1890 * Given that we want to show a vertical meter bar, we assume that the rect
1891 * has vertical dimensions but we can't correctly draw a meter widget inside
1892 * such a rectangle so we need to inverse width and height (and re-position)
1893 * to get a rectangle with horizontal dimensions.
1894 * Finally, we want to show a vertical meter so we want to rotate the result
1895 * so it is vertical. We do that by changing the context.
1897 CGFloat tmp = rect.size.width;
1898 rect.size.width = rect.size.height;
1899 rect.size.height = tmp;
1900 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1901 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1903 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1904 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1905 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1908 if (mCellDrawWindow) {
1909 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state
1911 DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor,
1912 mCellDrawView, !vertical && aParams.rtl);
1914 CGContextRestoreGState(cgContext);
1916 NS_OBJC_END_TRY_IGNORE_BLOCK
1919 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1920 bool aIsInsideActiveWindow) {
1921 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1923 HIThemeTabPaneDrawInfo tpdi;
1926 tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1927 tpdi.direction = kThemeTabNorth;
1928 tpdi.size = kHIThemeTabSizeNormal;
1929 tpdi.kind = kHIThemeTabKindNormal;
1931 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1933 NS_OBJC_END_TRY_IGNORE_BLOCK;
1936 Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
1937 nsIFrame* aFrame, EventStates aEventState) {
1938 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1943 bool isHorizontal = IsRangeHorizontal(aFrame);
1945 // ScaleParams requires integer min, max and value. This is purely for
1946 // drawing, so we normalize to a range 0-1000 here.
1948 params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1951 params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1952 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1953 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
1954 params.disabled = aEventState.HasState(NS_EVENT_STATE_DISABLED);
1955 params.horizontal = isHorizontal;
1956 return Some(params);
1959 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1960 const ScaleParams& aParams) {
1961 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1963 HIThemeTrackDrawInfo tdi;
1966 tdi.kind = kThemeMediumSlider;
1967 tdi.bounds = inBoxRect;
1968 tdi.min = aParams.min;
1969 tdi.max = aParams.max;
1970 tdi.value = aParams.value;
1971 tdi.attributes = kThemeTrackShowThumb;
1972 if (aParams.horizontal) {
1973 tdi.attributes |= kThemeTrackHorizontal;
1975 if (aParams.reverse) {
1976 tdi.attributes |= kThemeTrackRightToLeft;
1978 if (aParams.focused) {
1979 tdi.attributes |= kThemeTrackHasFocus;
1981 if (aParams.disabled) {
1982 tdi.enableState = kThemeTrackDisabled;
1984 tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
1986 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1987 tdi.trackInfo.slider.pressState = 0;
1989 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1991 NS_OBJC_END_TRY_IGNORE_BLOCK;
1994 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) {
1995 // Usually a separator is drawn by the segment to the right of the
1996 // separator, but pressed and selected segments have higher priority.
1997 if (!aBefore || !aAfter) return nullptr;
1998 if (IsSelectedButton(aAfter)) return aAfter;
1999 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
2003 static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
2004 // A separator between two segments should always be located in the leftmost
2005 // pixel column of the segment to the right of the separator, regardless of
2006 // who ends up drawing it.
2007 // CoreUI draws the separators inside the drawing rect.
2008 if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
2009 // The segment to the left of us draws the separator, so we need to make
2011 aRect.origin.x += 1;
2012 aRect.size.width -= 1;
2014 if (aParams.drawsRightSeparator) {
2015 // We draw the right separator, so we need to extend the draw rect into the
2016 // segment to our right.
2017 aRect.size.width += 1;
2022 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
2024 if (aIsLast) return @"kCUISegmentPositionOnly";
2025 return @"kCUISegmentPositionFirst";
2027 if (aIsLast) return @"kCUISegmentPositionLast";
2028 return @"kCUISegmentPositionMiddle";
2031 struct SegmentedControlRenderSettings {
2032 const CGFloat* heights;
2033 const NSString* widgetName;
2036 static const CGFloat tabHeights[3] = {17, 20, 23};
2038 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
2040 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2042 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2043 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2045 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2046 nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
2047 SegmentParams params;
2048 params.segmentType = aSegmentType;
2049 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2050 params.pressed = IsPressedButton(aFrame);
2051 params.selected = IsSelectedButton(aFrame);
2052 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
2053 bool isRTL = IsFrameRTL(aFrame);
2054 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2055 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2056 params.atLeftEnd = !left;
2057 params.atRightEnd = !right;
2058 params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2059 params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2064 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2065 nsNativeThemeCocoa::SegmentType aSegmentType) {
2066 switch (aSegmentType) {
2067 case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2068 return toolbarButtonRenderSettings;
2069 case nsNativeThemeCocoa::SegmentType::eTab:
2070 return tabRenderSettings;
2074 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
2075 const SegmentParams& aParams) {
2076 SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
2077 NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2078 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2080 NSDictionary* dict = @{
2081 @"widget" : renderSettings.widgetName,
2082 @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2083 : @"kCUIPresentationStateInactive"),
2084 @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2085 @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2086 @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
2087 @"value" : [NSNumber numberWithBool:aParams.selected],
2089 (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2090 @"focus" : [NSNumber numberWithBool:aParams.focused],
2091 @"size" : CUIControlSizeForCocoaSize(controlSize),
2092 @"is.flipped" : [NSNumber numberWithBool:YES],
2093 @"direction" : @"up"
2096 RenderWithCoreUI(drawRect, cgContext, dict);
2099 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
2101 CGRect drawRect = inBoxRect;
2104 drawRect.size.height = 1.0f;
2105 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2108 drawRect.origin.y += drawRect.size.height;
2109 drawRect.size.height = inBoxRect.size.height - 2.0f;
2110 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2113 drawRect.origin.y += drawRect.size.height;
2114 drawRect.size.height = 1.0f;
2115 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
2118 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2119 if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2121 ToolbarWindow* win = (ToolbarWindow*)aWindow;
2122 float unifiedToolbarHeight = [win unifiedToolbarHeight];
2123 return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2124 aRect.YMost() <= unifiedToolbarHeight;
2127 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2129 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2131 if (inBoxRect.size.height < 2.0f) return;
2133 CGContextSaveGState(cgContext);
2134 CGContextClipToRect(cgContext, inBoxRect);
2136 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2137 // and bottom bar. We only want the bottom bar, so we extend the draw rect
2138 // upwards to make space for the title bar, and then we clip it away.
2139 CGRect drawRect = inBoxRect;
2140 const int extendUpwards = 40;
2141 drawRect.origin.y -= extendUpwards;
2142 drawRect.size.height += extendUpwards;
2144 drawRect, cgContext,
2146 dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2147 @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2148 [NSNumber numberWithInt:inBoxRect.size.height],
2149 @"kCUIWindowFrameBottomBarHeightKey",
2150 [NSNumber numberWithBool:YES],
2151 @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2152 [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2154 CGContextRestoreGState(cgContext);
2156 NS_OBJC_END_TRY_IGNORE_BLOCK;
2159 static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
2160 HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2161 HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2164 void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) {
2165 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
2167 HIThemeGrowBoxDrawInfo drawInfo;
2168 drawInfo.version = 0;
2169 drawInfo.state = kThemeStateActive;
2170 drawInfo.kind = kHIThemeGrowBoxKindNormal;
2171 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2172 drawInfo.size = kHIThemeGrowBoxSizeNormal;
2174 RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
2176 NS_OBJC_END_TRY_IGNORE_BLOCK;
2179 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
2181 mTextFieldCell.enabled = YES;
2182 mTextFieldCell.showsFirstResponder = aIsFocused;
2184 if (mCellDrawWindow) {
2185 mCellDrawWindow.cellsShouldLookActive = YES;
2188 // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do the usual
2189 // save+restore dance.
2190 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
2191 NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext
2193 DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView);
2194 NSGraphicsContext.currentContext = savedContext;
2197 void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect,
2198 bool aWindowIsActive, bool aSelectionIsActive) {
2200 if (aSelectionIsActive) {
2201 // Active selection, blue or graphite.
2202 fillColor = ControlAccentColor();
2204 // Inactive selection, gray.
2205 if (aWindowIsActive) {
2206 fillColor = [NSColor colorWithWhite:0.871 alpha:1.0];
2208 fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
2211 CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
2212 CGContextFillRect(aContext, aRect);
2215 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2216 return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2219 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2220 nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2221 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2223 // setup to draw into the correct port
2224 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2226 gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2227 nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2228 float originalHeight = nativeWidgetRect.Height();
2229 nativeWidgetRect.Round();
2230 if (nativeWidgetRect.IsEmpty()) {
2231 return Nothing(); // Don't attempt to draw invisible widgets.
2234 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2236 // Use high-resolution drawing.
2237 nativeWidgetRect.Scale(0.5f);
2238 originalHeight *= 0.5f;
2241 EventStates eventState = GetContentState(aFrame, aAppearance);
2243 switch (aAppearance) {
2244 case StyleAppearance::Menupopup:
2247 case StyleAppearance::Menuarrow:
2249 WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
2251 case StyleAppearance::Menuitem:
2252 case StyleAppearance::Checkmenuitem:
2253 return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
2254 aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
2256 case StyleAppearance::Menuseparator:
2257 return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
2259 case StyleAppearance::ButtonArrowUp:
2260 case StyleAppearance::ButtonArrowDown: {
2261 MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
2262 ? MenuIcon::eMenuUpScrollArrow
2263 : MenuIcon::eMenuDownScrollArrow;
2264 return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
2267 case StyleAppearance::Tooltip:
2270 case StyleAppearance::Checkbox:
2271 case StyleAppearance::Radio: {
2272 bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
2274 CheckboxOrRadioParams params;
2275 params.state = CheckboxOrRadioState::eOff;
2276 if (eventState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
2277 params.state = CheckboxOrRadioState::eIndeterminate;
2278 } else if (eventState.HasState(NS_EVENT_STATE_CHECKED)) {
2279 params.state = CheckboxOrRadioState::eOn;
2281 params.controlParams = ComputeControlParams(aFrame, eventState);
2282 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2284 return Some(WidgetInfo::Checkbox(params));
2286 return Some(WidgetInfo::Radio(params));
2289 case StyleAppearance::Button:
2290 if (IsDefaultButton(aFrame)) {
2291 // Check whether the default button is in a document that does not
2292 // match the :-moz-window-inactive pseudoclass. This activeness check
2293 // is different from the other "active window" checks in this file
2294 // because we absolutely need the button's default button appearance to
2295 // be in sync with its text color, and the text color is changed by
2296 // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2297 // default buttons in active windows have blue background and white
2298 // text, and default buttons in inactive windows have white background
2300 EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
2301 ControlParams params = ComputeControlParams(aFrame, eventState);
2302 params.insideActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2303 return Some(WidgetInfo::Button(ButtonParams{params, ButtonType::eDefaultPushButton}));
2305 if (IsButtonTypeMenu(aFrame)) {
2306 ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2307 controlParams.pressed = IsOpenButton(aFrame);
2308 DropdownParams params;
2309 params.controlParams = controlParams;
2310 params.pullsDown = true;
2311 params.editable = false;
2312 return Some(WidgetInfo::Dropdown(params));
2314 if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2315 // If the button is tall enough, draw the square button style so that
2316 // buttons with non-standard content look good. Otherwise draw normal
2317 // rounded aqua buttons.
2318 // This comparison is done based on the height that is calculated without
2319 // the top, because the snapped height can be affected by the top of the
2320 // rect and that may result in different height depending on the top value.
2321 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2322 ButtonType::eSquareBezelPushButton}));
2324 return Some(WidgetInfo::Button(
2325 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eRegularPushButton}));
2327 case StyleAppearance::FocusOutline:
2328 return Some(WidgetInfo::FocusOutline());
2330 case StyleAppearance::MozMacHelpButton:
2331 return Some(WidgetInfo::Button(
2332 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
2334 case StyleAppearance::MozMacDisclosureButtonOpen:
2335 case StyleAppearance::MozMacDisclosureButtonClosed: {
2336 ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2337 ? ButtonType::eDisclosureButtonClosed
2338 : ButtonType::eDisclosureButtonOpen;
2340 WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
2343 case StyleAppearance::Spinner: {
2344 bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2345 nsIContent* content = aFrame->GetContent();
2346 if (isSpinner && content->IsHTMLElement()) {
2347 // In HTML the theming for the spin buttons is drawn individually into
2348 // their own backgrounds instead of being drawn into the background of
2349 // their spinner parent as it is for XUL.
2352 SpinButtonParams params;
2353 if (content->IsElement()) {
2354 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns,
2356 params.pressedButton = Some(SpinButton::eUp);
2357 } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2358 u"down"_ns, eCaseMatters)) {
2359 params.pressedButton = Some(SpinButton::eDown);
2362 params.disabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
2363 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2365 return Some(WidgetInfo::SpinButtons(params));
2368 case StyleAppearance::SpinnerUpbutton:
2369 case StyleAppearance::SpinnerDownbutton: {
2370 nsNumberControlFrame* numberControlFrame =
2371 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2372 if (numberControlFrame) {
2373 SpinButtonParams params;
2374 if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2375 params.pressedButton = Some(SpinButton::eUp);
2376 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2377 params.pressedButton = Some(SpinButton::eDown);
2379 params.disabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
2380 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2381 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2382 return Some(WidgetInfo::SpinButtonUp(params));
2384 return Some(WidgetInfo::SpinButtonDown(params));
2388 case StyleAppearance::Toolbarbutton: {
2389 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
2390 params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2391 return Some(WidgetInfo::Segment(params));
2394 case StyleAppearance::Separator:
2395 return Some(WidgetInfo::Separator());
2397 case StyleAppearance::Toolbar: {
2398 NSWindow* win = NativeWindowForFrame(aFrame);
2399 bool isMain = [win isMainWindow];
2400 if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2401 // Unified toolbars are drawn similar to vibrancy; we communicate their extents via the
2402 // theme geometry mechanism and then place native views under Gecko's rendering. So Gecko
2403 // just needs to be transparent in the place where the toolbar should be visible.
2406 return Some(WidgetInfo::Toolbar(isMain));
2409 case StyleAppearance::MozWindowTitlebar: {
2413 case StyleAppearance::Statusbar:
2414 return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2416 case StyleAppearance::MenulistButton:
2417 case StyleAppearance::Menulist: {
2418 ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2419 controlParams.pressed = IsOpenButton(aFrame);
2420 DropdownParams params;
2421 params.controlParams = controlParams;
2422 params.pullsDown = false;
2423 params.editable = false;
2424 return Some(WidgetInfo::Dropdown(params));
2427 case StyleAppearance::MozMenulistArrowButton:
2428 return Some(WidgetInfo::Button(
2429 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
2431 case StyleAppearance::Groupbox:
2432 return Some(WidgetInfo::GroupBox());
2434 case StyleAppearance::Textfield:
2435 case StyleAppearance::NumberInput:
2436 return Some(WidgetInfo::TextField(ComputeTextFieldParams(aFrame, eventState)));
2438 case StyleAppearance::Searchfield:
2439 return Some(WidgetInfo::SearchField(ComputeTextFieldParams(aFrame, eventState)));
2441 case StyleAppearance::ProgressBar: {
2442 if (eventState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
2443 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2444 NS_WARNING("Unable to animate progressbar!");
2447 return Some(WidgetInfo::ProgressBar(
2448 ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
2451 case StyleAppearance::Meter:
2452 return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2454 case StyleAppearance::Progresschunk:
2455 case StyleAppearance::Meterchunk:
2456 // Do nothing: progress and meter bars cases will draw chunks.
2459 case StyleAppearance::Treetwisty:
2460 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2461 ButtonType::eTreeTwistyPointingRight}));
2463 case StyleAppearance::Treetwistyopen:
2464 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2465 ButtonType::eTreeTwistyPointingDown}));
2467 case StyleAppearance::Treeheadercell:
2468 return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
2470 case StyleAppearance::Treeitem:
2471 case StyleAppearance::Treeview:
2472 return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2474 case StyleAppearance::Treeheader:
2475 // do nothing, taken care of by individual header cells
2476 case StyleAppearance::Treeheadersortarrow:
2477 // do nothing, taken care of by treeview header
2478 case StyleAppearance::Treeline:
2479 // do nothing, these lines don't exist on macos
2482 case StyleAppearance::Range: {
2483 Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
2485 return Some(WidgetInfo::Scale(*params));
2490 case StyleAppearance::Textarea:
2491 return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
2493 case StyleAppearance::Listbox:
2494 return Some(WidgetInfo::ListBox());
2496 case StyleAppearance::MozMacSourceList: {
2500 case StyleAppearance::MozMacSourceListSelection:
2501 case StyleAppearance::MozMacActiveSourceListSelection: {
2502 // We only support vibrancy for source list selections if we're inside
2503 // a source list, because we need the background to be transparent.
2504 if (IsInSourceList(aFrame)) {
2507 bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
2508 if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
2509 return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
2511 return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
2514 case StyleAppearance::Tab: {
2515 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
2516 params.pressed = params.pressed && !params.selected;
2517 return Some(WidgetInfo::Segment(params));
2520 case StyleAppearance::Tabpanels:
2521 return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2523 case StyleAppearance::Resizer:
2524 return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
2532 NS_OBJC_END_TRY_BLOCK_RETURN(Nothing());
2536 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2537 StyleAppearance aAppearance, const nsRect& aRect,
2538 const nsRect& aDirtyRect, DrawOverflow aDrawOverflow) {
2539 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2541 if (IsWidgetScrollbarPart(aAppearance)) {
2542 return ThemeCocoa::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect, aDirtyRect,
2546 Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2552 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2554 gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2555 nativeWidgetRect.Round();
2557 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2559 auto colorScheme = LookAndFeel::ColorSchemeForFrame(aFrame);
2561 RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(), nativeWidgetRect,
2562 NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
2566 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
2569 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo,
2570 LookAndFeel::ColorScheme aScheme, DrawTarget& aDrawTarget,
2571 const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
2573 // Some of the drawing below uses NSAppearance.currentAppearance behind the scenes.
2574 // Set it to the appearance we want, the same way as nsLookAndFeel::NativeGetColor.
2575 NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme);
2577 // Also set the cell draw window's appearance; this is respected by NSTextFieldCell (and its
2578 // subclass NSSearchFieldCell).
2579 if (mCellDrawWindow) {
2580 mCellDrawWindow.appearance = NSAppearance.currentAppearance;
2583 const Widget widget = aWidgetInfo.Widget();
2585 // Some widgets render using DrawTarget, and some using CGContext.
2587 case Widget::eColorFill: {
2588 sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2589 aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color)));
2593 AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2594 gfx::Rect widgetRect = aWidgetRect;
2595 gfx::Rect dirtyRect = aDirtyRect;
2597 dirtyRect.Scale(1.0f / aScale);
2598 widgetRect.Scale(1.0f / aScale);
2599 aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
2601 // The remaining widgets require a CGContext.
2603 CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
2605 gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2607 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2608 if (cgContext == nullptr) {
2609 // The Quartz surface handles 0x0 surfaces by internally
2610 // making all operations no-ops; there's no cgcontext created for them.
2611 // Unfortunately, this means that callers that want to render
2612 // directly to the CGContext need to be aware of this quirk.
2616 // Set the context's "base transform" to in order to get correctly-sized focus rings.
2617 CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
2620 case Widget::eColorFill:
2621 MOZ_CRASH("already handled in outer switch");
2623 case Widget::eMenuIcon: {
2624 MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
2625 DrawMenuIcon(cgContext, macRect, params);
2628 case Widget::eMenuItem: {
2629 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2630 DrawMenuItem(cgContext, macRect, params);
2633 case Widget::eMenuSeparator: {
2634 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2635 DrawMenuSeparator(cgContext, macRect, params);
2638 case Widget::eCheckbox: {
2639 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2640 DrawCheckboxOrRadio(cgContext, true, macRect, params);
2643 case Widget::eRadio: {
2644 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2645 DrawCheckboxOrRadio(cgContext, false, macRect, params);
2648 case Widget::eButton: {
2649 ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2650 DrawButton(cgContext, macRect, params);
2653 case Widget::eDropdown: {
2654 DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2655 DrawDropdown(cgContext, macRect, params);
2658 case Widget::eFocusOutline: {
2659 DrawFocusOutline(cgContext, macRect);
2662 case Widget::eSpinButtons: {
2663 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2664 DrawSpinButtons(cgContext, macRect, params);
2667 case Widget::eSpinButtonUp: {
2668 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2669 DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2672 case Widget::eSpinButtonDown: {
2673 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2674 DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2677 case Widget::eSegment: {
2678 SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2679 DrawSegment(cgContext, macRect, params);
2682 case Widget::eSeparator: {
2683 HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2684 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2687 case Widget::eToolbar: {
2688 bool isMain = aWidgetInfo.Params<bool>();
2689 DrawToolbar(cgContext, macRect, isMain);
2692 case Widget::eStatusBar: {
2693 bool isMain = aWidgetInfo.Params<bool>();
2694 DrawStatusBar(cgContext, macRect, isMain);
2697 case Widget::eGroupBox: {
2698 HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
2699 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2702 case Widget::eTextField: {
2703 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2704 DrawTextField(cgContext, macRect, params);
2707 case Widget::eSearchField: {
2708 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>();
2709 DrawSearchField(cgContext, macRect, params);
2712 case Widget::eProgressBar: {
2713 ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2714 DrawProgress(cgContext, macRect, params);
2717 case Widget::eMeter: {
2718 MeterParams params = aWidgetInfo.Params<MeterParams>();
2719 DrawMeter(cgContext, macRect, params);
2722 case Widget::eTreeHeaderCell: {
2723 TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
2724 DrawTreeHeaderCell(cgContext, macRect, params);
2727 case Widget::eScale: {
2728 ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2729 DrawScale(cgContext, macRect, params);
2732 case Widget::eMultilineTextField: {
2733 bool isFocused = aWidgetInfo.Params<bool>();
2734 DrawMultilineTextField(cgContext, macRect, isFocused);
2737 case Widget::eListBox: {
2738 // Fill the content with the control background color.
2739 CGContextSetFillColorWithColor(cgContext, [NSColor.controlBackgroundColor CGColor]);
2740 CGContextFillRect(cgContext, macRect);
2741 // Draw the frame using kCUIWidgetScrollViewFrame. This is what NSScrollView uses in
2742 // -[NSScrollView drawRect:] if you give it a borderType of NSBezelBorder.
2744 macRect, cgContext, @{
2745 @"widget" : @"kCUIWidgetScrollViewFrame",
2746 @"kCUIIsFlippedKey" : @YES,
2747 @"kCUIVariantMetal" : @NO,
2751 case Widget::eActiveSourceListSelection:
2752 case Widget::eInactiveSourceListSelection: {
2753 bool isInActiveWindow = aWidgetInfo.Params<bool>();
2754 bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
2755 DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection);
2758 case Widget::eTabPanel: {
2759 bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
2760 DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
2763 case Widget::eResizer: {
2764 bool isRTL = aWidgetInfo.Params<bool>();
2765 DrawResizer(cgContext, macRect, isRTL);
2770 // Reset the base CTM.
2771 CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
2773 nativeDrawing.EndNativeDrawing();
2778 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
2779 mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
2780 const mozilla::layers::StackingContextHelper& aSc,
2781 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
2782 StyleAppearance aAppearance, const nsRect& aRect) {
2783 if (IsWidgetScrollbarPart(aAppearance)) {
2784 return ThemeCocoa::CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, aManager, aFrame,
2785 aAppearance, aRect);
2788 // This list needs to stay consistent with the list in DrawWidgetBackground.
2789 // For every switch case in DrawWidgetBackground, there are three choices:
2790 // - If the case in DrawWidgetBackground draws nothing for the given widget
2791 // type, then don't list it here. We will hit the "default: return true;"
2793 // - If the case in DrawWidgetBackground draws something simple for the given
2794 // widget type, imitate that drawing using WebRender commands.
2795 // - If the case in DrawWidgetBackground draws something complicated for the
2796 // given widget type, return false here.
2797 switch (aAppearance) {
2798 case StyleAppearance::Menuarrow:
2799 case StyleAppearance::Menuitem:
2800 case StyleAppearance::Checkmenuitem:
2801 case StyleAppearance::Menuseparator:
2802 case StyleAppearance::ButtonArrowUp:
2803 case StyleAppearance::ButtonArrowDown:
2804 case StyleAppearance::Checkbox:
2805 case StyleAppearance::Radio:
2806 case StyleAppearance::Button:
2807 case StyleAppearance::FocusOutline:
2808 case StyleAppearance::MozMacHelpButton:
2809 case StyleAppearance::MozMacDisclosureButtonOpen:
2810 case StyleAppearance::MozMacDisclosureButtonClosed:
2811 case StyleAppearance::Spinner:
2812 case StyleAppearance::SpinnerUpbutton:
2813 case StyleAppearance::SpinnerDownbutton:
2814 case StyleAppearance::Toolbarbutton:
2815 case StyleAppearance::Separator:
2816 case StyleAppearance::Toolbar:
2817 case StyleAppearance::MozWindowTitlebar:
2818 case StyleAppearance::Statusbar:
2819 case StyleAppearance::Menulist:
2820 case StyleAppearance::MenulistButton:
2821 case StyleAppearance::MozMenulistArrowButton:
2822 case StyleAppearance::Groupbox:
2823 case StyleAppearance::Textfield:
2824 case StyleAppearance::NumberInput:
2825 case StyleAppearance::Searchfield:
2826 case StyleAppearance::ProgressBar:
2827 case StyleAppearance::Meter:
2828 case StyleAppearance::Treeheadercell:
2829 case StyleAppearance::Treetwisty:
2830 case StyleAppearance::Treetwistyopen:
2831 case StyleAppearance::Treeitem:
2832 case StyleAppearance::Treeview:
2833 case StyleAppearance::Range:
2836 case StyleAppearance::Textarea:
2837 case StyleAppearance::Listbox:
2838 case StyleAppearance::Tab:
2839 case StyleAppearance::Tabpanels:
2840 case StyleAppearance::Resizer:
2848 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
2850 // Assuming aMargin was originally specified for a horizontal LTR context,
2851 // reinterpret the values as logical, and then map to physical coords
2852 // according to aFrame's actual writing mode.
2853 WritingMode wm = aFrame->GetWritingMode();
2854 nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)
2855 .GetPhysicalMargin(wm);
2856 return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
2859 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
2860 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
2861 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
2862 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
2864 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
2866 StyleAppearance aAppearance) {
2867 LayoutDeviceIntMargin result;
2869 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
2870 switch (aAppearance) {
2871 case StyleAppearance::Button: {
2872 if (IsButtonTypeMenu(aFrame)) {
2873 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2875 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
2880 case StyleAppearance::Toolbarbutton: {
2881 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
2885 case StyleAppearance::Checkbox:
2886 case StyleAppearance::Radio: {
2887 // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
2888 // assume a border width of 2px.
2889 result.SizeTo(2, 2, 2, 2);
2893 case StyleAppearance::Menulist:
2894 case StyleAppearance::MenulistButton:
2895 case StyleAppearance::MozMenulistArrowButton:
2896 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
2899 case StyleAppearance::Menuarrow:
2900 if (nsCocoaFeatures::OnBigSurOrLater()) {
2901 result.SizeTo(0, 0, 0, 28);
2905 case StyleAppearance::NumberInput:
2906 case StyleAppearance::Textfield: {
2907 SInt32 frameOutset = 0;
2908 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2910 SInt32 textPadding = 0;
2911 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2913 frameOutset += textPadding;
2915 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2919 case StyleAppearance::Textarea:
2920 result.SizeTo(1, 1, 1, 1);
2923 case StyleAppearance::Searchfield: {
2924 auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur
2925 : kAquaSearchfieldBorder;
2926 result = DirectionAwareMargin(border, aFrame);
2930 case StyleAppearance::Listbox: {
2931 SInt32 frameOutset = 0;
2932 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2933 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2937 case StyleAppearance::Statusbar:
2938 result.SizeTo(1, 0, 0, 0);
2945 if (IsHiDPIContext(aContext)) {
2946 result = result + result; // doubled
2949 NS_OBJC_END_TRY_BLOCK_RETURN(result);
2952 // Return false here to indicate that CSS padding values should be used. There is
2953 // no reason to make a distinction between padding and border values, just specify
2954 // whatever values you want in GetWidgetBorder and only use this to return true
2955 // if you want to override CSS padding values.
2956 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
2957 StyleAppearance aAppearance,
2958 LayoutDeviceIntMargin* aResult) {
2959 // We don't want CSS padding being used for certain widgets.
2960 // See bug 381639 for an example of why.
2961 switch (aAppearance) {
2962 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2963 // and have a meaningful baseline, so they can't have
2964 // author-specified padding.
2965 case StyleAppearance::Checkbox:
2966 case StyleAppearance::Radio:
2967 aResult->SizeTo(0, 0, 0, 0);
2970 case StyleAppearance::Menuarrow:
2971 case StyleAppearance::Searchfield:
2972 if (nsCocoaFeatures::OnBigSurOrLater()) {
2983 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
2984 StyleAppearance aAppearance, nsRect* aOverflowRect) {
2985 nsIntMargin overflow;
2986 switch (aAppearance) {
2987 case StyleAppearance::Button:
2988 case StyleAppearance::MozMacDisclosureButtonOpen:
2989 case StyleAppearance::MozMacDisclosureButtonClosed:
2990 case StyleAppearance::MozMacHelpButton:
2991 case StyleAppearance::Toolbarbutton:
2992 case StyleAppearance::NumberInput:
2993 case StyleAppearance::Textfield:
2994 case StyleAppearance::Textarea:
2995 case StyleAppearance::Searchfield:
2996 case StyleAppearance::Listbox:
2997 case StyleAppearance::Menulist:
2998 case StyleAppearance::MenulistButton:
2999 case StyleAppearance::MozMenulistArrowButton:
3000 case StyleAppearance::Checkbox:
3001 case StyleAppearance::Radio:
3002 case StyleAppearance::Tab:
3003 case StyleAppearance::FocusOutline: {
3004 overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
3005 kMaxFocusRingWidth);
3008 case StyleAppearance::ProgressBar: {
3009 // Progress bars draw a 2 pixel white shadow under their progress indicators.
3010 overflow.bottom = 2;
3013 case StyleAppearance::Meter: {
3014 // Meter bars overflow their boxes by about 2 pixels.
3015 overflow.SizeTo(2, 2, 2, 2);
3022 if (IsHiDPIContext(aContext)) {
3023 // Double the number of device pixels.
3024 overflow += overflow;
3027 if (overflow != nsIntMargin()) {
3028 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
3029 aOverflowRect->Inflate(nsMargin(
3030 NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
3031 NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
3039 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
3040 StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
3041 bool* aIsOverridable) {
3042 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
3044 aResult->SizeTo(0, 0);
3045 *aIsOverridable = true;
3047 if (IsWidgetScrollbarPart(aAppearance)) {
3048 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance, aResult,
3052 switch (aAppearance) {
3053 case StyleAppearance::Button: {
3054 aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
3055 pushButtonSettings.naturalSizes[miniControlSize].height);
3059 case StyleAppearance::ButtonArrowUp:
3060 case StyleAppearance::ButtonArrowDown: {
3061 aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
3062 *aIsOverridable = false;
3066 case StyleAppearance::Menuarrow: {
3067 aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
3068 *aIsOverridable = false;
3072 case StyleAppearance::MozMacDisclosureButtonOpen:
3073 case StyleAppearance::MozMacDisclosureButtonClosed: {
3074 aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
3075 *aIsOverridable = false;
3079 case StyleAppearance::MozMacHelpButton: {
3080 aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
3081 *aIsOverridable = false;
3085 case StyleAppearance::Toolbarbutton: {
3086 aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
3090 case StyleAppearance::Spinner:
3091 case StyleAppearance::SpinnerUpbutton:
3092 case StyleAppearance::SpinnerDownbutton: {
3093 SInt32 buttonHeight = 0, buttonWidth = 0;
3094 if (aFrame->GetContent()->IsXULElement()) {
3095 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
3096 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
3098 NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
3099 buttonWidth = size.width;
3100 buttonHeight = size.height;
3101 if (aAppearance != StyleAppearance::Spinner) {
3102 // the buttons are half the height of the spinner
3106 aResult->SizeTo(buttonWidth, buttonHeight);
3107 *aIsOverridable = true;
3111 case StyleAppearance::Menulist:
3112 case StyleAppearance::MenulistButton: {
3113 SInt32 popupHeight = 0;
3114 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3115 aResult->SizeTo(0, popupHeight);
3119 case StyleAppearance::NumberInput:
3120 case StyleAppearance::Textfield:
3121 case StyleAppearance::Textarea:
3122 case StyleAppearance::Searchfield: {
3123 // at minimum, we should be tall enough for 9pt text.
3124 // I'm using hardcoded values here because the appearance manager
3125 // values for the frame size are incorrect.
3126 aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3130 case StyleAppearance::MozWindowButtonBox: {
3131 NSSize size = WindowButtonsSize(aFrame);
3132 aResult->SizeTo(size.width, size.height);
3133 *aIsOverridable = false;
3137 case StyleAppearance::ProgressBar: {
3138 SInt32 barHeight = 0;
3139 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3140 aResult->SizeTo(0, barHeight);
3144 case StyleAppearance::Separator: {
3145 aResult->SizeTo(1, 1);
3149 case StyleAppearance::Treetwisty:
3150 case StyleAppearance::Treetwistyopen: {
3151 SInt32 twistyHeight = 0, twistyWidth = 0;
3152 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3153 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3154 aResult->SizeTo(twistyWidth, twistyHeight);
3155 *aIsOverridable = false;
3159 case StyleAppearance::Treeheader:
3160 case StyleAppearance::Treeheadercell: {
3161 SInt32 headerHeight = 0;
3162 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3163 aResult->SizeTo(0, headerHeight);
3167 case StyleAppearance::Tab: {
3168 aResult->SizeTo(0, tabHeights[miniControlSize]);
3172 case StyleAppearance::RangeThumb: {
3175 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3176 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3177 aResult->SizeTo(width, height);
3178 *aIsOverridable = false;
3182 case StyleAppearance::MozMenulistArrowButton:
3183 return ThemeCocoa::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance, aResult,
3186 case StyleAppearance::Resizer: {
3187 HIThemeGrowBoxDrawInfo drawInfo;
3188 drawInfo.version = 0;
3189 drawInfo.state = kThemeStateActive;
3190 drawInfo.kind = kHIThemeGrowBoxKindNormal;
3191 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3192 drawInfo.size = kHIThemeGrowBoxSizeNormal;
3193 HIPoint pnt = {0, 0};
3195 HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3196 aResult->SizeTo(bounds.size.width, bounds.size.height);
3197 *aIsOverridable = false;
3204 if (IsHiDPIContext(aPresContext->DeviceContext())) {
3205 *aResult = *aResult * 2;
3210 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
3214 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
3215 nsAtom* aAttribute, bool* aShouldRepaint,
3216 const nsAttrValue* aOldValue) {
3217 // Some widget types just never change state.
3218 switch (aAppearance) {
3219 case StyleAppearance::MozWindowTitlebar:
3220 case StyleAppearance::Toolbox:
3221 case StyleAppearance::Toolbar:
3222 case StyleAppearance::Statusbar:
3223 case StyleAppearance::Statusbarpanel:
3224 case StyleAppearance::Resizerpanel:
3225 case StyleAppearance::Tooltip:
3226 case StyleAppearance::Tabpanels:
3227 case StyleAppearance::Tabpanel:
3228 case StyleAppearance::Dialog:
3229 case StyleAppearance::Menupopup:
3230 case StyleAppearance::Groupbox:
3231 case StyleAppearance::Progresschunk:
3232 case StyleAppearance::ProgressBar:
3233 case StyleAppearance::Meter:
3234 case StyleAppearance::Meterchunk:
3235 case StyleAppearance::MozMacVibrantTitlebarLight:
3236 case StyleAppearance::MozMacVibrantTitlebarDark:
3237 *aShouldRepaint = false;
3243 // XXXdwh Not sure what can really be done here. Can at least guess for
3244 // specific widgets that they're highly unlikely to have certain states.
3245 // For example, a toolbar doesn't care about any states.
3247 // Hover/focus/active changed. Always repaint.
3248 *aShouldRepaint = true;
3250 // Check the attribute to see if it's relevant.
3251 // disabled, checked, dlgtype, default, etc.
3252 *aShouldRepaint = false;
3253 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3254 aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected ||
3255 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection ||
3256 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3257 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3258 *aShouldRepaint = true;
3265 nsNativeThemeCocoa::ThemeChanged() {
3266 // This is unimplemented because we don't care if gecko changes its theme
3267 // and macOS system appearance changes are handled by
3268 // nsLookAndFeel::SystemWantsDarkTheme.
3272 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3273 StyleAppearance aAppearance) {
3274 if (IsWidgetScrollbarPart(aAppearance)) {
3275 return ThemeCocoa::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
3277 // if this is a dropdown button in a combobox the answer is always no
3278 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3279 nsIFrame* parentFrame = aFrame->GetParent();
3280 if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3283 switch (aAppearance) {
3284 // Combobox dropdowns don't support native theming in vertical mode.
3285 case StyleAppearance::Menulist:
3286 case StyleAppearance::MenulistButton:
3287 case StyleAppearance::MozMenulistArrowButton:
3288 case StyleAppearance::MenulistText:
3289 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3294 case StyleAppearance::Listbox:
3295 case StyleAppearance::Dialog:
3296 case StyleAppearance::Window:
3297 case StyleAppearance::MozWindowButtonBox:
3298 case StyleAppearance::MozWindowTitlebar:
3299 case StyleAppearance::Checkmenuitem:
3300 case StyleAppearance::Menupopup:
3301 case StyleAppearance::Menuarrow:
3302 case StyleAppearance::Menuitem:
3303 case StyleAppearance::Menuseparator:
3304 case StyleAppearance::Tooltip:
3306 case StyleAppearance::Checkbox:
3307 case StyleAppearance::CheckboxContainer:
3308 case StyleAppearance::Radio:
3309 case StyleAppearance::RadioContainer:
3310 case StyleAppearance::Groupbox:
3311 case StyleAppearance::MozMacHelpButton:
3312 case StyleAppearance::MozMacDisclosureButtonOpen:
3313 case StyleAppearance::MozMacDisclosureButtonClosed:
3314 case StyleAppearance::Button:
3315 case StyleAppearance::ButtonArrowUp:
3316 case StyleAppearance::ButtonArrowDown:
3317 case StyleAppearance::Toolbarbutton:
3318 case StyleAppearance::Spinner:
3319 case StyleAppearance::SpinnerUpbutton:
3320 case StyleAppearance::SpinnerDownbutton:
3321 case StyleAppearance::Toolbar:
3322 case StyleAppearance::Statusbar:
3323 case StyleAppearance::NumberInput:
3324 case StyleAppearance::Textfield:
3325 case StyleAppearance::Textarea:
3326 case StyleAppearance::Searchfield:
3327 case StyleAppearance::Toolbox:
3328 case StyleAppearance::ProgressBar:
3329 case StyleAppearance::Progresschunk:
3330 case StyleAppearance::Meter:
3331 case StyleAppearance::Meterchunk:
3332 case StyleAppearance::Separator:
3334 case StyleAppearance::Tabpanels:
3335 case StyleAppearance::Tab:
3337 case StyleAppearance::Treetwisty:
3338 case StyleAppearance::Treetwistyopen:
3339 case StyleAppearance::Treeview:
3340 case StyleAppearance::Treeheader:
3341 case StyleAppearance::Treeheadercell:
3342 case StyleAppearance::Treeheadersortarrow:
3343 case StyleAppearance::Treeitem:
3344 case StyleAppearance::Treeline:
3345 case StyleAppearance::MozMacSourceList:
3346 case StyleAppearance::MozMacSourceListSelection:
3347 case StyleAppearance::MozMacActiveSourceListSelection:
3349 case StyleAppearance::Range:
3350 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3352 case StyleAppearance::Resizer: {
3353 nsIFrame* parentFrame = aFrame->GetParent();
3354 if (!parentFrame || !parentFrame->IsScrollFrame()) return true;
3356 // Note that IsWidgetStyled is not called for resizers on Mac. This is
3357 // because for scrollable containers, the native resizer looks better
3358 // when (non-overlay) scrollbars are present even when the style is
3359 // overriden, and the custom transparent resizer looks better when
3360 // scrollbars are not present.
3361 nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3362 return (!LookAndFeel::UseOverlayScrollbars() && scrollFrame &&
3363 (!scrollFrame->GetScrollbarVisibility().isEmpty()));
3366 case StyleAppearance::FocusOutline:
3369 case StyleAppearance::MozMacVibrantTitlebarLight:
3370 case StyleAppearance::MozMacVibrantTitlebarDark:
3379 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3380 // flesh this out at some point
3381 switch (aAppearance) {
3382 case StyleAppearance::MozMenulistArrowButton:
3383 case StyleAppearance::Radio:
3384 case StyleAppearance::Checkbox:
3385 case StyleAppearance::ProgressBar:
3386 case StyleAppearance::Meter:
3387 case StyleAppearance::Range:
3388 case StyleAppearance::MozMacHelpButton:
3389 case StyleAppearance::MozMacDisclosureButtonOpen:
3390 case StyleAppearance::MozMacDisclosureButtonClosed:
3398 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance aAppearance) {
3399 switch (aAppearance) {
3400 case StyleAppearance::Textarea:
3401 case StyleAppearance::Textfield:
3402 case StyleAppearance::Searchfield:
3403 case StyleAppearance::NumberInput:
3404 case StyleAppearance::Menulist:
3405 case StyleAppearance::MenulistButton:
3406 case StyleAppearance::Button:
3407 case StyleAppearance::MozMacHelpButton:
3408 case StyleAppearance::MozMacDisclosureButtonOpen:
3409 case StyleAppearance::MozMacDisclosureButtonClosed:
3410 case StyleAppearance::Radio:
3411 case StyleAppearance::Range:
3412 case StyleAppearance::Checkbox:
3419 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3421 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
3422 switch (aAppearance) {
3423 case StyleAppearance::Dialog:
3424 case StyleAppearance::Groupbox:
3425 case StyleAppearance::Tabpanels:
3426 case StyleAppearance::ButtonArrowUp:
3427 case StyleAppearance::ButtonArrowDown:
3428 case StyleAppearance::Checkmenuitem:
3429 case StyleAppearance::Menupopup:
3430 case StyleAppearance::Menuarrow:
3431 case StyleAppearance::Menuitem:
3432 case StyleAppearance::Menuseparator:
3433 case StyleAppearance::Tooltip:
3434 case StyleAppearance::Spinner:
3435 case StyleAppearance::SpinnerUpbutton:
3436 case StyleAppearance::SpinnerDownbutton:
3437 case StyleAppearance::Separator:
3438 case StyleAppearance::Toolbox:
3439 case StyleAppearance::NumberInput:
3440 case StyleAppearance::Textfield:
3441 case StyleAppearance::Treeview:
3442 case StyleAppearance::Treeline:
3443 case StyleAppearance::Textarea:
3444 case StyleAppearance::Listbox:
3445 case StyleAppearance::Resizer:
3452 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3453 nsIFrame* aFrame, StyleAppearance aAppearance) {
3454 switch (aAppearance) {
3455 case StyleAppearance::MozWindowTitlebar:
3456 return eThemeGeometryTypeTitlebar;
3457 case StyleAppearance::Toolbar:
3458 return eThemeGeometryTypeToolbar;
3459 case StyleAppearance::Toolbox:
3460 return eThemeGeometryTypeToolbox;
3461 case StyleAppearance::MozWindowButtonBox:
3462 return eThemeGeometryTypeWindowButtons;
3463 case StyleAppearance::MozMacVibrantTitlebarLight:
3464 return eThemeGeometryTypeVibrantTitlebarLight;
3465 case StyleAppearance::MozMacVibrantTitlebarDark:
3466 return eThemeGeometryTypeVibrantTitlebarDark;
3467 case StyleAppearance::Tooltip:
3468 return eThemeGeometryTypeTooltip;
3469 case StyleAppearance::Menupopup:
3470 return eThemeGeometryTypeMenu;
3471 case StyleAppearance::Menuitem:
3472 case StyleAppearance::Checkmenuitem: {
3473 EventStates eventState = GetContentState(aFrame, aAppearance);
3474 bool isDisabled = eventState.HasState(NS_EVENT_STATE_DISABLED);
3475 bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
3476 return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
3478 case StyleAppearance::MozMacSourceList:
3479 return eThemeGeometryTypeSourceList;
3480 case StyleAppearance::MozMacSourceListSelection:
3481 return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
3482 : eThemeGeometryTypeUnknown;
3483 case StyleAppearance::MozMacActiveSourceListSelection:
3484 return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
3485 : eThemeGeometryTypeUnknown;
3487 return eThemeGeometryTypeUnknown;
3491 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
3492 StyleAppearance aAppearance) {
3493 if (IsWidgetScrollbarPart(aAppearance)) {
3494 return ThemeCocoa::GetWidgetTransparency(aFrame, aAppearance);
3497 switch (aAppearance) {
3498 case StyleAppearance::Menupopup:
3499 case StyleAppearance::Tooltip:
3500 case StyleAppearance::Dialog:
3501 case StyleAppearance::Toolbar:
3502 return eTransparent;
3504 case StyleAppearance::Statusbar:
3505 // Knowing that scrollbars and statusbars are opaque improves
3506 // performance, because we create layers for them.
3510 return eUnknownTransparency;
3514 already_AddRefed<widget::Theme> do_CreateNativeThemeDoNotUseDirectly() {
3515 return do_AddRef(new nsNativeThemeCocoa());