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/. */ 5 6#include "nsNativeThemeCocoa.h" 7#include <objc/NSObjCRuntime.h> 8 9#include "mozilla/gfx/2D.h" 10#include "mozilla/gfx/Helpers.h" 11#include "mozilla/gfx/PathHelpers.h" 12#include "nsChildView.h" 13#include "nsDeviceContext.h" 14#include "nsLayoutUtils.h" 15#include "nsObjCExceptions.h" 16#include "nsNumberControlFrame.h" 17#include "nsRangeFrame.h" 18#include "nsRect.h" 19#include "nsSize.h" 20#include "nsStyleConsts.h" 21#include "nsPresContext.h" 22#include "nsIContent.h" 23#include "mozilla/dom/Document.h" 24#include "nsIFrame.h" 25#include "nsAtom.h" 26#include "nsNameSpaceManager.h" 27#include "nsPresContext.h" 28#include "nsGkAtoms.h" 29#include "nsCocoaFeatures.h" 30#include "nsCocoaWindow.h" 31#include "nsNativeBasicTheme.h" 32#include "nsNativeThemeColors.h" 33#include "nsIScrollableFrame.h" 34#include "mozilla/ClearOnShutdown.h" 35#include "mozilla/EventStates.h" 36#include "mozilla/Range.h" 37#include "mozilla/dom/Element.h" 38#include "mozilla/dom/HTMLMeterElement.h" 39#include "mozilla/layers/StackingContextHelper.h" 40#include "mozilla/StaticPrefs_layout.h" 41#include "mozilla/StaticPrefs_widget.h" 42#include "nsLookAndFeel.h" 43#include "MacThemeGeometryType.h" 44#include "SDKDeclarations.h" 45#include "VibrancyManager.h" 46 47#include "gfxContext.h" 48#include "gfxQuartzSurface.h" 49#include "gfxQuartzNativeDrawing.h" 50#include "gfxUtils.h" // for ToDeviceColor 51#include <algorithm> 52 53using namespace mozilla; 54using namespace mozilla::gfx; 55using mozilla::dom::HTMLMeterElement; 56 57#define DRAW_IN_FRAME_DEBUG 0 58#define SCROLLBARS_VISUAL_DEBUG 0 59 60// private Quartz routines needed here 61extern "C" { 62CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); 63CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform); 64typedef CFTypeRef CUIRendererRef; 65void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options, 66 CFDictionaryRef* result); 67} 68 69// Workaround for NSCell control tint drawing 70// Without this workaround, NSCells are always drawn with the clear control tint 71// as long as they're not attached to an NSControl which is a subview of an active window. 72// XXXmstange Why doesn't Webkit need this? 73@implementation NSCell (ControlTintWorkaround) 74- (int)_realControlTint { 75 return [self controlTint]; 76} 77@end 78 79// This is the window for our MOZCellDrawView. When an NSCell is drawn, some NSCell implementations 80// look at the draw view's window to determine whether the cell should draw with the active look. 81@interface MOZCellDrawWindow : NSWindow 82@property BOOL cellsShouldLookActive; 83@end 84 85@implementation MOZCellDrawWindow 86 87// Override three different methods, for good measure. The NSCell implementation could call any one 88// of them. 89- (BOOL)_hasActiveAppearance { 90 return self.cellsShouldLookActive; 91} 92- (BOOL)hasKeyAppearance { 93 return self.cellsShouldLookActive; 94} 95- (BOOL)_hasKeyAppearance { 96 return self.cellsShouldLookActive; 97} 98 99@end 100 101// The purpose of this class is to provide objects that can be used when drawing 102// NSCells using drawWithFrame:inView: without causing any harm. Only a small 103// number of methods are called on the draw view, among those "isFlipped" and 104// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs 105// on 10.4 (see bug 465069); currentEditor (which isn't even a method of 106// NSView) will be called when drawing search fields, and we only provide it in 107// order to prevent "unrecognized selector" exceptions. 108// There's no need to pass the actual NSView that we're drawing into to 109// drawWithFrame:inView:. What's more, doing so even causes unnecessary 110// invalidations as soon as we draw a focusring! 111// This class needs to be an NSControl so that NSTextFieldCell (and 112// NSSearchFieldCell, which is a subclass of NSTextFieldCell) draws a focus ring. 113@interface MOZCellDrawView : NSControl 114// Called by NSTreeHeaderCell during drawing. 115@property BOOL _drawingEndSeparator; 116@end 117 118@implementation MOZCellDrawView 119 120- (BOOL)isFlipped { 121 return YES; 122} 123 124- (NSText*)currentEditor { 125 return nil; 126} 127 128@end 129 130static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) { 131 if ([aCell showsFirstResponder]) { 132 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 133 CGContextSaveGState(cgContext); 134 135 // It's important to set the focus ring style before we enter the 136 // transparency layer so that the transparency layer only contains 137 // the normal button mask without the focus ring, and the conversion 138 // to the focus ring shape happens only when the transparency layer is 139 // ended. 140 NSSetFocusRingStyle(NSFocusRingOnly); 141 142 // We need to draw the whole button into a transparency layer because 143 // many button types are composed of multiple parts, and if these parts 144 // were drawn while the focus ring style was active, each individual part 145 // would produce a focus ring for itself. But we only want one focus ring 146 // for the whole button. The transparency layer is a way to merge the 147 // individual button parts together before the focus ring shape is 148 // calculated. 149 CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0); 150 [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView]; 151 CGContextEndTransparencyLayer(cgContext); 152 153 CGContextRestoreGState(cgContext); 154 } 155} 156 157static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) { 158 [aCell drawWithFrame:aWithFrame inView:aInView]; 159 DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView); 160} 161 162/** 163 * NSProgressBarCell is used to draw progress bars of any size. 164 */ 165@interface NSProgressBarCell : NSCell { 166 /*All instance variables are private*/ 167 double mValue; 168 double mMax; 169 bool mIsIndeterminate; 170 bool mIsHorizontal; 171} 172 173- (void)setValue:(double)value; 174- (double)value; 175- (void)setMax:(double)max; 176- (double)max; 177- (void)setIndeterminate:(bool)aIndeterminate; 178- (bool)isIndeterminate; 179- (void)setHorizontal:(bool)aIsHorizontal; 180- (bool)isHorizontal; 181- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView; 182@end 183 184@implementation NSProgressBarCell 185 186- (void)setMax:(double)aMax { 187 mMax = aMax; 188} 189 190- (double)max { 191 return mMax; 192} 193 194- (void)setValue:(double)aValue { 195 mValue = aValue; 196} 197 198- (double)value { 199 return mValue; 200} 201 202- (void)setIndeterminate:(bool)aIndeterminate { 203 mIsIndeterminate = aIndeterminate; 204} 205 206- (bool)isIndeterminate { 207 return mIsIndeterminate; 208} 209 210- (void)setHorizontal:(bool)aIsHorizontal { 211 mIsHorizontal = aIsHorizontal; 212} 213 214- (bool)isHorizontal { 215 return mIsHorizontal; 216} 217 218- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 219 CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 220 221 HIThemeTrackDrawInfo tdi; 222 223 tdi.version = 0; 224 tdi.min = 0; 225 226 tdi.value = INT32_MAX * (mValue / mMax); 227 tdi.max = INT32_MAX; 228 tdi.bounds = NSRectToCGRect(cellFrame); 229 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0; 230 tdi.enableState = 231 [self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive; 232 233 NSControlSize size = [self controlSize]; 234 if (size == NSControlSizeRegular) { 235 tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar; 236 } else { 237 NS_ASSERTION(size == NSControlSizeSmall, 238 "We shouldn't have another size than small and regular for the moment"); 239 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar; 240 } 241 242 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30; 243 int32_t milliSecondsPerStep = 1000 / stepsPerSecond; 244 tdi.trackInfo.progress.phase = 245 uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep); 246 247 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal); 248} 249 250@end 251 252@interface MOZSearchFieldCell : NSSearchFieldCell 253@property BOOL shouldUseToolbarStyle; 254@end 255 256@implementation MOZSearchFieldCell 257 258- (instancetype)init { 259 // We would like to render a search field which has the magnifying glass icon at the start of the 260 // search field, and no cancel button. 261 // On 10.12 and 10.13, empty search fields render the magnifying glass icon in the middle of the 262 // field. So in order to get the icon to show at the start of the field, we need to give the field 263 // some content. We achieve this with a single space character. 264 self = [super initTextCell:@" "]; 265 266 // However, because the field is now non-empty, by default it shows a cancel button. To hide the 267 // cancel button, override it with a custom NSButtonCell which renders nothing. 268 NSButtonCell* invisibleCell = [[NSButtonCell alloc] initImageCell:nil]; 269 invisibleCell.bezeled = NO; 270 invisibleCell.bordered = NO; 271 self.cancelButtonCell = invisibleCell; 272 [invisibleCell release]; 273 274 return self; 275} 276 277- (BOOL)_isToolbarMode { 278 return self.shouldUseToolbarStyle; 279} 280 281@end 282 283#define HITHEME_ORIENTATION kHIThemeOrientationNormal 284 285static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor 286 287// These enums are for indexing into the margin array. 288enum { 289 leopardOSorlater = 0, // 10.6 - 10.9 290 yosemiteOSorlater = 1 // 10.10+ 291}; 292 293enum { miniControlSize, smallControlSize, regularControlSize }; 294 295enum { leftMargin, topMargin, rightMargin, bottomMargin }; 296 297static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) { 298 if (cocoaControlSize == NSControlSizeMini) 299 return miniControlSize; 300 else if (cocoaControlSize == NSControlSizeSmall) 301 return smallControlSize; 302 else 303 return regularControlSize; 304} 305 306static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) { 307 if (enumControlSize == miniControlSize) 308 return NSControlSizeMini; 309 else if (enumControlSize == smallControlSize) 310 return NSControlSizeSmall; 311 else 312 return NSControlSizeRegular; 313} 314 315static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) { 316 if (aControlSize == NSControlSizeRegular) 317 return @"regular"; 318 else if (aControlSize == NSControlSizeSmall) 319 return @"small"; 320 else 321 return @"mini"; 322} 323 324static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, 325 const float marginSet[][3][4]) { 326 if (!marginSet) return; 327 328 static int osIndex = yosemiteOSorlater; 329 size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize); 330 const float* buttonMargins = marginSet[osIndex][controlSize]; 331 rect->origin.x -= buttonMargins[leftMargin]; 332 rect->origin.y -= buttonMargins[bottomMargin]; 333 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin]; 334 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin]; 335} 336 337static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) { 338 if (!aFrame) return nil; 339 340 nsIWidget* widget = aFrame->GetNearestWidget(); 341 if (!widget) return nil; 342 343 nsIWidget* topLevelWidget = widget->GetTopLevelWidget(); 344 if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget; 345 346 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW); 347} 348 349static NSSize WindowButtonsSize(nsIFrame* aFrame) { 350 NSWindow* window = NativeWindowForFrame(aFrame); 351 if (!window) { 352 // Return fallback values. 353 return NSMakeSize(54, 16); 354 } 355 356 NSRect buttonBox = NSZeroRect; 357 NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton]; 358 if (closeButton) { 359 buttonBox = NSUnionRect(buttonBox, [closeButton frame]); 360 } 361 NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton]; 362 if (minimizeButton) { 363 buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]); 364 } 365 NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton]; 366 if (zoomButton) { 367 buttonBox = NSUnionRect(buttonBox, [zoomButton frame]); 368 } 369 return buttonBox.size; 370} 371 372static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) { 373 nsIWidget* topLevelWidget = NULL; 374 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget); 375 if (!topLevelWidget || !win) return YES; 376 377 // XUL popups, e.g. the toolbar customization popup, can't become key windows, 378 // but controls in these windows should still get the active look. 379 if (topLevelWidget->WindowType() == eWindowType_popup) return YES; 380 if ([win isSheet]) return [win isKeyWindow]; 381 return [win isMainWindow] && ![win attachedSheet]; 382} 383 384// Toolbar controls and content controls respond to different window 385// activeness states. 386static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) { 387 if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow]; 388 return FrameIsInActiveWindow(aFrame); 389} 390 391static bool IsInSourceList(nsIFrame* aFrame) { 392 for (nsIFrame* frame = aFrame->GetParent(); frame; 393 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) { 394 if (frame->StyleDisplay()->EffectiveAppearance() == StyleAppearance::MozMacSourceList) { 395 return true; 396 } 397 } 398 return false; 399} 400 401NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme) 402 403nsNativeThemeCocoa::nsNativeThemeCocoa() { 404 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 405 406 kMaxFocusRingWidth = 7; 407 408 // provide a local autorelease pool, as this is called during startup 409 // before the main event-loop pool is in place 410 nsAutoreleasePool pool; 411 412 mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""]; 413 [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle]; 414 [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton]; 415 [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask]; 416 417 mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""]; 418 [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle]; 419 [mHelpButtonCell setButtonType:NSMomentaryPushInButton]; 420 [mHelpButtonCell setHighlightsBy:NSPushInCellMask]; 421 422 mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""]; 423 [mPushButtonCell setButtonType:NSMomentaryPushInButton]; 424 [mPushButtonCell setHighlightsBy:NSPushInCellMask]; 425 426 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:@""]; 427 [mRadioButtonCell setButtonType:NSRadioButton]; 428 429 mCheckboxCell = [[NSButtonCell alloc] initTextCell:@""]; 430 [mCheckboxCell setButtonType:NSSwitchButton]; 431 [mCheckboxCell setAllowsMixedState:YES]; 432 433 mTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""]; 434 [mTextFieldCell setBezeled:YES]; 435 [mTextFieldCell setEditable:YES]; 436 [mTextFieldCell setFocusRingType:NSFocusRingTypeExterior]; 437 438 mSearchFieldCell = [[MOZSearchFieldCell alloc] init]; 439 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel]; 440 [mSearchFieldCell setBezeled:YES]; 441 [mSearchFieldCell setEditable:YES]; 442 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior]; 443 444 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; 445 446 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""]; 447 [mComboBoxCell setBezeled:YES]; 448 [mComboBoxCell setEditable:YES]; 449 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior]; 450 451 mProgressBarCell = [[NSProgressBarCell alloc] init]; 452 453 mMeterBarCell = [[NSLevelIndicatorCell alloc] 454 initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle]; 455 456 mTreeHeaderCell = [[NSTableHeaderCell alloc] init]; 457 458 mCellDrawView = [[MOZCellDrawView alloc] init]; 459 460 if (XRE_IsParentProcess()) { 461 // Put the cell draw view into a window that is never shown. 462 // This allows us to convince some NSCell implementations (such as NSButtonCell for default 463 // buttons) to draw with the active appearance. Another benefit of putting the draw view in a 464 // window is the fact that it lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit 465 // the current NSApplication effectiveAppearance automatically, so the field adapts to Dark Mode 466 // correctly. 467 // We don't create this window when the native theme is used in the content process because 468 // NSWindow creation runs into the sandbox and because we never run default buttons in content 469 // processes anyway. 470 mCellDrawWindow = [[MOZCellDrawWindow alloc] initWithContentRect:NSZeroRect 471 styleMask:NSWindowStyleMaskBorderless 472 backing:NSBackingStoreBuffered 473 defer:NO]; 474 [mCellDrawWindow.contentView addSubview:mCellDrawView]; 475 } 476 477 NS_OBJC_END_TRY_IGNORE_BLOCK; 478} 479 480nsNativeThemeCocoa::~nsNativeThemeCocoa() { 481 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 482 483 [mMeterBarCell release]; 484 [mProgressBarCell release]; 485 [mDisclosureButtonCell release]; 486 [mHelpButtonCell release]; 487 [mPushButtonCell release]; 488 [mRadioButtonCell release]; 489 [mCheckboxCell release]; 490 [mTextFieldCell release]; 491 [mSearchFieldCell release]; 492 [mDropdownCell release]; 493 [mComboBoxCell release]; 494 [mTreeHeaderCell release]; 495 [mCellDrawWindow release]; 496 [mCellDrawView release]; 497 498 NS_OBJC_END_TRY_IGNORE_BLOCK; 499} 500 501// Limit on the area of the target rect (in pixels^2) in 502// DrawCellWithScaling() and DrawButton() and above which we 503// don't draw the object into a bitmap buffer. This is to avoid crashes in 504// [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and 505// CGContextDrawImage(), and also to avoid very poor drawing performance in 506// CGContextDrawImage() when it scales the bitmap (particularly if xscale or 507// yscale is less than but near 1 -- e.g. 0.9). This value was determined 508// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with 509// different amounts of RAM. 510#define BITMAP_MAX_AREA 500000 511 512static int GetBackingScaleFactorForRendering(CGContextRef cgContext) { 513 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext); 514 CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm); 515 float maxScale = std::max(fabs(transformedUserSpacePixel.size.width), 516 fabs(transformedUserSpacePixel.size.height)); 517 return maxScale > 1.0 ? 2 : 1; 518} 519 520/* 521 * Draw the given NSCell into the given cgContext. 522 * 523 * destRect - the size and position of the resulting control rectangle 524 * controlSize - the NSControlSize which will be given to the NSCell before 525 * asking it to render 526 * naturalSize - The natural dimensions of this control. 527 * If the control rect size is not equal to either of these, a scale 528 * will be applied to the context so that rendering the control at the 529 * natural size will result in it filling the destRect space. 530 * If a control has no natural dimensions in either/both axes, pass 0.0f. 531 * minimumSize - The minimum dimensions of this control. 532 * If the control rect size is less than the minimum for a given axis, 533 * a scale will be applied to the context so that the minimum is used 534 * for drawing. If a control has no minimum dimensions in either/both 535 * axes, pass 0.0f. 536 * marginSet - an array of margins; a multidimensional array of [2][3][4], 537 * with the first dimension being the OS version (Tiger or Leopard), 538 * the second being the control size (mini, small, regular), and the third 539 * being the 4 margin values (left, top, right, bottom). 540 * view - The NSView that we're drawing into. As far as I can tell, it doesn't 541 * matter if this is really the right view; it just has to return YES when 542 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4. 543 * mirrorHorizontal - whether to mirror the cell horizontally 544 */ 545static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect, 546 NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize, 547 const float marginSet[][3][4], NSView* view, 548 BOOL mirrorHorizontal) { 549 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 550 551 NSRect drawRect = 552 NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height); 553 554 if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width; 555 if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height; 556 557 // Keep aspect ratio when scaling if one dimension is free. 558 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f) 559 drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height; 560 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f) 561 drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width; 562 563 // Honor minimum sizes. 564 if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width; 565 if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height; 566 567 [NSGraphicsContext saveGraphicsState]; 568 569 // Only skip the buffer if the area of our cell (in pixels^2) is too large. 570 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) { 571 // Inflate the rect Gecko gave us by the margin for the control. 572 InflateControlRect(&drawRect, controlSize, marginSet); 573 574 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext]; 575 [NSGraphicsContext 576 setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext 577 flipped:YES]]; 578 579 DrawCellIncludingFocusRing(cell, drawRect, view); 580 581 [NSGraphicsContext setCurrentContext:savedContext]; 582 } else { 583 float w = ceil(drawRect.size.width); 584 float h = ceil(drawRect.size.height); 585 NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h); 586 587 // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 588 // 0,0,w,h 589 InflateControlRect(&tmpRect, controlSize, marginSet); 590 591 // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring 592 w += kMaxFocusRingWidth * 2.0; 593 h += kMaxFocusRingWidth * 2.0; 594 595 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext); 596 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); 597 CGContextRef ctx = CGBitmapContextCreate( 598 NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8, 599 (int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst); 600 CGColorSpaceRelease(rgb); 601 602 // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069. 603 // This is the first flip transform, applied to cgContext. 604 CGContextScaleCTM(cgContext, 1.0f, -1.0f); 605 CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height)); 606 if (mirrorHorizontal) { 607 CGContextScaleCTM(cgContext, -1.0f, 1.0f); 608 CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f); 609 } 610 611 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext]; 612 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx 613 flipped:YES]]; 614 615 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor); 616 617 // Set the context's "base transform" to in order to get correctly-sized focus rings. 618 CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor)); 619 620 // This is the second flip transform, applied to ctx. 621 CGContextScaleCTM(ctx, 1.0f, -1.0f); 622 CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height)); 623 624 DrawCellIncludingFocusRing(cell, tmpRect, view); 625 626 [NSGraphicsContext setCurrentContext:savedContext]; 627 628 CGImageRef img = CGBitmapContextCreateImage(ctx); 629 630 // Drop the image into the original destination rectangle, scaling to fit 631 // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect 632 // doesn't extend beyond the overflow rect 633 float xscale = destRect.size.width / drawRect.size.width; 634 float yscale = destRect.size.height / drawRect.size.height; 635 float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth; 636 float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth; 637 CGContextDrawImage( 638 cgContext, 639 CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY, 640 destRect.size.width + scaledFocusRingX * 2, 641 destRect.size.height + scaledFocusRingY * 2), 642 img); 643 644 CGImageRelease(img); 645 CGContextRelease(ctx); 646 } 647 648 [NSGraphicsContext restoreGraphicsState]; 649 650#if DRAW_IN_FRAME_DEBUG 651 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); 652 CGContextFillRect(cgContext, destRect); 653#endif 654 655 NS_OBJC_END_TRY_IGNORE_BLOCK; 656} 657 658struct CellRenderSettings { 659 // The natural dimensions of the control. 660 // If a control has no natural dimensions in either/both axes, set to 0.0f. 661 NSSize naturalSizes[3]; 662 663 // The minimum dimensions of the control. 664 // If a control has no minimum dimensions in either/both axes, set to 0.0f. 665 NSSize minimumSizes[3]; 666 667 // A three-dimensional array, 668 // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above), 669 // the second being the control size (mini, small, regular), and the third 670 // being the 4 margin values (left, top, right, bottom). 671 float margins[2][3][4]; 672}; 673 674/* 675 * This is a helper method that returns the required NSControlSize given a size 676 * and the size of the three controls plus a tolerance. 677 * size - The width or the height of the element to draw. 678 * sizes - An array with the all the width/height of the element for its 679 * different sizes. 680 * tolerance - The tolerance as passed to DrawCellWithSnapping. 681 * NOTE: returns NSControlSizeRegular if all values in 'sizes' are zero. 682 */ 683static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) { 684 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) { 685 if (sizes[i] == 0) { 686 continue; 687 } 688 689 CGFloat next = 0; 690 // Find next value. 691 for (uint32_t j = i + 1; j <= regularControlSize; ++j) { 692 if (sizes[j] != 0) { 693 next = sizes[j]; 694 break; 695 } 696 } 697 698 // If it's the latest value, we pick it. 699 if (next == 0) { 700 return CocoaSizeForEnum(i); 701 } 702 703 if (size <= sizes[i] + tolerance && size < next) { 704 return CocoaSizeForEnum(i); 705 } 706 } 707 708 // If we are here, that means sizes[] was an array with only empty values 709 // or the algorithm above is wrong. 710 // The former can happen but the later would be wrong. 711 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0, 712 "We found no control! We shouldn't be there!"); 713 return CocoaSizeForEnum(regularControlSize); 714} 715 716/* 717 * Draw the given NSCell into the given cgContext with a nice control size. 718 * 719 * This function is similar to DrawCellWithScaling, but it decides what 720 * control size to use based on the destRect's size. 721 * Scaling is only applied when the difference between the destRect's size 722 * and the next smaller natural size is greater than snapTolerance. Otherwise 723 * it snaps to the next smaller control size without scaling because unscaled 724 * controls look nicer. 725 */ 726static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect, 727 const CellRenderSettings settings, float verticalAlignFactor, 728 NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) { 729 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 730 731 const float rectWidth = destRect.size.width, rectHeight = destRect.size.height; 732 const NSSize* sizes = settings.naturalSizes; 733 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSControlSizeMini)]; 734 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSControlSizeSmall)]; 735 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSControlSizeRegular)]; 736 737 HIRect drawRect = destRect; 738 739 CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width}; 740 NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance); 741 CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height}; 742 NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance); 743 744 NSControlSize controlSize = NSControlSizeRegular; 745 size_t sizeIndex = 0; 746 747 // At some sizes, don't scale but snap. 748 const NSControlSize smallerControlSize = 749 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX 750 : controlSizeY; 751 const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize); 752 const NSSize size = sizes[smallerControlSizeIndex]; 753 float diffWidth = size.width ? rectWidth - size.width : 0.0f; 754 float diffHeight = size.height ? rectHeight - size.height : 0.0f; 755 if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance && 756 diffHeight <= snapTolerance) { 757 // Snap to the smaller control size. 758 controlSize = smallerControlSize; 759 sizeIndex = smallerControlSizeIndex; 760 MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes)); 761 762 // Resize and center the drawRect. 763 if (sizes[sizeIndex].width) { 764 drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2); 765 drawRect.size.width = sizes[sizeIndex].width; 766 } 767 if (sizes[sizeIndex].height) { 768 drawRect.origin.y += 769 floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor); 770 drawRect.size.height = sizes[sizeIndex].height; 771 } 772 } else { 773 // Use the larger control size. 774 controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) 775 ? controlSizeX 776 : controlSizeY; 777 sizeIndex = EnumSizeForCocoaSize(controlSize); 778 } 779 780 [cell setControlSize:controlSize]; 781 782 MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes)); 783 const NSSize minimumSize = settings.minimumSizes[sizeIndex]; 784 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize, 785 settings.margins, view, mirrorHorizontal); 786 787 NS_OBJC_END_TRY_IGNORE_BLOCK; 788} 789 790@interface NSWindow (CoreUIRendererPrivate) 791+ (CUIRendererRef)coreUIRenderer; 792@end 793 794@interface NSObject (NSAppearanceCoreUIRendering) 795- (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options; 796@end 797 798static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions, 799 bool aSkipAreaCheck = false) { 800 if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) { 801 return; 802 } 803 804 NSAppearance* appearance = NSAppearance.currentAppearance; 805 if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) { 806 // Render through NSAppearance on Mac OS 10.10 and up. This will call 807 // CUIDraw with a CoreUI renderer that will give us the correct 10.10 808 // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still 809 // renders 10.9-style widgets on 10.10. 810 [appearance _drawInRect:aRect context:cgContext options:aOptions]; 811 } else { 812 // 10.9 and below 813 CUIRendererRef renderer = 814 [NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil; 815 CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL); 816 } 817} 818 819static float VerticalAlignFactor(nsIFrame* aFrame) { 820 if (!aFrame) return 0.5f; // default: center 821 822 const auto& va = aFrame->StyleDisplay()->mVerticalAlign; 823 auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle; 824 switch (kw) { 825 case StyleVerticalAlignKeyword::Top: 826 case StyleVerticalAlignKeyword::TextTop: 827 return 0.0f; 828 829 case StyleVerticalAlignKeyword::Sub: 830 case StyleVerticalAlignKeyword::Super: 831 case StyleVerticalAlignKeyword::Middle: 832 case StyleVerticalAlignKeyword::MozMiddleWithBaseline: 833 return 0.5f; 834 835 case StyleVerticalAlignKeyword::Baseline: 836 case StyleVerticalAlignKeyword::Bottom: 837 case StyleVerticalAlignKeyword::TextBottom: 838 return 1.0f; 839 840 default: 841 MOZ_ASSERT_UNREACHABLE("invalid vertical-align"); 842 return 0.5f; 843 } 844} 845 846static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams, 847 NSCell* aCell) { 848 [aCell setEnabled:!aControlParams.disabled]; 849 [aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled && 850 aControlParams.insideActiveWindow)]; 851 [aCell setHighlighted:aControlParams.pressed]; 852} 853 854// These are the sizes that Gecko needs to request to draw if it wants 855// to get a standard-sized Aqua radio button drawn. Note that the rects 856// that draw these are actually a little bigger. 857static const CellRenderSettings radioSettings = {{ 858 NSMakeSize(11, 11), // mini 859 NSMakeSize(13, 13), // small 860 NSMakeSize(16, 16) // regular 861 }, 862 {NSZeroSize, NSZeroSize, NSZeroSize}, 863 {{ 864 // Leopard 865 {0, 0, 0, 0}, // mini 866 {0, 1, 1, 1}, // small 867 {0, 0, 0, 0} // regular 868 }, 869 { 870 // Yosemite 871 {0, 0, 0, 0}, // mini 872 {1, 1, 1, 2}, // small 873 {0, 0, 0, 0} // regular 874 }}}; 875 876static const CellRenderSettings checkboxSettings = {{ 877 NSMakeSize(11, 11), // mini 878 NSMakeSize(13, 13), // small 879 NSMakeSize(16, 16) // regular 880 }, 881 {NSZeroSize, NSZeroSize, NSZeroSize}, 882 {{ 883 // Leopard 884 {0, 1, 0, 0}, // mini 885 {0, 1, 0, 1}, // small 886 {0, 1, 0, 1} // regular 887 }, 888 { 889 // Yosemite 890 {0, 1, 0, 0}, // mini 891 {0, 1, 0, 1}, // small 892 {0, 1, 0, 1} // regular 893 }}}; 894 895static NSCellStateValue CellStateForCheckboxOrRadioState( 896 nsNativeThemeCocoa::CheckboxOrRadioState aState) { 897 switch (aState) { 898 case nsNativeThemeCocoa::CheckboxOrRadioState::eOff: 899 return NSOffState; 900 case nsNativeThemeCocoa::CheckboxOrRadioState::eOn: 901 return NSOnState; 902 case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate: 903 return NSMixedState; 904 } 905} 906 907void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox, 908 const HIRect& inBoxRect, 909 const CheckboxOrRadioParams& aParams) { 910 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 911 912 NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell; 913 ApplyControlParamsToNSCell(aParams.controlParams, cell); 914 915 [cell setState:CellStateForCheckboxOrRadioState(aParams.state)]; 916 [cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint] 917 : NSClearControlTint)]; 918 919 // Ensure that the control is square. 920 float length = std::min(inBoxRect.size.width, inBoxRect.size.height); 921 HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f), 922 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f), 923 length, length); 924 925 if (mCellDrawWindow) { 926 mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow; 927 } 928 DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings, 929 aParams.verticalAlignFactor, mCellDrawView, NO); 930 931 NS_OBJC_END_TRY_IGNORE_BLOCK; 932} 933 934static const CellRenderSettings searchFieldSettings = {{ 935 NSMakeSize(0, 16), // mini 936 NSMakeSize(0, 19), // small 937 NSMakeSize(0, 22) // regular 938 }, 939 { 940 NSMakeSize(32, 0), // mini 941 NSMakeSize(38, 0), // small 942 NSMakeSize(44, 0) // regular 943 }, 944 {{ 945 // Leopard 946 {0, 0, 0, 0}, // mini 947 {0, 0, 0, 0}, // small 948 {0, 0, 0, 0} // regular 949 }, 950 { 951 // Yosemite 952 {0, 0, 0, 0}, // mini 953 {0, 0, 0, 0}, // small 954 {0, 0, 0, 0} // regular 955 }}}; 956 957static bool IsToolbarStyleContainer(nsIFrame* aFrame) { 958 nsIContent* content = aFrame->GetContent(); 959 if (!content) { 960 return false; 961 } 962 963 if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) { 964 return true; 965 } 966 967 switch (aFrame->StyleDisplay()->EffectiveAppearance()) { 968 case StyleAppearance::Toolbar: 969 case StyleAppearance::Statusbar: 970 return true; 971 default: 972 return false; 973 } 974} 975 976static bool IsInsideToolbar(nsIFrame* aFrame) { 977 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { 978 if (IsToolbarStyleContainer(frame)) { 979 return true; 980 } 981 } 982 return false; 983} 984 985nsNativeThemeCocoa::TextFieldParams nsNativeThemeCocoa::ComputeTextFieldParams( 986 nsIFrame* aFrame, EventStates aEventState) { 987 TextFieldParams params; 988 params.insideToolbar = IsInsideToolbar(aFrame); 989 params.disabled = IsDisabled(aFrame, aEventState); 990 991 // See ShouldUnconditionallyDrawFocusRingIfFocused. 992 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS); 993 // XUL textboxes set the native appearance on the containing box, while 994 // concrete focus is set on the html:input element within it. We can 995 // though, check the focused attribute of xul textboxes in this case. 996 // On Mac, focus rings are always shown for textboxes, so we do not need 997 // to check the window's focus ring state here 998 if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) { 999 params.focused = true; 1000 } 1001 1002 params.rtl = IsFrameRTL(aFrame); 1003 params.verticalAlignFactor = VerticalAlignFactor(aFrame); 1004 return params; 1005} 1006 1007void nsNativeThemeCocoa::DrawTextField(CGContextRef cgContext, const HIRect& inBoxRect, 1008 const TextFieldParams& aParams) { 1009 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1010 1011 NSTextFieldCell* cell = mTextFieldCell; 1012 [cell setEnabled:!aParams.disabled]; 1013 [cell setShowsFirstResponder:aParams.focused]; 1014 1015 if (mCellDrawWindow) { 1016 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state 1017 } 1018 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor, 1019 mCellDrawView, aParams.rtl); 1020 1021 NS_OBJC_END_TRY_IGNORE_BLOCK; 1022} 1023 1024void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect, 1025 const TextFieldParams& aParams) { 1026 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1027 1028 mSearchFieldCell.enabled = !aParams.disabled; 1029 mSearchFieldCell.showsFirstResponder = aParams.focused; 1030 mSearchFieldCell.placeholderString = @""; 1031 mSearchFieldCell.shouldUseToolbarStyle = aParams.insideToolbar; 1032 1033 if (mCellDrawWindow) { 1034 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state 1035 } 1036 DrawCellWithSnapping(mSearchFieldCell, cgContext, inBoxRect, searchFieldSettings, 1037 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl); 1038 1039 NS_OBJC_END_TRY_IGNORE_BLOCK; 1040} 1041 1042static const NSSize kCheckmarkSize = NSMakeSize(11, 11); 1043static const NSSize kMenuarrowSize = NSMakeSize(9, 10); 1044static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8); 1045static NSString* kCheckmarkImage = @"MenuOnState"; 1046static NSString* kMenuarrowRightImage = @"MenuSubmenu"; 1047static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft"; 1048static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown"; 1049static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp"; 1050static const CGFloat kMenuIconIndent = 6.0f; 1051 1052NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) { 1053 switch (aParams.icon) { 1054 case MenuIcon::eCheckmark: 1055 return kCheckmarkImage; 1056 case MenuIcon::eMenuArrow: 1057 return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage; 1058 case MenuIcon::eMenuDownScrollArrow: 1059 return kMenuDownScrollArrowImage; 1060 case MenuIcon::eMenuUpScrollArrow: 1061 return kMenuUpScrollArrowImage; 1062 } 1063} 1064 1065NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) { 1066 switch (aIcon) { 1067 case MenuIcon::eCheckmark: 1068 return kCheckmarkSize; 1069 case MenuIcon::eMenuArrow: 1070 return kMenuarrowSize; 1071 case MenuIcon::eMenuDownScrollArrow: 1072 case MenuIcon::eMenuUpScrollArrow: 1073 return kMenuScrollArrowSize; 1074 } 1075} 1076 1077nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams( 1078 nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) { 1079 bool isDisabled = IsDisabled(aFrame, aEventState); 1080 1081 MenuIconParams params; 1082 params.icon = aIcon; 1083 params.disabled = isDisabled; 1084 params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); 1085 params.centerHorizontally = true; 1086 params.rtl = IsFrameRTL(aFrame); 1087 return params; 1088} 1089 1090void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect, 1091 const MenuIconParams& aParams) { 1092 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1093 1094 NSSize size = GetMenuIconSize(aParams.icon); 1095 1096 // Adjust size and position of our drawRect. 1097 CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width); 1098 CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height); 1099 CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent); 1100 CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent); 1101 CGRect drawRect = CGRectMake(aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2) 1102 : aParams.rtl ? paddingEndX 1103 : paddingStartX), 1104 aRect.origin.y + ceil(paddingY / 2), size.width, size.height); 1105 1106 NSString* state = 1107 aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal"); 1108 1109 NSString* imageName = GetMenuIconName(aParams); 1110 1111 RenderWithCoreUI( 1112 drawRect, cgContext, 1113 [NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey", 1114 imageName, @"imageNameKey", state, @"state", 1115 @"image", @"widget", [NSNumber numberWithBool:YES], 1116 @"is.flipped", nil]); 1117 1118#if DRAW_IN_FRAME_DEBUG 1119 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); 1120 CGContextFillRect(cgContext, drawRect); 1121#endif 1122 1123 NS_OBJC_END_TRY_IGNORE_BLOCK; 1124} 1125 1126nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams( 1127 nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) { 1128 bool isDisabled = IsDisabled(aFrame, aEventState); 1129 1130 MenuItemParams params; 1131 params.checked = aIsChecked; 1132 params.disabled = isDisabled; 1133 params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); 1134 params.rtl = IsFrameRTL(aFrame); 1135 return params; 1136} 1137 1138void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect, 1139 const MenuItemParams& aParams) { 1140 if (aParams.checked) { 1141 MenuIconParams params; 1142 params.disabled = aParams.disabled; 1143 params.insideActiveMenuItem = aParams.selected; 1144 params.rtl = aParams.rtl; 1145 params.icon = MenuIcon::eCheckmark; 1146 DrawMenuIcon(cgContext, inBoxRect, params); 1147 } 1148} 1149 1150void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect, 1151 const MenuItemParams& aParams) { 1152 // Workaround for visual artifacts issues with 1153 // HIThemeDrawMenuSeparator on macOS Big Sur. 1154 if (nsCocoaFeatures::OnBigSurOrLater()) { 1155 CGRect separatorRect = inBoxRect; 1156 separatorRect.size.height = 1; 1157 separatorRect.size.width -= 42; 1158 separatorRect.origin.x += 21; 1159 // Use transparent black with an alpha similar to the native separator. 1160 // The values 231 (menu background) and 205 (separator color) have been 1161 // sampled from a window screenshot of a native context menu. 1162 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.0, (231 - 205) / 231.0); 1163 CGContextFillRect(cgContext, separatorRect); 1164 return; 1165 } 1166 1167 ThemeMenuState menuState; 1168 if (aParams.disabled) { 1169 menuState = kThemeMenuDisabled; 1170 } else { 1171 menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive; 1172 } 1173 1174 HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState}; 1175 HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION); 1176} 1177 1178static bool ShouldUnconditionallyDrawFocusRingIfFocused(nsIFrame* aFrame) { 1179 // Mac always draws focus rings for textboxes and lists. 1180 switch (aFrame->StyleDisplay()->EffectiveAppearance()) { 1181 case StyleAppearance::NumberInput: 1182 case StyleAppearance::Textfield: 1183 case StyleAppearance::Textarea: 1184 case StyleAppearance::Searchfield: 1185 case StyleAppearance::Listbox: 1186 return true; 1187 default: 1188 return false; 1189 } 1190} 1191 1192nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams( 1193 nsIFrame* aFrame, EventStates aEventState) { 1194 ControlParams params; 1195 params.disabled = IsDisabled(aFrame, aEventState); 1196 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 1197 params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER); 1198 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS) && 1199 (aEventState.HasState(NS_EVENT_STATE_FOCUSRING) || 1200 ShouldUnconditionallyDrawFocusRingIfFocused(aFrame)); 1201 params.rtl = IsFrameRTL(aFrame); 1202 return params; 1203} 1204 1205static const NSSize kHelpButtonSize = NSMakeSize(20, 20); 1206static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21); 1207 1208static const CellRenderSettings pushButtonSettings = {{ 1209 NSMakeSize(0, 16), // mini 1210 NSMakeSize(0, 19), // small 1211 NSMakeSize(0, 22) // regular 1212 }, 1213 { 1214 NSMakeSize(18, 0), // mini 1215 NSMakeSize(26, 0), // small 1216 NSMakeSize(30, 0) // regular 1217 }, 1218 {{ 1219 // Leopard 1220 {0, 0, 0, 0}, // mini 1221 {4, 0, 4, 1}, // small 1222 {5, 0, 5, 2} // regular 1223 }, 1224 { 1225 // Yosemite 1226 {0, 0, 0, 0}, // mini 1227 {4, 0, 4, 1}, // small 1228 {5, 0, 5, 2} // regular 1229 }}}; 1230 1231// The height at which we start doing square buttons instead of rounded buttons 1232// Rounded buttons look bad if drawn at a height greater than 26, so at that point 1233// we switch over to doing square buttons which looks fine at any size. 1234#define DO_SQUARE_BUTTON_HEIGHT 26 1235 1236void nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect, 1237 ButtonType aButtonType, ControlParams aControlParams) { 1238 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1239 1240 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell); 1241 [mPushButtonCell setBezelStyle:NSRoundedBezelStyle]; 1242 mPushButtonCell.keyEquivalent = aButtonType == ButtonType::eDefaultPushButton ? @"\r" : @""; 1243 1244 if (mCellDrawWindow) { 1245 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow; 1246 } 1247 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f, 1248 mCellDrawView, aControlParams.rtl, 1.0f); 1249 1250 NS_OBJC_END_TRY_IGNORE_BLOCK; 1251} 1252 1253void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect, 1254 ControlParams aControlParams) { 1255 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1256 1257 ApplyControlParamsToNSCell(aControlParams, mPushButtonCell); 1258 [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle]; 1259 1260 if (mCellDrawWindow) { 1261 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow; 1262 } 1263 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize, 1264 NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl); 1265 1266 NS_OBJC_END_TRY_IGNORE_BLOCK; 1267} 1268 1269void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect, 1270 ControlParams aControlParams) { 1271 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1272 1273 ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell); 1274 1275 if (mCellDrawWindow) { 1276 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow; 1277 } 1278 DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize, 1279 kHelpButtonSize, NULL, mCellDrawView, 1280 false); // Don't mirror icon in RTL. 1281 1282 NS_OBJC_END_TRY_IGNORE_BLOCK; 1283} 1284 1285void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect, 1286 ControlParams aControlParams, 1287 NSCellStateValue aCellState) { 1288 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1289 1290 ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell); 1291 [mDisclosureButtonCell setState:aCellState]; 1292 1293 if (mCellDrawWindow) { 1294 mCellDrawWindow.cellsShouldLookActive = aControlParams.insideActiveWindow; 1295 } 1296 DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSControlSizeRegular, NSZeroSize, 1297 kDisclosureButtonSize, NULL, mCellDrawView, 1298 false); // Don't mirror icon in RTL. 1299 1300 NS_OBJC_END_TRY_IGNORE_BLOCK; 1301} 1302 1303void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) { 1304 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1305 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext]; 1306 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext 1307 flipped:YES]]; 1308 CGContextSaveGState(cgContext); 1309 NSSetFocusRingStyle(NSFocusRingOnly); 1310 NSRectFill(NSRectFromCGRect(inBoxRect)); 1311 CGContextRestoreGState(cgContext); 1312 [NSGraphicsContext setCurrentContext:savedContext]; 1313 1314 NS_OBJC_END_TRY_IGNORE_BLOCK; 1315} 1316 1317typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, 1318 void* aData); 1319 1320static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect, 1321 RenderHIThemeControlFunction aFunc, void* aData, 1322 BOOL mirrorHorizontally = NO) { 1323 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext); 1324 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y); 1325 1326 bool drawDirect; 1327 HIRect drawRect = aRect; 1328 drawRect.origin = CGPointZero; 1329 1330 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f && 1331 (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) { 1332 drawDirect = TRUE; 1333 } else { 1334 drawDirect = FALSE; 1335 } 1336 1337 // Fall back to no bitmap buffer if the area of our control (in pixels^2) 1338 // is too large. 1339 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) { 1340 aFunc(aCGContext, drawRect, aData); 1341 } else { 1342 // Inflate the buffer to capture focus rings. 1343 int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth; 1344 int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth; 1345 1346 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext); 1347 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 1348 CGContextRef bitmapctx = CGBitmapContextCreate( 1349 NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4, 1350 colorSpace, kCGImageAlphaPremultipliedFirst); 1351 CGColorSpaceRelease(colorSpace); 1352 1353 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor); 1354 CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth); 1355 1356 // Set the context's "base transform" to in order to get correctly-sized focus rings. 1357 CGContextSetBaseCTM(bitmapctx, 1358 CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor)); 1359 1360 // HITheme always wants to draw into a flipped context, or things 1361 // get confused. 1362 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height); 1363 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f); 1364 1365 aFunc(bitmapctx, drawRect, aData); 1366 1367 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx); 1368 1369 CGAffineTransform ctm = CGContextGetCTM(aCGContext); 1370 1371 // We need to unflip, so that we can do a DrawImage without getting a flipped image. 1372 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height); 1373 CGContextScaleCTM(aCGContext, 1.0f, -1.0f); 1374 1375 if (mirrorHorizontally) { 1376 CGContextTranslateCTM(aCGContext, aRect.size.width, 0); 1377 CGContextScaleCTM(aCGContext, -1.0f, 1.0f); 1378 } 1379 1380 HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h); 1381 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap); 1382 1383 CGContextSetCTM(aCGContext, ctm); 1384 1385 CGImageRelease(bitmap); 1386 CGContextRelease(bitmapctx); 1387 } 1388 1389 CGContextSetCTM(aCGContext, savedCTM); 1390} 1391 1392static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) { 1393 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData; 1394 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL); 1395} 1396 1397static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) { 1398 if (aParams.disabled) { 1399 return kThemeStateUnavailable; 1400 } 1401 if (aParams.pressed) { 1402 return kThemeStatePressed; 1403 } 1404 return kThemeStateActive; 1405} 1406 1407void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect, 1408 ThemeButtonKind aKind, ThemeButtonValue aValue, 1409 ThemeDrawState aState, ThemeButtonAdornment aAdornment, 1410 const ControlParams& aParams) { 1411 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1412 1413 HIThemeButtonDrawInfo bdi; 1414 bdi.version = 0; 1415 bdi.kind = aKind; 1416 bdi.value = aValue; 1417 bdi.state = aState; 1418 bdi.adornment = aAdornment; 1419 1420 if (aParams.focused && aParams.insideActiveWindow) { 1421 bdi.adornment |= kThemeAdornmentFocus; 1422 } 1423 1424 RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl); 1425 1426#if DRAW_IN_FRAME_DEBUG 1427 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); 1428 CGContextFillRect(cgContext, inBoxRect); 1429#endif 1430 1431 NS_OBJC_END_TRY_IGNORE_BLOCK; 1432} 1433 1434void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect, 1435 const ButtonParams& aParams) { 1436 ControlParams controlParams = aParams.controlParams; 1437 1438 switch (aParams.button) { 1439 case ButtonType::eRegularPushButton: 1440 case ButtonType::eDefaultPushButton: 1441 DrawPushButton(cgContext, inBoxRect, aParams.button, controlParams); 1442 return; 1443 case ButtonType::eSquareBezelPushButton: 1444 DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams); 1445 return; 1446 case ButtonType::eArrowButton: 1447 DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn, 1448 kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams); 1449 return; 1450 case ButtonType::eHelpButton: 1451 DrawHelpButton(cgContext, inBoxRect, controlParams); 1452 return; 1453 case ButtonType::eTreeTwistyPointingRight: 1454 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight, 1455 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams); 1456 return; 1457 case ButtonType::eTreeTwistyPointingDown: 1458 DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown, 1459 ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams); 1460 return; 1461 case ButtonType::eDisclosureButtonClosed: 1462 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState); 1463 return; 1464 case ButtonType::eDisclosureButtonOpen: 1465 DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState); 1466 return; 1467 } 1468} 1469 1470nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams( 1471 nsIFrame* aFrame, EventStates aEventState) { 1472 TreeHeaderCellParams params; 1473 params.controlParams = ComputeControlParams(aFrame, aEventState); 1474 params.sortDirection = GetTreeSortDirection(aFrame); 1475 params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame); 1476 return params; 1477} 1478 1479@interface NSTableHeaderCell (NSTableHeaderCell_setSortable) 1480// This method has been present in the same form since at least macOS 10.4. 1481- (void)_setSortable:(BOOL)arg1 1482 showSortIndicator:(BOOL)arg2 1483 ascending:(BOOL)arg3 1484 priority:(NSInteger)arg4 1485 highlightForSort:(BOOL)arg5; 1486@end 1487 1488void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect, 1489 const TreeHeaderCellParams& aParams) { 1490 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1491 1492 // Without clearing the cell's title, it takes on a default value of "Field", 1493 // which is displayed underneath the title set in the front-end. 1494 NSCell* cell = (NSCell*)mTreeHeaderCell; 1495 cell.title = @""; 1496 1497 if ([mTreeHeaderCell respondsToSelector:@selector 1498 (_setSortable:showSortIndicator:ascending:priority:highlightForSort:)]) { 1499 switch (aParams.sortDirection) { 1500 case eTreeSortDirection_Ascending: 1501 [mTreeHeaderCell _setSortable:YES 1502 showSortIndicator:YES 1503 ascending:YES 1504 priority:0 1505 highlightForSort:YES]; 1506 break; 1507 case eTreeSortDirection_Descending: 1508 [mTreeHeaderCell _setSortable:YES 1509 showSortIndicator:YES 1510 ascending:NO 1511 priority:0 1512 highlightForSort:YES]; 1513 break; 1514 default: 1515 // eTreeSortDirection_Natural 1516 [mTreeHeaderCell _setSortable:YES 1517 showSortIndicator:NO 1518 ascending:YES 1519 priority:0 1520 highlightForSort:NO]; 1521 break; 1522 } 1523 } 1524 1525 mTreeHeaderCell.enabled = !aParams.controlParams.disabled; 1526 mTreeHeaderCell.state = 1527 (mTreeHeaderCell.enabled && aParams.controlParams.pressed) ? NSOnState : NSOffState; 1528 1529 mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell; 1530 1531 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext; 1532 NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext 1533 flipped:YES]; 1534 DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView); 1535 NSGraphicsContext.currentContext = savedContext; 1536 1537#if DRAW_IN_FRAME_DEBUG 1538 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); 1539 CGContextFillRect(cgContext, inBoxRect); 1540#endif 1541 1542 NS_OBJC_END_TRY_IGNORE_BLOCK; 1543} 1544 1545static const CellRenderSettings dropdownSettings = {{ 1546 NSMakeSize(0, 16), // mini 1547 NSMakeSize(0, 19), // small 1548 NSMakeSize(0, 22) // regular 1549 }, 1550 { 1551 NSMakeSize(18, 0), // mini 1552 NSMakeSize(38, 0), // small 1553 NSMakeSize(44, 0) // regular 1554 }, 1555 {{ 1556 // Leopard 1557 {1, 1, 2, 1}, // mini 1558 {3, 0, 3, 1}, // small 1559 {3, 0, 3, 0} // regular 1560 }, 1561 { 1562 // Yosemite 1563 {1, 1, 2, 1}, // mini 1564 {3, 0, 3, 1}, // small 1565 {3, 0, 3, 0} // regular 1566 }}}; 1567 1568static const CellRenderSettings editableMenulistSettings = {{ 1569 NSMakeSize(0, 15), // mini 1570 NSMakeSize(0, 18), // small 1571 NSMakeSize(0, 21) // regular 1572 }, 1573 { 1574 NSMakeSize(18, 0), // mini 1575 NSMakeSize(38, 0), // small 1576 NSMakeSize(44, 0) // regular 1577 }, 1578 {{ 1579 // Leopard 1580 {0, 0, 2, 2}, // mini 1581 {0, 0, 3, 2}, // small 1582 {0, 1, 3, 3} // regular 1583 }, 1584 { 1585 // Yosemite 1586 {0, 0, 2, 2}, // mini 1587 {0, 0, 3, 2}, // small 1588 {0, 1, 3, 3} // regular 1589 }}}; 1590 1591void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect, 1592 const DropdownParams& aParams) { 1593 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1594 1595 [mDropdownCell setPullsDown:aParams.pullsDown]; 1596 NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell; 1597 1598 ApplyControlParamsToNSCell(aParams.controlParams, cell); 1599 1600 if (aParams.controlParams.insideActiveWindow) { 1601 [cell setControlTint:[NSColor currentControlTint]]; 1602 } else { 1603 [cell setControlTint:NSClearControlTint]; 1604 } 1605 1606 const CellRenderSettings& settings = 1607 aParams.editable ? editableMenulistSettings : dropdownSettings; 1608 1609 if (mCellDrawWindow) { 1610 mCellDrawWindow.cellsShouldLookActive = aParams.controlParams.insideActiveWindow; 1611 } 1612 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView, 1613 aParams.controlParams.rtl); 1614 1615 NS_OBJC_END_TRY_IGNORE_BLOCK; 1616} 1617 1618static const CellRenderSettings spinnerSettings = { 1619 { 1620 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) 1621 NSMakeSize(15, 22), // small 1622 NSMakeSize(19, 27) // regular 1623 }, 1624 { 1625 NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) 1626 NSMakeSize(15, 22), // small 1627 NSMakeSize(19, 27) // regular 1628 }, 1629 {{ 1630 // Leopard 1631 {0, 0, 0, 0}, // mini 1632 {0, 0, 0, 0}, // small 1633 {0, 0, 0, 0} // regular 1634 }, 1635 { 1636 // Yosemite 1637 {0, 0, 0, 0}, // mini 1638 {0, 0, 0, 0}, // small 1639 {0, 0, 0, 0} // regular 1640 }}}; 1641 1642HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind, 1643 const SpinButtonParams& aParams) { 1644 HIThemeButtonDrawInfo bdi; 1645 bdi.version = 0; 1646 bdi.kind = aKind; 1647 bdi.value = kThemeButtonOff; 1648 bdi.adornment = kThemeAdornmentNone; 1649 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; 1655 } else { 1656 bdi.state = kThemeStatePressedDown; 1657 } 1658 } else { 1659 bdi.state = kThemeStateActive; 1660 } 1661 1662 return bdi; 1663} 1664 1665void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect, 1666 const SpinButtonParams& aParams) { 1667 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1668 1669 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams); 1670 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL); 1671 1672 NS_OBJC_END_TRY_IGNORE_BLOCK; 1673} 1674 1675void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect, 1676 SpinButton aDrawnButton, const SpinButtonParams& aParams) { 1677 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1678 1679 HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams); 1680 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; 1689 } 1690 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; 1694 1695 CGContextSaveGState(cgContext); 1696 CGContextClipToRect(cgContext, inBoxRect); 1697 1698 HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL); 1699 1700 CGContextRestoreGState(cgContext); 1701 1702 NS_OBJC_END_TRY_IGNORE_BLOCK; 1703} 1704 1705static const CellRenderSettings progressSettings[2][2] = { 1706 // Vertical progress bar. 1707 {// Determined settings. 1708 {{ 1709 NSZeroSize, // mini 1710 NSMakeSize(10, 0), // small 1711 NSMakeSize(16, 0) // regular 1712 }, 1713 {NSZeroSize, NSZeroSize, NSZeroSize}, 1714 {{ 1715 // Leopard 1716 {0, 0, 0, 0}, // mini 1717 {1, 1, 1, 1}, // small 1718 {1, 1, 1, 1} // regular 1719 }}}, 1720 // There is no horizontal margin in regular undetermined size. 1721 {{ 1722 NSZeroSize, // mini 1723 NSMakeSize(10, 0), // small 1724 NSMakeSize(16, 0) // regular 1725 }, 1726 {NSZeroSize, NSZeroSize, NSZeroSize}, 1727 {{ 1728 // Leopard 1729 {0, 0, 0, 0}, // mini 1730 {1, 1, 1, 1}, // small 1731 {1, 0, 1, 0} // regular 1732 }, 1733 { 1734 // Yosemite 1735 {0, 0, 0, 0}, // mini 1736 {1, 1, 1, 1}, // small 1737 {1, 0, 1, 0} // regular 1738 }}}}, 1739 // Horizontal progress bar. 1740 {// Determined settings. 1741 {{ 1742 NSZeroSize, // mini 1743 NSMakeSize(0, 10), // small 1744 NSMakeSize(0, 16) // regular 1745 }, 1746 {NSZeroSize, NSZeroSize, NSZeroSize}, 1747 {{ 1748 // Leopard 1749 {0, 0, 0, 0}, // mini 1750 {1, 1, 1, 1}, // small 1751 {1, 1, 1, 1} // regular 1752 }, 1753 { 1754 // Yosemite 1755 {0, 0, 0, 0}, // mini 1756 {1, 1, 1, 1}, // small 1757 {1, 1, 1, 1} // regular 1758 }}}, 1759 // There is no horizontal margin in regular undetermined size. 1760 {{ 1761 NSZeroSize, // mini 1762 NSMakeSize(0, 10), // small 1763 NSMakeSize(0, 16) // regular 1764 }, 1765 {NSZeroSize, NSZeroSize, NSZeroSize}, 1766 {{ 1767 // Leopard 1768 {0, 0, 0, 0}, // mini 1769 {1, 1, 1, 1}, // small 1770 {0, 1, 0, 1} // regular 1771 }, 1772 { 1773 // Yosemite 1774 {0, 0, 0, 0}, // mini 1775 {1, 1, 1, 1}, // small 1776 {0, 1, 0, 1} // regular 1777 }}}}}; 1778 1779nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams( 1780 nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) { 1781 ProgressParams params; 1782 params.value = GetProgressValue(aFrame); 1783 params.max = GetProgressMaxValue(aFrame); 1784 params.verticalAlignFactor = VerticalAlignFactor(aFrame); 1785 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 1786 params.indeterminate = IsIndeterminateProgress(aFrame, aEventState); 1787 params.horizontal = aIsHorizontal; 1788 params.rtl = IsFrameRTL(aFrame); 1789 return params; 1790} 1791 1792void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect, 1793 const ProgressParams& aParams) { 1794 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1795 1796 NSProgressBarCell* cell = mProgressBarCell; 1797 1798 [cell setValue:aParams.value]; 1799 [cell setMax:aParams.max]; 1800 [cell setIndeterminate:aParams.indeterminate]; 1801 [cell setHorizontal:aParams.horizontal]; 1802 [cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint] 1803 : NSClearControlTint)]; 1804 1805 if (mCellDrawWindow) { 1806 mCellDrawWindow.cellsShouldLookActive = aParams.insideActiveWindow; 1807 } 1808 DrawCellWithSnapping(cell, cgContext, inBoxRect, 1809 progressSettings[aParams.horizontal][aParams.indeterminate], 1810 aParams.verticalAlignFactor, mCellDrawView, aParams.rtl); 1811 1812 NS_OBJC_END_TRY_IGNORE_BLOCK; 1813} 1814 1815static const CellRenderSettings meterSetting = {{ 1816 NSMakeSize(0, 16), // mini 1817 NSMakeSize(0, 16), // small 1818 NSMakeSize(0, 16) // regular 1819 }, 1820 {NSZeroSize, NSZeroSize, NSZeroSize}, 1821 {{ 1822 // Leopard 1823 {1, 1, 1, 1}, // mini 1824 {1, 1, 1, 1}, // small 1825 {1, 1, 1, 1} // regular 1826 }, 1827 { 1828 // Yosemite 1829 {1, 1, 1, 1}, // mini 1830 {1, 1, 1, 1}, // small 1831 {1, 1, 1, 1} // regular 1832 }}}; 1833 1834nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) { 1835 nsIContent* content = aFrame->GetContent(); 1836 if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) { 1837 return MeterParams(); 1838 } 1839 1840 HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content); 1841 MeterParams params; 1842 params.value = meterElement->Value(); 1843 params.min = meterElement->Min(); 1844 params.max = meterElement->Max(); 1845 EventStates states = meterElement->State(); 1846 if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) { 1847 params.optimumState = OptimumState::eSubOptimum; 1848 } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) { 1849 params.optimumState = OptimumState::eSubSubOptimum; 1850 } 1851 params.horizontal = !IsVerticalMeter(aFrame); 1852 params.verticalAlignFactor = VerticalAlignFactor(aFrame); 1853 params.rtl = IsFrameRTL(aFrame); 1854 1855 return params; 1856} 1857 1858void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect, 1859 const MeterParams& aParams) { 1860 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK 1861 1862 NSLevelIndicatorCell* cell = mMeterBarCell; 1863 1864 [cell setMinValue:aParams.min]; 1865 [cell setMaxValue:aParams.max]; 1866 [cell setDoubleValue:aParams.value]; 1867 1868 /** 1869 * The way HTML and Cocoa defines the meter/indicator widget are different. 1870 * So, we are going to use a trick to get the Cocoa widget showing what we 1871 * are expecting: we set the warningValue or criticalValue to the current 1872 * value when we want to have the widget to be in the warning or critical 1873 * state. 1874 */ 1875 switch (aParams.optimumState) { 1876 case OptimumState::eOptimum: 1877 [cell setWarningValue:aParams.max + 1]; 1878 [cell setCriticalValue:aParams.max + 1]; 1879 break; 1880 case OptimumState::eSubOptimum: 1881 [cell setWarningValue:aParams.value]; 1882 [cell setCriticalValue:aParams.max + 1]; 1883 break; 1884 case OptimumState::eSubSubOptimum: 1885 [cell setWarningValue:aParams.max + 1]; 1886 [cell setCriticalValue:aParams.value]; 1887 break; 1888 } 1889 1890 HIRect rect = CGRectStandardize(inBoxRect); 1891 BOOL vertical = !aParams.horizontal; 1892 1893 CGContextSaveGState(cgContext); 1894 1895 if (vertical) { 1896 /** 1897 * Cocoa doesn't provide a vertical meter bar so to show one, we have to 1898 * show a rotated horizontal meter bar. 1899 * Given that we want to show a vertical meter bar, we assume that the rect 1900 * has vertical dimensions but we can't correctly draw a meter widget inside 1901 * such a rectangle so we need to inverse width and height (and re-position) 1902 * to get a rectangle with horizontal dimensions. 1903 * Finally, we want to show a vertical meter so we want to rotate the result 1904 * so it is vertical. We do that by changing the context. 1905 */ 1906 CGFloat tmp = rect.size.width; 1907 rect.size.width = rect.size.height; 1908 rect.size.height = tmp; 1909 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f; 1910 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f; 1911 1912 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect)); 1913 CGContextRotateCTM(cgContext, -M_PI / 2.f); 1914 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect)); 1915 } 1916 1917 if (mCellDrawWindow) { 1918 mCellDrawWindow.cellsShouldLookActive = YES; // TODO: propagate correct activeness state 1919 } 1920 DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor, 1921 mCellDrawView, !vertical && aParams.rtl); 1922 1923 CGContextRestoreGState(cgContext); 1924 1925 NS_OBJC_END_TRY_IGNORE_BLOCK 1926} 1927 1928void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect, 1929 bool aIsInsideActiveWindow) { 1930 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1931 1932 HIThemeTabPaneDrawInfo tpdi; 1933 1934 tpdi.version = 1; 1935 tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive; 1936 tpdi.direction = kThemeTabNorth; 1937 tpdi.size = kHIThemeTabSizeNormal; 1938 tpdi.kind = kHIThemeTabKindNormal; 1939 1940 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION); 1941 1942 NS_OBJC_END_TRY_IGNORE_BLOCK; 1943} 1944 1945Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams( 1946 nsIFrame* aFrame, EventStates aEventState) { 1947 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame); 1948 if (!rangeFrame) { 1949 return Nothing(); 1950 } 1951 1952 bool isHorizontal = IsRangeHorizontal(aFrame); 1953 1954 // ScaleParams requires integer min, max and value. This is purely for 1955 // drawing, so we normalize to a range 0-1000 here. 1956 ScaleParams params; 1957 params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000); 1958 params.min = 0; 1959 params.max = 1000; 1960 params.reverse = !isHorizontal || rangeFrame->IsRightToLeft(); 1961 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 1962 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING); 1963 params.disabled = IsDisabled(aFrame, aEventState); 1964 params.horizontal = isHorizontal; 1965 return Some(params); 1966} 1967 1968void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect, 1969 const ScaleParams& aParams) { 1970 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 1971 1972 HIThemeTrackDrawInfo tdi; 1973 1974 tdi.version = 0; 1975 tdi.kind = kThemeMediumSlider; 1976 tdi.bounds = inBoxRect; 1977 tdi.min = aParams.min; 1978 tdi.max = aParams.max; 1979 tdi.value = aParams.value; 1980 tdi.attributes = kThemeTrackShowThumb; 1981 if (aParams.horizontal) { 1982 tdi.attributes |= kThemeTrackHorizontal; 1983 } 1984 if (aParams.reverse) { 1985 tdi.attributes |= kThemeTrackRightToLeft; 1986 } 1987 if (aParams.focused) { 1988 tdi.attributes |= kThemeTrackHasFocus; 1989 } 1990 if (aParams.disabled) { 1991 tdi.enableState = kThemeTrackDisabled; 1992 } else { 1993 tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive; 1994 } 1995 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain; 1996 tdi.trackInfo.slider.pressState = 0; 1997 1998 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION); 1999 2000 NS_OBJC_END_TRY_IGNORE_BLOCK; 2001} 2002 2003nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) { 2004 // Usually a separator is drawn by the segment to the right of the 2005 // separator, but pressed and selected segments have higher priority. 2006 if (!aBefore || !aAfter) return nullptr; 2007 if (IsSelectedButton(aAfter)) return aAfter; 2008 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore; 2009 return aAfter; 2010} 2011 2012static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) { 2013 // A separator between two segments should always be located in the leftmost 2014 // pixel column of the segment to the right of the separator, regardless of 2015 // who ends up drawing it. 2016 // CoreUI draws the separators inside the drawing rect. 2017 if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) { 2018 // The segment to the left of us draws the separator, so we need to make 2019 // room for it. 2020 aRect.origin.x += 1; 2021 aRect.size.width -= 1; 2022 } 2023 if (aParams.drawsRightSeparator) { 2024 // We draw the right separator, so we need to extend the draw rect into the 2025 // segment to our right. 2026 aRect.size.width += 1; 2027 } 2028 return aRect; 2029} 2030 2031static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) { 2032 if (aIsFirst) { 2033 if (aIsLast) return @"kCUISegmentPositionOnly"; 2034 return @"kCUISegmentPositionFirst"; 2035 } 2036 if (aIsLast) return @"kCUISegmentPositionLast"; 2037 return @"kCUISegmentPositionMiddle"; 2038} 2039 2040struct SegmentedControlRenderSettings { 2041 const CGFloat* heights; 2042 const NSString* widgetName; 2043}; 2044 2045static const CGFloat tabHeights[3] = {17, 20, 23}; 2046 2047static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"}; 2048 2049static const CGFloat toolbarButtonHeights[3] = {15, 18, 22}; 2050 2051static const SegmentedControlRenderSettings toolbarButtonRenderSettings = { 2052 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"}; 2053 2054nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams( 2055 nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) { 2056 SegmentParams params; 2057 params.segmentType = aSegmentType; 2058 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 2059 params.pressed = IsPressedButton(aFrame); 2060 params.selected = IsSelectedButton(aFrame); 2061 params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUSRING); 2062 bool isRTL = IsFrameRTL(aFrame); 2063 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL); 2064 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL); 2065 params.atLeftEnd = !left; 2066 params.atRightEnd = !right; 2067 params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame; 2068 params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame; 2069 params.rtl = isRTL; 2070 return params; 2071} 2072 2073static SegmentedControlRenderSettings RenderSettingsForSegmentType( 2074 nsNativeThemeCocoa::SegmentType aSegmentType) { 2075 switch (aSegmentType) { 2076 case nsNativeThemeCocoa::SegmentType::eToolbarButton: 2077 return toolbarButtonRenderSettings; 2078 case nsNativeThemeCocoa::SegmentType::eTab: 2079 return tabRenderSettings; 2080 } 2081} 2082 2083void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect, 2084 const SegmentParams& aParams) { 2085 SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType); 2086 NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f); 2087 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams); 2088 2089 NSDictionary* dict = @{ 2090 @"widget" : renderSettings.widgetName, 2091 @"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey" 2092 : @"kCUIPresentationStateInactive"), 2093 @"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd), 2094 @"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator], 2095 @"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator], 2096 @"value" : [NSNumber numberWithBool:aParams.selected], 2097 @"state" : 2098 (aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")), 2099 @"focus" : [NSNumber numberWithBool:aParams.focused], 2100 @"size" : CUIControlSizeForCocoaSize(controlSize), 2101 @"is.flipped" : [NSNumber numberWithBool:YES], 2102 @"direction" : @"up" 2103 }; 2104 2105 RenderWithCoreUI(drawRect, cgContext, dict); 2106} 2107 2108void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect, 2109 bool aIsMain) { 2110 CGRect drawRect = inBoxRect; 2111 2112 // top border 2113 drawRect.size.height = 1.0f; 2114 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain); 2115 2116 // background 2117 drawRect.origin.y += drawRect.size.height; 2118 drawRect.size.height = inBoxRect.size.height - 2.0f; 2119 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain); 2120 2121 // bottom border 2122 drawRect.origin.y += drawRect.size.height; 2123 drawRect.size.height = 1.0f; 2124 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain); 2125} 2126 2127static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) { 2128 if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false; 2129 2130 ToolbarWindow* win = (ToolbarWindow*)aWindow; 2131 float unifiedToolbarHeight = [win unifiedToolbarHeight]; 2132 return aRect.X() == 0 && aRect.Width() >= [win frame].size.width && 2133 aRect.YMost() <= unifiedToolbarHeight; 2134} 2135 2136void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect, 2137 bool aIsMain) { 2138 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2139 2140 if (inBoxRect.size.height < 2.0f) return; 2141 2142 CGContextSaveGState(cgContext); 2143 CGContextClipToRect(cgContext, inBoxRect); 2144 2145 // kCUIWidgetWindowFrame draws a complete window frame with both title bar 2146 // and bottom bar. We only want the bottom bar, so we extend the draw rect 2147 // upwards to make space for the title bar, and then we clip it away. 2148 CGRect drawRect = inBoxRect; 2149 const int extendUpwards = 40; 2150 drawRect.origin.y -= extendUpwards; 2151 drawRect.size.height += extendUpwards; 2152 RenderWithCoreUI( 2153 drawRect, cgContext, 2154 [NSDictionary 2155 dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin", 2156 @"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state", 2157 [NSNumber numberWithInt:inBoxRect.size.height], 2158 @"kCUIWindowFrameBottomBarHeightKey", 2159 [NSNumber numberWithBool:YES], 2160 @"kCUIWindowFrameDrawBottomBarSeparatorKey", 2161 [NSNumber numberWithBool:YES], @"is.flipped", nil]); 2162 2163 CGContextRestoreGState(cgContext); 2164 2165 NS_OBJC_END_TRY_IGNORE_BLOCK; 2166} 2167 2168static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) { 2169 HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData; 2170 HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal); 2171} 2172 2173void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) { 2174 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 2175 2176 HIThemeGrowBoxDrawInfo drawInfo; 2177 drawInfo.version = 0; 2178 drawInfo.state = kThemeStateActive; 2179 drawInfo.kind = kHIThemeGrowBoxKindNormal; 2180 drawInfo.direction = kThemeGrowRight | kThemeGrowDown; 2181 drawInfo.size = kHIThemeGrowBoxSizeNormal; 2182 2183 RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL); 2184 2185 NS_OBJC_END_TRY_IGNORE_BLOCK; 2186} 2187 2188void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect, 2189 bool aIsFocused) { 2190 mTextFieldCell.enabled = YES; 2191 mTextFieldCell.showsFirstResponder = aIsFocused; 2192 2193 if (mCellDrawWindow) { 2194 mCellDrawWindow.cellsShouldLookActive = YES; 2195 } 2196 2197 // DrawCellIncludingFocusRing draws into the current NSGraphicsContext, so do the usual 2198 // save+restore dance. 2199 NSGraphicsContext* savedContext = NSGraphicsContext.currentContext; 2200 NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext 2201 flipped:YES]; 2202 DrawCellIncludingFocusRing(mTextFieldCell, inBoxRect, mCellDrawView); 2203 NSGraphicsContext.currentContext = savedContext; 2204} 2205 2206void nsNativeThemeCocoa::DrawSourceListSelection(CGContextRef aContext, const CGRect& aRect, 2207 bool aWindowIsActive, bool aSelectionIsActive) { 2208 NSColor* fillColor; 2209 if (aSelectionIsActive) { 2210 // Active selection, blue or graphite. 2211 fillColor = ControlAccentColor(); 2212 } else { 2213 // Inactive selection, gray. 2214 if (aWindowIsActive) { 2215 fillColor = [NSColor colorWithWhite:0.871 alpha:1.0]; 2216 } else { 2217 fillColor = [NSColor colorWithWhite:0.808 alpha:1.0]; 2218 } 2219 } 2220 CGContextSetFillColorWithColor(aContext, [fillColor CGColor]); 2221 CGContextFillRect(aContext, aRect); 2222} 2223 2224static bool IsHiDPIContext(nsDeviceContext* aContext) { 2225 return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom(); 2226} 2227 2228Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo( 2229 nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) { 2230 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2231 2232 // setup to draw into the correct port 2233 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel(); 2234 2235 gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height); 2236 nativeWidgetRect.Scale(1.0 / gfxFloat(p2a)); 2237 float originalHeight = nativeWidgetRect.Height(); 2238 nativeWidgetRect.Round(); 2239 if (nativeWidgetRect.IsEmpty()) { 2240 return Nothing(); // Don't attempt to draw invisible widgets. 2241 } 2242 2243 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext()); 2244 if (hidpi) { 2245 // Use high-resolution drawing. 2246 nativeWidgetRect.Scale(0.5f); 2247 originalHeight *= 0.5f; 2248 } 2249 2250 EventStates eventState = GetContentState(aFrame, aAppearance); 2251 2252 switch (aAppearance) { 2253 case StyleAppearance::Menupopup: 2254 return Nothing(); 2255 2256 case StyleAppearance::Menuarrow: 2257 return Some( 2258 WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow))); 2259 2260 case StyleAppearance::Menuitem: 2261 case StyleAppearance::Checkmenuitem: 2262 return Some(WidgetInfo::MenuItem(ComputeMenuItemParams( 2263 aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem))); 2264 2265 case StyleAppearance::Menuseparator: 2266 return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false))); 2267 2268 case StyleAppearance::ButtonArrowUp: 2269 case StyleAppearance::ButtonArrowDown: { 2270 MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp 2271 ? MenuIcon::eMenuUpScrollArrow 2272 : MenuIcon::eMenuDownScrollArrow; 2273 return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon))); 2274 } 2275 2276 case StyleAppearance::Tooltip: 2277 return Nothing(); 2278 2279 case StyleAppearance::Checkbox: 2280 case StyleAppearance::Radio: { 2281 bool isCheckbox = (aAppearance == StyleAppearance::Checkbox); 2282 2283 CheckboxOrRadioParams params; 2284 params.state = CheckboxOrRadioState::eOff; 2285 if (isCheckbox && GetIndeterminate(aFrame)) { 2286 params.state = CheckboxOrRadioState::eIndeterminate; 2287 } else if (GetCheckedOrSelected(aFrame, !isCheckbox)) { 2288 params.state = CheckboxOrRadioState::eOn; 2289 } 2290 params.controlParams = ComputeControlParams(aFrame, eventState); 2291 params.verticalAlignFactor = VerticalAlignFactor(aFrame); 2292 if (isCheckbox) { 2293 return Some(WidgetInfo::Checkbox(params)); 2294 } 2295 return Some(WidgetInfo::Radio(params)); 2296 } 2297 2298 case StyleAppearance::Button: 2299 if (IsDefaultButton(aFrame)) { 2300 // Check whether the default button is in a document that does not 2301 // match the :-moz-window-inactive pseudoclass. This activeness check 2302 // is different from the other "active window" checks in this file 2303 // because we absolutely need the button's default button appearance to 2304 // be in sync with its text color, and the text color is changed by 2305 // such a :-moz-window-inactive rule. (That's because on 10.10 and up, 2306 // default buttons in active windows have blue background and white 2307 // text, and default buttons in inactive windows have white background 2308 // and black text.) 2309 EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState(); 2310 bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE); 2311 bool hasDefaultButtonLook = isInActiveWindow && !eventState.HasState(NS_EVENT_STATE_ACTIVE); 2312 ButtonType buttonType = 2313 hasDefaultButtonLook ? ButtonType::eDefaultPushButton : ButtonType::eRegularPushButton; 2314 ControlParams params = ComputeControlParams(aFrame, eventState); 2315 params.insideActiveWindow = isInActiveWindow; 2316 return Some(WidgetInfo::Button(ButtonParams{params, buttonType})); 2317 } 2318 if (IsButtonTypeMenu(aFrame)) { 2319 ControlParams controlParams = ComputeControlParams(aFrame, eventState); 2320 controlParams.focused = controlParams.focused || IsFocused(aFrame); 2321 controlParams.pressed = IsOpenButton(aFrame); 2322 DropdownParams params; 2323 params.controlParams = controlParams; 2324 params.pullsDown = true; 2325 params.editable = false; 2326 return Some(WidgetInfo::Dropdown(params)); 2327 } 2328 if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) { 2329 // If the button is tall enough, draw the square button style so that 2330 // buttons with non-standard content look good. Otherwise draw normal 2331 // rounded aqua buttons. 2332 // This comparison is done based on the height that is calculated without 2333 // the top, because the snapped height can be affected by the top of the 2334 // rect and that may result in different height depending on the top value. 2335 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), 2336 ButtonType::eSquareBezelPushButton})); 2337 } 2338 return Some(WidgetInfo::Button( 2339 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eRegularPushButton})); 2340 2341 case StyleAppearance::FocusOutline: 2342 return Some(WidgetInfo::FocusOutline()); 2343 2344 case StyleAppearance::MozMacHelpButton: 2345 return Some(WidgetInfo::Button( 2346 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton})); 2347 2348 case StyleAppearance::MozMacDisclosureButtonOpen: 2349 case StyleAppearance::MozMacDisclosureButtonClosed: { 2350 ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed) 2351 ? ButtonType::eDisclosureButtonClosed 2352 : ButtonType::eDisclosureButtonOpen; 2353 return Some( 2354 WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType})); 2355 } 2356 2357 case StyleAppearance::Spinner: { 2358 bool isSpinner = (aAppearance == StyleAppearance::Spinner); 2359 nsIContent* content = aFrame->GetContent(); 2360 if (isSpinner && content->IsHTMLElement()) { 2361 // In HTML the theming for the spin buttons is drawn individually into 2362 // their own backgrounds instead of being drawn into the background of 2363 // their spinner parent as it is for XUL. 2364 break; 2365 } 2366 SpinButtonParams params; 2367 if (content->IsElement()) { 2368 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, u"up"_ns, 2369 eCaseMatters)) { 2370 params.pressedButton = Some(SpinButton::eUp); 2371 } else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, 2372 u"down"_ns, eCaseMatters)) { 2373 params.pressedButton = Some(SpinButton::eDown); 2374 } 2375 } 2376 params.disabled = IsDisabled(aFrame, eventState); 2377 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 2378 2379 return Some(WidgetInfo::SpinButtons(params)); 2380 } 2381 2382 case StyleAppearance::SpinnerUpbutton: 2383 case StyleAppearance::SpinnerDownbutton: { 2384 nsNumberControlFrame* numberControlFrame = 2385 nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame); 2386 if (numberControlFrame) { 2387 SpinButtonParams params; 2388 if (numberControlFrame->SpinnerUpButtonIsDepressed()) { 2389 params.pressedButton = Some(SpinButton::eUp); 2390 } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) { 2391 params.pressedButton = Some(SpinButton::eDown); 2392 } 2393 params.disabled = IsDisabled(aFrame, eventState); 2394 params.insideActiveWindow = FrameIsInActiveWindow(aFrame); 2395 if (aAppearance == StyleAppearance::SpinnerUpbutton) { 2396 return Some(WidgetInfo::SpinButtonUp(params)); 2397 } 2398 return Some(WidgetInfo::SpinButtonDown(params)); 2399 } 2400 } break; 2401 2402 case StyleAppearance::Toolbarbutton: { 2403 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton); 2404 params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow]; 2405 return Some(WidgetInfo::Segment(params)); 2406 } 2407 2408 case StyleAppearance::Separator: 2409 return Some(WidgetInfo::Separator()); 2410 2411 case StyleAppearance::Toolbar: { 2412 NSWindow* win = NativeWindowForFrame(aFrame); 2413 bool isMain = [win isMainWindow]; 2414 if (ToolbarCanBeUnified(nativeWidgetRect, win)) { 2415 // Unified toolbars are drawn similar to vibrancy; we communicate their extents via the 2416 // theme geometry mechanism and then place native views under Gecko's rendering. So Gecko 2417 // just needs to be transparent in the place where the toolbar should be visible. 2418 return Nothing(); 2419 } 2420 return Some(WidgetInfo::Toolbar(isMain)); 2421 } 2422 2423 case StyleAppearance::MozWindowTitlebar: { 2424 return Nothing(); 2425 } 2426 2427 case StyleAppearance::Statusbar: 2428 return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES))); 2429 2430 case StyleAppearance::MenulistButton: 2431 case StyleAppearance::Menulist: { 2432 ControlParams controlParams = ComputeControlParams(aFrame, eventState); 2433 controlParams.focused = controlParams.focused || IsFocused(aFrame); 2434 controlParams.pressed = IsOpenButton(aFrame); 2435 DropdownParams params; 2436 params.controlParams = controlParams; 2437 params.pullsDown = false; 2438 params.editable = false; 2439 return Some(WidgetInfo::Dropdown(params)); 2440 } 2441 2442 case StyleAppearance::MozMenulistArrowButton: 2443 return Some(WidgetInfo::Button( 2444 ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton})); 2445 2446 case StyleAppearance::Groupbox: 2447 return Some(WidgetInfo::GroupBox()); 2448 2449 case StyleAppearance::Textfield: 2450 case StyleAppearance::NumberInput: 2451 return Some(WidgetInfo::TextField(ComputeTextFieldParams(aFrame, eventState))); 2452 2453 case StyleAppearance::Searchfield: 2454 return Some(WidgetInfo::SearchField(ComputeTextFieldParams(aFrame, eventState))); 2455 2456 case StyleAppearance::ProgressBar: { 2457 if (IsIndeterminateProgress(aFrame, eventState)) { 2458 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { 2459 NS_WARNING("Unable to animate progressbar!"); 2460 } 2461 } 2462 return Some(WidgetInfo::ProgressBar( 2463 ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame)))); 2464 } 2465 2466 case StyleAppearance::Meter: 2467 return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame))); 2468 2469 case StyleAppearance::Progresschunk: 2470 case StyleAppearance::Meterchunk: 2471 // Do nothing: progress and meter bars cases will draw chunks. 2472 break; 2473 2474 case StyleAppearance::Treetwisty: 2475 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), 2476 ButtonType::eTreeTwistyPointingRight})); 2477 2478 case StyleAppearance::Treetwistyopen: 2479 return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), 2480 ButtonType::eTreeTwistyPointingDown})); 2481 2482 case StyleAppearance::Treeheadercell: 2483 return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState))); 2484 2485 case StyleAppearance::Treeitem: 2486 case StyleAppearance::Treeview: 2487 return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0))); 2488 2489 case StyleAppearance::Treeheader: 2490 // do nothing, taken care of by individual header cells 2491 case StyleAppearance::Treeheadersortarrow: 2492 // do nothing, taken care of by treeview header 2493 case StyleAppearance::Treeline: 2494 // do nothing, these lines don't exist on macos 2495 break; 2496 2497 case StyleAppearance::Range: { 2498 Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState); 2499 if (params) { 2500 return Some(WidgetInfo::Scale(*params)); 2501 } 2502 break; 2503 } 2504 2505 case StyleAppearance::ScrollbarHorizontal: 2506 case StyleAppearance::ScrollbarVertical: 2507 case StyleAppearance::ScrollbarbuttonUp: 2508 case StyleAppearance::ScrollbarbuttonLeft: 2509 case StyleAppearance::ScrollbarbuttonDown: 2510 case StyleAppearance::ScrollbarbuttonRight: 2511 break; 2512 2513 case StyleAppearance::ScrollbarthumbVertical: 2514 case StyleAppearance::ScrollbarthumbHorizontal: 2515 case StyleAppearance::ScrollbartrackHorizontal: 2516 case StyleAppearance::ScrollbartrackVertical: 2517 case StyleAppearance::Scrollcorner: { 2518 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarthumbHorizontal || 2519 aAppearance == StyleAppearance::ScrollbartrackHorizontal; 2520 ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams( 2521 aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame), isHorizontal); 2522 switch (aAppearance) { 2523 case StyleAppearance::ScrollbarthumbVertical: 2524 case StyleAppearance::ScrollbarthumbHorizontal: 2525 return Some(WidgetInfo::ScrollbarThumb(params)); 2526 case StyleAppearance::ScrollbartrackHorizontal: 2527 case StyleAppearance::ScrollbartrackVertical: 2528 return Some(WidgetInfo::ScrollbarTrack(params)); 2529 case StyleAppearance::Scrollcorner: 2530 return Some(WidgetInfo::ScrollCorner(params)); 2531 default: 2532 MOZ_CRASH("unexpected aAppearance"); 2533 } 2534 break; 2535 } 2536 2537 case StyleAppearance::Textarea: 2538 return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS))); 2539 2540 case StyleAppearance::Listbox: 2541 return Some(WidgetInfo::ListBox()); 2542 2543 case StyleAppearance::MozMacSourceList: { 2544 return Nothing(); 2545 } 2546 2547 case StyleAppearance::MozMacSourceListSelection: 2548 case StyleAppearance::MozMacActiveSourceListSelection: { 2549 // We only support vibrancy for source list selections if we're inside 2550 // a source list, because we need the background to be transparent. 2551 if (IsInSourceList(aFrame)) { 2552 return Nothing(); 2553 } 2554 bool isInActiveWindow = FrameIsInActiveWindow(aFrame); 2555 if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) { 2556 return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow)); 2557 } 2558 return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow)); 2559 } 2560 2561 case StyleAppearance::Tab: { 2562 SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab); 2563 params.pressed = params.pressed && !params.selected; 2564 return Some(WidgetInfo::Segment(params)); 2565 } 2566 2567 case StyleAppearance::Tabpanels: 2568 return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame))); 2569 2570 case StyleAppearance::Resizer: 2571 return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame))); 2572 2573 default: 2574 break; 2575 } 2576 2577 return Nothing(); 2578 2579 NS_OBJC_END_TRY_BLOCK_RETURN(Nothing()); 2580} 2581 2582NS_IMETHODIMP 2583nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, 2584 StyleAppearance aAppearance, const nsRect& aRect, 2585 const nsRect& aDirtyRect, DrawOverflow) { 2586 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2587 2588 Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect); 2589 2590 if (!widgetInfo) { 2591 return NS_OK; 2592 } 2593 2594 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel(); 2595 2596 gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a); 2597 nativeWidgetRect.Round(); 2598 2599 bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext()); 2600 2601 auto colorScheme = LookAndFeel::ColorSchemeForDocument(*aFrame->PresContext()->Document()); 2602 2603 RenderWidget(*widgetInfo, colorScheme, *aContext->GetDrawTarget(), nativeWidgetRect, 2604 NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f); 2605 2606 return NS_OK; 2607 2608 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 2609} 2610 2611void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo, 2612 LookAndFeel::ColorScheme aScheme, DrawTarget& aDrawTarget, 2613 const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect, 2614 float aScale) { 2615 // Some of the drawing below uses NSAppearance.currentAppearance behind the scenes. 2616 // Set it to the appearance we want, the same way as nsLookAndFeel::NativeGetColor. 2617 NSAppearance.currentAppearance = NSAppearanceForColorScheme(aScheme); 2618 2619 // Also set the cell draw window's appearance; this is respected by NSTextFieldCell (and its 2620 // subclass NSSearchFieldCell). 2621 if (mCellDrawWindow) { 2622 mCellDrawWindow.appearance = NSAppearance.currentAppearance; 2623 } 2624 2625 const Widget widget = aWidgetInfo.Widget(); 2626 2627 // Some widgets render using DrawTarget, and some using CGContext. 2628 switch (widget) { 2629 case Widget::eColorFill: { 2630 sRGBColor color = aWidgetInfo.Params<sRGBColor>(); 2631 aDrawTarget.FillRect(aWidgetRect, ColorPattern(ToDeviceColor(color))); 2632 break; 2633 } 2634 case Widget::eScrollbarThumb: { 2635 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>(); 2636 auto thumb = ScrollbarDrawingMac::GetThumbRect(aWidgetRect, params, aScale); 2637 float cornerRadius = (params.horizontal ? thumb.mRect.Height() : thumb.mRect.Width()) / 2.0f; 2638 aDrawTarget.FillRoundedRect(RoundedRect(thumb.mRect, RectCornerRadii(cornerRadius)), 2639 ColorPattern(ToDeviceColor(thumb.mFillColor))); 2640 if (thumb.mStrokeColor) { 2641 auto strokeRect = thumb.mRect; 2642 strokeRect.Inflate(thumb.mStrokeOutset); 2643 float strokeRadius = (params.horizontal ? strokeRect.Height() : strokeRect.Width()) / 2.0f; 2644 RefPtr<Path> path = 2645 MakePathForRoundedRect(aDrawTarget, strokeRect, RectCornerRadii(strokeRadius)); 2646 aDrawTarget.Stroke(path, ColorPattern(ToDeviceColor(thumb.mStrokeColor)), 2647 StrokeOptions(thumb.mStrokeWidth)); 2648 } 2649 break; 2650 } 2651 case Widget::eScrollbarTrack: { 2652 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>(); 2653 ScrollbarDrawingMac::ScrollbarTrackRects rects; 2654 if (ScrollbarDrawingMac::GetScrollbarTrackRects(aWidgetRect, params, aScale, rects)) { 2655 for (const auto& rect : rects) { 2656 aDrawTarget.FillRect(rect.mRect, ColorPattern(ToDeviceColor(rect.mColor))); 2657 } 2658 } 2659 break; 2660 } 2661 case Widget::eScrollCorner: { 2662 ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>(); 2663 ScrollbarDrawingMac::ScrollCornerRects rects; 2664 if (ScrollbarDrawingMac::GetScrollCornerRects(aWidgetRect, params, aScale, rects)) { 2665 for (const auto& rect : rects) { 2666 aDrawTarget.FillRect(rect.mRect, ColorPattern(ToDeviceColor(rect.mColor))); 2667 } 2668 } 2669 break; 2670 } 2671 default: { 2672 AutoRestoreTransform autoRestoreTransform(&aDrawTarget); 2673 gfx::Rect widgetRect = aWidgetRect; 2674 gfx::Rect dirtyRect = aDirtyRect; 2675 2676 dirtyRect.Scale(1.0f / aScale); 2677 widgetRect.Scale(1.0f / aScale); 2678 aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale)); 2679 2680 // The remaining widgets require a CGContext. 2681 CGRect macRect = 2682 CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height()); 2683 2684 gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect); 2685 2686 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing(); 2687 if (cgContext == nullptr) { 2688 // The Quartz surface handles 0x0 surfaces by internally 2689 // making all operations no-ops; there's no cgcontext created for them. 2690 // Unfortunately, this means that callers that want to render 2691 // directly to the CGContext need to be aware of this quirk. 2692 return; 2693 } 2694 2695 // Set the context's "base transform" to in order to get correctly-sized focus rings. 2696 CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale)); 2697 2698 switch (widget) { 2699 case Widget::eColorFill: 2700 case Widget::eScrollbarThumb: 2701 case Widget::eScrollbarTrack: 2702 case Widget::eScrollCorner: { 2703 MOZ_CRASH("already handled in outer switch"); 2704 break; 2705 } 2706 case Widget::eMenuIcon: { 2707 MenuIconParams params = aWidgetInfo.Params<MenuIconParams>(); 2708 DrawMenuIcon(cgContext, macRect, params); 2709 break; 2710 } 2711 case Widget::eMenuItem: { 2712 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>(); 2713 DrawMenuItem(cgContext, macRect, params); 2714 break; 2715 } 2716 case Widget::eMenuSeparator: { 2717 MenuItemParams params = aWidgetInfo.Params<MenuItemParams>(); 2718 DrawMenuSeparator(cgContext, macRect, params); 2719 break; 2720 } 2721 case Widget::eCheckbox: { 2722 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>(); 2723 DrawCheckboxOrRadio(cgContext, true, macRect, params); 2724 break; 2725 } 2726 case Widget::eRadio: { 2727 CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>(); 2728 DrawCheckboxOrRadio(cgContext, false, macRect, params); 2729 break; 2730 } 2731 case Widget::eButton: { 2732 ButtonParams params = aWidgetInfo.Params<ButtonParams>(); 2733 DrawButton(cgContext, macRect, params); 2734 break; 2735 } 2736 case Widget::eDropdown: { 2737 DropdownParams params = aWidgetInfo.Params<DropdownParams>(); 2738 DrawDropdown(cgContext, macRect, params); 2739 break; 2740 } 2741 case Widget::eFocusOutline: { 2742 DrawFocusOutline(cgContext, macRect); 2743 break; 2744 } 2745 case Widget::eSpinButtons: { 2746 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>(); 2747 DrawSpinButtons(cgContext, macRect, params); 2748 break; 2749 } 2750 case Widget::eSpinButtonUp: { 2751 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>(); 2752 DrawSpinButton(cgContext, macRect, SpinButton::eUp, params); 2753 break; 2754 } 2755 case Widget::eSpinButtonDown: { 2756 SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>(); 2757 DrawSpinButton(cgContext, macRect, SpinButton::eDown, params); 2758 break; 2759 } 2760 case Widget::eSegment: { 2761 SegmentParams params = aWidgetInfo.Params<SegmentParams>(); 2762 DrawSegment(cgContext, macRect, params); 2763 break; 2764 } 2765 case Widget::eSeparator: { 2766 HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive}; 2767 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION); 2768 break; 2769 } 2770 case Widget::eToolbar: { 2771 bool isMain = aWidgetInfo.Params<bool>(); 2772 DrawToolbar(cgContext, macRect, isMain); 2773 break; 2774 } 2775 case Widget::eStatusBar: { 2776 bool isMain = aWidgetInfo.Params<bool>(); 2777 DrawStatusBar(cgContext, macRect, isMain); 2778 break; 2779 } 2780 case Widget::eGroupBox: { 2781 HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary}; 2782 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION); 2783 break; 2784 } 2785 case Widget::eTextField: { 2786 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>(); 2787 DrawTextField(cgContext, macRect, params); 2788 break; 2789 } 2790 case Widget::eSearchField: { 2791 TextFieldParams params = aWidgetInfo.Params<TextFieldParams>(); 2792 DrawSearchField(cgContext, macRect, params); 2793 break; 2794 } 2795 case Widget::eProgressBar: { 2796 ProgressParams params = aWidgetInfo.Params<ProgressParams>(); 2797 DrawProgress(cgContext, macRect, params); 2798 break; 2799 } 2800 case Widget::eMeter: { 2801 MeterParams params = aWidgetInfo.Params<MeterParams>(); 2802 DrawMeter(cgContext, macRect, params); 2803 break; 2804 } 2805 case Widget::eTreeHeaderCell: { 2806 TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>(); 2807 DrawTreeHeaderCell(cgContext, macRect, params); 2808 break; 2809 } 2810 case Widget::eScale: { 2811 ScaleParams params = aWidgetInfo.Params<ScaleParams>(); 2812 DrawScale(cgContext, macRect, params); 2813 break; 2814 } 2815 case Widget::eMultilineTextField: { 2816 bool isFocused = aWidgetInfo.Params<bool>(); 2817 DrawMultilineTextField(cgContext, macRect, isFocused); 2818 break; 2819 } 2820 case Widget::eListBox: { 2821 // Fill the content with the control background color. 2822 CGContextSetFillColorWithColor(cgContext, [NSColor.controlBackgroundColor CGColor]); 2823 CGContextFillRect(cgContext, macRect); 2824 // Draw the frame using kCUIWidgetScrollViewFrame. This is what NSScrollView uses in 2825 // -[NSScrollView drawRect:] if you give it a borderType of NSBezelBorder. 2826 RenderWithCoreUI( 2827 macRect, cgContext, @{ 2828 @"widget" : @"kCUIWidgetScrollViewFrame", 2829 @"kCUIIsFlippedKey" : @YES, 2830 @"kCUIVariantMetal" : @NO, 2831 }); 2832 break; 2833 } 2834 case Widget::eActiveSourceListSelection: 2835 case Widget::eInactiveSourceListSelection: { 2836 bool isInActiveWindow = aWidgetInfo.Params<bool>(); 2837 bool isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection; 2838 DrawSourceListSelection(cgContext, macRect, isInActiveWindow, isActiveSelection); 2839 break; 2840 } 2841 case Widget::eTabPanel: { 2842 bool isInsideActiveWindow = aWidgetInfo.Params<bool>(); 2843 DrawTabPanel(cgContext, macRect, isInsideActiveWindow); 2844 break; 2845 } 2846 case Widget::eResizer: { 2847 bool isRTL = aWidgetInfo.Params<bool>(); 2848 DrawResizer(cgContext, macRect, isRTL); 2849 break; 2850 } 2851 } 2852 2853 // Reset the base CTM. 2854 CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity); 2855 2856 nativeDrawing.EndNativeDrawing(); 2857 } 2858 } 2859} 2860 2861bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget( 2862 mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, 2863 const mozilla::layers::StackingContextHelper& aSc, 2864 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame, 2865 StyleAppearance aAppearance, const nsRect& aRect) { 2866 // This list needs to stay consistent with the list in DrawWidgetBackground. 2867 // For every switch case in DrawWidgetBackground, there are three choices: 2868 // - If the case in DrawWidgetBackground draws nothing for the given widget 2869 // type, then don't list it here. We will hit the "default: return true;" 2870 // case. 2871 // - If the case in DrawWidgetBackground draws something simple for the given 2872 // widget type, imitate that drawing using WebRender commands. 2873 // - If the case in DrawWidgetBackground draws something complicated for the 2874 // given widget type, return false here. 2875 switch (aAppearance) { 2876 case StyleAppearance::Menuarrow: 2877 case StyleAppearance::Menuitem: 2878 case StyleAppearance::Checkmenuitem: 2879 case StyleAppearance::Menuseparator: 2880 case StyleAppearance::ButtonArrowUp: 2881 case StyleAppearance::ButtonArrowDown: 2882 case StyleAppearance::Checkbox: 2883 case StyleAppearance::Radio: 2884 case StyleAppearance::Button: 2885 case StyleAppearance::FocusOutline: 2886 case StyleAppearance::MozMacHelpButton: 2887 case StyleAppearance::MozMacDisclosureButtonOpen: 2888 case StyleAppearance::MozMacDisclosureButtonClosed: 2889 case StyleAppearance::Spinner: 2890 case StyleAppearance::SpinnerUpbutton: 2891 case StyleAppearance::SpinnerDownbutton: 2892 case StyleAppearance::Toolbarbutton: 2893 case StyleAppearance::Separator: 2894 case StyleAppearance::Toolbar: 2895 case StyleAppearance::MozWindowTitlebar: 2896 case StyleAppearance::Statusbar: 2897 case StyleAppearance::Menulist: 2898 case StyleAppearance::MenulistButton: 2899 case StyleAppearance::MozMenulistArrowButton: 2900 case StyleAppearance::Groupbox: 2901 case StyleAppearance::Textfield: 2902 case StyleAppearance::NumberInput: 2903 case StyleAppearance::Searchfield: 2904 case StyleAppearance::ProgressBar: 2905 case StyleAppearance::Meter: 2906 case StyleAppearance::Treeheadercell: 2907 case StyleAppearance::Treetwisty: 2908 case StyleAppearance::Treetwistyopen: 2909 case StyleAppearance::Treeitem: 2910 case StyleAppearance::Treeview: 2911 case StyleAppearance::Range: 2912 case StyleAppearance::ScrollbarthumbVertical: 2913 case StyleAppearance::ScrollbarthumbHorizontal: 2914 return false; 2915 2916 case StyleAppearance::Scrollcorner: 2917 case StyleAppearance::ScrollbartrackHorizontal: 2918 case StyleAppearance::ScrollbartrackVertical: { 2919 const ComputedStyle& style = *nsLayoutUtils::StyleForScrollbar(aFrame); 2920 ScrollbarParams params = ScrollbarDrawingMac::ComputeScrollbarParams( 2921 aFrame, style, aAppearance == StyleAppearance::ScrollbartrackHorizontal); 2922 if (params.overlay && !params.rolledOver) { 2923 // There is no scrollbar track, draw nothing and return true. 2924 return true; 2925 } 2926 // There is a scrollbar track and it needs to be drawn using fallback. 2927 return false; 2928 } 2929 2930 case StyleAppearance::Textarea: 2931 case StyleAppearance::Listbox: 2932 case StyleAppearance::Tab: 2933 case StyleAppearance::Tabpanels: 2934 case StyleAppearance::Resizer: 2935 return false; 2936 2937 default: 2938 return true; 2939 } 2940} 2941 2942LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin, 2943 nsIFrame* aFrame) { 2944 // Assuming aMargin was originally specified for a horizontal LTR context, 2945 // reinterpret the values as logical, and then map to physical coords 2946 // according to aFrame's actual writing mode. 2947 WritingMode wm = aFrame->GetWritingMode(); 2948 nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left) 2949 .GetPhysicalMargin(wm); 2950 return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left); 2951} 2952 2953static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5); 2954static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4); 2955static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19); 2956static const LayoutDeviceIntMargin kAquaSearchfieldBorderBigSur(5, 5, 4, 26); 2957 2958LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext, 2959 nsIFrame* aFrame, 2960 StyleAppearance aAppearance) { 2961 LayoutDeviceIntMargin result; 2962 2963 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 2964 2965 switch (aAppearance) { 2966 case StyleAppearance::Button: { 2967 if (IsButtonTypeMenu(aFrame)) { 2968 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame); 2969 } else { 2970 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame); 2971 } 2972 break; 2973 } 2974 2975 case StyleAppearance::Toolbarbutton: { 2976 result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame); 2977 break; 2978 } 2979 2980 case StyleAppearance::Checkbox: 2981 case StyleAppearance::Radio: { 2982 // nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight 2983 // assume a border width of 2px. 2984 result.SizeTo(2, 2, 2, 2); 2985 break; 2986 } 2987 2988 case StyleAppearance::Menulist: 2989 case StyleAppearance::MenulistButton: 2990 case StyleAppearance::MozMenulistArrowButton: 2991 result = DirectionAwareMargin(kAquaDropdownBorder, aFrame); 2992 break; 2993 2994 case StyleAppearance::Menuarrow: 2995 if (nsCocoaFeatures::OnBigSurOrLater()) { 2996 result.SizeTo(0, 0, 0, 28); 2997 } 2998 break; 2999 3000 case StyleAppearance::NumberInput: 3001 case StyleAppearance::Textfield: { 3002 SInt32 frameOutset = 0; 3003 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset); 3004 3005 SInt32 textPadding = 0; 3006 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding); 3007 3008 frameOutset += textPadding; 3009 3010 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset); 3011 break; 3012 } 3013 3014 case StyleAppearance::Textarea: 3015 result.SizeTo(1, 1, 1, 1); 3016 break; 3017 3018 case StyleAppearance::Searchfield: { 3019 auto border = nsCocoaFeatures::OnBigSurOrLater() ? kAquaSearchfieldBorderBigSur 3020 : kAquaSearchfieldBorder; 3021 result = DirectionAwareMargin(border, aFrame); 3022 break; 3023 } 3024 3025 case StyleAppearance::Listbox: { 3026 SInt32 frameOutset = 0; 3027 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset); 3028 result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset); 3029 break; 3030 } 3031 3032 case StyleAppearance::ScrollbartrackHorizontal: 3033 case StyleAppearance::ScrollbartrackVertical: { 3034 bool isHorizontal = (aAppearance == StyleAppearance::ScrollbartrackHorizontal); 3035 if (LookAndFeel::UseOverlayScrollbars()) { 3036 // Leave a bit of space at the start and the end on all OS X versions. 3037 if (isHorizontal) { 3038 result.left = 1; 3039 result.right = 1; 3040 } else { 3041 result.top = 1; 3042 result.bottom = 1; 3043 } 3044 } 3045 3046 break; 3047 } 3048 3049 case StyleAppearance::Statusbar: 3050 result.SizeTo(1, 0, 0, 0); 3051 break; 3052 3053 default: 3054 break; 3055 } 3056 3057 if (IsHiDPIContext(aContext)) { 3058 result = result + result; // doubled 3059 } 3060 3061 NS_OBJC_END_TRY_BLOCK_RETURN(result); 3062} 3063 3064// Return false here to indicate that CSS padding values should be used. There is 3065// no reason to make a distinction between padding and border values, just specify 3066// whatever values you want in GetWidgetBorder and only use this to return true 3067// if you want to override CSS padding values. 3068bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, 3069 StyleAppearance aAppearance, 3070 LayoutDeviceIntMargin* aResult) { 3071 // We don't want CSS padding being used for certain widgets. 3072 // See bug 381639 for an example of why. 3073 switch (aAppearance) { 3074 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize 3075 // and have a meaningful baseline, so they can't have 3076 // author-specified padding. 3077 case StyleAppearance::Checkbox: 3078 case StyleAppearance::Radio: 3079 aResult->SizeTo(0, 0, 0, 0); 3080 return true; 3081 3082 case StyleAppearance::Menuarrow: 3083 case StyleAppearance::Searchfield: 3084 if (nsCocoaFeatures::OnBigSurOrLater()) { 3085 return true; 3086 } 3087 break; 3088 3089 default: 3090 break; 3091 } 3092 return false; 3093} 3094 3095bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, 3096 StyleAppearance aAppearance, nsRect* aOverflowRect) { 3097 nsIntMargin overflow; 3098 switch (aAppearance) { 3099 case StyleAppearance::Button: 3100 case StyleAppearance::MozMacDisclosureButtonOpen: 3101 case StyleAppearance::MozMacDisclosureButtonClosed: 3102 case StyleAppearance::MozMacHelpButton: 3103 case StyleAppearance::Toolbarbutton: 3104 case StyleAppearance::NumberInput: 3105 case StyleAppearance::Textfield: 3106 case StyleAppearance::Textarea: 3107 case StyleAppearance::Searchfield: 3108 case StyleAppearance::Listbox: 3109 case StyleAppearance::Menulist: 3110 case StyleAppearance::MenulistButton: 3111 case StyleAppearance::MozMenulistArrowButton: 3112 case StyleAppearance::Checkbox: 3113 case StyleAppearance::Radio: 3114 case StyleAppearance::Tab: 3115 case StyleAppearance::FocusOutline: { 3116 overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth, 3117 kMaxFocusRingWidth); 3118 break; 3119 } 3120 case StyleAppearance::ProgressBar: { 3121 // Progress bars draw a 2 pixel white shadow under their progress indicators. 3122 overflow.bottom = 2; 3123 break; 3124 } 3125 case StyleAppearance::Meter: { 3126 // Meter bars overflow their boxes by about 2 pixels. 3127 overflow.SizeTo(2, 2, 2, 2); 3128 break; 3129 } 3130 default: 3131 break; 3132 } 3133 3134 if (IsHiDPIContext(aContext)) { 3135 // Double the number of device pixels. 3136 overflow += overflow; 3137 } 3138 3139 if (overflow != nsIntMargin()) { 3140 int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel(); 3141 aOverflowRect->Inflate(nsMargin( 3142 NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a), 3143 NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a))); 3144 return true; 3145 } 3146 3147 return false; 3148} 3149 3150auto nsNativeThemeCocoa::GetScrollbarSizes(nsPresContext* aPresContext, StyleScrollbarWidth aWidth, 3151 Overlay aOverlay) -> ScrollbarSizes { 3152 auto size = ScrollbarDrawingMac::GetScrollbarSize(aWidth, aOverlay == Overlay::Yes); 3153 if (IsHiDPIContext(aPresContext->DeviceContext())) { 3154 size *= 2; 3155 } 3156 return {int32_t(size), int32_t(size)}; 3157} 3158 3159NS_IMETHODIMP 3160nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, 3161 StyleAppearance aAppearance, LayoutDeviceIntSize* aResult, 3162 bool* aIsOverridable) { 3163 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 3164 3165 aResult->SizeTo(0, 0); 3166 *aIsOverridable = true; 3167 3168 switch (aAppearance) { 3169 case StyleAppearance::Button: { 3170 aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width, 3171 pushButtonSettings.naturalSizes[miniControlSize].height); 3172 break; 3173 } 3174 3175 case StyleAppearance::ButtonArrowUp: 3176 case StyleAppearance::ButtonArrowDown: { 3177 aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height); 3178 *aIsOverridable = false; 3179 break; 3180 } 3181 3182 case StyleAppearance::Menuarrow: { 3183 aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height); 3184 *aIsOverridable = false; 3185 break; 3186 } 3187 3188 case StyleAppearance::MozMacDisclosureButtonOpen: 3189 case StyleAppearance::MozMacDisclosureButtonClosed: { 3190 aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height); 3191 *aIsOverridable = false; 3192 break; 3193 } 3194 3195 case StyleAppearance::MozMacHelpButton: { 3196 aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height); 3197 *aIsOverridable = false; 3198 break; 3199 } 3200 3201 case StyleAppearance::Toolbarbutton: { 3202 aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]); 3203 break; 3204 } 3205 3206 case StyleAppearance::Spinner: 3207 case StyleAppearance::SpinnerUpbutton: 3208 case StyleAppearance::SpinnerDownbutton: { 3209 SInt32 buttonHeight = 0, buttonWidth = 0; 3210 if (aFrame->GetContent()->IsXULElement()) { 3211 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth); 3212 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight); 3213 } else { 3214 NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSControlSizeMini)]; 3215 buttonWidth = size.width; 3216 buttonHeight = size.height; 3217 if (aAppearance != StyleAppearance::Spinner) { 3218 // the buttons are half the height of the spinner 3219 buttonHeight /= 2; 3220 } 3221 } 3222 aResult->SizeTo(buttonWidth, buttonHeight); 3223 *aIsOverridable = true; 3224 break; 3225 } 3226 3227 case StyleAppearance::Menulist: 3228 case StyleAppearance::MenulistButton: { 3229 SInt32 popupHeight = 0; 3230 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight); 3231 aResult->SizeTo(0, popupHeight); 3232 break; 3233 } 3234 3235 case StyleAppearance::NumberInput: 3236 case StyleAppearance::Textfield: 3237 case StyleAppearance::Textarea: 3238 case StyleAppearance::Searchfield: { 3239 // at minimum, we should be tall enough for 9pt text. 3240 // I'm using hardcoded values here because the appearance manager 3241 // values for the frame size are incorrect. 3242 aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */); 3243 break; 3244 } 3245 3246 case StyleAppearance::MozWindowButtonBox: { 3247 NSSize size = WindowButtonsSize(aFrame); 3248 aResult->SizeTo(size.width, size.height); 3249 *aIsOverridable = false; 3250 break; 3251 } 3252 3253 case StyleAppearance::ProgressBar: { 3254 SInt32 barHeight = 0; 3255 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight); 3256 aResult->SizeTo(0, barHeight); 3257 break; 3258 } 3259 3260 case StyleAppearance::Separator: { 3261 aResult->SizeTo(1, 1); 3262 break; 3263 } 3264 3265 case StyleAppearance::Treetwisty: 3266 case StyleAppearance::Treetwistyopen: { 3267 SInt32 twistyHeight = 0, twistyWidth = 0; 3268 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth); 3269 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight); 3270 aResult->SizeTo(twistyWidth, twistyHeight); 3271 *aIsOverridable = false; 3272 break; 3273 } 3274 3275 case StyleAppearance::Treeheader: 3276 case StyleAppearance::Treeheadercell: { 3277 SInt32 headerHeight = 0; 3278 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight); 3279 aResult->SizeTo(0, headerHeight); 3280 break; 3281 } 3282 3283 case StyleAppearance::Tab: { 3284 aResult->SizeTo(0, tabHeights[miniControlSize]); 3285 break; 3286 } 3287 3288 case StyleAppearance::RangeThumb: { 3289 SInt32 width = 0; 3290 SInt32 height = 0; 3291 ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width); 3292 ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height); 3293 aResult->SizeTo(width, height); 3294 *aIsOverridable = false; 3295 break; 3296 } 3297 3298 case StyleAppearance::ScrollbarthumbHorizontal: 3299 case StyleAppearance::ScrollbarthumbVertical: 3300 case StyleAppearance::ScrollbarHorizontal: 3301 case StyleAppearance::ScrollbarVertical: 3302 case StyleAppearance::ScrollbartrackVertical: 3303 case StyleAppearance::ScrollbartrackHorizontal: 3304 case StyleAppearance::ScrollbarbuttonUp: 3305 case StyleAppearance::ScrollbarbuttonDown: 3306 case StyleAppearance::ScrollbarbuttonLeft: 3307 case StyleAppearance::ScrollbarbuttonRight: { 3308 *aIsOverridable = false; 3309 *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f); 3310 break; 3311 } 3312 3313 case StyleAppearance::MozMenulistArrowButton: 3314 *aResult = ScrollbarDrawingMac::GetMinimumWidgetSize(aAppearance, aFrame, 1.0f); 3315 break; 3316 3317 case StyleAppearance::Resizer: { 3318 HIThemeGrowBoxDrawInfo drawInfo; 3319 drawInfo.version = 0; 3320 drawInfo.state = kThemeStateActive; 3321 drawInfo.kind = kHIThemeGrowBoxKindNormal; 3322 drawInfo.direction = kThemeGrowRight | kThemeGrowDown; 3323 drawInfo.size = kHIThemeGrowBoxSizeNormal; 3324 HIPoint pnt = {0, 0}; 3325 HIRect bounds; 3326 HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds); 3327 aResult->SizeTo(bounds.size.width, bounds.size.height); 3328 *aIsOverridable = false; 3329 } 3330 default: 3331 break; 3332 } 3333 3334 if (IsHiDPIContext(aPresContext->DeviceContext())) { 3335 *aResult = *aResult * 2; 3336 } 3337 3338 return NS_OK; 3339 3340 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 3341} 3342 3343NS_IMETHODIMP 3344nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance, 3345 nsAtom* aAttribute, bool* aShouldRepaint, 3346 const nsAttrValue* aOldValue) { 3347 // Some widget types just never change state. 3348 switch (aAppearance) { 3349 case StyleAppearance::MozWindowTitlebar: 3350 case StyleAppearance::Toolbox: 3351 case StyleAppearance::Toolbar: 3352 case StyleAppearance::Statusbar: 3353 case StyleAppearance::Statusbarpanel: 3354 case StyleAppearance::Resizerpanel: 3355 case StyleAppearance::Tooltip: 3356 case StyleAppearance::Tabpanels: 3357 case StyleAppearance::Tabpanel: 3358 case StyleAppearance::Dialog: 3359 case StyleAppearance::Menupopup: 3360 case StyleAppearance::Groupbox: 3361 case StyleAppearance::Progresschunk: 3362 case StyleAppearance::ProgressBar: 3363 case StyleAppearance::Meter: 3364 case StyleAppearance::Meterchunk: 3365 case StyleAppearance::MozMacVibrantTitlebarLight: 3366 case StyleAppearance::MozMacVibrantTitlebarDark: 3367 *aShouldRepaint = false; 3368 return NS_OK; 3369 default: 3370 break; 3371 } 3372 3373 // XXXdwh Not sure what can really be done here. Can at least guess for 3374 // specific widgets that they're highly unlikely to have certain states. 3375 // For example, a toolbar doesn't care about any states. 3376 if (!aAttribute) { 3377 // Hover/focus/active changed. Always repaint. 3378 *aShouldRepaint = true; 3379 } else { 3380 // Check the attribute to see if it's relevant. 3381 // disabled, checked, dlgtype, default, etc. 3382 *aShouldRepaint = false; 3383 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked || 3384 aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected || 3385 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection || 3386 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default || 3387 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) 3388 *aShouldRepaint = true; 3389 } 3390 3391 return NS_OK; 3392} 3393 3394NS_IMETHODIMP 3395nsNativeThemeCocoa::ThemeChanged() { 3396 // This is unimplemented because we don't care if gecko changes its theme 3397 // and macOS system appearance changes are handled by 3398 // nsLookAndFeel::SystemWantsDarkTheme. 3399 return NS_OK; 3400} 3401 3402bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, 3403 StyleAppearance aAppearance) { 3404 // if this is a dropdown button in a combobox the answer is always no 3405 if (aAppearance == StyleAppearance::MozMenulistArrowButton) { 3406 nsIFrame* parentFrame = aFrame->GetParent(); 3407 if (parentFrame && parentFrame->IsComboboxControlFrame()) return false; 3408 } 3409 3410 switch (aAppearance) { 3411 // Combobox dropdowns don't support native theming in vertical mode. 3412 case StyleAppearance::Menulist: 3413 case StyleAppearance::MenulistButton: 3414 case StyleAppearance::MozMenulistArrowButton: 3415 case StyleAppearance::MenulistText: 3416 if (aFrame && aFrame->GetWritingMode().IsVertical()) { 3417 return false; 3418 } 3419 [[fallthrough]]; 3420 3421 case StyleAppearance::Listbox: 3422 case StyleAppearance::Dialog: 3423 case StyleAppearance::Window: 3424 case StyleAppearance::MozWindowButtonBox: 3425 case StyleAppearance::MozWindowTitlebar: 3426 case StyleAppearance::Checkmenuitem: 3427 case StyleAppearance::Menupopup: 3428 case StyleAppearance::Menuarrow: 3429 case StyleAppearance::Menuitem: 3430 case StyleAppearance::Menuseparator: 3431 case StyleAppearance::Tooltip: 3432 3433 case StyleAppearance::Checkbox: 3434 case StyleAppearance::CheckboxContainer: 3435 case StyleAppearance::Radio: 3436 case StyleAppearance::RadioContainer: 3437 case StyleAppearance::Groupbox: 3438 case StyleAppearance::MozMacHelpButton: 3439 case StyleAppearance::MozMacDisclosureButtonOpen: 3440 case StyleAppearance::MozMacDisclosureButtonClosed: 3441 case StyleAppearance::Button: 3442 case StyleAppearance::ButtonArrowUp: 3443 case StyleAppearance::ButtonArrowDown: 3444 case StyleAppearance::Toolbarbutton: 3445 case StyleAppearance::Spinner: 3446 case StyleAppearance::SpinnerUpbutton: 3447 case StyleAppearance::SpinnerDownbutton: 3448 case StyleAppearance::Toolbar: 3449 case StyleAppearance::Statusbar: 3450 case StyleAppearance::NumberInput: 3451 case StyleAppearance::Textfield: 3452 case StyleAppearance::Textarea: 3453 case StyleAppearance::Searchfield: 3454 case StyleAppearance::Toolbox: 3455 case StyleAppearance::ProgressBar: 3456 case StyleAppearance::Progresschunk: 3457 case StyleAppearance::Meter: 3458 case StyleAppearance::Meterchunk: 3459 case StyleAppearance::Separator: 3460 3461 case StyleAppearance::Tabpanels: 3462 case StyleAppearance::Tab: 3463 3464 case StyleAppearance::Treetwisty: 3465 case StyleAppearance::Treetwistyopen: 3466 case StyleAppearance::Treeview: 3467 case StyleAppearance::Treeheader: 3468 case StyleAppearance::Treeheadercell: 3469 case StyleAppearance::Treeheadersortarrow: 3470 case StyleAppearance::Treeitem: 3471 case StyleAppearance::Treeline: 3472 case StyleAppearance::MozMacSourceList: 3473 case StyleAppearance::MozMacSourceListSelection: 3474 case StyleAppearance::MozMacActiveSourceListSelection: 3475 3476 case StyleAppearance::Range: 3477 3478 case StyleAppearance::ScrollbarHorizontal: 3479 case StyleAppearance::ScrollbarVertical: 3480 case StyleAppearance::ScrollbarbuttonUp: 3481 case StyleAppearance::ScrollbarbuttonDown: 3482 case StyleAppearance::ScrollbarbuttonLeft: 3483 case StyleAppearance::ScrollbarbuttonRight: 3484 case StyleAppearance::ScrollbarthumbHorizontal: 3485 case StyleAppearance::ScrollbarthumbVertical: 3486 case StyleAppearance::ScrollbartrackVertical: 3487 case StyleAppearance::ScrollbartrackHorizontal: 3488 return !IsWidgetStyled(aPresContext, aFrame, aAppearance); 3489 3490 case StyleAppearance::Scrollcorner: 3491 return true; 3492 3493 case StyleAppearance::Resizer: { 3494 nsIFrame* parentFrame = aFrame->GetParent(); 3495 if (!parentFrame || !parentFrame->IsScrollFrame()) return true; 3496 3497 // Note that IsWidgetStyled is not called for resizers on Mac. This is 3498 // because for scrollable containers, the native resizer looks better 3499 // when (non-overlay) scrollbars are present even when the style is 3500 // overriden, and the custom transparent resizer looks better when 3501 // scrollbars are not present. 3502 nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame); 3503 return (!LookAndFeel::UseOverlayScrollbars() && scrollFrame && 3504 (!scrollFrame->GetScrollbarVisibility().isEmpty())); 3505 } 3506 3507 case StyleAppearance::FocusOutline: 3508 return true; 3509 3510 case StyleAppearance::MozMacVibrantTitlebarLight: 3511 case StyleAppearance::MozMacVibrantTitlebarDark: 3512 return true; 3513 default: 3514 break; 3515 } 3516 3517 return false; 3518} 3519 3520bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) { 3521 // flesh this out at some point 3522 switch (aAppearance) { 3523 case StyleAppearance::MozMenulistArrowButton: 3524 case StyleAppearance::Radio: 3525 case StyleAppearance::Checkbox: 3526 case StyleAppearance::ProgressBar: 3527 case StyleAppearance::Meter: 3528 case StyleAppearance::Range: 3529 case StyleAppearance::MozMacHelpButton: 3530 case StyleAppearance::MozMacDisclosureButtonOpen: 3531 case StyleAppearance::MozMacDisclosureButtonClosed: 3532 return false; 3533 default: 3534 break; 3535 } 3536 return true; 3537} 3538 3539bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) { 3540 switch (aAppearance) { 3541 case StyleAppearance::Textarea: 3542 case StyleAppearance::Textfield: 3543 case StyleAppearance::Searchfield: 3544 case StyleAppearance::NumberInput: 3545 case StyleAppearance::Menulist: 3546 case StyleAppearance::MenulistButton: 3547 case StyleAppearance::Button: 3548 case StyleAppearance::MozMacHelpButton: 3549 case StyleAppearance::MozMacDisclosureButtonOpen: 3550 case StyleAppearance::MozMacDisclosureButtonClosed: 3551 case StyleAppearance::Radio: 3552 case StyleAppearance::Range: 3553 case StyleAppearance::Checkbox: 3554 return true; 3555 default: 3556 return false; 3557 } 3558} 3559 3560bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; } 3561 3562bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) { 3563 switch (aAppearance) { 3564 case StyleAppearance::Dialog: 3565 case StyleAppearance::Groupbox: 3566 case StyleAppearance::Tabpanels: 3567 case StyleAppearance::ButtonArrowUp: 3568 case StyleAppearance::ButtonArrowDown: 3569 case StyleAppearance::Checkmenuitem: 3570 case StyleAppearance::Menupopup: 3571 case StyleAppearance::Menuarrow: 3572 case StyleAppearance::Menuitem: 3573 case StyleAppearance::Menuseparator: 3574 case StyleAppearance::Tooltip: 3575 case StyleAppearance::Spinner: 3576 case StyleAppearance::SpinnerUpbutton: 3577 case StyleAppearance::SpinnerDownbutton: 3578 case StyleAppearance::Separator: 3579 case StyleAppearance::Toolbox: 3580 case StyleAppearance::NumberInput: 3581 case StyleAppearance::Textfield: 3582 case StyleAppearance::Treeview: 3583 case StyleAppearance::Treeline: 3584 case StyleAppearance::Textarea: 3585 case StyleAppearance::Listbox: 3586 case StyleAppearance::Resizer: 3587 return false; 3588 default: 3589 return true; 3590 } 3591} 3592 3593nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget( 3594 nsIFrame* aFrame, StyleAppearance aAppearance) { 3595 switch (aAppearance) { 3596 case StyleAppearance::MozWindowTitlebar: 3597 return eThemeGeometryTypeTitlebar; 3598 case StyleAppearance::Toolbar: 3599 return eThemeGeometryTypeToolbar; 3600 case StyleAppearance::Toolbox: 3601 return eThemeGeometryTypeToolbox; 3602 case StyleAppearance::MozWindowButtonBox: 3603 return eThemeGeometryTypeWindowButtons; 3604 case StyleAppearance::MozMacVibrantTitlebarLight: 3605 return eThemeGeometryTypeVibrantTitlebarLight; 3606 case StyleAppearance::MozMacVibrantTitlebarDark: 3607 return eThemeGeometryTypeVibrantTitlebarDark; 3608 case StyleAppearance::Tooltip: 3609 return eThemeGeometryTypeTooltip; 3610 case StyleAppearance::Menupopup: 3611 return eThemeGeometryTypeMenu; 3612 case StyleAppearance::Menuitem: 3613 case StyleAppearance::Checkmenuitem: { 3614 EventStates eventState = GetContentState(aFrame, aAppearance); 3615 bool isDisabled = IsDisabled(aFrame, eventState); 3616 bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); 3617 return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu; 3618 } 3619 case StyleAppearance::MozMacSourceList: 3620 return eThemeGeometryTypeSourceList; 3621 case StyleAppearance::MozMacSourceListSelection: 3622 return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection 3623 : eThemeGeometryTypeUnknown; 3624 case StyleAppearance::MozMacActiveSourceListSelection: 3625 return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection 3626 : eThemeGeometryTypeUnknown; 3627 default: 3628 return eThemeGeometryTypeUnknown; 3629 } 3630} 3631 3632nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, 3633 StyleAppearance aAppearance) { 3634 switch (aAppearance) { 3635 case StyleAppearance::Menupopup: 3636 case StyleAppearance::Tooltip: 3637 case StyleAppearance::Dialog: 3638 case StyleAppearance::Toolbar: 3639 return eTransparent; 3640 3641 case StyleAppearance::ScrollbarHorizontal: 3642 case StyleAppearance::ScrollbarVertical: 3643 case StyleAppearance::Scrollcorner: { 3644 // We don't use custom scrollbars when using overlay scrollbars. 3645 if (LookAndFeel::UseOverlayScrollbars()) { 3646 return eTransparent; 3647 } 3648 const nsStyleUI* ui = nsLayoutUtils::StyleForScrollbar(aFrame)->StyleUI(); 3649 if (!ui->mScrollbarColor.IsAuto() && 3650 ui->mScrollbarColor.AsColors().track.MaybeTransparent()) { 3651 return eTransparent; 3652 } 3653 return eOpaque; 3654 } 3655 3656 case StyleAppearance::Statusbar: 3657 // Knowing that scrollbars and statusbars are opaque improves 3658 // performance, because we create layers for them. 3659 return eOpaque; 3660 3661 default: 3662 return eUnknownTransparency; 3663 } 3664} 3665 3666already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() { 3667 static nsCOMPtr<nsITheme> inst; 3668 3669 if (!inst) { 3670 inst = new nsNativeThemeCocoa(); 3671 ClearOnShutdown(&inst); 3672 } 3673 3674 return do_AddRef(inst); 3675} 3676