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"
8 #include "mozilla/gfx/2D.h"
9 #include "mozilla/gfx/Helpers.h"
10 #include "nsChildView.h"
11 #include "nsDeviceContext.h"
12 #include "nsLayoutUtils.h"
13 #include "nsObjCExceptions.h"
14 #include "nsNumberControlFrame.h"
15 #include "nsRangeFrame.h"
18 #include "nsStyleConsts.h"
19 #include "nsPresContext.h"
20 #include "nsIContent.h"
21 #include "mozilla/dom/Document.h"
24 #include "nsNameSpaceManager.h"
25 #include "nsPresContext.h"
26 #include "nsGkAtoms.h"
27 #include "nsCocoaFeatures.h"
28 #include "nsCocoaWindow.h"
29 #include "nsNativeBasicTheme.h"
30 #include "nsNativeThemeColors.h"
31 #include "nsIScrollableFrame.h"
32 #include "mozilla/ClearOnShutdown.h"
33 #include "mozilla/EventStates.h"
34 #include "mozilla/Range.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/HTMLMeterElement.h"
37 #include "mozilla/layers/StackingContextHelper.h"
38 #include "mozilla/StaticPrefs_layout.h"
39 #include "mozilla/StaticPrefs_widget.h"
40 #include "nsLookAndFeel.h"
41 #include "VibrancyManager.h"
43 #include "gfxContext.h"
44 #include "gfxQuartzSurface.h"
45 #include "gfxQuartzNativeDrawing.h"
46 #include "gfxUtils.h" // for ToDeviceColor
49 using namespace mozilla;
50 using namespace mozilla::gfx;
51 using mozilla::dom::HTMLMeterElement;
53 #define DRAW_IN_FRAME_DEBUG 0
54 #define SCROLLBARS_VISUAL_DEBUG 0
56 // private Quartz routines needed here
58 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
59 CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
60 typedef CFTypeRef CUIRendererRef;
61 void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
62 CFDictionaryRef* result);
65 // Workaround for NSCell control tint drawing
66 // Without this workaround, NSCells are always drawn with the clear control tint
67 // as long as they're not attached to an NSControl which is a subview of an active window.
68 // XXXmstange Why doesn't Webkit need this?
69 @implementation NSCell (ControlTintWorkaround)
70 - (int)_realControlTint {
71 return [self controlTint];
75 // The purpose of this class is to provide objects that can be used when drawing
76 // NSCells using drawWithFrame:inView: without causing any harm. The only
77 // messages that will be sent to such an object are "isFlipped" and
78 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
79 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
80 // NSView) will be called when drawing search fields, and we only provide it in
81 // order to prevent "unrecognized selector" exceptions.
82 // There's no need to pass the actual NSView that we're drawing into to
83 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
84 // invalidations as soon as we draw a focusring!
85 @interface CellDrawView : NSView
89 @implementation CellDrawView
95 - (NSText*)currentEditor {
101 // These two classes don't actually add any behavior over NSButtonCell. Their
102 // purpose is to make it easy to distinguish NSCell objects that are used for
103 // drawing radio buttons / checkboxes from other cell types.
104 // The class names are made up, there are no classes with these names in AppKit.
105 // The reason we need them is that calling [cell setButtonType:NSRadioButton]
106 // doesn't leave an easy-to-check "marker" on the cell object - there is no
107 // -[NSButtonCell buttonType] method.
108 @interface RadioButtonCell : NSButtonCell
111 @implementation RadioButtonCell
114 @interface CheckboxCell : NSButtonCell
117 @implementation CheckboxCell
120 static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
121 if ([aCell showsFirstResponder]) {
122 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
123 CGContextSaveGState(cgContext);
125 // It's important to set the focus ring style before we enter the
126 // transparency layer so that the transparency layer only contains
127 // the normal button mask without the focus ring, and the conversion
128 // to the focus ring shape happens only when the transparency layer is
130 NSSetFocusRingStyle(NSFocusRingOnly);
132 // We need to draw the whole button into a transparency layer because
133 // many button types are composed of multiple parts, and if these parts
134 // were drawn while the focus ring style was active, each individual part
135 // would produce a focus ring for itself. But we only want one focus ring
136 // for the whole button. The transparency layer is a way to merge the
137 // individual button parts together before the focus ring shape is
139 CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
140 [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
141 CGContextEndTransparencyLayer(cgContext);
143 CGContextRestoreGState(cgContext);
147 static bool FocusIsDrawnByDrawWithFrame(NSCell* aCell) {
148 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
149 // When building with the 10.8 SDK or higher, focus rings don't draw as part
150 // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
151 // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
152 // See the NSButtonCell section under
153 // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
156 // On 10.10, whether the focus ring is drawn as part of
157 // -[NSCell drawWithFrame:inView:] depends on the cell type.
158 // Radio buttons and checkboxes draw their own focus rings, other cell
159 // types need -[NSCell drawFocusRingMaskWithFrame:inView:].
161 [aCell isKindOfClass:[RadioButtonCell class]] || [aCell isKindOfClass:[CheckboxCell class]];
165 static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
166 [aCell drawWithFrame:aWithFrame inView:aInView];
168 if (!FocusIsDrawnByDrawWithFrame(aCell)) {
169 DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
174 * NSProgressBarCell is used to draw progress bars of any size.
176 @interface NSProgressBarCell : NSCell {
177 /*All instance variables are private*/
180 bool mIsIndeterminate;
184 - (void)setValue:(double)value;
186 - (void)setMax:(double)max;
188 - (void)setIndeterminate:(bool)aIndeterminate;
189 - (bool)isIndeterminate;
190 - (void)setHorizontal:(bool)aIsHorizontal;
191 - (bool)isHorizontal;
192 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
195 @implementation NSProgressBarCell
197 - (void)setMax:(double)aMax {
205 - (void)setValue:(double)aValue {
213 - (void)setIndeterminate:(bool)aIndeterminate {
214 mIsIndeterminate = aIndeterminate;
217 - (bool)isIndeterminate {
218 return mIsIndeterminate;
221 - (void)setHorizontal:(bool)aIsHorizontal {
222 mIsHorizontal = aIsHorizontal;
225 - (bool)isHorizontal {
226 return mIsHorizontal;
229 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
230 CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
232 HIThemeTrackDrawInfo tdi;
237 tdi.value = INT32_MAX * (mValue / mMax);
239 tdi.bounds = NSRectToCGRect(cellFrame);
240 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
242 [self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive;
244 NSControlSize size = [self controlSize];
245 if (size == NSControlSizeRegular) {
246 tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
248 NS_ASSERTION(size == NSControlSizeSmall,
249 "We shouldn't have another size than small and regular for the moment");
250 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
253 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
254 int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
255 tdi.trackInfo.progress.phase =
256 uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
258 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
263 @interface SearchFieldCellWithFocusRing : NSSearchFieldCell {
267 // Workaround for Bug 542048
268 // On 64-bit, NSSearchFieldCells don't draw focus rings.
269 @implementation SearchFieldCellWithFocusRing
271 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView {
272 [super drawWithFrame:rect inView:controlView];
274 if (FocusIsDrawnByDrawWithFrame(self)) {
275 // For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
276 // focus ring in 64 bit mode, no matter what SDK is used or what OS X version
277 // we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
278 // caller expects us to draw a focus ring. So we just do that here.
279 DrawFocusRingForCellIfNeeded(self, rect, controlView);
283 - (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView {
284 // By default this draws nothing. I don't know why.
285 // We just draw the search field again. It's a great mask shape for its own
287 [super drawWithFrame:rect inView:controlView];
292 @interface ToolbarSearchFieldCellWithFocusRing : SearchFieldCellWithFocusRing
295 @implementation ToolbarSearchFieldCellWithFocusRing
297 - (BOOL)_isToolbarMode {
298 // This function is called during -[NSSearchFieldCell drawWithFrame:inView:].
299 // On earlier macOS versions, returning YES from it selects the style
300 // that's appropriate for search fields inside toolbars. On Big Sur,
301 // returning YES causes the search field to be drawn incorrectly, with
302 // the toolbar gradient appearing as the field background.
303 if (nsCocoaFeatures::OnBigSurOrLater()) {
311 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
313 static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
315 // These enums are for indexing into the margin array.
317 leopardOSorlater = 0, // 10.6 - 10.9
318 yosemiteOSorlater = 1 // 10.10+
321 enum { miniControlSize, smallControlSize, regularControlSize };
323 enum { leftMargin, topMargin, rightMargin, bottomMargin };
325 static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
326 if (cocoaControlSize == NSControlSizeMini)
327 return miniControlSize;
328 else if (cocoaControlSize == NSControlSizeSmall)
329 return smallControlSize;
331 return regularControlSize;
334 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
335 if (enumControlSize == miniControlSize)
336 return NSControlSizeMini;
337 else if (enumControlSize == smallControlSize)
338 return NSControlSizeSmall;
340 return NSControlSizeRegular;
343 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
344 if (aControlSize == NSControlSizeRegular)
346 else if (aControlSize == NSControlSizeSmall)
352 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
353 const float marginSet[][3][4]) {
354 if (!marginSet) return;
356 static int osIndex = yosemiteOSorlater;
357 size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
358 const float* buttonMargins = marginSet[osIndex][controlSize];
359 rect->origin.x -= buttonMargins[leftMargin];
360 rect->origin.y -= buttonMargins[bottomMargin];
361 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
362 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
365 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) {
366 if (!aFrame) return nil;
368 nsIWidget* widget = aFrame->GetNearestWidget();
369 if (!widget) return nil;
371 nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
372 if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
374 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
377 static NSSize WindowButtonsSize(nsIFrame* aFrame) {
378 NSWindow* window = NativeWindowForFrame(aFrame);
380 // Return fallback values.
381 return NSMakeSize(54, 16);
384 NSRect buttonBox = NSZeroRect;
385 NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
387 buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
389 NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
390 if (minimizeButton) {
391 buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
393 NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
395 buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
397 return buttonBox.size;
400 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
401 nsIWidget* topLevelWidget = NULL;
402 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
403 if (!topLevelWidget || !win) return YES;
405 // XUL popups, e.g. the toolbar customization popup, can't become key windows,
406 // but controls in these windows should still get the active look.
407 if (topLevelWidget->WindowType() == eWindowType_popup) return YES;
408 if ([win isSheet]) return [win isKeyWindow];
409 return [win isMainWindow] && ![win attachedSheet];
412 // Toolbar controls and content controls respond to different window
413 // activeness states.
414 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
415 if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
416 return FrameIsInActiveWindow(aFrame);
419 static bool IsInSourceList(nsIFrame* aFrame) {
420 for (nsIFrame* frame = aFrame->GetParent(); frame;
421 frame = nsLayoutUtils::GetCrossDocParentFrame(frame)) {
422 if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) {
429 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
431 nsNativeThemeCocoa::nsNativeThemeCocoa() {
432 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
434 kMaxFocusRingWidth = 7;
436 // provide a local autorelease pool, as this is called during startup
437 // before the main event-loop pool is in place
438 nsAutoreleasePool pool;
440 mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
441 [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
442 [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
443 [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
445 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
446 [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
447 [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
448 [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
450 mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
451 [mPushButtonCell setButtonType:NSMomentaryPushInButton];
452 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
454 mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
455 [mRadioButtonCell setButtonType:NSRadioButton];
457 mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
458 [mCheckboxCell setButtonType:NSSwitchButton];
459 [mCheckboxCell setAllowsMixedState:YES];
461 mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
462 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
463 [mSearchFieldCell setBezeled:YES];
464 [mSearchFieldCell setEditable:YES];
465 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
467 mToolbarSearchFieldCell = [[ToolbarSearchFieldCellWithFocusRing alloc] initTextCell:@""];
468 [mToolbarSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
469 [mToolbarSearchFieldCell setBezeled:YES];
470 [mToolbarSearchFieldCell setEditable:YES];
471 [mToolbarSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
473 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
475 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
476 [mComboBoxCell setBezeled:YES];
477 [mComboBoxCell setEditable:YES];
478 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
480 mProgressBarCell = [[NSProgressBarCell alloc] init];
482 mMeterBarCell = [[NSLevelIndicatorCell alloc]
483 initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
485 mCellDrawView = [[CellDrawView alloc] init];
487 NS_OBJC_END_TRY_ABORT_BLOCK;
490 nsNativeThemeCocoa::~nsNativeThemeCocoa() {
491 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
493 [mMeterBarCell release];
494 [mProgressBarCell release];
495 [mDisclosureButtonCell release];
496 [mHelpButtonCell release];
497 [mPushButtonCell release];
498 [mRadioButtonCell release];
499 [mCheckboxCell release];
500 [mSearchFieldCell release];
501 [mToolbarSearchFieldCell release];
502 [mDropdownCell release];
503 [mComboBoxCell release];
504 [mCellDrawView release];
506 NS_OBJC_END_TRY_ABORT_BLOCK;
509 // Limit on the area of the target rect (in pixels^2) in
510 // DrawCellWithScaling() and DrawButton() and above which we
511 // don't draw the object into a bitmap buffer. This is to avoid crashes in
512 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
513 // CGContextDrawImage(), and also to avoid very poor drawing performance in
514 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
515 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
516 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
517 // different amounts of RAM.
518 #define BITMAP_MAX_AREA 500000
520 static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
521 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
522 CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
523 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
524 fabs(transformedUserSpacePixel.size.height));
525 return maxScale > 1.0 ? 2 : 1;
529 * Draw the given NSCell into the given cgContext.
531 * destRect - the size and position of the resulting control rectangle
532 * controlSize - the NSControlSize which will be given to the NSCell before
533 * asking it to render
534 * naturalSize - The natural dimensions of this control.
535 * If the control rect size is not equal to either of these, a scale
536 * will be applied to the context so that rendering the control at the
537 * natural size will result in it filling the destRect space.
538 * If a control has no natural dimensions in either/both axes, pass 0.0f.
539 * minimumSize - The minimum dimensions of this control.
540 * If the control rect size is less than the minimum for a given axis,
541 * a scale will be applied to the context so that the minimum is used
542 * for drawing. If a control has no minimum dimensions in either/both
544 * marginSet - an array of margins; a multidimensional array of [2][3][4],
545 * with the first dimension being the OS version (Tiger or Leopard),
546 * the second being the control size (mini, small, regular), and the third
547 * being the 4 margin values (left, top, right, bottom).
548 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
549 * matter if this is really the right view; it just has to return YES when
550 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
551 * mirrorHorizontal - whether to mirror the cell horizontally
553 static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
554 NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize,
555 const float marginSet[][3][4], NSView* view,
556 BOOL mirrorHorizontal) {
557 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
560 NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
562 if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
563 if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
565 // Keep aspect ratio when scaling if one dimension is free.
566 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
567 drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
568 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
569 drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
571 // Honor minimum sizes.
572 if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width;
573 if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height;
575 [NSGraphicsContext saveGraphicsState];
577 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
578 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
579 // Inflate the rect Gecko gave us by the margin for the control.
580 InflateControlRect(&drawRect, controlSize, marginSet);
582 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
584 setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
587 DrawCellIncludingFocusRing(cell, drawRect, view);
589 [NSGraphicsContext setCurrentContext:savedContext];
591 float w = ceil(drawRect.size.width);
592 float h = ceil(drawRect.size.height);
593 NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
595 // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's
597 InflateControlRect(&tmpRect, controlSize, marginSet);
599 // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
600 w += kMaxFocusRingWidth * 2.0;
601 h += kMaxFocusRingWidth * 2.0;
603 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
604 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
605 CGContextRef ctx = CGBitmapContextCreate(
606 NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
607 (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
608 CGColorSpaceRelease(rgb);
610 // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
611 // This is the first flip transform, applied to cgContext.
612 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
613 CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
614 if (mirrorHorizontal) {
615 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
616 CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
619 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
620 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx
623 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
625 // Set the context's "base transform" to in order to get correctly-sized focus rings.
626 CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
628 // This is the second flip transform, applied to ctx.
629 CGContextScaleCTM(ctx, 1.0f, -1.0f);
630 CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
632 DrawCellIncludingFocusRing(cell, tmpRect, view);
634 [NSGraphicsContext setCurrentContext:savedContext];
636 CGImageRef img = CGBitmapContextCreateImage(ctx);
638 // Drop the image into the original destination rectangle, scaling to fit
639 // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
640 // doesn't extend beyond the overflow rect
641 float xscale = destRect.size.width / drawRect.size.width;
642 float yscale = destRect.size.height / drawRect.size.height;
643 float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
644 float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
647 CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY,
648 destRect.size.width + scaledFocusRingX * 2,
649 destRect.size.height + scaledFocusRingY * 2),
653 CGContextRelease(ctx);
656 [NSGraphicsContext restoreGraphicsState];
658 #if DRAW_IN_FRAME_DEBUG
659 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
660 CGContextFillRect(cgContext, destRect);
663 NS_OBJC_END_TRY_ABORT_BLOCK;
666 struct CellRenderSettings {
667 // The natural dimensions of the control.
668 // If a control has no natural dimensions in either/both axes, set to 0.0f.
669 NSSize naturalSizes[3];
671 // The minimum dimensions of the control.
672 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
673 NSSize minimumSizes[3];
675 // A three-dimensional array,
676 // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
677 // the second being the control size (mini, small, regular), and the third
678 // being the 4 margin values (left, top, right, bottom).
679 float margins[2][3][4];
683 * This is a helper method that returns the required NSControlSize given a size
684 * and the size of the three controls plus a tolerance.
685 * size - The width or the height of the element to draw.
686 * sizes - An array with the all the width/height of the element for its
688 * tolerance - The tolerance as passed to DrawCellWithSnapping.
689 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero.
691 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) {
692 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
699 for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
706 // If it's the latest value, we pick it.
708 return CocoaSizeForEnum(i);
711 if (size <= sizes[i] + tolerance && size < next) {
712 return CocoaSizeForEnum(i);
716 // If we are here, that means sizes[] was an array with only empty values
717 // or the algorithm above is wrong.
718 // The former can happen but the later would be wrong.
719 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
720 "We found no control! We shouldn't be there!");
721 return CocoaSizeForEnum(regularControlSize);
725 * Draw the given NSCell into the given cgContext with a nice control size.
727 * This function is similar to DrawCellWithScaling, but it decides what
728 * control size to use based on the destRect's size.
729 * Scaling is only applied when the difference between the destRect's size
730 * and the next smaller natural size is greater than snapTolerance. Otherwise
731 * it snaps to the next smaller control size without scaling because unscaled
732 * controls look nicer.
734 static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
735 const CellRenderSettings settings, float verticalAlignFactor,
736 NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) {
737 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
739 const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
740 const NSSize* sizes = settings.naturalSizes;
741 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)];
742 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)];
743 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)];
745 HIRect drawRect = destRect;
747 CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width};
748 NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
749 CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height};
750 NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
752 NSControlSize controlSize = NSControlSizeRegular;
753 size_t sizeIndex = 0;
755 // At some sizes, don't scale but snap.
756 const NSControlSize smallerControlSize =
757 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX
759 const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
760 const NSSize size = sizes[smallerControlSizeIndex];
761 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
762 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
763 if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
764 diffHeight <= snapTolerance) {
765 // Snap to the smaller control size.
766 controlSize = smallerControlSize;
767 sizeIndex = smallerControlSizeIndex;
768 MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
770 // Resize and center the drawRect.
771 if (sizes[sizeIndex].width) {
772 drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
773 drawRect.size.width = sizes[sizeIndex].width;
775 if (sizes[sizeIndex].height) {
777 floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
778 drawRect.size.height = sizes[sizeIndex].height;
781 // Use the larger control size.
782 controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
785 sizeIndex = EnumSizeForCocoaSize(controlSize);
788 [cell setControlSize:controlSize];
790 MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
791 const NSSize minimumSize = settings.minimumSizes[sizeIndex];
792 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize,
793 settings.margins, view, mirrorHorizontal);
795 NS_OBJC_END_TRY_ABORT_BLOCK;
798 @interface NSWindow (CoreUIRendererPrivate)
799 + (CUIRendererRef)coreUIRenderer;
802 static id GetAquaAppearance() {
803 Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
804 if (NSAppearanceClass && [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
805 return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
806 withObject:@"NSAppearanceNameAqua"];
811 @interface NSObject (NSAppearanceCoreUIRendering)
812 - (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
815 static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions,
816 bool aSkipAreaCheck = false) {
817 id appearance = GetAquaAppearance();
819 if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
823 if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
824 // Render through NSAppearance on Mac OS 10.10 and up. This will call
825 // CUIDraw with a CoreUI renderer that will give us the correct 10.10
826 // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
827 // renders 10.9-style widgets on 10.10.
828 [appearance _drawInRect:aRect context:cgContext options:aOptions];
831 CUIRendererRef renderer =
832 [NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil;
833 CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
837 static float VerticalAlignFactor(nsIFrame* aFrame) {
838 if (!aFrame) return 0.5f; // default: center
840 const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
841 auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
843 case StyleVerticalAlignKeyword::Top:
844 case StyleVerticalAlignKeyword::TextTop:
847 case StyleVerticalAlignKeyword::Sub:
848 case StyleVerticalAlignKeyword::Super:
849 case StyleVerticalAlignKeyword::Middle:
850 case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
853 case StyleVerticalAlignKeyword::Baseline:
854 case StyleVerticalAlignKeyword::Bottom:
855 case StyleVerticalAlignKeyword::TextBottom:
859 MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
864 static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
866 [aCell setEnabled:!aControlParams.disabled];
867 [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
868 aControlParams.insideActiveWindow)];
869 [aCell setHighlighted:aControlParams.pressed];
872 // These are the sizes that Gecko needs to request to draw if it wants
873 // to get a standard-sized Aqua radio button drawn. Note that the rects
874 // that draw these are actually a little bigger.
875 static const CellRenderSettings radioSettings = {{
876 NSMakeSize(11, 11), // mini
877 NSMakeSize(13, 13), // small
878 NSMakeSize(16, 16) // regular
880 {NSZeroSize, NSZeroSize, NSZeroSize},
883 {0, 0, 0, 0}, // mini
884 {0, 1, 1, 1}, // small
885 {0, 0, 0, 0} // regular
889 {0, 0, 0, 0}, // mini
890 {1, 1, 1, 2}, // small
891 {0, 0, 0, 0} // regular
894 static const CellRenderSettings checkboxSettings = {{
895 NSMakeSize(11, 11), // mini
896 NSMakeSize(13, 13), // small
897 NSMakeSize(16, 16) // regular
899 {NSZeroSize, NSZeroSize, NSZeroSize},
902 {0, 1, 0, 0}, // mini
903 {0, 1, 0, 1}, // small
904 {0, 1, 0, 1} // regular
908 {0, 1, 0, 0}, // mini
909 {0, 1, 0, 1}, // small
910 {0, 1, 0, 1} // regular
913 static NSCellStateValue CellStateForCheckboxOrRadioState(
914 nsNativeThemeCocoa::CheckboxOrRadioState aState) {
916 case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
918 case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
920 case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
925 void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
926 const HIRect& inBoxRect,
927 const CheckboxOrRadioParams& aParams) {
928 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
930 NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
931 ApplyControlParamsToNSCell(aParams.controlParams, cell);
933 [cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
934 [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint]
935 : NSClearControlTint)];
937 // Ensure that the control is square.
938 float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
939 HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
940 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
943 DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings,
944 aParams.verticalAlignFactor, mCellDrawView, NO);
946 NS_OBJC_END_TRY_ABORT_BLOCK;
949 static const CellRenderSettings searchFieldSettings = {{
950 NSMakeSize(0, 16), // mini
951 NSMakeSize(0, 19), // small
952 NSMakeSize(0, 22) // regular
955 NSMakeSize(32, 0), // mini
956 NSMakeSize(38, 0), // small
957 NSMakeSize(44, 0) // regular
961 {0, 0, 0, 0}, // mini
962 {0, 0, 0, 0}, // small
963 {0, 0, 0, 0} // regular
967 {0, 0, 0, 0}, // mini
968 {0, 0, 0, 0}, // small
969 {0, 0, 0, 0} // regular
972 static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
973 nsIContent* content = aFrame->GetContent();
978 if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
982 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
983 case StyleAppearance::Toolbar:
984 case StyleAppearance::Statusbar:
991 static bool IsInsideToolbar(nsIFrame* aFrame) {
992 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
993 if (IsToolbarStyleContainer(frame)) {
1000 nsNativeThemeCocoa::SearchFieldParams nsNativeThemeCocoa::ComputeSearchFieldParams(
1001 nsIFrame* aFrame, EventStates aEventState) {
1002 SearchFieldParams params;
1003 params.insideToolbar = IsInsideToolbar(aFrame);
1004 params.disabled = IsDisabled(aFrame, aEventState);
1005 params.focused = IsFocused(aFrame);
1006 params.rtl = IsFrameRTL(aFrame);
1007 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1011 void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
1012 const SearchFieldParams& aParams) {
1013 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1015 NSSearchFieldCell* cell = aParams.insideToolbar ? mToolbarSearchFieldCell : mSearchFieldCell;
1016 [cell setEnabled:!aParams.disabled];
1017 [cell setShowsFirstResponder:aParams.focused];
1019 // When using the 10.11 SDK, the default string will be shown if we don't
1020 // set the placeholder string.
1021 [cell setPlaceholderString:@""];
1023 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor,
1024 mCellDrawView, aParams.rtl);
1026 NS_OBJC_END_TRY_ABORT_BLOCK;
1029 static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
1030 static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
1031 static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
1032 static NSString* kCheckmarkImage = @"MenuOnState";
1033 static NSString* kMenuarrowRightImage = @"MenuSubmenu";
1034 static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
1035 static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
1036 static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
1037 static const CGFloat kMenuIconIndent = 6.0f;
1039 NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
1040 switch (aParams.icon) {
1041 case MenuIcon::eCheckmark:
1042 return kCheckmarkImage;
1043 case MenuIcon::eMenuArrow:
1044 return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
1045 case MenuIcon::eMenuDownScrollArrow:
1046 return kMenuDownScrollArrowImage;
1047 case MenuIcon::eMenuUpScrollArrow:
1048 return kMenuUpScrollArrowImage;
1052 NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
1054 case MenuIcon::eCheckmark:
1055 return kCheckmarkSize;
1056 case MenuIcon::eMenuArrow:
1057 return kMenuarrowSize;
1058 case MenuIcon::eMenuDownScrollArrow:
1059 case MenuIcon::eMenuUpScrollArrow:
1060 return kMenuScrollArrowSize;
1064 nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
1065 nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
1066 bool isDisabled = IsDisabled(aFrame, aEventState);
1068 MenuIconParams params;
1069 params.icon = aIcon;
1070 params.disabled = isDisabled;
1071 params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1072 params.centerHorizontally = true;
1073 params.rtl = IsFrameRTL(aFrame);
1077 void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
1078 const MenuIconParams& aParams) {
1079 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1081 NSSize size = GetMenuIconSize(aParams.icon);
1083 // Adjust size and position of our drawRect.
1084 CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
1085 CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
1086 CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
1087 CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
1088 CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
1089 : aParams.rtl ? paddingEndX
1091 aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
1094 aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
1096 NSString* imageName = GetMenuIconName(aParams);
1099 drawRect, cgContext,
1100 [NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
1101 imageName, @"imageNameKey", state, @"state",
1102 @"image", @"widget", [NSNumber numberWithBool:YES],
1103 @"is.flipped", nil]);
1105 #if DRAW_IN_FRAME_DEBUG
1106 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1107 CGContextFillRect(cgContext, drawRect);
1110 NS_OBJC_END_TRY_ABORT_BLOCK;
1113 nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
1114 nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
1115 bool isDisabled = IsDisabled(aFrame, aEventState);
1117 MenuItemParams params;
1118 params.checked = aIsChecked;
1119 params.disabled = isDisabled;
1120 params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
1121 params.rtl = IsFrameRTL(aFrame);
1125 static void SetCGContextFillColor(CGContextRef cgContext, const sRGBColor& aColor) {
1126 DeviceColor color = ToDeviceColor(aColor);
1127 CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
1130 void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
1131 const MenuItemParams& aParams) {
1132 if (aParams.checked) {
1133 MenuIconParams params;
1134 params.disabled = aParams.disabled;
1135 params.insideActiveMenuItem = aParams.selected;
1136 params.rtl = aParams.rtl;
1137 params.icon = MenuIcon::eCheckmark;
1138 DrawMenuIcon(cgContext, inBoxRect, params);
1142 void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
1143 const MenuItemParams& aParams) {
1144 // Workaround for visual artifacts issues with
1145 // HIThemeDrawMenuSeparator on macOS Big Sur.
1146 if (nsCocoaFeatures::OnBigSurOrLater()) {
1147 CGRect separatorRect = inBoxRect;
1148 separatorRect.size.height = 1;
1149 separatorRect.size.width -= 42;
1150 separatorRect.origin.x += 21;
1151 // Use transparent black with an alpha similar to the native separator.
1152 // The values 231 (menu background) and 205 (separator color) have been
1153 // sampled from a window screenshot of a native context menu.
1154 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0);
1155 CGContextFillRect(cgContext, separatorRect);
1159 ThemeMenuState menuState;
1160 if (aParams.disabled) {
1161 menuState = kThemeMenuDisabled;
1163 menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
1166 HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
1167 HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
1170 static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) {
1171 // Mac always draws focus rings for textboxes and lists.
1172 switch (aFrame->StyleDisplay()->EffectiveAppearance()) {
1173 case StyleAppearance::NumberInput:
1174 case StyleAppearance::Textfield:
1175 case StyleAppearance::Textarea:
1176 case StyleAppearance::Searchfield:
1177 case StyleAppearance::Listbox:
1184 nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
1185 nsIFrame* aFrame, EventStates aEventState) {
1186 ControlParams params;
1187 params.disabled = IsDisabled(aFrame, aEventState);
1188 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1189 params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
1190 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) &&
1191 (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) ||
1192 ShouldUnconditionallyDrawFocusRingIfFocused(aFrame));
1193 params.rtl = IsFrameRTL(aFrame);
1197 static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
1198 static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
1200 static const CellRenderSettings pushButtonSettings = {{
1201 NSMakeSize(0, 16), // mini
1202 NSMakeSize(0, 19), // small
1203 NSMakeSize(0, 22) // regular
1206 NSMakeSize(18, 0), // mini
1207 NSMakeSize(26, 0), // small
1208 NSMakeSize(30, 0) // regular
1212 {0, 0, 0, 0}, // mini
1213 {4, 0, 4, 1}, // small
1214 {5, 0, 5, 2} // regular
1218 {0, 0, 0, 0}, // mini
1219 {4, 0, 4, 1}, // small
1220 {5, 0, 5, 2} // regular
1223 // The height at which we start doing square buttons instead of rounded buttons
1224 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
1225 // we switch over to doing square buttons which looks fine at any size.
1226 #define DO_SQUARE_BUTTON_HEIGHT 26
1228 void nsNativeThemeCocoa::DrawRoundedBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1229 ControlParams aControlParams) {
1230 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1232 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1233 [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
1234 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
1235 mCellDrawView, aControlParams.rtl, 1.0f);
1237 NS_OBJC_END_TRY_ABORT_BLOCK;
1240 void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
1241 ControlParams aControlParams) {
1242 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1244 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
1245 [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
1246 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1247 NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
1249 NS_OBJC_END_TRY_ABORT_BLOCK;
1252 void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
1253 ControlParams aControlParams) {
1254 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1256 ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
1257 DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1258 kHelpButtonSize, NULL, mCellDrawView,
1259 false); // Don't mirror icon in RTL.
1261 NS_OBJC_END_TRY_ABORT_BLOCK;
1264 void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
1265 ControlParams aControlParams,
1266 NSCellStateValue aCellState) {
1267 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1269 ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
1270 [mDisclosureButtonCell setState:aCellState];
1271 DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize,
1272 kDisclosureButtonSize, NULL, mCellDrawView,
1273 false); // Don't mirror icon in RTL.
1275 NS_OBJC_END_TRY_ABORT_BLOCK;
1278 void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
1279 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1280 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
1281 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
1283 CGContextSaveGState(cgContext);
1284 NSSetFocusRingStyle(NSFocusRingOnly);
1285 NSRectFill(NSRectFromCGRect(inBoxRect));
1286 CGContextRestoreGState(cgContext);
1287 [NSGraphicsContext setCurrentContext:savedContext];
1289 NS_OBJC_END_TRY_ABORT_BLOCK;
1292 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect,
1295 static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
1296 RenderHIThemeControlFunction aFunc, void* aData,
1297 BOOL mirrorHorizontally = NO) {
1298 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
1299 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
1302 HIRect drawRect = aRect;
1303 drawRect.origin = CGPointZero;
1305 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f &&
1306 (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
1312 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
1314 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
1315 aFunc(aCGContext, drawRect, aData);
1317 // Inflate the buffer to capture focus rings.
1318 int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
1319 int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
1321 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
1322 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1323 CGContextRef bitmapctx = CGBitmapContextCreate(
1324 NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4,
1325 colorSpace, kCGImageAlphaPremultipliedFirst);
1326 CGColorSpaceRelease(colorSpace);
1328 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
1329 CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
1331 // Set the context's "base transform" to in order to get correctly-sized focus rings.
1332 CGContextSetBaseCTM(bitmapctx,
1333 CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
1335 // HITheme always wants to draw into a flipped context, or things
1337 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1338 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1340 aFunc(bitmapctx, drawRect, aData);
1342 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1344 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1346 // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1347 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1348 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1350 if (mirrorHorizontally) {
1351 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1352 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1355 HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
1356 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1358 CGContextSetCTM(aCGContext, ctm);
1360 CGImageRelease(bitmap);
1361 CGContextRelease(bitmapctx);
1364 CGContextSetCTM(aCGContext, savedCTM);
1367 static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
1368 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1369 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1372 static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
1373 if (aParams.disabled) {
1374 return kThemeStateUnavailable;
1376 if (aParams.pressed) {
1377 return kThemeStatePressed;
1379 return kThemeStateActive;
1382 void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
1383 ThemeButtonKind aKind, ThemeButtonValue aValue,
1384 ThemeDrawState aState, ThemeButtonAdornment aAdornment,
1385 const ControlParams& aParams) {
1386 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1388 HIThemeButtonDrawInfo bdi;
1393 bdi.adornment = aAdornment;
1395 if (aParams.focused && aParams.insideActiveWindow) {
1396 bdi.adornment |= kThemeAdornmentFocus;
1399 if ((aAdornment & kThemeAdornmentDefault) && !aParams.disabled) {
1400 bdi.animation.time.start = 0;
1401 bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1404 RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
1406 #if DRAW_IN_FRAME_DEBUG
1407 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1408 CGContextFillRect(cgContext, inBoxRect);
1411 NS_OBJC_END_TRY_ABORT_BLOCK;
1414 void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
1415 const ButtonParams& aParams) {
1416 ControlParams controlParams = aParams.controlParams;
1418 switch (aParams.button) {
1419 case ButtonType::eRegularPushButton:
1420 case ButtonType::eDefaultPushButton: {
1421 ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultPushButton
1422 ? kThemeAdornmentDefault
1423 : kThemeAdornmentNone;
1424 HIRect drawFrame = inBoxRect;
1425 drawFrame.size.height -= 2;
1426 if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[regularControlSize].height) {
1427 DrawHIThemeButton(cgContext, drawFrame, kThemePushButton, kThemeButtonOff,
1428 ToThemeDrawState(controlParams), adornment, controlParams);
1431 if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[smallControlSize].height) {
1432 drawFrame.origin.y -= 1;
1433 drawFrame.origin.x += 1;
1434 drawFrame.size.width -= 2;
1435 DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonSmall, kThemeButtonOff,
1436 ToThemeDrawState(controlParams), adornment, controlParams);
1439 DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonMini, kThemeButtonOff,
1440 ToThemeDrawState(controlParams), adornment, controlParams);
1443 case ButtonType::eRegularBevelButton:
1444 case ButtonType::eDefaultBevelButton: {
1445 ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultBevelButton
1446 ? kThemeAdornmentDefault
1447 : kThemeAdornmentNone;
1448 DrawHIThemeButton(cgContext, inBoxRect, kThemeMediumBevelButton, kThemeButtonOff,
1449 ToThemeDrawState(controlParams), adornment, controlParams);
1452 case ButtonType::eRoundedBezelPushButton:
1453 DrawRoundedBezelPushButton(cgContext, inBoxRect, controlParams);
1455 case ButtonType::eSquareBezelPushButton:
1456 DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
1458 case ButtonType::eArrowButton:
1459 DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
1460 kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
1462 case ButtonType::eHelpButton:
1463 DrawHelpButton(cgContext, inBoxRect, controlParams);
1465 case ButtonType::eTreeTwistyPointingRight:
1466 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
1467 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1469 case ButtonType::eTreeTwistyPointingDown:
1470 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
1471 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
1473 case ButtonType::eDisclosureButtonClosed:
1474 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
1476 case ButtonType::eDisclosureButtonOpen:
1477 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
1482 nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
1483 nsIFrame* aFrame, EventStates aEventState) {
1484 TreeHeaderCellParams params;
1485 params.controlParams = ComputeControlParams(aFrame, aEventState);
1486 params.sortDirection = GetTreeSortDirection(aFrame);
1487 params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
1491 void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
1492 const TreeHeaderCellParams& aParams) {
1493 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1495 HIThemeButtonDrawInfo bdi;
1497 bdi.kind = kThemeListHeaderButton;
1498 bdi.value = kThemeButtonOff;
1499 bdi.adornment = kThemeAdornmentNone;
1501 switch (aParams.sortDirection) {
1502 case eTreeSortDirection_Natural:
1504 case eTreeSortDirection_Ascending:
1505 bdi.value = kThemeButtonOn;
1506 bdi.adornment = kThemeAdornmentHeaderButtonSortUp;
1508 case eTreeSortDirection_Descending:
1509 bdi.value = kThemeButtonOn;
1513 if (aParams.controlParams.disabled) {
1514 bdi.state = kThemeStateUnavailable;
1515 } else if (aParams.controlParams.pressed) {
1516 bdi.state = kThemeStatePressed;
1517 } else if (!aParams.controlParams.insideActiveWindow) {
1518 bdi.state = kThemeStateInactive;
1520 bdi.state = kThemeStateActive;
1523 CGContextClipToRect(cgContext, inBoxRect);
1525 HIRect drawFrame = inBoxRect;
1526 // Always remove the top border.
1527 drawFrame.origin.y -= 1;
1528 drawFrame.size.height += 1;
1529 // Remove the left border in LTR mode and the right border in RTL mode.
1530 drawFrame.size.width += 1;
1531 if (aParams.lastTreeHeaderCell) {
1532 drawFrame.size.width += 1; // Also remove the other border.
1534 if (!aParams.controlParams.rtl || aParams.lastTreeHeaderCell) {
1535 drawFrame.origin.x -= 1;
1538 RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1539 aParams.controlParams.rtl);
1541 #if DRAW_IN_FRAME_DEBUG
1542 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1543 CGContextFillRect(cgContext, inBoxRect);
1546 NS_OBJC_END_TRY_ABORT_BLOCK;
1549 static const CellRenderSettings dropdownSettings = {{
1550 NSMakeSize(0, 16), // mini
1551 NSMakeSize(0, 19), // small
1552 NSMakeSize(0, 22) // regular
1555 NSMakeSize(18, 0), // mini
1556 NSMakeSize(38, 0), // small
1557 NSMakeSize(44, 0) // regular
1561 {1, 1, 2, 1}, // mini
1562 {3, 0, 3, 1}, // small
1563 {3, 0, 3, 0} // regular
1567 {1, 1, 2, 1}, // mini
1568 {3, 0, 3, 1}, // small
1569 {3, 0, 3, 0} // regular
1572 static const CellRenderSettings editableMenulistSettings = {{
1573 NSMakeSize(0, 15), // mini
1574 NSMakeSize(0, 18), // small
1575 NSMakeSize(0, 21) // regular
1578 NSMakeSize(18, 0), // mini
1579 NSMakeSize(38, 0), // small
1580 NSMakeSize(44, 0) // regular
1584 {0, 0, 2, 2}, // mini
1585 {0, 0, 3, 2}, // small
1586 {0, 1, 3, 3} // regular
1590 {0, 0, 2, 2}, // mini
1591 {0, 0, 3, 2}, // small
1592 {0, 1, 3, 3} // regular
1595 void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1596 const DropdownParams& aParams) {
1597 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1599 [mDropdownCell setPullsDown:aParams.pullsDown];
1600 NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1602 ApplyControlParamsToNSCell(aParams.controlParams, cell);
1604 if (aParams.controlParams.insideActiveWindow) {
1605 [cell setControlTint:[NSColor currentControlTint]];
1607 [cell setControlTint:NSClearControlTint];
1610 const CellRenderSettings& settings =
1611 aParams.editable ? editableMenulistSettings : dropdownSettings;
1612 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
1613 aParams.controlParams.rtl);
1615 NS_OBJC_END_TRY_ABORT_BLOCK;
1618 static const CellRenderSettings spinnerSettings = {
1620 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1621 NSMakeSize(15, 22), // small
1622 NSMakeSize(19, 27) // regular
1625 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
1626 NSMakeSize(15, 22), // small
1627 NSMakeSize(19, 27) // regular
1631 {0, 0, 0, 0}, // mini
1632 {0, 0, 0, 0}, // small
1633 {0, 0, 0, 0} // regular
1637 {0, 0, 0, 0}, // mini
1638 {0, 0, 0, 0}, // small
1639 {0, 0, 0, 0} // regular
1642 HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
1643 const SpinButtonParams& aParams) {
1644 HIThemeButtonDrawInfo bdi;
1647 bdi.value = kThemeButtonOff;
1648 bdi.adornment = kThemeAdornmentNone;
1650 if (aParams.disabled) {
1651 bdi.state = kThemeStateUnavailable;
1652 } else if (aParams.insideActiveWindow && aParams.pressedButton) {
1653 if (*aParams.pressedButton == SpinButton::eUp) {
1654 bdi.state = kThemeStatePressedUp;
1656 bdi.state = kThemeStatePressedDown;
1659 bdi.state = kThemeStateActive;
1665 void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
1666 const SpinButtonParams& aParams) {
1667 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1669 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
1670 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1672 NS_OBJC_END_TRY_ABORT_BLOCK;
1675 void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
1676 SpinButton aDrawnButton, const SpinButtonParams& aParams) {
1677 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1679 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
1681 // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
1682 // together as a single unit (presumably because when one button is active,
1683 // the appearance of both changes (in different ways)). Here we have to paint
1684 // both buttons, using clip to hide the one we don't want to paint.
1685 HIRect drawRect = inBoxRect;
1686 drawRect.size.height *= 2;
1687 if (aDrawnButton == SpinButton::eDown) {
1688 drawRect.origin.y -= inBoxRect.size.height;
1691 // Shift the drawing a little to the left, since cocoa paints with more
1692 // blank space around the visual buttons than we'd like:
1693 drawRect.origin.x -= 1;
1695 CGContextSaveGState(cgContext);
1696 CGContextClipToRect(cgContext, inBoxRect);
1698 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1700 CGContextRestoreGState(cgContext);
1702 NS_OBJC_END_TRY_ABORT_BLOCK;
1705 void nsNativeThemeCocoa::DrawTextBox(CGContextRef cgContext, const HIRect& inBoxRect,
1706 TextBoxParams aParams) {
1707 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1709 SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
1710 CGContextFillRect(cgContext, inBoxRect);
1712 #if DRAW_IN_FRAME_DEBUG
1713 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1714 CGContextFillRect(cgContext, inBoxRect);
1717 if (aParams.borderless) {
1721 HIThemeFrameDrawInfo fdi;
1723 fdi.kind = kHIThemeFrameTextFieldSquare;
1725 // We don't ever set an inactive state for this because it doesn't
1726 // look right (see other apps).
1727 fdi.state = aParams.disabled ? kThemeStateUnavailable : kThemeStateActive;
1728 fdi.isFocused = aParams.focused;
1730 // HIThemeDrawFrame takes the rect for the content area of the frame, not
1731 // the bounding rect for the frame. Here we reduce the size of the rect we
1732 // will pass to make it the size of the content.
1733 HIRect drawRect = inBoxRect;
1734 SInt32 frameOutset = 0;
1735 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1736 drawRect.origin.x += frameOutset;
1737 drawRect.origin.y += frameOutset;
1738 drawRect.size.width -= frameOutset * 2;
1739 drawRect.size.height -= frameOutset * 2;
1741 HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1743 NS_OBJC_END_TRY_ABORT_BLOCK;
1746 static const CellRenderSettings progressSettings[2][2] = {
1747 // Vertical progress bar.
1748 {// Determined settings.
1751 NSMakeSize(10, 0), // small
1752 NSMakeSize(16, 0) // regular
1754 {NSZeroSize, NSZeroSize, NSZeroSize},
1757 {0, 0, 0, 0}, // mini
1758 {1, 1, 1, 1}, // small
1759 {1, 1, 1, 1} // regular
1761 // There is no horizontal margin in regular undetermined size.
1764 NSMakeSize(10, 0), // small
1765 NSMakeSize(16, 0) // regular
1767 {NSZeroSize, NSZeroSize, NSZeroSize},
1770 {0, 0, 0, 0}, // mini
1771 {1, 1, 1, 1}, // small
1772 {1, 0, 1, 0} // regular
1776 {0, 0, 0, 0}, // mini
1777 {1, 1, 1, 1}, // small
1778 {1, 0, 1, 0} // regular
1780 // Horizontal progress bar.
1781 {// Determined settings.
1784 NSMakeSize(0, 10), // small
1785 NSMakeSize(0, 16) // regular
1787 {NSZeroSize, NSZeroSize, NSZeroSize},
1790 {0, 0, 0, 0}, // mini
1791 {1, 1, 1, 1}, // small
1792 {1, 1, 1, 1} // regular
1796 {0, 0, 0, 0}, // mini
1797 {1, 1, 1, 1}, // small
1798 {1, 1, 1, 1} // regular
1800 // There is no horizontal margin in regular undetermined size.
1803 NSMakeSize(0, 10), // small
1804 NSMakeSize(0, 16) // regular
1806 {NSZeroSize, NSZeroSize, NSZeroSize},
1809 {0, 0, 0, 0}, // mini
1810 {1, 1, 1, 1}, // small
1811 {0, 1, 0, 1} // regular
1815 {0, 0, 0, 0}, // mini
1816 {1, 1, 1, 1}, // small
1817 {0, 1, 0, 1} // regular
1820 nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
1821 nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
1822 ProgressParams params;
1823 params.value = GetProgressValue(aFrame);
1824 params.max = GetProgressMaxValue(aFrame);
1825 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1826 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1827 params.indeterminate = IsIndeterminateProgress(aFrame, aEventState);
1828 params.horizontal = aIsHorizontal;
1829 params.rtl = IsFrameRTL(aFrame);
1833 void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1834 const ProgressParams& aParams) {
1835 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1837 NSProgressBarCell* cell = mProgressBarCell;
1839 [cell setValue:aParams.value];
1840 [cell setMax:aParams.max];
1841 [cell setIndeterminate:aParams.indeterminate];
1842 [cell setHorizontal:aParams.horizontal];
1843 [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
1844 : NSClearControlTint)];
1846 DrawCellWithSnapping(cell, cgContext, inBoxRect,
1847 progressSettings[aParams.horizontal][aParams.indeterminate],
1848 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
1850 NS_OBJC_END_TRY_ABORT_BLOCK;
1853 static const CellRenderSettings meterSetting = {{
1854 NSMakeSize(0, 16), // mini
1855 NSMakeSize(0, 16), // small
1856 NSMakeSize(0, 16) // regular
1858 {NSZeroSize, NSZeroSize, NSZeroSize},
1861 {1, 1, 1, 1}, // mini
1862 {1, 1, 1, 1}, // small
1863 {1, 1, 1, 1} // regular
1867 {1, 1, 1, 1}, // mini
1868 {1, 1, 1, 1}, // small
1869 {1, 1, 1, 1} // regular
1872 nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
1873 nsIContent* content = aFrame->GetContent();
1874 if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
1875 return MeterParams();
1878 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
1880 params.value = meterElement->Value();
1881 params.min = meterElement->Min();
1882 params.max = meterElement->Max();
1883 EventStates states = meterElement->State();
1884 if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1885 params.optimumState = OptimumState::eSubOptimum;
1886 } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1887 params.optimumState = OptimumState::eSubSubOptimum;
1889 params.horizontal = !IsVerticalMeter(aFrame);
1890 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
1891 params.rtl = IsFrameRTL(aFrame);
1896 void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1897 const MeterParams& aParams) {
1898 NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1900 NSLevelIndicatorCell* cell = mMeterBarCell;
1902 [cell setMinValue:aParams.min];
1903 [cell setMaxValue:aParams.max];
1904 [cell setDoubleValue:aParams.value];
1907 * The way HTML and Cocoa defines the meter/indicator widget are different.
1908 * So, we are going to use a trick to get the Cocoa widget showing what we
1909 * are expecting: we set the warningValue or criticalValue to the current
1910 * value when we want to have the widget to be in the warning or critical
1913 switch (aParams.optimumState) {
1914 case OptimumState::eOptimum:
1915 [cell setWarningValue:aParams.max + 1];
1916 [cell setCriticalValue:aParams.max + 1];
1918 case OptimumState::eSubOptimum:
1919 [cell setWarningValue:aParams.value];
1920 [cell setCriticalValue:aParams.max + 1];
1922 case OptimumState::eSubSubOptimum:
1923 [cell setWarningValue:aParams.max + 1];
1924 [cell setCriticalValue:aParams.value];
1928 HIRect rect = CGRectStandardize(inBoxRect);
1929 BOOL vertical = !aParams.horizontal;
1931 CGContextSaveGState(cgContext);
1935 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1936 * show a rotated horizontal meter bar.
1937 * Given that we want to show a vertical meter bar, we assume that the rect
1938 * has vertical dimensions but we can't correctly draw a meter widget inside
1939 * such a rectangle so we need to inverse width and height (and re-position)
1940 * to get a rectangle with horizontal dimensions.
1941 * Finally, we want to show a vertical meter so we want to rotate the result
1942 * so it is vertical. We do that by changing the context.
1944 CGFloat tmp = rect.size.width;
1945 rect.size.width = rect.size.height;
1946 rect.size.height = tmp;
1947 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1948 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1950 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1951 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1952 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1955 DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor,
1956 mCellDrawView, !vertical && aParams.rtl);
1958 CGContextRestoreGState(cgContext);
1960 NS_OBJC_END_TRY_ABORT_BLOCK
1963 void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1964 bool aIsInsideActiveWindow) {
1965 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1967 HIThemeTabPaneDrawInfo tpdi;
1970 tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
1971 tpdi.direction = kThemeTabNorth;
1972 tpdi.size = kHIThemeTabSizeNormal;
1973 tpdi.kind = kHIThemeTabKindNormal;
1975 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1977 NS_OBJC_END_TRY_ABORT_BLOCK;
1980 Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
1981 nsIFrame* aFrame, EventStates aEventState) {
1982 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1987 bool isHorizontal = IsRangeHorizontal(aFrame);
1989 // ScaleParams requires integer min, max and value. This is purely for
1990 // drawing, so we normalize to a range 0-1000 here.
1992 params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
1995 params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
1996 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
1997 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
1998 params.disabled = IsDisabled(aFrame, aEventState);
1999 params.horizontal = isHorizontal;
2000 return Some(params);
2003 void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
2004 const ScaleParams& aParams) {
2005 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2007 HIThemeTrackDrawInfo tdi;
2010 tdi.kind = kThemeMediumSlider;
2011 tdi.bounds = inBoxRect;
2012 tdi.min = aParams.min;
2013 tdi.max = aParams.max;
2014 tdi.value = aParams.value;
2015 tdi.attributes = kThemeTrackShowThumb;
2016 if (aParams.horizontal) {
2017 tdi.attributes |= kThemeTrackHorizontal;
2019 if (aParams.reverse) {
2020 tdi.attributes |= kThemeTrackRightToLeft;
2022 if (aParams.focused) {
2023 tdi.attributes |= kThemeTrackHasFocus;
2025 if (aParams.disabled) {
2026 tdi.enableState = kThemeTrackDisabled;
2028 tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
2030 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
2031 tdi.trackInfo.slider.pressState = 0;
2033 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
2035 NS_OBJC_END_TRY_ABORT_BLOCK;
2038 nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) {
2039 // Usually a separator is drawn by the segment to the right of the
2040 // separator, but pressed and selected segments have higher priority.
2041 if (!aBefore || !aAfter) return nullptr;
2042 if (IsSelectedButton(aAfter)) return aAfter;
2043 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
2047 static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
2048 // A separator between two segments should always be located in the leftmost
2049 // pixel column of the segment to the right of the separator, regardless of
2050 // who ends up drawing it.
2051 // CoreUI draws the separators inside the drawing rect.
2052 if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
2053 // The segment to the left of us draws the separator, so we need to make
2055 aRect.origin.x += 1;
2056 aRect.size.width -= 1;
2058 if (aParams.drawsRightSeparator) {
2059 // We draw the right separator, so we need to extend the draw rect into the
2060 // segment to our right.
2061 aRect.size.width += 1;
2066 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
2068 if (aIsLast) return @"kCUISegmentPositionOnly";
2069 return @"kCUISegmentPositionFirst";
2071 if (aIsLast) return @"kCUISegmentPositionLast";
2072 return @"kCUISegmentPositionMiddle";
2075 struct SegmentedControlRenderSettings {
2076 const CGFloat* heights;
2077 const NSString* widgetName;
2080 static const CGFloat tabHeights[3] = {17, 20, 23};
2082 static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
2084 static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
2086 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
2087 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
2089 nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
2090 nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
2091 SegmentParams params;
2092 params.segmentType = aSegmentType;
2093 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2094 params.pressed = IsPressedButton(aFrame);
2095 params.selected = IsSelectedButton(aFrame);
2096 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING);
2097 bool isRTL = IsFrameRTL(aFrame);
2098 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
2099 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
2100 params.atLeftEnd = !left;
2101 params.atRightEnd = !right;
2102 params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
2103 params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
2108 static SegmentedControlRenderSettings RenderSettingsForSegmentType(
2109 nsNativeThemeCocoa::SegmentType aSegmentType) {
2110 switch (aSegmentType) {
2111 case nsNativeThemeCocoa::SegmentType::eToolbarButton:
2112 return toolbarButtonRenderSettings;
2113 case nsNativeThemeCocoa::SegmentType::eTab:
2114 return tabRenderSettings;
2118 void nsNativeThemeCocoa::DrawSegmentBackground(CGContextRef cgContext, const HIRect& inBoxRect,
2119 const SegmentParams& aParams) {
2120 // On earlier macOS versions, the segment background is automatically
2121 // drawn correctly and this method should not be used. ASSERT here
2122 // to catch unnecessary usage, but the method implementation is not
2123 // dependent on Big Sur in any way.
2124 MOZ_ASSERT(nsCocoaFeatures::OnBigSurOrLater());
2126 // Use colors resembling 10.15.
2127 if (aParams.selected) {
2128 DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(93, 93, 93, 255));
2129 CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
2131 DeviceColor color = ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(247, 247, 247, 255));
2132 CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
2135 // Create a rect for the background fill.
2136 CGRect bgRect = inBoxRect;
2137 bgRect.size.height -= 3.0;
2138 bgRect.size.width -= 4.0;
2139 bgRect.origin.x += 2.0;
2140 bgRect.origin.y += 1.0;
2142 // Round the corners unless the button is a middle button. Buttons in
2143 // a grouping but on the edge will have the inner edge filled below.
2144 if (aParams.atLeftEnd || aParams.atRightEnd) {
2145 CGPathRef path = CGPathCreateWithRoundedRect(bgRect, 5, 4, nullptr);
2146 CGContextAddPath(cgContext, path);
2147 CGPathRelease(path);
2148 CGContextClosePath(cgContext);
2149 CGContextFillPath(cgContext);
2152 // Handle buttons grouped together where either or both of
2153 // the side edges do not have curved corners.
2154 if (!aParams.atLeftEnd && aParams.atRightEnd) {
2155 // Shift the rect left to draw the left side of the
2156 // rect with right angle corners leaving the right side
2157 // to have rounded corners drawn with the curve above.
2158 // For example, the left side of the forward button in
2159 // the Library window.
2160 CGRect leftRectEdge = bgRect;
2161 leftRectEdge.size.width -= 10;
2162 leftRectEdge.origin.x -= 2;
2163 CGContextFillRect(cgContext, leftRectEdge);
2164 } else if (aParams.atLeftEnd && !aParams.atRightEnd) {
2165 // Shift the rect right to draw the right side of the
2166 // rect with right angle corners leaving the left side
2167 // to have rounded corners drawn with the curve above.
2168 // For example, the right side of the back button in
2169 // the Library window.
2170 CGRect rightRectEdge = bgRect;
2171 rightRectEdge.size.width -= 10;
2172 rightRectEdge.origin.x += 12;
2173 CGContextFillRect(cgContext, rightRectEdge);
2174 } else if (!aParams.atLeftEnd && !aParams.atRightEnd) {
2175 // The middle button in a group of buttons. Widen the
2176 // background rect to meet adjacent buttons seamlessly.
2177 CGRect middleRect = bgRect;
2178 middleRect.size.width += 4;
2179 middleRect.origin.x -= 2;
2180 CGContextFillRect(cgContext, middleRect);
2184 void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
2185 const SegmentParams& aParams) {
2186 SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
2188 // On Big Sur, manually draw the background of the buttons to workaround a
2189 // change in Big Sur where the backround is filled with the toolbar gradient.
2190 if (nsCocoaFeatures::OnBigSurOrLater() &&
2191 (aParams.segmentType == nsNativeThemeCocoa::SegmentType::eToolbarButton)) {
2192 DrawSegmentBackground(cgContext, inBoxRect, aParams);
2195 NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
2196 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
2198 NSDictionary* dict = @{
2199 @"widget" : renderSettings.widgetName,
2200 @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
2201 : @"kCUIPresentationStateInactive"),
2202 @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
2203 @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
2204 @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
2205 @"value" : [NSNumber numberWithBool:aParams.selected],
2207 (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
2208 @"focus" : [NSNumber numberWithBool:aParams.focused],
2209 @"size" : CUIControlSizeForCocoaSize(controlSize),
2210 @"is.flipped" : [NSNumber numberWithBool:YES],
2211 @"direction" : @"up"
2214 RenderWithCoreUI(drawRect, cgContext, dict);
2217 void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
2219 CGRect drawRect = inBoxRect;
2222 drawRect.size.height = 1.0f;
2223 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
2226 drawRect.origin.y += drawRect.size.height;
2227 drawRect.size.height = inBoxRect.size.height - 2.0f;
2228 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
2231 drawRect.origin.y += drawRect.size.height;
2232 drawRect.size.height = 1.0f;
2233 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
2236 static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
2237 if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
2239 ToolbarWindow* win = (ToolbarWindow*)aWindow;
2240 float unifiedToolbarHeight = [win unifiedToolbarHeight];
2241 return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
2242 aRect.YMost() <= unifiedToolbarHeight;
2245 // By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
2246 // upper corners. Depending on the context type, it fills the background in
2247 // the corners with black or leaves it transparent. Unfortunately, this corner
2248 // rounding interacts poorly with the window corner masking we apply during
2249 // titlebar drawing and results in small remnants of the corner background
2250 // appearing at the rounded edge.
2251 // So we draw square corners.
2252 static void DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
2253 CGFloat aUnifiedHeight, BOOL aIsMain,
2255 // We extend the draw rect horizontally and clip away the rounded corners.
2256 const CGFloat extendHorizontal = 10;
2257 CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
2258 CGContextSaveGState(aContext);
2259 CGContextClipToRect(aContext, aRect);
2264 dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2265 @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2266 [NSNumber numberWithDouble:aUnifiedHeight],
2267 @"kCUIWindowFrameUnifiedTitleBarHeightKey",
2268 [NSNumber numberWithBool:YES],
2269 @"kCUIWindowFrameDrawTitleSeparatorKey",
2270 [NSNumber numberWithBool:aIsFlipped], @"is.flipped", nil]);
2272 CGContextRestoreGState(aContext);
2275 void nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
2276 const UnifiedToolbarParams& aParams) {
2277 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2279 CGContextSaveGState(cgContext);
2280 CGContextClipToRect(cgContext, inBoxRect);
2282 CGFloat titlebarHeight = aParams.unifiedHeight - inBoxRect.size.height;
2283 CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
2284 inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
2285 DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, aParams.unifiedHeight,
2286 aParams.isMain, YES);
2288 CGContextRestoreGState(cgContext);
2290 NS_OBJC_END_TRY_ABORT_BLOCK;
2293 void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
2295 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2297 if (inBoxRect.size.height < 2.0f) return;
2299 CGContextSaveGState(cgContext);
2300 CGContextClipToRect(cgContext, inBoxRect);
2302 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
2303 // and bottom bar. We only want the bottom bar, so we extend the draw rect
2304 // upwards to make space for the title bar, and then we clip it away.
2305 CGRect drawRect = inBoxRect;
2306 const int extendUpwards = 40;
2307 drawRect.origin.y -= extendUpwards;
2308 drawRect.size.height += extendUpwards;
2310 drawRect, cgContext,
2312 dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
2313 @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
2314 [NSNumber numberWithInt:inBoxRect.size.height],
2315 @"kCUIWindowFrameBottomBarHeightKey",
2316 [NSNumber numberWithBool:YES],
2317 @"kCUIWindowFrameDrawBottomBarSeparatorKey",
2318 [NSNumber numberWithBool:YES], @"is.flipped", nil]);
2320 CGContextRestoreGState(cgContext);
2322 NS_OBJC_END_TRY_ABORT_BLOCK;
2325 void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2326 CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped) {
2327 CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
2328 DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain,
2332 void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
2333 const UnifiedToolbarParams& aParams) {
2334 DrawNativeTitlebar(aContext, aTitlebarRect, aParams.unifiedHeight, aParams.isMain, YES);
2337 static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
2338 HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
2339 HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
2342 void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) {
2343 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2345 HIThemeGrowBoxDrawInfo drawInfo;
2346 drawInfo.version = 0;
2347 drawInfo.state = kThemeStateActive;
2348 drawInfo.kind = kHIThemeGrowBoxKindNormal;
2349 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2350 drawInfo.size = kHIThemeGrowBoxSizeNormal;
2352 RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
2354 NS_OBJC_END_TRY_ABORT_BLOCK;
2357 static const sRGBColor kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
2358 static const sRGBColor kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
2359 static const sRGBColor kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
2360 static const sRGBColor kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);
2362 void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
2364 SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
2366 CGContextFillRect(cgContext, inBoxRect);
2368 float x = inBoxRect.origin.x, y = inBoxRect.origin.y;
2369 float w = inBoxRect.size.width, h = inBoxRect.size.height;
2370 SetCGContextFillColor(cgContext, kMultilineTextFieldTopBorderColor);
2371 CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2372 SetCGContextFillColor(cgContext, kMultilineTextFieldSidesAndBottomBorderColor);
2373 CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2374 CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2375 CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2378 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
2380 setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
2382 CGContextSaveGState(cgContext);
2383 NSSetFocusRingStyle(NSFocusRingOnly);
2384 NSRectFill(NSRectFromCGRect(inBoxRect));
2385 CGContextRestoreGState(cgContext);
2386 [NSGraphicsContext setCurrentContext:savedContext];
2390 void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect,
2391 bool aWindowIsActive, bool aSelectionIsActive) {
2393 if (aSelectionIsActive) {
2394 // Active selection, blue or graphite.
2395 fillColor = ControlAccentColor();
2397 // Inactive selection, gray.
2398 if (aWindowIsActive) {
2399 fillColor = [NSColor colorWithWhite:0.871 alpha:1.0];
2401 fillColor = [NSColor colorWithWhite:0.808 alpha:1.0];
2404 CGContextSetFillColorWithColor(aContext, [fillColor CGColor]);
2405 CGContextFillRect(aContext, aRect);
2408 static bool IsHiDPIContext(nsDeviceContext* aContext) {
2409 return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
2412 Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
2413 nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
2414 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2416 // setup to draw into the correct port
2417 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2419 gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
2420 nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
2421 float originalHeight = nativeWidgetRect.Height();
2422 nativeWidgetRect.Round();
2423 if (nativeWidgetRect.IsEmpty()) {
2424 return Nothing(); // Don't attempt to draw invisible widgets.
2427 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2429 // Use high-resolution drawing.
2430 nativeWidgetRect.Scale(0.5f);
2431 originalHeight *= 0.5f;
2434 EventStates eventState = GetContentState(aFrame, aAppearance);
2436 switch (aAppearance) {
2437 case StyleAppearance::Menupopup:
2440 case StyleAppearance::Menuarrow:
2442 WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
2444 case StyleAppearance::Menuitem:
2445 case StyleAppearance::Checkmenuitem:
2446 return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
2447 aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
2449 case StyleAppearance::Menuseparator:
2450 return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
2452 case StyleAppearance::ButtonArrowUp:
2453 case StyleAppearance::ButtonArrowDown: {
2454 MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
2455 ? MenuIcon::eMenuUpScrollArrow
2456 : MenuIcon::eMenuDownScrollArrow;
2457 return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
2460 case StyleAppearance::Tooltip:
2463 case StyleAppearance::Checkbox:
2464 case StyleAppearance::Radio: {
2465 bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
2467 CheckboxOrRadioParams params;
2468 params.state = CheckboxOrRadioState::eOff;
2469 if (isCheckbox && GetIndeterminate(aFrame)) {
2470 params.state = CheckboxOrRadioState::eIndeterminate;
2471 } else if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
2472 params.state = CheckboxOrRadioState::eOn;
2474 params.controlParams = ComputeControlParams(aFrame, eventState);
2475 params.verticalAlignFactor = VerticalAlignFactor(aFrame);
2477 return Some(WidgetInfo::Checkbox(params));
2479 return Some(WidgetInfo::Radio(params));
2482 case StyleAppearance::Button:
2483 if (IsDefaultButton(aFrame)) {
2484 // Check whether the default button is in a document that does not
2485 // match the :-moz-window-inactive pseudoclass. This activeness check
2486 // is different from the other "active window" checks in this file
2487 // because we absolutely need the button's default button appearance to
2488 // be in sync with its text color, and the text color is changed by
2489 // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
2490 // default buttons in active windows have blue background and white
2491 // text, and default buttons in inactive windows have white background
2493 EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
2494 bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
2495 bool hasDefaultButtonLook = isInActiveWindow && !eventState.HasState(NS_EVENT_STATE_ACTIVE);
2496 ButtonType buttonType =
2497 hasDefaultButtonLook ? ButtonType::eDefaultPushButton : ButtonType::eRegularPushButton;
2498 ControlParams params = ComputeControlParams(aFrame, eventState);
2499 params.insideActiveWindow = isInActiveWindow;
2500 return Some(WidgetInfo::Button(ButtonParams{params, buttonType}));
2502 if (IsButtonTypeMenu(aFrame)) {
2503 ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2504 controlParams.focused = controlParams.focused || IsFocused(aFrame);
2505 controlParams.pressed = IsOpenButton(aFrame);
2506 DropdownParams params;
2507 params.controlParams = controlParams;
2508 params.pullsDown = true;
2509 params.editable = false;
2510 return Some(WidgetInfo::Dropdown(params));
2512 if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
2513 // If the button is tall enough, draw the square button style so that
2514 // buttons with non-standard content look good. Otherwise draw normal
2515 // rounded aqua buttons.
2516 // This comparison is done based on the height that is calculated without
2517 // the top, because the snapped height can be affected by the top of the
2518 // rect and that may result in different height depending on the top value.
2519 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2520 ButtonType::eSquareBezelPushButton}));
2522 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2523 ButtonType::eRoundedBezelPushButton}));
2525 case StyleAppearance::FocusOutline:
2526 return Some(WidgetInfo::FocusOutline());
2528 case StyleAppearance::MozMacHelpButton:
2529 return Some(WidgetInfo::Button(
2530 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
2532 case StyleAppearance::MozMacDisclosureButtonOpen:
2533 case StyleAppearance::MozMacDisclosureButtonClosed: {
2534 ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
2535 ? ButtonType::eDisclosureButtonClosed
2536 : ButtonType::eDisclosureButtonOpen;
2538 WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
2541 case StyleAppearance::Spinner: {
2542 bool isSpinner = (aAppearance == StyleAppearance::Spinner);
2543 nsIContent* content = aFrame->GetContent();
2544 if (isSpinner && content->IsHTMLElement()) {
2545 // In HTML the theming for the spin buttons is drawn individually into
2546 // their own backgrounds instead of being drawn into the background of
2547 // their spinner parent as it is for XUL.
2550 SpinButtonParams params;
2551 if (content->IsElement()) {
2552 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns,
2554 params.pressedButton = Some(SpinButton::eUp);
2555 } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2556 u"down"_ns, eCaseMatters)) {
2557 params.pressedButton = Some(SpinButton::eDown);
2560 params.disabled = IsDisabled(aFrame, eventState);
2561 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2563 return Some(WidgetInfo::SpinButtons(params));
2566 case StyleAppearance::SpinnerUpbutton:
2567 case StyleAppearance::SpinnerDownbutton: {
2568 nsNumberControlFrame* numberControlFrame =
2569 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
2570 if (numberControlFrame) {
2571 SpinButtonParams params;
2572 if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
2573 params.pressedButton = Some(SpinButton::eUp);
2574 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
2575 params.pressedButton = Some(SpinButton::eDown);
2577 params.disabled = IsDisabled(aFrame, eventState);
2578 params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
2579 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
2580 return Some(WidgetInfo::SpinButtonUp(params));
2582 return Some(WidgetInfo::SpinButtonDown(params));
2586 case StyleAppearance::Toolbarbutton: {
2587 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
2588 params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
2589 return Some(WidgetInfo::Segment(params));
2592 case StyleAppearance::Separator:
2593 return Some(WidgetInfo::Separator());
2595 case StyleAppearance::Toolbar: {
2596 NSWindow* win = NativeWindowForFrame(aFrame);
2597 bool isMain = [win isMainWindow];
2598 if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
2599 float unifiedHeight =
2600 std::max(float([(ToolbarWindow*)win unifiedToolbarHeight]), nativeWidgetRect.Height());
2601 return Some(WidgetInfo::UnifiedToolbar(UnifiedToolbarParams{unifiedHeight, isMain}));
2603 return Some(WidgetInfo::Toolbar(isMain));
2606 case StyleAppearance::MozWindowTitlebar: {
2607 NSWindow* win = NativeWindowForFrame(aFrame);
2608 bool isMain = [win isMainWindow];
2609 float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]]
2610 ? [(ToolbarWindow*)win unifiedToolbarHeight]
2611 : nativeWidgetRect.Height();
2612 return Some(WidgetInfo::NativeTitlebar(UnifiedToolbarParams{unifiedToolbarHeight, isMain}));
2615 case StyleAppearance::Statusbar:
2616 return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
2618 case StyleAppearance::MenulistButton:
2619 case StyleAppearance::Menulist: {
2620 ControlParams controlParams = ComputeControlParams(aFrame, eventState);
2621 controlParams.focused = controlParams.focused || IsFocused(aFrame);
2622 controlParams.pressed = IsOpenButton(aFrame);
2623 DropdownParams params;
2624 params.controlParams = controlParams;
2625 params.pullsDown = false;
2626 params.editable = false;
2627 return Some(WidgetInfo::Dropdown(params));
2630 case StyleAppearance::MozMenulistArrowButton:
2631 return Some(WidgetInfo::Button(
2632 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
2634 case StyleAppearance::Groupbox:
2635 return Some(WidgetInfo::GroupBox());
2637 case StyleAppearance::Textfield:
2638 case StyleAppearance::NumberInput: {
2639 // See ShouldUnconditionallyDrawFocusRingIfFocused.
2640 bool isFocused = eventState.HasState(NS_EVENT_STATE_FOCUS);
2641 // XUL textboxes set the native appearance on the containing box, while
2642 // concrete focus is set on the html:input element within it. We can
2643 // though, check the focused attribute of xul textboxes in this case.
2644 // On Mac, focus rings are always shown for textboxes, so we do not need
2645 // to check the window's focus ring state here
2646 if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
2650 bool isDisabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
2652 WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused, /* borderless = */ false}));
2655 case StyleAppearance::Searchfield:
2656 return Some(WidgetInfo::SearchField(ComputeSearchFieldParams(aFrame, eventState)));
2658 case StyleAppearance::ProgressBar: {
2659 if (IsIndeterminateProgress(aFrame, eventState)) {
2660 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2661 NS_WARNING("Unable to animate progressbar!");
2664 return Some(WidgetInfo::ProgressBar(
2665 ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
2668 case StyleAppearance::Meter:
2669 return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
2671 case StyleAppearance::Progresschunk:
2672 case StyleAppearance::Meterchunk:
2673 // Do nothing: progress and meter bars cases will draw chunks.
2676 case StyleAppearance::Treetwisty:
2677 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2678 ButtonType::eTreeTwistyPointingRight}));
2680 case StyleAppearance::Treetwistyopen:
2681 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
2682 ButtonType::eTreeTwistyPointingDown}));
2684 case StyleAppearance::Treeheadercell:
2685 return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
2687 case StyleAppearance::Treeitem:
2688 case StyleAppearance::Treeview:
2689 return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
2691 case StyleAppearance::Treeheader:
2692 // do nothing, taken care of by individual header cells
2693 case StyleAppearance::Treeheadersortarrow:
2694 // do nothing, taken care of by treeview header
2695 case StyleAppearance::Treeline:
2696 // do nothing, these lines don't exist on macos
2699 case StyleAppearance::Range: {
2700 Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
2702 return Some(WidgetInfo::Scale(*params));
2707 case StyleAppearance::ScrollbarHorizontal:
2708 case StyleAppearance::ScrollbarVertical:
2709 case StyleAppearance::ScrollbarbuttonUp:
2710 case StyleAppearance::ScrollbarbuttonLeft:
2711 case StyleAppearance::ScrollbarbuttonDown:
2712 case StyleAppearance::ScrollbarbuttonRight:
2715 case StyleAppearance::ScrollbarthumbVertical:
2716 case StyleAppearance::ScrollbarthumbHorizontal:
2717 case StyleAppearance::ScrollbartrackHorizontal:
2718 case StyleAppearance::ScrollbartrackVertical:
2719 case StyleAppearance::Scrollcorner: {
2720 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarthumbHorizontal ||
2721 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
2722 ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
2723 aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), isHorizontal);
2724 switch (aAppearance) {
2725 case StyleAppearance::ScrollbarthumbVertical:
2726 case StyleAppearance::ScrollbarthumbHorizontal:
2727 return Some(WidgetInfo::ScrollbarThumb(params));
2728 case StyleAppearance::ScrollbartrackHorizontal:
2729 case StyleAppearance::ScrollbartrackVertical:
2730 return Some(WidgetInfo::ScrollbarTrack(params));
2731 case StyleAppearance::Scrollcorner:
2732 return Some(WidgetInfo::ScrollCorner(params));
2734 MOZ_CRASH("unexpected aAppearance");
2739 case StyleAppearance::Textarea:
2740 return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
2742 case StyleAppearance::Listbox:
2743 return Some(WidgetInfo::ListBox());
2745 case StyleAppearance::MozMacSourceList: {
2749 case StyleAppearance::MozMacSourceListSelection:
2750 case StyleAppearance::MozMacActiveSourceListSelection: {
2751 // We only support vibrancy for source list selections if we're inside
2752 // a source list, because we need the background to be transparent.
2753 if (IsInSourceList(aFrame)) {
2756 bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
2757 if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
2758 return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
2760 return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
2763 case StyleAppearance::Tab: {
2764 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
2765 params.pressed = params.pressed && !params.selected;
2766 return Some(WidgetInfo::Segment(params));
2769 case StyleAppearance::Tabpanels:
2770 return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
2772 case StyleAppearance::Resizer:
2773 return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
2781 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(Nothing());
2785 nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
2786 StyleAppearance aAppearance, const nsRect& aRect,
2787 const nsRect& aDirtyRect) {
2788 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2790 Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
2796 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
2798 gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
2799 nativeWidgetRect.Round();
2801 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
2803 RenderWidget(*widgetInfo, *aContext->GetDrawTarget(), nativeWidgetRect,
2804 NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
2808 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2811 void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo, DrawTarget& aDrawTarget,
2812 const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
2814 AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
2816 gfx::Rect dirtyRect = aDirtyRect;
2817 gfx::Rect widgetRect = aWidgetRect;
2818 dirtyRect.Scale(1.0f / aScale);
2819 widgetRect.Scale(1.0f / aScale);
2820 aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
2822 const Widget widget = aWidgetInfo.Widget();
2824 // Some widgets render using DrawTarget, and some using CGContext.
2826 case Widget::eColorFill: {
2827 sRGBColor color = aWidgetInfo.Params<sRGBColor>();
2828 aDrawTarget.FillRect(widgetRect, ColorPattern(ToDeviceColor(color)));
2831 case Widget::eScrollbarThumb: {
2832 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2833 ScrollbarDrawingMac::DrawScrollbarThumb(aDrawTarget, widgetRect, params);
2836 case Widget::eScrollbarTrack: {
2837 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2838 ScrollbarDrawingMac::DrawScrollbarTrack(aDrawTarget, widgetRect, params);
2841 case Widget::eScrollCorner: {
2842 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
2843 ScrollbarDrawingMac::DrawScrollCorner(aDrawTarget, widgetRect, params);
2847 // The remaining widgets require a CGContext.
2849 CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
2851 gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
2853 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
2854 if (cgContext == nullptr) {
2855 // The Quartz surface handles 0x0 surfaces by internally
2856 // making all operations no-ops; there's no cgcontext created for them.
2857 // Unfortunately, this means that callers that want to render
2858 // directly to the CGContext need to be aware of this quirk.
2862 // Set the context's "base transform" to in order to get correctly-sized focus rings.
2863 CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
2866 case Widget::eColorFill:
2867 case Widget::eScrollbarThumb:
2868 case Widget::eScrollbarTrack:
2869 case Widget::eScrollCorner: {
2870 MOZ_CRASH("already handled in outer switch");
2873 case Widget::eMenuIcon: {
2874 MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
2875 DrawMenuIcon(cgContext, macRect, params);
2878 case Widget::eMenuItem: {
2879 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2880 DrawMenuItem(cgContext, macRect, params);
2883 case Widget::eMenuSeparator: {
2884 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
2885 DrawMenuSeparator(cgContext, macRect, params);
2888 case Widget::eCheckbox: {
2889 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2890 DrawCheckboxOrRadio(cgContext, true, macRect, params);
2893 case Widget::eRadio: {
2894 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
2895 DrawCheckboxOrRadio(cgContext, false, macRect, params);
2898 case Widget::eButton: {
2899 ButtonParams params = aWidgetInfo.Params<ButtonParams>();
2900 DrawButton(cgContext, macRect, params);
2903 case Widget::eDropdown: {
2904 DropdownParams params = aWidgetInfo.Params<DropdownParams>();
2905 DrawDropdown(cgContext, macRect, params);
2908 case Widget::eFocusOutline: {
2909 DrawFocusOutline(cgContext, macRect);
2912 case Widget::eSpinButtons: {
2913 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2914 DrawSpinButtons(cgContext, macRect, params);
2917 case Widget::eSpinButtonUp: {
2918 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2919 DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
2922 case Widget::eSpinButtonDown: {
2923 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
2924 DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
2927 case Widget::eSegment: {
2928 SegmentParams params = aWidgetInfo.Params<SegmentParams>();
2929 DrawSegment(cgContext, macRect, params);
2932 case Widget::eSeparator: {
2933 HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
2934 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2937 case Widget::eUnifiedToolbar: {
2938 UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
2939 DrawUnifiedToolbar(cgContext, macRect, params);
2942 case Widget::eToolbar: {
2943 bool isMain = aWidgetInfo.Params<bool>();
2944 DrawToolbar(cgContext, macRect, isMain);
2947 case Widget::eNativeTitlebar: {
2948 UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
2949 DrawNativeTitlebar(cgContext, macRect, params);
2952 case Widget::eStatusBar: {
2953 bool isMain = aWidgetInfo.Params<bool>();
2954 DrawStatusBar(cgContext, macRect, isMain);
2957 case Widget::eGroupBox: {
2958 HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
2959 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2962 case Widget::eTextBox: {
2963 TextBoxParams params = aWidgetInfo.Params<TextBoxParams>();
2964 DrawTextBox(cgContext, macRect, params);
2967 case Widget::eSearchField: {
2968 SearchFieldParams params = aWidgetInfo.Params<SearchFieldParams>();
2969 DrawSearchField(cgContext, macRect, params);
2972 case Widget::eProgressBar: {
2973 ProgressParams params = aWidgetInfo.Params<ProgressParams>();
2974 DrawProgress(cgContext, macRect, params);
2977 case Widget::eMeter: {
2978 MeterParams params = aWidgetInfo.Params<MeterParams>();
2979 DrawMeter(cgContext, macRect, params);
2982 case Widget::eTreeHeaderCell: {
2983 TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
2984 DrawTreeHeaderCell(cgContext, macRect, params);
2987 case Widget::eScale: {
2988 ScaleParams params = aWidgetInfo.Params<ScaleParams>();
2989 DrawScale(cgContext, macRect, params);
2992 case Widget::eMultilineTextField: {
2993 bool isFocused = aWidgetInfo.Params<bool>();
2994 DrawMultilineTextField(cgContext, macRect, isFocused);
2997 case Widget::eListBox: {
2998 // We have to draw this by hand because kHIThemeFrameListBox drawing
2999 // is buggy on 10.5, see bug 579259.
3000 SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
3001 CGContextFillRect(cgContext, macRect);
3003 float x = macRect.origin.x, y = macRect.origin.y;
3004 float w = macRect.size.width, h = macRect.size.height;
3005 SetCGContextFillColor(cgContext, kListboxTopBorderColor);
3006 CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
3007 SetCGContextFillColor(cgContext, kListBoxSidesAndBottomBorderColor);
3008 CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
3009 CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
3010 CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
3013 case Widget::eActiveSourceListSelection:
3014 case Widget::eInactiveSourceListSelection: {
3015 bool isInActiveWindow = aWidgetInfo.Params<bool>();
3016 bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
3017 DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection);
3020 case Widget::eTabPanel: {
3021 bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
3022 DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
3025 case Widget::eResizer: {
3026 bool isRTL = aWidgetInfo.Params<bool>();
3027 DrawResizer(cgContext, macRect, isRTL);
3032 // Reset the base CTM.
3033 CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
3035 nativeDrawing.EndNativeDrawing();
3040 bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
3041 mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
3042 const mozilla::layers::StackingContextHelper& aSc,
3043 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
3044 StyleAppearance aAppearance, const nsRect& aRect) {
3045 nsPresContext* presContext = aFrame->PresContext();
3046 wr::LayoutRect bounds =
3047 wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, presContext->AppUnitsPerDevPixel()));
3049 EventStates eventState = GetContentState(aFrame, aAppearance);
3051 // This list needs to stay consistent with the list in DrawWidgetBackground.
3052 // For every switch case in DrawWidgetBackground, there are three choices:
3053 // - If the case in DrawWidgetBackground draws nothing for the given widget
3054 // type, then don't list it here. We will hit the "default: return true;"
3056 // - If the case in DrawWidgetBackground draws something simple for the given
3057 // widget type, imitate that drawing using WebRender commands.
3058 // - If the case in DrawWidgetBackground draws something complicated for the
3059 // given widget type, return false here.
3060 switch (aAppearance) {
3061 case StyleAppearance::Menuarrow:
3062 case StyleAppearance::Menuitem:
3063 case StyleAppearance::Checkmenuitem:
3064 case StyleAppearance::Menuseparator:
3065 case StyleAppearance::ButtonArrowUp:
3066 case StyleAppearance::ButtonArrowDown:
3067 case StyleAppearance::Checkbox:
3068 case StyleAppearance::Radio:
3069 case StyleAppearance::Button:
3070 case StyleAppearance::FocusOutline:
3071 case StyleAppearance::MozMacHelpButton:
3072 case StyleAppearance::MozMacDisclosureButtonOpen:
3073 case StyleAppearance::MozMacDisclosureButtonClosed:
3074 case StyleAppearance::Spinner:
3075 case StyleAppearance::SpinnerUpbutton:
3076 case StyleAppearance::SpinnerDownbutton:
3077 case StyleAppearance::Toolbarbutton:
3078 case StyleAppearance::Separator:
3079 case StyleAppearance::Toolbar:
3080 case StyleAppearance::MozWindowTitlebar:
3081 case StyleAppearance::Statusbar:
3082 case StyleAppearance::Menulist:
3083 case StyleAppearance::MenulistButton:
3084 case StyleAppearance::MozMenulistArrowButton:
3085 case StyleAppearance::Groupbox:
3086 case StyleAppearance::Textfield:
3087 case StyleAppearance::NumberInput:
3088 case StyleAppearance::Searchfield:
3089 case StyleAppearance::ProgressBar:
3090 case StyleAppearance::Meter:
3091 case StyleAppearance::Treetwisty:
3092 case StyleAppearance::Treetwistyopen:
3093 case StyleAppearance::Treeheadercell:
3094 case StyleAppearance::Treeitem:
3095 case StyleAppearance::Treeview:
3096 case StyleAppearance::Range:
3097 case StyleAppearance::ScrollbarthumbVertical:
3098 case StyleAppearance::ScrollbarthumbHorizontal:
3101 case StyleAppearance::Scrollcorner:
3102 case StyleAppearance::ScrollbartrackHorizontal:
3103 case StyleAppearance::ScrollbartrackVertical: {
3104 const ComputedStyle& style = *nsLayoutUtils::StyleForScrollbar(aFrame);
3105 ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams(
3106 aFrame, style, aAppearance == StyleAppearance::ScrollbartrackHorizontal);
3107 if (params.overlay && !params.rolledOver) {
3108 // There is no scrollbar track, draw nothing and return true.
3111 // There is a scrollbar track and it needs to be drawn using fallback.
3115 case StyleAppearance::Textarea: {
3116 if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
3117 // We can't draw the focus ring using webrender, so fall back to regular
3118 // drawing if we're focused.
3123 aBuilder.PushRect(bounds, bounds, true,
3124 wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
3126 wr::BorderSide side[4] = {
3127 wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldTopBorderColor),
3128 StyleBorderStyle::Solid),
3129 wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3130 StyleBorderStyle::Solid),
3131 wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3132 StyleBorderStyle::Solid),
3133 wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
3134 StyleBorderStyle::Solid),
3137 wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
3138 float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
3139 wr::LayoutSideOffsets borderWidths =
3140 wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
3142 mozilla::Range<const wr::BorderSide> wrsides(side, 4);
3143 aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
3148 case StyleAppearance::Listbox: {
3150 aBuilder.PushRect(bounds, bounds, true,
3151 wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
3153 wr::BorderSide side[4] = {
3154 wr::ToBorderSide(ToDeviceColor(kListboxTopBorderColor), StyleBorderStyle::Solid),
3155 wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3156 StyleBorderStyle::Solid),
3157 wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3158 StyleBorderStyle::Solid),
3159 wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
3160 StyleBorderStyle::Solid),
3163 wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
3164 float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
3165 wr::LayoutSideOffsets borderWidths =
3166 wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
3168 mozilla::Range<const wr::BorderSide> wrsides(side, 4);
3169 aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
3173 case StyleAppearance::Tab:
3174 case StyleAppearance::Tabpanels:
3175 case StyleAppearance::Resizer:
3183 LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
3185 // Assuming aMargin was originally specified for a horizontal LTR context,
3186 // reinterpret the values as logical, and then map to physical coords
3187 // according to aFrame's actual writing mode.
3188 WritingMode wm = aFrame->GetWritingMode();
3189 nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)
3190 .GetPhysicalMargin(wm);
3191 return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
3194 static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
3195 static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
3196 static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
3197 static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26);
3199 LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
3201 StyleAppearance aAppearance) {
3202 LayoutDeviceIntMargin result;
3204 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3206 switch (aAppearance) {
3207 case StyleAppearance::Button: {
3208 if (IsButtonTypeMenu(aFrame)) {
3209 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
3211 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
3216 case StyleAppearance::Toolbarbutton: {
3217 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
3221 case StyleAppearance::Checkbox:
3222 case StyleAppearance::Radio: {
3223 // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
3224 // assume a border width of 2px.
3225 result.SizeTo(2, 2, 2, 2);
3229 case StyleAppearance::Menulist:
3230 case StyleAppearance::MenulistButton:
3231 case StyleAppearance::MozMenulistArrowButton:
3232 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
3235 case StyleAppearance::Menuarrow:
3236 if (nsCocoaFeatures::OnBigSurOrLater()) {
3237 result.SizeTo(0, 0, 0, 28);
3241 case StyleAppearance::NumberInput:
3242 case StyleAppearance::Textfield: {
3243 SInt32 frameOutset = 0;
3244 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
3246 SInt32 textPadding = 0;
3247 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
3249 frameOutset += textPadding;
3251 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
3255 case StyleAppearance::Textarea:
3256 result.SizeTo(1, 1, 1, 1);
3259 case StyleAppearance::Searchfield: {
3260 auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur
3261 : kAquaSearchfieldBorder;
3262 result = DirectionAwareMargin(border, aFrame);
3266 case StyleAppearance::Listbox: {
3267 SInt32 frameOutset = 0;
3268 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
3269 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
3273 case StyleAppearance::ScrollbartrackHorizontal:
3274 case StyleAppearance::ScrollbartrackVertical: {
3275 bool isHorizontal = (aAppearance == StyleAppearance::ScrollbartrackHorizontal);
3276 if (nsLookAndFeel::UseOverlayScrollbars()) {
3277 // Leave a bit of space at the start and the end on all OS X versions.
3290 case StyleAppearance::Statusbar:
3291 result.SizeTo(1, 0, 0, 0);
3298 if (IsHiDPIContext(aContext)) {
3299 result = result + result; // doubled
3302 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(result);
3305 // Return false here to indicate that CSS padding values should be used. There is
3306 // no reason to make a distinction between padding and border values, just specify
3307 // whatever values you want in GetWidgetBorder and only use this to return true
3308 // if you want to override CSS padding values.
3309 bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
3310 StyleAppearance aAppearance,
3311 LayoutDeviceIntMargin* aResult) {
3312 // We don't want CSS padding being used for certain widgets.
3313 // See bug 381639 for an example of why.
3314 switch (aAppearance) {
3315 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
3316 // and have a meaningful baseline, so they can't have
3317 // author-specified padding.
3318 case StyleAppearance::Checkbox:
3319 case StyleAppearance::Radio:
3320 aResult->SizeTo(0, 0, 0, 0);
3323 case StyleAppearance::Menuarrow:
3324 case StyleAppearance::Searchfield:
3325 if (nsCocoaFeatures::OnBigSurOrLater()) {
3336 bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
3337 StyleAppearance aAppearance, nsRect* aOverflowRect) {
3338 nsIntMargin overflow;
3339 switch (aAppearance) {
3340 case StyleAppearance::Button:
3341 case StyleAppearance::MozMacDisclosureButtonOpen:
3342 case StyleAppearance::MozMacDisclosureButtonClosed:
3343 case StyleAppearance::MozMacHelpButton:
3344 case StyleAppearance::Toolbarbutton:
3345 case StyleAppearance::NumberInput:
3346 case StyleAppearance::Textfield:
3347 case StyleAppearance::Textarea:
3348 case StyleAppearance::Searchfield:
3349 case StyleAppearance::Listbox:
3350 case StyleAppearance::Menulist:
3351 case StyleAppearance::MenulistButton:
3352 case StyleAppearance::MozMenulistArrowButton:
3353 case StyleAppearance::Checkbox:
3354 case StyleAppearance::Radio:
3355 case StyleAppearance::Tab:
3356 case StyleAppearance::FocusOutline: {
3357 overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
3358 kMaxFocusRingWidth);
3361 case StyleAppearance::ProgressBar: {
3362 // Progress bars draw a 2 pixel white shadow under their progress indicators.
3363 overflow.bottom = 2;
3366 case StyleAppearance::Meter: {
3367 // Meter bars overflow their boxes by about 2 pixels.
3368 overflow.SizeTo(2, 2, 2, 2);
3375 if (IsHiDPIContext(aContext)) {
3376 // Double the number of device pixels.
3377 overflow += overflow;
3380 if (overflow != nsIntMargin()) {
3381 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
3382 aOverflowRect->Inflate(nsMargin(
3383 NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
3384 NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
3392 nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
3393 StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
3394 bool* aIsOverridable) {
3395 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3397 aResult->SizeTo(0, 0);
3398 *aIsOverridable = true;
3400 switch (aAppearance) {
3401 case StyleAppearance::Button: {
3402 aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
3403 pushButtonSettings.naturalSizes[miniControlSize].height);
3407 case StyleAppearance::ButtonArrowUp:
3408 case StyleAppearance::ButtonArrowDown: {
3409 aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
3410 *aIsOverridable = false;
3414 case StyleAppearance::Menuarrow: {
3415 aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
3416 *aIsOverridable = false;
3420 case StyleAppearance::MozMacDisclosureButtonOpen:
3421 case StyleAppearance::MozMacDisclosureButtonClosed: {
3422 aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
3423 *aIsOverridable = false;
3427 case StyleAppearance::MozMacHelpButton: {
3428 aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
3429 *aIsOverridable = false;
3433 case StyleAppearance::Toolbarbutton: {
3434 aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
3438 case StyleAppearance::Spinner:
3439 case StyleAppearance::SpinnerUpbutton:
3440 case StyleAppearance::SpinnerDownbutton: {
3441 SInt32 buttonHeight = 0, buttonWidth = 0;
3442 if (aFrame->GetContent()->IsXULElement()) {
3443 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
3444 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
3446 NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)];
3447 buttonWidth = size.width;
3448 buttonHeight = size.height;
3449 if (aAppearance != StyleAppearance::Spinner) {
3450 // the buttons are half the height of the spinner
3454 aResult->SizeTo(buttonWidth, buttonHeight);
3455 *aIsOverridable = true;
3459 case StyleAppearance::Menulist:
3460 case StyleAppearance::MenulistButton: {
3461 SInt32 popupHeight = 0;
3462 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
3463 aResult->SizeTo(0, popupHeight);
3467 case StyleAppearance::NumberInput:
3468 case StyleAppearance::Textfield:
3469 case StyleAppearance::Textarea:
3470 case StyleAppearance::Searchfield: {
3471 // at minimum, we should be tall enough for 9pt text.
3472 // I'm using hardcoded values here because the appearance manager
3473 // values for the frame size are incorrect.
3474 aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
3478 case StyleAppearance::MozWindowButtonBox: {
3479 NSSize size = WindowButtonsSize(aFrame);
3480 aResult->SizeTo(size.width, size.height);
3481 *aIsOverridable = false;
3485 case StyleAppearance::MozMacFullscreenButton: {
3486 *aIsOverridable = false;
3490 case StyleAppearance::ProgressBar: {
3491 SInt32 barHeight = 0;
3492 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
3493 aResult->SizeTo(0, barHeight);
3497 case StyleAppearance::Separator: {
3498 aResult->SizeTo(1, 1);
3502 case StyleAppearance::Treetwisty:
3503 case StyleAppearance::Treetwistyopen: {
3504 SInt32 twistyHeight = 0, twistyWidth = 0;
3505 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
3506 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
3507 aResult->SizeTo(twistyWidth, twistyHeight);
3508 *aIsOverridable = false;
3512 case StyleAppearance::Treeheader:
3513 case StyleAppearance::Treeheadercell: {
3514 SInt32 headerHeight = 0;
3515 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
3516 aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
3520 case StyleAppearance::Tab: {
3521 aResult->SizeTo(0, tabHeights[miniControlSize]);
3525 case StyleAppearance::RangeThumb: {
3528 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
3529 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
3530 aResult->SizeTo(width, height);
3531 *aIsOverridable = false;
3535 case StyleAppearance::ScrollbarthumbHorizontal:
3536 case StyleAppearance::ScrollbarthumbVertical:
3537 case StyleAppearance::ScrollbarHorizontal:
3538 case StyleAppearance::ScrollbarVertical:
3539 case StyleAppearance::ScrollbartrackVertical:
3540 case StyleAppearance::ScrollbartrackHorizontal:
3541 case StyleAppearance::ScrollbarbuttonUp:
3542 case StyleAppearance::ScrollbarbuttonDown:
3543 case StyleAppearance::ScrollbarbuttonLeft:
3544 case StyleAppearance::ScrollbarbuttonRight: {
3545 *aIsOverridable = false;
3546 *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
3550 case StyleAppearance::MozMenulistArrowButton:
3551 case StyleAppearance::ScrollbarNonDisappearing: {
3552 *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f);
3556 case StyleAppearance::Resizer: {
3557 HIThemeGrowBoxDrawInfo drawInfo;
3558 drawInfo.version = 0;
3559 drawInfo.state = kThemeStateActive;
3560 drawInfo.kind = kHIThemeGrowBoxKindNormal;
3561 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
3562 drawInfo.size = kHIThemeGrowBoxSizeNormal;
3563 HIPoint pnt = {0, 0};
3565 HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
3566 aResult->SizeTo(bounds.size.width, bounds.size.height);
3567 *aIsOverridable = false;
3573 if (IsHiDPIContext(aPresContext->DeviceContext())) {
3574 *aResult = *aResult * 2;
3579 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3583 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
3584 nsAtom* aAttribute, bool* aShouldRepaint,
3585 const nsAttrValue* aOldValue) {
3586 // Some widget types just never change state.
3587 switch (aAppearance) {
3588 case StyleAppearance::MozWindowTitlebar:
3589 case StyleAppearance::Toolbox:
3590 case StyleAppearance::Toolbar:
3591 case StyleAppearance::Statusbar:
3592 case StyleAppearance::Statusbarpanel:
3593 case StyleAppearance::Resizerpanel:
3594 case StyleAppearance::Tooltip:
3595 case StyleAppearance::Tabpanels:
3596 case StyleAppearance::Tabpanel:
3597 case StyleAppearance::Dialog:
3598 case StyleAppearance::Menupopup:
3599 case StyleAppearance::Groupbox:
3600 case StyleAppearance::Progresschunk:
3601 case StyleAppearance::ProgressBar:
3602 case StyleAppearance::Meter:
3603 case StyleAppearance::Meterchunk:
3604 case StyleAppearance::MozMacVibrancyLight:
3605 case StyleAppearance::MozMacVibrancyDark:
3606 case StyleAppearance::MozMacVibrantTitlebarLight:
3607 case StyleAppearance::MozMacVibrantTitlebarDark:
3608 *aShouldRepaint = false;
3614 // XXXdwh Not sure what can really be done here. Can at least guess for
3615 // specific widgets that they're highly unlikely to have certain states.
3616 // For example, a toolbar doesn't care about any states.
3618 // Hover/focus/active changed. Always repaint.
3619 *aShouldRepaint = true;
3621 // Check the attribute to see if it's relevant.
3622 // disabled, checked, dlgtype, default, etc.
3623 *aShouldRepaint = false;
3624 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
3625 aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected ||
3626 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection ||
3627 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
3628 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
3629 *aShouldRepaint = true;
3636 nsNativeThemeCocoa::ThemeChanged() {
3637 // This is unimplemented because we don't care if gecko changes its theme
3638 // and macOS system appearance changes are handled by
3639 // nsLookAndFeel::SystemWantsDarkTheme.
3643 bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
3644 StyleAppearance aAppearance) {
3645 // if this is a dropdown button in a combobox the answer is always no
3646 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
3647 nsIFrame* parentFrame = aFrame->GetParent();
3648 if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
3651 switch (aAppearance) {
3652 // Combobox dropdowns don't support native theming in vertical mode.
3653 case StyleAppearance::Menulist:
3654 case StyleAppearance::MenulistButton:
3655 case StyleAppearance::MozMenulistArrowButton:
3656 case StyleAppearance::MenulistText:
3657 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
3662 case StyleAppearance::Listbox:
3663 case StyleAppearance::Dialog:
3664 case StyleAppearance::Window:
3665 case StyleAppearance::MozWindowButtonBox:
3666 case StyleAppearance::MozWindowTitlebar:
3667 case StyleAppearance::Checkmenuitem:
3668 case StyleAppearance::Menupopup:
3669 case StyleAppearance::Menuarrow:
3670 case StyleAppearance::Menuitem:
3671 case StyleAppearance::Menuseparator:
3672 case StyleAppearance::MozMacFullscreenButton:
3673 case StyleAppearance::Tooltip:
3675 case StyleAppearance::Checkbox:
3676 case StyleAppearance::CheckboxContainer:
3677 case StyleAppearance::Radio:
3678 case StyleAppearance::RadioContainer:
3679 case StyleAppearance::Groupbox:
3680 case StyleAppearance::MozMacHelpButton:
3681 case StyleAppearance::MozMacDisclosureButtonOpen:
3682 case StyleAppearance::MozMacDisclosureButtonClosed:
3683 case StyleAppearance::Button:
3684 case StyleAppearance::ButtonArrowUp:
3685 case StyleAppearance::ButtonArrowDown:
3686 case StyleAppearance::Toolbarbutton:
3687 case StyleAppearance::Spinner:
3688 case StyleAppearance::SpinnerUpbutton:
3689 case StyleAppearance::SpinnerDownbutton:
3690 case StyleAppearance::Toolbar:
3691 case StyleAppearance::Statusbar:
3692 case StyleAppearance::NumberInput:
3693 case StyleAppearance::Textfield:
3694 case StyleAppearance::Textarea:
3695 case StyleAppearance::Searchfield:
3696 case StyleAppearance::Toolbox:
3697 // case StyleAppearance::Toolbarbutton:
3698 case StyleAppearance::ProgressBar:
3699 case StyleAppearance::Progresschunk:
3700 case StyleAppearance::Meter:
3701 case StyleAppearance::Meterchunk:
3702 case StyleAppearance::Separator:
3704 case StyleAppearance::Tabpanels:
3705 case StyleAppearance::Tab:
3707 case StyleAppearance::Treetwisty:
3708 case StyleAppearance::Treetwistyopen:
3709 case StyleAppearance::Treeview:
3710 case StyleAppearance::Treeheader:
3711 case StyleAppearance::Treeheadercell:
3712 case StyleAppearance::Treeheadersortarrow:
3713 case StyleAppearance::Treeitem:
3714 case StyleAppearance::Treeline:
3715 case StyleAppearance::MozMacSourceList:
3716 case StyleAppearance::MozMacSourceListSelection:
3717 case StyleAppearance::MozMacActiveSourceListSelection:
3719 case StyleAppearance::Range:
3721 case StyleAppearance::ScrollbarHorizontal:
3722 case StyleAppearance::ScrollbarVertical:
3723 case StyleAppearance::ScrollbarbuttonUp:
3724 case StyleAppearance::ScrollbarbuttonDown:
3725 case StyleAppearance::ScrollbarbuttonLeft:
3726 case StyleAppearance::ScrollbarbuttonRight:
3727 case StyleAppearance::ScrollbarthumbHorizontal:
3728 case StyleAppearance::ScrollbarthumbVertical:
3729 case StyleAppearance::ScrollbartrackVertical:
3730 case StyleAppearance::ScrollbartrackHorizontal:
3731 case StyleAppearance::ScrollbarNonDisappearing:
3732 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
3734 case StyleAppearance::Scrollcorner:
3737 case StyleAppearance::Resizer: {
3738 nsIFrame* parentFrame = aFrame->GetParent();
3739 if (!parentFrame || !parentFrame->IsScrollFrame()) return true;
3741 // Note that IsWidgetStyled is not called for resizers on Mac. This is
3742 // because for scrollable containers, the native resizer looks better
3743 // when (non-overlay) scrollbars are present even when the style is
3744 // overriden, and the custom transparent resizer looks better when
3745 // scrollbars are not present.
3746 nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
3747 return (!nsLookAndFeel::UseOverlayScrollbars() && scrollFrame &&
3748 scrollFrame->GetScrollbarVisibility());
3751 case StyleAppearance::FocusOutline:
3754 case StyleAppearance::MozMacVibrancyLight:
3755 case StyleAppearance::MozMacVibrancyDark:
3756 case StyleAppearance::MozMacVibrantTitlebarLight:
3757 case StyleAppearance::MozMacVibrantTitlebarDark:
3766 bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
3767 // flesh this out at some point
3768 switch (aAppearance) {
3769 case StyleAppearance::MozMenulistArrowButton:
3770 case StyleAppearance::Radio:
3771 case StyleAppearance::Checkbox:
3772 case StyleAppearance::ProgressBar:
3773 case StyleAppearance::Meter:
3774 case StyleAppearance::Range:
3775 case StyleAppearance::MozMacHelpButton:
3776 case StyleAppearance::MozMacDisclosureButtonOpen:
3777 case StyleAppearance::MozMacDisclosureButtonClosed:
3785 bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
3786 switch (aAppearance) {
3787 case StyleAppearance::Textarea:
3788 case StyleAppearance::Textfield:
3789 case StyleAppearance::Searchfield:
3790 case StyleAppearance::NumberInput:
3791 case StyleAppearance::Menulist:
3792 case StyleAppearance::MenulistButton:
3793 case StyleAppearance::Button:
3794 case StyleAppearance::MozMacHelpButton:
3795 case StyleAppearance::MozMacDisclosureButtonOpen:
3796 case StyleAppearance::MozMacDisclosureButtonClosed:
3797 case StyleAppearance::Radio:
3798 case StyleAppearance::Range:
3799 case StyleAppearance::Checkbox:
3806 bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
3808 bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
3809 switch (aAppearance) {
3810 case StyleAppearance::Dialog:
3811 case StyleAppearance::Groupbox:
3812 case StyleAppearance::Tabpanels:
3813 case StyleAppearance::ButtonArrowUp:
3814 case StyleAppearance::ButtonArrowDown:
3815 case StyleAppearance::Checkmenuitem:
3816 case StyleAppearance::Menupopup:
3817 case StyleAppearance::Menuarrow:
3818 case StyleAppearance::Menuitem:
3819 case StyleAppearance::Menuseparator:
3820 case StyleAppearance::Tooltip:
3821 case StyleAppearance::Spinner:
3822 case StyleAppearance::SpinnerUpbutton:
3823 case StyleAppearance::SpinnerDownbutton:
3824 case StyleAppearance::Separator:
3825 case StyleAppearance::Toolbox:
3826 case StyleAppearance::NumberInput:
3827 case StyleAppearance::Textfield:
3828 case StyleAppearance::Treeview:
3829 case StyleAppearance::Treeline:
3830 case StyleAppearance::Textarea:
3831 case StyleAppearance::Listbox:
3832 case StyleAppearance::Resizer:
3839 bool nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame) {
3840 NSWindow* win = NativeWindowForFrame(aFrame);
3841 id winDelegate = [win delegate];
3842 nsIWidget* widget = [(WindowDelegate*)winDelegate geckoWidget];
3846 return (widget->WindowType() == eWindowType_sheet);
3849 nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
3850 nsIFrame* aFrame, StyleAppearance aAppearance) {
3851 switch (aAppearance) {
3852 case StyleAppearance::MozWindowTitlebar:
3853 return eThemeGeometryTypeTitlebar;
3854 case StyleAppearance::Toolbar:
3855 return eThemeGeometryTypeToolbar;
3856 case StyleAppearance::Toolbox:
3857 return eThemeGeometryTypeToolbox;
3858 case StyleAppearance::MozWindowButtonBox:
3859 return eThemeGeometryTypeWindowButtons;
3860 case StyleAppearance::MozMacFullscreenButton:
3861 return eThemeGeometryTypeFullscreenButton;
3862 case StyleAppearance::MozMacVibrancyLight:
3863 return eThemeGeometryTypeVibrancyLight;
3864 case StyleAppearance::MozMacVibrancyDark:
3865 return eThemeGeometryTypeVibrancyDark;
3866 case StyleAppearance::MozMacVibrantTitlebarLight:
3867 return eThemeGeometryTypeVibrantTitlebarLight;
3868 case StyleAppearance::MozMacVibrantTitlebarDark:
3869 return eThemeGeometryTypeVibrantTitlebarDark;
3870 case StyleAppearance::Tooltip:
3871 return eThemeGeometryTypeTooltip;
3872 case StyleAppearance::Menupopup:
3873 return eThemeGeometryTypeMenu;
3874 case StyleAppearance::Menuitem:
3875 case StyleAppearance::Checkmenuitem: {
3876 EventStates eventState = GetContentState(aFrame, aAppearance);
3877 bool isDisabled = IsDisabled(aFrame, eventState);
3878 bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
3879 return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
3881 case StyleAppearance::Dialog:
3882 return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
3883 case StyleAppearance::MozMacSourceList:
3884 return eThemeGeometryTypeSourceList;
3885 case StyleAppearance::MozMacSourceListSelection:
3886 return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
3887 : eThemeGeometryTypeUnknown;
3888 case StyleAppearance::MozMacActiveSourceListSelection:
3889 return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
3890 : eThemeGeometryTypeUnknown;
3892 return eThemeGeometryTypeUnknown;
3896 nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
3897 StyleAppearance aAppearance) {
3898 switch (aAppearance) {
3899 case StyleAppearance::Menupopup:
3900 case StyleAppearance::Tooltip:
3901 case StyleAppearance::Dialog:
3902 return eTransparent;
3904 case StyleAppearance::ScrollbarHorizontal:
3905 case StyleAppearance::ScrollbarVertical:
3906 case StyleAppearance::Scrollcorner: {
3907 // We don't use custom scrollbars when using overlay scrollbars.
3908 if (nsLookAndFeel::UseOverlayScrollbars()) {
3909 return eTransparent;
3911 const nsStyleUI* ui = nsLayoutUtils::StyleForScrollbar(aFrame)->StyleUI();
3912 if (!ui->mScrollbarColor.IsAuto() &&
3913 ui->mScrollbarColor.AsColors().track.MaybeTransparent()) {
3914 return eTransparent;
3919 case StyleAppearance::Statusbar:
3920 // Knowing that scrollbars and statusbars are opaque improves
3921 // performance, because we create layers for them.
3924 case StyleAppearance::Toolbar:
3928 return eUnknownTransparency;
3932 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
3933 static nsCOMPtr<nsITheme> inst;
3936 inst = new nsNativeThemeCocoa();
3937 ClearOnShutdown(&inst);
3940 return do_AddRef(inst);