2 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
23 #import "RenderThemeMac.h"
25 #import "BitmapImage.h"
26 #import "CSSValueKeywords.h"
27 #import "CSSValueList.h"
29 #import "CoreGraphicsSPI.h"
32 #import "ExceptionCodePlaceholder.h"
34 #import "FloatRoundedRect.h"
35 #import "FocusController.h"
37 #import "FrameSelection.h"
39 #import "GeometryUtilities.h"
40 #import "GraphicsContextCG.h"
41 #import "HTMLAttachmentElement.h"
42 #import "HTMLAudioElement.h"
43 #import "HTMLInputElement.h"
44 #import "HTMLMediaElement.h"
46 #import "HTMLPlugInImageElement.h"
49 #import "ImageBuffer.h"
50 #import "LocalCurrentGraphicsContext.h"
51 #import "LocalizedStrings.h"
52 #import "MediaControlElements.h"
53 #import "NSColorSPI.h"
54 #import "NSSharingServicePickerSPI.h"
57 #import "PathUtilities.h"
58 #import "RenderAttachment.h"
59 #import "RenderLayer.h"
60 #import "RenderMedia.h"
61 #import "RenderMediaControlElements.h"
62 #import "RenderMediaControls.h"
63 #import "RenderProgress.h"
64 #import "RenderSlider.h"
65 #import "RenderSnapshottedPlugIn.h"
66 #import "RenderView.h"
67 #import "SharedBuffer.h"
68 #import "StringTruncator.h"
69 #import "StyleResolver.h"
71 #import "TimeRanges.h"
72 #import "UTIUtilities.h"
73 #import "UserAgentScripts.h"
74 #import "UserAgentStyleSheets.h"
75 #import "WebCoreSystemInterface.h"
76 #import <wtf/MathExtras.h>
77 #import <wtf/RetainPtr.h>
78 #import <wtf/RetainPtr.h>
79 #import <wtf/StdLibExtras.h>
80 #import <wtf/text/StringBuilder.h>
81 #import <Carbon/Carbon.h>
82 #import <Cocoa/Cocoa.h>
85 #if ENABLE(METER_ELEMENT)
86 #import "RenderMeter.h"
87 #import "HTMLMeterElement.h"
90 #if defined(__LP64__) && __LP64__
91 #define HAVE_APPKIT_SERVICE_CONTROLS_SUPPORT 1
93 #define HAVE_APPKIT_SERVICE_CONTROLS_SUPPORT 0
96 #if ENABLE(SERVICE_CONTROLS) && HAVE(APPKIT_SERVICE_CONTROLS_SUPPORT)
98 #if USE(APPLE_INTERNAL_SDK)
99 #import <AppKit/AppKitDefines_Private.h>
100 #import <AppKit/NSServicesRolloverButtonCell.h>
102 #define APPKIT_PRIVATE_CLASS
103 @interface NSServicesRolloverButtonCell : NSButtonCell
107 @interface NSServicesRolloverButtonCell ()
108 + (NSServicesRolloverButtonCell *)serviceRolloverButtonCellForStyle:(NSSharingServicePickerStyle)style;
109 - (NSRect)rectForBounds:(NSRect)bounds preferredEdge:(NSRectEdge)preferredEdge;
112 #endif // ENABLE(SERVICE_CONTROLS)
114 // The methods in this file are specific to the Mac OS X platform.
116 // We estimate the animation rate of a Mac OS X progress bar is 33 fps.
117 // Hard code the value here because we haven't found API for it.
118 const double progressAnimationFrameRate = 0.033;
120 // Mac OS X progress bar animation seems to have 256 frames.
121 const double progressAnimationNumFrames = 256;
123 @interface WebCoreRenderThemeNotificationObserver : NSObject
125 WebCore::RenderTheme *_theme;
128 - (id)initWithTheme:(WebCore::RenderTheme *)theme;
129 - (void)systemColorsDidChange:(NSNotification *)notification;
133 @implementation WebCoreRenderThemeNotificationObserver
135 - (id)initWithTheme:(WebCore::RenderTheme *)theme
137 if (!(self = [super init]))
144 - (void)systemColorsDidChange:(NSNotification *)unusedNotification
146 ASSERT_UNUSED(unusedNotification, [[unusedNotification name] isEqualToString:NSSystemColorsDidChangeNotification]);
147 _theme->platformColorsDidChange();
152 @interface NSTextFieldCell (WKDetails)
153 - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame inView:(NSView *)controlView includeFocus:(BOOL)includeFocus;
157 @interface WebCoreTextFieldCell : NSTextFieldCell
158 - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame inView:(NSView *)controlView includeFocus:(BOOL)includeFocus;
161 @implementation WebCoreTextFieldCell
162 - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame inView:(NSView *)controlView includeFocus:(BOOL)includeFocus
164 // FIXME: This is a post-Lion-only workaround for <rdar://problem/11385461>. When that bug is resolved, we should remove this code.
165 CFMutableDictionaryRef coreUIDrawOptions = CFDictionaryCreateMutableCopy(NULL, 0, [super _coreUIDrawOptionsWithFrame:cellFrame inView:controlView includeFocus:includeFocus]);
166 CFDictionarySetValue(coreUIDrawOptions, @"borders only", kCFBooleanTrue);
167 return (CFDictionaryRef)[NSMakeCollectable(coreUIDrawOptions) autorelease];
171 @interface WebCoreRenderThemeBundle : NSObject
174 @implementation WebCoreRenderThemeBundle
177 @interface NSSearchFieldCell()
178 @property (getter=isCenteredLook) BOOL centeredLook;
183 using namespace HTMLNames;
199 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*)
201 static RenderTheme& rt = RenderThemeMac::create().leakRef();
205 Ref<RenderTheme> RenderThemeMac::create()
207 return adoptRef(*new RenderThemeMac);
210 RenderThemeMac::RenderThemeMac()
211 : m_isSliderThumbHorizontalPressed(false)
212 , m_isSliderThumbVerticalPressed(false)
213 , m_notificationObserver(adoptNS([[WebCoreRenderThemeNotificationObserver alloc] initWithTheme:this]))
215 [[NSNotificationCenter defaultCenter] addObserver:m_notificationObserver.get()
216 selector:@selector(systemColorsDidChange:)
217 name:NSSystemColorsDidChangeNotification
221 RenderThemeMac::~RenderThemeMac()
223 [[NSNotificationCenter defaultCenter] removeObserver:m_notificationObserver.get()];
226 NSView* RenderThemeMac::documentViewFor(const RenderObject& o) const
228 ControlStates states(extractControlStatesForRenderer(o));
229 return ThemeMac::ensuredView(&o.view().frameView(), states);
233 String RenderThemeMac::mediaControlsStyleSheet()
235 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
236 if (m_mediaControlsStyleSheet.isEmpty()) {
237 StringBuilder styleSheetBuilder;
238 styleSheetBuilder.append(String([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsApple" ofType:@"css"] encoding:NSUTF8StringEncoding error:nil]));
239 m_mediaControlsStyleSheet = styleSheetBuilder.toString();
241 return m_mediaControlsStyleSheet;
243 return emptyString();
247 String RenderThemeMac::mediaControlsScript()
249 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
250 if (m_mediaControlsScript.isEmpty()) {
251 StringBuilder scriptBuilder;
252 scriptBuilder.append(String([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsLocalizedStrings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]));
253 scriptBuilder.append(String([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsApple" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]));
254 m_mediaControlsScript = scriptBuilder.toString();
256 return m_mediaControlsScript;
258 return emptyString();
262 #endif // ENABLE(VIDEO)
265 #if ENABLE(SERVICE_CONTROLS)
266 String RenderThemeMac::imageControlsStyleSheet() const
268 return String(imageControlsMacUserAgentStyleSheet, sizeof(imageControlsMacUserAgentStyleSheet));
272 Color RenderThemeMac::platformActiveSelectionBackgroundColor() const
274 NSColor* color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
275 return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent]));
278 Color RenderThemeMac::platformInactiveSelectionBackgroundColor() const
280 NSColor* color = [[NSColor secondarySelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
281 return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent]));
284 Color RenderThemeMac::platformActiveListBoxSelectionBackgroundColor() const
286 NSColor* color = [[NSColor alternateSelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
287 return Color(static_cast<int>(255.0 * [color redComponent]), static_cast<int>(255.0 * [color greenComponent]), static_cast<int>(255.0 * [color blueComponent]));
290 Color RenderThemeMac::platformActiveListBoxSelectionForegroundColor() const
295 Color RenderThemeMac::platformInactiveListBoxSelectionForegroundColor() const
300 Color RenderThemeMac::platformFocusRingColor() const
302 if (usesTestModeFocusRingColor())
303 return oldAquaFocusRingColor();
305 return systemColor(CSSValueWebkitFocusRingColor);
308 Color RenderThemeMac::platformInactiveListBoxSelectionBackgroundColor() const
310 return platformInactiveSelectionBackgroundColor();
313 static FontWeight toFontWeight(NSInteger appKitFontWeight)
315 ASSERT(appKitFontWeight > 0 && appKitFontWeight < 15);
316 if (appKitFontWeight > 14)
317 appKitFontWeight = 14;
318 else if (appKitFontWeight < 1)
319 appKitFontWeight = 1;
321 static const FontWeight fontWeights[] = {
337 return fontWeights[appKitFontWeight - 1];
340 void RenderThemeMac::updateCachedSystemFontDescription(CSSValueID cssValueId, FontCascadeDescription& fontDescription) const
343 // System-font-ness can't be encapsulated by simply a font name. Instead, we must use a token
344 // which FontCache will look for.
345 // Make sure we keep this list of possible tokens in sync with FontCascade::primaryFontIsSystemFont()
346 AtomicString fontName;
347 switch (cssValueId) {
348 case CSSValueSmallCaption:
349 font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
352 font = [NSFont menuFontOfSize:[NSFont systemFontSize]];
353 fontName = AtomicString("-apple-menu", AtomicString::ConstructFromLiteral);
355 case CSSValueStatusBar:
356 font = [NSFont labelFontOfSize:[NSFont labelFontSize]];
357 fontName = AtomicString("-apple-status-bar", AtomicString::ConstructFromLiteral);
359 case CSSValueWebkitMiniControl:
360 #pragma clang diagnostic push
361 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
362 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]];
363 #pragma clang diagnostic pop
365 case CSSValueWebkitSmallControl:
366 #pragma clang diagnostic push
367 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
368 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
369 #pragma clang diagnostic pop
371 case CSSValueWebkitControl:
372 #pragma clang diagnostic push
373 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
374 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
375 #pragma clang diagnostic pop
378 font = [NSFont systemFontOfSize:[NSFont systemFontSize]];
384 if (fontName.isNull())
385 fontName = AtomicString("-apple-system", AtomicString::ConstructFromLiteral);
387 NSFontManager *fontManager = [NSFontManager sharedFontManager];
388 fontDescription.setIsAbsoluteSize(true);
389 fontDescription.setOneFamily(fontName);
390 fontDescription.setSpecifiedSize([font pointSize]);
391 fontDescription.setWeight(toFontWeight([fontManager weightOfFont:font]));
392 fontDescription.setIsItalic([fontManager traitsOfFont:font] & NSItalicFontMask);
395 static RGBA32 convertNSColorToColor(NSColor *color)
397 NSColor *colorInColorSpace = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
398 if (colorInColorSpace) {
399 static const double scaleFactor = nextafter(256.0, 0.0);
400 return makeRGB(static_cast<int>(scaleFactor * [colorInColorSpace redComponent]),
401 static_cast<int>(scaleFactor * [colorInColorSpace greenComponent]),
402 static_cast<int>(scaleFactor * [colorInColorSpace blueComponent]));
405 // This conversion above can fail if the NSColor in question is an NSPatternColor
406 // (as many system colors are). These colors are actually a repeating pattern
407 // not just a solid color. To work around this we simply draw a 1x1 image of
408 // the color and use that pixel's color. It might be better to use an average of
409 // the colors in the pattern instead.
410 NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
417 colorSpaceName:NSDeviceRGBColorSpace
421 [NSGraphicsContext saveGraphicsState];
422 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep]];
423 NSEraseRect(NSMakeRect(0, 0, 1, 1));
424 [color drawSwatchInRect:NSMakeRect(0, 0, 1, 1)];
425 [NSGraphicsContext restoreGraphicsState];
428 [offscreenRep getPixel:pixel atX:0 y:0];
430 [offscreenRep release];
432 return makeRGB(pixel[0], pixel[1], pixel[2]);
435 static RGBA32 menuBackgroundColor()
437 NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
444 colorSpaceName:NSDeviceRGBColorSpace
448 CGContextRef context = static_cast<CGContextRef>([[NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep] graphicsPort]);
449 CGRect rect = CGRectMake(0, 0, 1, 1);
450 HIThemeMenuDrawInfo drawInfo;
451 drawInfo.version = 0;
452 drawInfo.menuType = kThemeMenuTypePopUp;
453 HIThemeDrawMenuBackground(&rect, &drawInfo, context, kHIThemeOrientationInverted);
456 [offscreenRep getPixel:pixel atX:0 y:0];
458 [offscreenRep release];
460 return makeRGB(pixel[0], pixel[1], pixel[2]);
463 void RenderThemeMac::platformColorsDidChange()
465 m_systemColorCache.clear();
466 RenderTheme::platformColorsDidChange();
469 Color RenderThemeMac::systemColor(CSSValueID cssValueID) const
471 auto addResult = m_systemColorCache.add(cssValueID, Color());
472 if (!addResult.isNewEntry)
473 return addResult.iterator->value;
476 switch (cssValueID) {
477 case CSSValueActiveborder:
478 color = convertNSColorToColor([NSColor keyboardFocusIndicatorColor]);
480 case CSSValueActivebuttontext:
481 // There is no corresponding NSColor for this so we use a hard coded value.
482 color = Color::white;
484 case CSSValueActivecaption:
485 color = convertNSColorToColor([NSColor windowFrameTextColor]);
487 case CSSValueAppworkspace:
488 color = convertNSColorToColor([NSColor headerColor]);
490 case CSSValueBackground:
491 // Use theme independent default
493 case CSSValueButtonface:
494 // We use this value instead of NSColor's controlColor to avoid website incompatibilities.
495 // We may want to change this to use the NSColor in future.
498 case CSSValueButtonhighlight:
499 color = convertNSColorToColor([NSColor controlHighlightColor]);
501 case CSSValueButtonshadow:
502 color = convertNSColorToColor([NSColor controlShadowColor]);
504 case CSSValueButtontext:
505 color = convertNSColorToColor([NSColor controlTextColor]);
507 case CSSValueCaptiontext:
508 color = convertNSColorToColor([NSColor textColor]);
510 case CSSValueGraytext:
511 color = convertNSColorToColor([NSColor disabledControlTextColor]);
513 case CSSValueHighlight:
514 color = convertNSColorToColor([NSColor selectedTextBackgroundColor]);
516 case CSSValueHighlighttext:
517 color = convertNSColorToColor([NSColor selectedTextColor]);
519 case CSSValueInactiveborder:
520 color = convertNSColorToColor([NSColor controlBackgroundColor]);
522 case CSSValueInactivecaption:
523 color = convertNSColorToColor([NSColor controlBackgroundColor]);
525 case CSSValueInactivecaptiontext:
526 color = convertNSColorToColor([NSColor textColor]);
528 case CSSValueInfobackground:
529 // There is no corresponding NSColor for this so we use a hard coded value.
532 case CSSValueInfotext:
533 color = convertNSColorToColor([NSColor textColor]);
536 color = menuBackgroundColor();
538 case CSSValueMenutext:
539 color = convertNSColorToColor([NSColor selectedMenuItemTextColor]);
541 case CSSValueScrollbar:
542 color = convertNSColorToColor([NSColor scrollBarColor]);
545 color = convertNSColorToColor([NSColor textColor]);
547 case CSSValueThreeddarkshadow:
548 color = convertNSColorToColor([NSColor controlDarkShadowColor]);
550 case CSSValueThreedshadow:
551 color = convertNSColorToColor([NSColor shadowColor]);
553 case CSSValueThreedface:
554 // We use this value instead of NSColor's controlColor to avoid website incompatibilities.
555 // We may want to change this to use the NSColor in future.
558 case CSSValueThreedhighlight:
559 color = convertNSColorToColor([NSColor highlightColor]);
561 case CSSValueThreedlightshadow:
562 color = convertNSColorToColor([NSColor controlLightHighlightColor]);
564 case CSSValueWebkitFocusRingColor:
565 color = convertNSColorToColor([NSColor keyboardFocusIndicatorColor]);
568 color = convertNSColorToColor([NSColor windowBackgroundColor]);
570 case CSSValueWindowframe:
571 color = convertNSColorToColor([NSColor windowFrameColor]);
573 case CSSValueWindowtext:
574 color = convertNSColorToColor([NSColor windowFrameTextColor]);
576 case CSSValueAppleWirelessPlaybackTargetActive:
577 color = convertNSColorToColor([NSColor systemBlueColor]);
579 case CSSValueAppleSystemBlue:
580 color = convertNSColorToColor([NSColor systemBlueColor]);
582 case CSSValueAppleSystemBrown:
583 color = convertNSColorToColor([NSColor systemBrownColor]);
585 case CSSValueAppleSystemGray:
586 color = convertNSColorToColor([NSColor systemGrayColor]);
588 case CSSValueAppleSystemGreen:
589 color = convertNSColorToColor([NSColor systemGreenColor]);
591 case CSSValueAppleSystemOrange:
592 color = convertNSColorToColor([NSColor systemOrangeColor]);
594 case CSSValueAppleSystemPink:
595 color = convertNSColorToColor([NSColor systemPinkColor]);
597 case CSSValueAppleSystemPurple:
598 color = convertNSColorToColor([NSColor systemPurpleColor]);
600 case CSSValueAppleSystemRed:
601 color = convertNSColorToColor([NSColor systemRedColor]);
603 case CSSValueAppleSystemYellow:
604 color = convertNSColorToColor([NSColor systemYellowColor]);
610 if (!color.isValid())
611 color = RenderTheme::systemColor(cssValueID);
613 addResult.iterator->value = color;
615 return addResult.iterator->value;
618 bool RenderThemeMac::usesTestModeFocusRingColor() const
620 return WebCore::usesTestModeFocusRingColor();
623 bool RenderThemeMac::isControlStyled(const RenderStyle& style, const BorderData& border,
624 const FillLayer& background, const Color& backgroundColor) const
626 if (style.appearance() == TextFieldPart || style.appearance() == TextAreaPart || style.appearance() == ListboxPart)
627 return style.border() != border;
629 // FIXME: This is horrible, but there is not much else that can be done. Menu lists cannot draw properly when
630 // scaled. They can't really draw properly when transformed either. We can't detect the transform case at style
631 // adjustment time so that will just have to stay broken. We can however detect that we're zooming. If zooming
632 // is in effect we treat it like the control is styled.
633 if (style.appearance() == MenulistPart && style.effectiveZoom() != 1.0f)
636 return RenderTheme::isControlStyled(style, border, background, backgroundColor);
639 static FloatRect inflateRect(const FloatRect& rect, const IntSize& size, const int* margins, float zoomLevel)
641 // Only do the inflation if the available width/height are too small. Otherwise try to
642 // fit the glow/check space into the available box's width/height.
643 int widthDelta = rect.width() - (size.width() + margins[leftMargin] * zoomLevel + margins[rightMargin] * zoomLevel);
644 int heightDelta = rect.height() - (size.height() + margins[topMargin] * zoomLevel + margins[bottomMargin] * zoomLevel);
645 FloatRect result(rect);
646 if (widthDelta < 0) {
647 result.setX(result.x() - margins[leftMargin] * zoomLevel);
648 result.setWidth(result.width() - widthDelta);
650 if (heightDelta < 0) {
651 result.setY(result.y() - margins[topMargin] * zoomLevel);
652 result.setHeight(result.height() - heightDelta);
657 void RenderThemeMac::adjustRepaintRect(const RenderObject& renderer, FloatRect& rect)
659 ControlPart part = renderer.style().appearance();
666 case SquareButtonPart:
667 case DefaultButtonPart:
669 case InnerSpinButtonPart:
670 return RenderTheme::adjustRepaintRect(renderer, rect);
676 float zoomLevel = renderer.style().effectiveZoom();
678 if (part == MenulistPart) {
679 setPopupButtonCellState(renderer, IntSize(rect.size()));
680 IntSize size = popupButtonSizes()[[popupButton() controlSize]];
681 size.setHeight(size.height() * zoomLevel);
682 size.setWidth(rect.width());
683 rect = inflateRect(rect, size, popupButtonMargins(), zoomLevel);
687 static FloatPoint convertToPaintingPosition(const RenderBox& inputRenderer, const RenderBox& customButtonRenderer, const FloatPoint& customButtonLocalPosition,
688 const IntPoint& paintOffset)
690 IntPoint offsetFromInputRenderer = roundedIntPoint(customButtonRenderer.localToContainerPoint(customButtonRenderer.contentBoxRect().location(), &inputRenderer));
691 FloatPoint paintingPosition = customButtonLocalPosition;
692 paintingPosition.moveBy(-offsetFromInputRenderer);
693 paintingPosition.moveBy(paintOffset);
694 return paintingPosition;
697 void RenderThemeMac::updateCheckedState(NSCell* cell, const RenderObject& o)
699 bool oldIndeterminate = [cell state] == NSMixedState;
700 bool indeterminate = isIndeterminate(o);
701 bool checked = isChecked(o);
703 if (oldIndeterminate != indeterminate) {
704 [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];
708 bool oldChecked = [cell state] == NSOnState;
709 if (checked != oldChecked)
710 [cell setState:checked ? NSOnState : NSOffState];
713 void RenderThemeMac::updateEnabledState(NSCell* cell, const RenderObject& o)
715 bool oldEnabled = [cell isEnabled];
716 bool enabled = isEnabled(o);
717 if (enabled != oldEnabled)
718 [cell setEnabled:enabled];
721 void RenderThemeMac::updateFocusedState(NSCell* cell, const RenderObject& o)
723 bool oldFocused = [cell showsFirstResponder];
724 bool focused = isFocused(o) && o.style().outlineStyleIsAuto();
725 if (focused != oldFocused)
726 [cell setShowsFirstResponder:focused];
729 void RenderThemeMac::updatePressedState(NSCell* cell, const RenderObject& o)
731 bool oldPressed = [cell isHighlighted];
732 bool pressed = is<Element>(o.node()) && downcast<Element>(*o.node()).active();
733 if (pressed != oldPressed)
734 [cell setHighlighted:pressed];
737 bool RenderThemeMac::controlSupportsTints(const RenderObject& o) const
739 // An alternate way to implement this would be to get the appropriate cell object
740 // and call the private _needRedrawOnWindowChangedKeyState method. An advantage of
741 // that would be that we would match AppKit behavior more closely, but a disadvantage
742 // would be that we would rely on an AppKit SPI method.
747 // Checkboxes only have tint when checked.
748 if (o.style().appearance() == CheckboxPart)
751 // For now assume other controls have tint if enabled.
755 NSControlSize RenderThemeMac::controlSizeForFont(const RenderStyle& style) const
757 int fontSize = style.fontSize();
758 #pragma clang diagnostic push
759 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
761 return NSRegularControlSize;
763 return NSSmallControlSize;
764 return NSMiniControlSize;
765 #pragma clang diagnostic pop
768 NSControlSize RenderThemeMac::controlSizeForCell(NSCell*, const IntSize* sizes, const IntSize& minSize, float zoomLevel) const
770 #pragma clang diagnostic push
771 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
772 if (minSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomLevel)
773 && minSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomLevel))
774 return NSRegularControlSize;
776 if (minSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomLevel)
777 && minSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomLevel))
778 return NSSmallControlSize;
780 return NSMiniControlSize;
781 #pragma clang diagnostic pop
784 void RenderThemeMac::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize, float zoomLevel)
786 NSControlSize size = controlSizeForCell(cell, sizes, minSize, zoomLevel);
787 if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
788 [cell setControlSize:size];
791 IntSize RenderThemeMac::sizeForFont(const RenderStyle& style, const IntSize* sizes) const
793 if (style.effectiveZoom() != 1.0f) {
794 IntSize result = sizes[controlSizeForFont(style)];
795 return IntSize(result.width() * style.effectiveZoom(), result.height() * style.effectiveZoom());
797 return sizes[controlSizeForFont(style)];
800 IntSize RenderThemeMac::sizeForSystemFont(const RenderStyle& style, const IntSize* sizes) const
802 if (style.effectiveZoom() != 1.0f) {
803 IntSize result = sizes[controlSizeForSystemFont(style)];
804 return IntSize(result.width() * style.effectiveZoom(), result.height() * style.effectiveZoom());
806 return sizes[controlSizeForSystemFont(style)];
809 void RenderThemeMac::setSizeFromFont(RenderStyle& style, const IntSize* sizes) const
811 // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
812 IntSize size = sizeForFont(style, sizes);
813 if (style.width().isIntrinsicOrAuto() && size.width() > 0)
814 style.setWidth(Length(size.width(), Fixed));
815 if (style.height().isAuto() && size.height() > 0)
816 style.setHeight(Length(size.height(), Fixed));
819 void RenderThemeMac::setFontFromControlSize(StyleResolver&, RenderStyle& style, NSControlSize controlSize) const
821 FontCascadeDescription fontDescription;
822 fontDescription.setIsAbsoluteSize(true);
824 NSFont* font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSize]];
825 fontDescription.setOneFamily(AtomicString("-apple-system", AtomicString::ConstructFromLiteral));
826 fontDescription.setComputedSize([font pointSize] * style.effectiveZoom());
827 fontDescription.setSpecifiedSize([font pointSize] * style.effectiveZoom());
830 style.setLineHeight(RenderStyle::initialLineHeight());
832 if (style.setFontDescription(fontDescription))
833 style.fontCascade().update(0);
836 NSControlSize RenderThemeMac::controlSizeForSystemFont(const RenderStyle& style) const
838 #pragma clang diagnostic push
839 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
840 int fontSize = style.fontSize();
841 if (fontSize >= [NSFont systemFontSizeForControlSize:NSRegularControlSize])
842 return NSRegularControlSize;
843 if (fontSize >= [NSFont systemFontSizeForControlSize:NSSmallControlSize])
844 return NSSmallControlSize;
845 return NSMiniControlSize;
846 #pragma clang diagnostic pop
849 bool RenderThemeMac::paintTextField(const RenderObject& o, const PaintInfo& paintInfo, const FloatRect& r)
851 LocalCurrentGraphicsContext localContext(paintInfo.context());
853 // <rdar://problem/22896977> We adjust the paint rect here to account for how AppKit draws the text
854 // field cell slightly smaller than the rect we pass to drawWithFrame.
855 FloatRect adjustedPaintRect(r);
856 AffineTransform transform = paintInfo.context().getCTM();
857 if (transform.xScale() > 1 || transform.yScale() > 1) {
858 adjustedPaintRect.inflateX(1 / transform.xScale());
859 adjustedPaintRect.inflateY(1 / transform.yScale());
861 NSTextFieldCell *textField = this->textField();
863 GraphicsContextStateSaver stateSaver(paintInfo.context());
865 [textField setEnabled:(isEnabled(o) && !isReadOnlyControl(o))];
866 [textField drawWithFrame:NSRect(adjustedPaintRect) inView:documentViewFor(o)];
868 [textField setControlView:nil];
873 void RenderThemeMac::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const
877 bool RenderThemeMac::paintTextArea(const RenderObject& o, const PaintInfo& paintInfo, const FloatRect& r)
879 LocalCurrentGraphicsContext localContext(paintInfo.context());
880 wkDrawBezeledTextArea(r, isEnabled(o) && !isReadOnlyControl(o));
884 void RenderThemeMac::adjustTextAreaStyle(StyleResolver&, RenderStyle&, const Element*) const
888 const int* RenderThemeMac::popupButtonMargins() const
890 static const int margins[3][4] =
896 return margins[[popupButton() controlSize]];
899 const IntSize* RenderThemeMac::popupButtonSizes() const
901 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
905 const int* RenderThemeMac::popupButtonPadding(NSControlSize size, bool isRTL) const
907 static const int paddingLTR[3][4] =
913 static const int paddingRTL[3][4] =
919 return isRTL ? paddingRTL[size] : paddingLTR[size];
922 bool RenderThemeMac::paintMenuList(const RenderObject& renderer, const PaintInfo& paintInfo, const FloatRect& rect)
924 LocalCurrentGraphicsContext localContext(paintInfo.context());
925 setPopupButtonCellState(renderer, IntSize(rect.size()));
927 NSPopUpButtonCell* popupButton = this->popupButton();
929 float zoomLevel = renderer.style().effectiveZoom();
930 IntSize size = popupButtonSizes()[[popupButton controlSize]];
931 size.setHeight(size.height() * zoomLevel);
932 size.setWidth(rect.width());
934 // Now inflate it to account for the shadow.
935 FloatRect inflatedRect = rect;
936 if (rect.width() >= minimumMenuListSize(renderer.style()))
937 inflatedRect = inflateRect(rect, size, popupButtonMargins(), zoomLevel);
939 GraphicsContextStateSaver stateSaver(paintInfo.context());
941 if (zoomLevel != 1.0f) {
942 inflatedRect.setWidth(inflatedRect.width() / zoomLevel);
943 inflatedRect.setHeight(inflatedRect.height() / zoomLevel);
944 paintInfo.context().translate(inflatedRect.x(), inflatedRect.y());
945 paintInfo.context().scale(FloatSize(zoomLevel, zoomLevel));
946 paintInfo.context().translate(-inflatedRect.x(), -inflatedRect.y());
949 paintCellAndSetFocusedElementNeedsRepaintIfNecessary(popupButton, renderer, paintInfo, inflatedRect);
950 [popupButton setControlView:nil];
955 #if ENABLE(METER_ELEMENT)
957 IntSize RenderThemeMac::meterSizeForBounds(const RenderMeter& renderMeter, const IntRect& bounds) const
959 if (NoControlPart == renderMeter.style().appearance())
960 return bounds.size();
962 NSLevelIndicatorCell* cell = levelIndicatorFor(renderMeter);
963 // Makes enough room for cell's intrinsic size.
964 NSSize cellSize = [cell cellSizeForBounds:IntRect(IntPoint(), bounds.size())];
965 return IntSize(bounds.width() < cellSize.width ? cellSize.width : bounds.width(),
966 bounds.height() < cellSize.height ? cellSize.height : bounds.height());
969 bool RenderThemeMac::paintMeter(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
971 if (!is<RenderMeter>(renderObject))
974 LocalCurrentGraphicsContext localContext(paintInfo.context());
976 NSLevelIndicatorCell* cell = levelIndicatorFor(downcast<RenderMeter>(renderObject));
977 GraphicsContextStateSaver stateSaver(paintInfo.context());
979 [cell drawWithFrame:rect inView:documentViewFor(renderObject)];
980 [cell setControlView:nil];
984 bool RenderThemeMac::supportsMeter(ControlPart part) const
987 case RelevancyLevelIndicatorPart:
988 case DiscreteCapacityLevelIndicatorPart:
989 case RatingLevelIndicatorPart:
991 case ContinuousCapacityLevelIndicatorPart:
998 NSLevelIndicatorStyle RenderThemeMac::levelIndicatorStyleFor(ControlPart part) const
1001 case RelevancyLevelIndicatorPart:
1002 return NSRelevancyLevelIndicatorStyle;
1003 case DiscreteCapacityLevelIndicatorPart:
1004 return NSDiscreteCapacityLevelIndicatorStyle;
1005 case RatingLevelIndicatorPart:
1006 return NSRatingLevelIndicatorStyle;
1008 case ContinuousCapacityLevelIndicatorPart:
1010 return NSContinuousCapacityLevelIndicatorStyle;
1015 NSLevelIndicatorCell* RenderThemeMac::levelIndicatorFor(const RenderMeter& renderMeter) const
1017 const RenderStyle& style = renderMeter.style();
1018 ASSERT(style.appearance() != NoControlPart);
1020 if (!m_levelIndicator)
1021 m_levelIndicator = adoptNS([[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle]);
1022 NSLevelIndicatorCell* cell = m_levelIndicator.get();
1024 HTMLMeterElement* element = renderMeter.meterElement();
1025 double value = element->value();
1027 // Because NSLevelIndicatorCell does not support optimum-in-the-middle type coloring,
1028 // we explicitly control the color instead giving low and high value to NSLevelIndicatorCell as is.
1029 switch (element->gaugeRegion()) {
1030 case HTMLMeterElement::GaugeRegionOptimum:
1031 // Make meter the green
1032 [cell setWarningValue:value + 1];
1033 [cell setCriticalValue:value + 2];
1035 case HTMLMeterElement::GaugeRegionSuboptimal:
1036 // Make the meter yellow
1037 [cell setWarningValue:value - 1];
1038 [cell setCriticalValue:value + 1];
1040 case HTMLMeterElement::GaugeRegionEvenLessGood:
1041 // Make the meter red
1042 [cell setWarningValue:value - 2];
1043 [cell setCriticalValue:value - 1];
1047 [cell setLevelIndicatorStyle:levelIndicatorStyleFor(style.appearance())];
1048 [cell setBaseWritingDirection:style.isLeftToRightDirection() ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft];
1049 [cell setMinValue:element->min()];
1050 [cell setMaxValue:element->max()];
1051 RetainPtr<NSNumber> valueObject = [NSNumber numberWithDouble:value];
1052 [cell setObjectValue:valueObject.get()];
1059 const IntSize* RenderThemeMac::progressBarSizes() const
1061 static const IntSize sizes[3] = { IntSize(0, 20), IntSize(0, 12), IntSize(0, 12) };
1065 const int* RenderThemeMac::progressBarMargins(NSControlSize controlSize) const
1067 static const int margins[3][4] =
1073 return margins[controlSize];
1076 IntRect RenderThemeMac::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
1078 // Workaround until <rdar://problem/15855086> is fixed.
1079 int maxDimension = static_cast<int>(std::numeric_limits<ushort>::max());
1080 IntRect progressBarBounds(bounds.x(), bounds.y(), std::min(bounds.width(), maxDimension), std::min(bounds.height(), maxDimension));
1081 if (NoControlPart == renderObject.style().appearance())
1082 return progressBarBounds;
1084 float zoomLevel = renderObject.style().effectiveZoom();
1085 NSControlSize controlSize = controlSizeForFont(renderObject.style());
1086 IntSize size = progressBarSizes()[controlSize];
1087 size.setHeight(size.height() * zoomLevel);
1088 size.setWidth(progressBarBounds.width());
1090 // Now inflate it to account for the shadow.
1091 IntRect inflatedRect = progressBarBounds;
1092 if (progressBarBounds.height() <= minimumProgressBarHeight(renderObject.style()))
1093 inflatedRect = IntRect(inflateRect(inflatedRect, size, progressBarMargins(controlSize), zoomLevel));
1095 return inflatedRect;
1098 int RenderThemeMac::minimumProgressBarHeight(const RenderStyle& style) const
1100 return sizeForSystemFont(style, progressBarSizes()).height();
1103 double RenderThemeMac::animationRepeatIntervalForProgressBar(RenderProgress&) const
1105 return progressAnimationFrameRate;
1108 double RenderThemeMac::animationDurationForProgressBar(RenderProgress&) const
1110 return progressAnimationNumFrames * progressAnimationFrameRate;
1113 void RenderThemeMac::adjustProgressBarStyle(StyleResolver&, RenderStyle&, const Element*) const
1117 bool RenderThemeMac::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1119 if (!is<RenderProgress>(renderObject))
1122 IntRect inflatedRect = progressBarRectForBounds(renderObject, rect);
1123 NSControlSize controlSize = controlSizeForFont(renderObject.style());
1125 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1126 HIThemeTrackDrawInfo trackInfo;
1127 trackInfo.version = 0;
1128 #pragma clang diagnostic push
1129 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1130 if (controlSize == NSRegularControlSize)
1131 #pragma clang diagnostic pop
1132 trackInfo.kind = renderProgress.position() < 0 ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
1134 trackInfo.kind = renderProgress.position() < 0 ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
1136 float deviceScaleFactor = renderObject.document().deviceScaleFactor();
1137 trackInfo.bounds = IntRect(IntPoint(), inflatedRect.size());
1139 trackInfo.max = std::numeric_limits<SInt32>::max();
1140 trackInfo.value = lround(renderProgress.position() * nextafter(trackInfo.max, 0));
1141 trackInfo.trackInfo.progress.phase = lround(renderProgress.animationProgress() * nextafter(progressAnimationNumFrames, 0) * deviceScaleFactor);
1142 trackInfo.attributes = kThemeTrackHorizontal;
1143 trackInfo.enableState = isActive(renderObject) ? kThemeTrackActive : kThemeTrackInactive;
1144 trackInfo.reserved = 0;
1145 trackInfo.filler1 = 0;
1147 std::unique_ptr<ImageBuffer> imageBuffer = ImageBuffer::createCompatibleBuffer(inflatedRect.size(), deviceScaleFactor, ColorSpaceSRGB, paintInfo.context());
1151 ContextContainer cgContextContainer(imageBuffer->context());
1152 CGContextRef cgContext = cgContextContainer.context();
1153 HIThemeDrawTrack(&trackInfo, 0, cgContext, kHIThemeOrientationNormal);
1155 GraphicsContextStateSaver stateSaver(paintInfo.context());
1157 if (!renderProgress.style().isLeftToRightDirection()) {
1158 paintInfo.context().translate(2 * inflatedRect.x() + inflatedRect.width(), 0);
1159 paintInfo.context().scale(FloatSize(-1, 1));
1162 paintInfo.context().drawConsumingImageBuffer(WTFMove(imageBuffer), inflatedRect.location());
1166 const float baseFontSize = 11.0f;
1167 const float baseArrowHeight = 4.0f;
1168 const float baseArrowWidth = 5.0f;
1169 const float baseSpaceBetweenArrows = 2.0f;
1170 const int arrowPaddingBefore = 6;
1171 const int arrowPaddingAfter = 6;
1172 const int paddingBeforeSeparator = 4;
1173 const int baseBorderRadius = 5;
1174 const int styledPopupPaddingLeft = 8;
1175 const int styledPopupPaddingTop = 1;
1176 const int styledPopupPaddingBottom = 2;
1178 static void TopGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData)
1180 static const float dark[4] = { 1.0f, 1.0f, 1.0f, 0.4f };
1181 static const float light[4] = { 1.0f, 1.0f, 1.0f, 0.15f };
1182 float a = inData[0];
1184 for (i = 0; i < 4; i++)
1185 outData[i] = (1.0f - a) * dark[i] + a * light[i];
1188 static void BottomGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData)
1190 static const float dark[4] = { 1.0f, 1.0f, 1.0f, 0.0f };
1191 static const float light[4] = { 1.0f, 1.0f, 1.0f, 0.3f };
1192 float a = inData[0];
1194 for (i = 0; i < 4; i++)
1195 outData[i] = (1.0f - a) * dark[i] + a * light[i];
1198 static void MainGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData)
1200 static const float dark[4] = { 0.0f, 0.0f, 0.0f, 0.15f };
1201 static const float light[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
1202 float a = inData[0];
1204 for (i = 0; i < 4; i++)
1205 outData[i] = (1.0f - a) * dark[i] + a * light[i];
1208 static void TrackGradientInterpolate(void*, const CGFloat* inData, CGFloat* outData)
1210 static const float dark[4] = { 0.0f, 0.0f, 0.0f, 0.678f };
1211 static const float light[4] = { 0.0f, 0.0f, 0.0f, 0.13f };
1212 float a = inData[0];
1214 for (i = 0; i < 4; i++)
1215 outData[i] = (1.0f - a) * dark[i] + a * light[i];
1218 void RenderThemeMac::paintMenuListButtonGradients(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1223 ContextContainer cgContextContainer(paintInfo.context());
1224 CGContextRef context = cgContextContainer.context();
1226 GraphicsContextStateSaver stateSaver(paintInfo.context());
1228 FloatRoundedRect border = FloatRoundedRect(o.style().getRoundedBorderFor(r));
1229 int radius = border.radii().topLeft().width();
1231 CGColorSpaceRef cspace = sRGBColorSpaceRef();
1233 FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0f);
1234 struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL };
1235 RetainPtr<CGFunctionRef> topFunction = adoptCF(CGFunctionCreate(NULL, 1, NULL, 4, NULL, &topCallbacks));
1236 RetainPtr<CGShadingRef> topShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(topGradient.x(), topGradient.y()), CGPointMake(topGradient.x(), topGradient.maxY()), topFunction.get(), false, false));
1238 FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0f, r.width() - 2.0f * radius, r.height() / 2.0f);
1239 struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL };
1240 RetainPtr<CGFunctionRef> bottomFunction = adoptCF(CGFunctionCreate(NULL, 1, NULL, 4, NULL, &bottomCallbacks));
1241 RetainPtr<CGShadingRef> bottomShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bottomGradient.x(), bottomGradient.y()), CGPointMake(bottomGradient.x(), bottomGradient.maxY()), bottomFunction.get(), false, false));
1243 struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL };
1244 RetainPtr<CGFunctionRef> mainFunction = adoptCF(CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
1245 RetainPtr<CGShadingRef> mainShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x(), r.maxY()), mainFunction.get(), false, false));
1247 RetainPtr<CGShadingRef> leftShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(r.x(), r.y()), CGPointMake(r.x() + radius, r.y()), mainFunction.get(), false, false));
1249 RetainPtr<CGShadingRef> rightShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(r.maxX(), r.y()), CGPointMake(r.maxX() - radius, r.y()), mainFunction.get(), false, false));
1252 GraphicsContextStateSaver stateSaver(paintInfo.context());
1253 CGContextClipToRect(context, r);
1254 paintInfo.context().clipRoundedRect(border);
1255 context = cgContextContainer.context();
1256 CGContextDrawShading(context, mainShading.get());
1260 GraphicsContextStateSaver stateSaver(paintInfo.context());
1261 CGContextClipToRect(context, topGradient);
1262 paintInfo.context().clipRoundedRect(FloatRoundedRect(enclosingIntRect(topGradient), border.radii().topLeft(), border.radii().topRight(), IntSize(), IntSize()));
1263 context = cgContextContainer.context();
1264 CGContextDrawShading(context, topShading.get());
1267 if (!bottomGradient.isEmpty()) {
1268 GraphicsContextStateSaver stateSaver(paintInfo.context());
1269 CGContextClipToRect(context, bottomGradient);
1270 paintInfo.context().clipRoundedRect(FloatRoundedRect(enclosingIntRect(bottomGradient), IntSize(), IntSize(), border.radii().bottomLeft(), border.radii().bottomRight()));
1271 context = cgContextContainer.context();
1272 CGContextDrawShading(context, bottomShading.get());
1276 GraphicsContextStateSaver stateSaver(paintInfo.context());
1277 CGContextClipToRect(context, r);
1278 paintInfo.context().clipRoundedRect(border);
1279 context = cgContextContainer.context();
1280 CGContextDrawShading(context, leftShading.get());
1281 CGContextDrawShading(context, rightShading.get());
1285 bool RenderThemeMac::paintMenuListButtonDecorations(const RenderBox& renderer, const PaintInfo& paintInfo, const FloatRect& rect)
1287 bool isRTL = renderer.style().direction() == RTL;
1288 IntRect bounds = IntRect(rect.x() + renderer.style().borderLeftWidth(),
1289 rect.y() + renderer.style().borderTopWidth(),
1290 rect.width() - renderer.style().borderLeftWidth() - renderer.style().borderRightWidth(),
1291 rect.height() - renderer.style().borderTopWidth() - renderer.style().borderBottomWidth());
1292 // Draw the gradients to give the styled popup menu a button appearance
1293 paintMenuListButtonGradients(renderer, paintInfo, bounds);
1295 // Since we actually know the size of the control here, we restrict the font scale to make sure the arrows will fit vertically in the bounds
1296 float fontScale = std::min(renderer.style().fontSize() / baseFontSize, bounds.height() / (baseArrowHeight * 2 + baseSpaceBetweenArrows));
1297 float centerY = bounds.y() + bounds.height() / 2.0f;
1298 float arrowHeight = baseArrowHeight * fontScale;
1299 float arrowWidth = baseArrowWidth * fontScale;
1302 leftEdge = bounds.x() + arrowPaddingAfter * renderer.style().effectiveZoom();
1304 leftEdge = bounds.maxX() - arrowPaddingAfter * renderer.style().effectiveZoom() - arrowWidth;
1305 float spaceBetweenArrows = baseSpaceBetweenArrows * fontScale;
1307 if (bounds.width() < arrowWidth + arrowPaddingBefore * renderer.style().effectiveZoom())
1310 GraphicsContextStateSaver stateSaver(paintInfo.context());
1312 paintInfo.context().setFillColor(renderer.style().visitedDependentColor(CSSPropertyColor));
1313 paintInfo.context().setStrokeStyle(NoStroke);
1315 // Draw the top arrow
1316 Vector<FloatPoint> arrow1 = {
1317 { leftEdge, centerY - spaceBetweenArrows / 2.0f },
1318 { leftEdge + arrowWidth, centerY - spaceBetweenArrows / 2.0f },
1319 { leftEdge + arrowWidth / 2.0f, centerY - spaceBetweenArrows / 2.0f - arrowHeight }
1321 paintInfo.context().fillPath(Path::polygonPathFromPoints(arrow1));
1323 // Draw the bottom arrow
1324 Vector<FloatPoint> arrow2 = {
1325 { leftEdge, centerY + spaceBetweenArrows / 2.0f },
1326 { leftEdge + arrowWidth, centerY + spaceBetweenArrows / 2.0f },
1327 { leftEdge + arrowWidth / 2.0f, centerY + spaceBetweenArrows / 2.0f + arrowHeight }
1329 paintInfo.context().fillPath(Path::polygonPathFromPoints(arrow2));
1331 Color leftSeparatorColor(0, 0, 0, 40);
1332 Color rightSeparatorColor(255, 255, 255, 40);
1334 // FIXME: Should the separator thickness and space be scaled up by fontScale?
1335 int separatorSpace = 2; // Deliberately ignores zoom since it looks nicer if it stays thin.
1336 int leftEdgeOfSeparator;
1338 leftEdgeOfSeparator = static_cast<int>(roundf(leftEdge + arrowWidth + arrowPaddingBefore * renderer.style().effectiveZoom()));
1340 leftEdgeOfSeparator = static_cast<int>(roundf(leftEdge - arrowPaddingBefore * renderer.style().effectiveZoom()));
1342 // Draw the separator to the left of the arrows
1343 paintInfo.context().setStrokeThickness(1); // Deliberately ignores zoom since it looks nicer if it stays thin.
1344 paintInfo.context().setStrokeStyle(SolidStroke);
1345 paintInfo.context().setStrokeColor(leftSeparatorColor);
1346 paintInfo.context().drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()),
1347 IntPoint(leftEdgeOfSeparator, bounds.maxY()));
1349 paintInfo.context().setStrokeColor(rightSeparatorColor);
1350 paintInfo.context().drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()),
1351 IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.maxY()));
1355 static const IntSize* menuListButtonSizes()
1357 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
1361 void RenderThemeMac::adjustMenuListStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
1363 NSControlSize controlSize = controlSizeForFont(style);
1365 style.resetBorder();
1366 style.resetPadding();
1368 // Height is locked to auto.
1369 style.setHeight(Length(Auto));
1371 // White-space is locked to pre
1372 style.setWhiteSpace(PRE);
1374 // Set the foreground color to black or gray when we have the aqua look.
1375 // Cast to RGB32 is to work around a compiler bug.
1376 style.setColor(e && !e->isDisabledFormControl() ? static_cast<RGBA32>(Color::black) : Color::darkGray);
1378 // Set the button's vertical size.
1379 setSizeFromFont(style, menuListButtonSizes());
1381 // Our font is locked to the appropriate system font size for the control. To clarify, we first use the CSS-specified font to figure out
1382 // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
1383 // system font for the control size instead.
1384 setFontFromControlSize(styleResolver, style, controlSize);
1386 style.setBoxShadow(nullptr);
1389 LengthBox RenderThemeMac::popupInternalPaddingBox(const RenderStyle& style) const
1391 if (style.appearance() == MenulistPart) {
1392 const int* padding = popupButtonPadding(controlSizeForFont(style), style.direction() == RTL);
1393 return { static_cast<int>(padding[topPadding] * style.effectiveZoom()),
1394 static_cast<int>(padding[rightPadding] * style.effectiveZoom()),
1395 static_cast<int>(padding[bottomPadding] * style.effectiveZoom()),
1396 static_cast<int>(padding[leftPadding] * style.effectiveZoom()) };
1399 if (style.appearance() == MenulistButtonPart) {
1400 float arrowWidth = baseArrowWidth * (style.fontSize() / baseFontSize);
1401 float rightPadding = ceilf(arrowWidth + (arrowPaddingBefore + arrowPaddingAfter + paddingBeforeSeparator) * style.effectiveZoom());
1402 float leftPadding = styledPopupPaddingLeft * style.effectiveZoom();
1403 if (style.direction() == RTL)
1404 std::swap(rightPadding, leftPadding);
1405 return { static_cast<int>(styledPopupPaddingTop * style.effectiveZoom()),
1406 static_cast<int>(rightPadding),
1407 static_cast<int>(styledPopupPaddingBottom * style.effectiveZoom()),
1408 static_cast<int>(leftPadding) };
1411 return { 0, 0, 0, 0 };
1414 PopupMenuStyle::PopupMenuSize RenderThemeMac::popupMenuSize(const RenderStyle& style, IntRect& rect) const
1416 NSPopUpButtonCell* popupButton = this->popupButton();
1417 NSControlSize size = controlSizeForCell(popupButton, popupButtonSizes(), rect.size(), style.effectiveZoom());
1419 #pragma clang diagnostic push
1420 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1421 case NSRegularControlSize:
1422 return PopupMenuStyle::PopupMenuSizeNormal;
1423 case NSSmallControlSize:
1424 return PopupMenuStyle::PopupMenuSizeSmall;
1425 case NSMiniControlSize:
1426 return PopupMenuStyle::PopupMenuSizeMini;
1427 #pragma clang diagnostic pop
1429 return PopupMenuStyle::PopupMenuSizeNormal;
1433 void RenderThemeMac::adjustMenuListButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1435 float fontScale = style.fontSize() / baseFontSize;
1437 style.resetPadding();
1438 style.setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up?
1440 const int minHeight = 15;
1441 style.setMinHeight(Length(minHeight, Fixed));
1443 style.setLineHeight(RenderStyle::initialLineHeight());
1446 void RenderThemeMac::setPopupButtonCellState(const RenderObject& o, const IntSize& buttonSize)
1448 NSPopUpButtonCell* popupButton = this->popupButton();
1450 // Set the control size based off the rectangle we're painting into.
1451 setControlSize(popupButton, popupButtonSizes(), buttonSize, o.style().effectiveZoom());
1453 popupButton.userInterfaceLayoutDirection = o.style().direction() == LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft;
1455 // Update the various states we respond to.
1456 updateCheckedState(popupButton, o);
1457 updateEnabledState(popupButton, o);
1458 updatePressedState(popupButton, o);
1461 void RenderThemeMac::paintCellAndSetFocusedElementNeedsRepaintIfNecessary(NSCell* cell, const RenderObject& renderer, const PaintInfo& paintInfo, const FloatRect& rect)
1463 Page* page = renderer.document().page();
1464 bool shouldDrawFocusRing = isFocused(renderer) && renderer.style().outlineStyleIsAuto();
1465 bool shouldUseImageBuffer = renderer.style().effectiveZoom() != 1 || page->pageScaleFactor() != 1;
1466 bool shouldDrawCell = true;
1467 if (ThemeMac::drawCellOrFocusRingWithViewIntoContext(cell, paintInfo.context(), rect, documentViewFor(renderer), shouldDrawCell, shouldDrawFocusRing, shouldUseImageBuffer, page->deviceScaleFactor()))
1468 page->focusController().setFocusedElementNeedsRepaint();
1471 const IntSize* RenderThemeMac::menuListSizes() const
1473 static const IntSize sizes[3] = { IntSize(9, 0), IntSize(5, 0), IntSize(0, 0) };
1477 int RenderThemeMac::minimumMenuListSize(const RenderStyle& style) const
1479 return sizeForSystemFont(style, menuListSizes()).width();
1482 const int trackWidth = 5;
1483 const int trackRadius = 2;
1485 void RenderThemeMac::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const
1487 style.setBoxShadow(nullptr);
1490 bool RenderThemeMac::paintSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1493 float zoomLevel = o.style().effectiveZoom();
1494 float zoomedTrackWidth = trackWidth * zoomLevel;
1496 if (o.style().appearance() == SliderHorizontalPart || o.style().appearance() == MediaSliderPart) {
1497 bounds.setHeight(zoomedTrackWidth);
1498 bounds.setY(r.y() + r.height() / 2 - zoomedTrackWidth / 2);
1499 } else if (o.style().appearance() == SliderVerticalPart) {
1500 bounds.setWidth(zoomedTrackWidth);
1501 bounds.setX(r.x() + r.width() / 2 - zoomedTrackWidth / 2);
1504 LocalCurrentGraphicsContext localContext(paintInfo.context());
1505 CGContextRef context = localContext.cgContext();
1506 CGColorSpaceRef cspace = sRGBColorSpaceRef();
1508 #if ENABLE(DATALIST_ELEMENT)
1509 paintSliderTicks(o, paintInfo, r);
1512 GraphicsContextStateSaver stateSaver(paintInfo.context());
1513 CGContextClipToRect(context, bounds);
1515 struct CGFunctionCallbacks mainCallbacks = { 0, TrackGradientInterpolate, NULL };
1516 RetainPtr<CGFunctionRef> mainFunction = adoptCF(CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
1517 RetainPtr<CGShadingRef> mainShading;
1518 if (o.style().appearance() == SliderVerticalPart)
1519 mainShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.maxY()), CGPointMake(bounds.maxX(), bounds.maxY()), mainFunction.get(), false, false));
1521 mainShading = adoptCF(CGShadingCreateAxial(cspace, CGPointMake(bounds.x(), bounds.y()), CGPointMake(bounds.x(), bounds.maxY()), mainFunction.get(), false, false));
1523 IntSize radius(trackRadius, trackRadius);
1524 paintInfo.context().clipRoundedRect(FloatRoundedRect(bounds, radius, radius, radius, radius));
1525 context = localContext.cgContext();
1526 CGContextDrawShading(context, mainShading.get());
1531 void RenderThemeMac::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const
1533 RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
1534 style.setBoxShadow(nullptr);
1537 const float verticalSliderHeightPadding = 0.1f;
1539 bool RenderThemeMac::paintSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1541 NSSliderCell* sliderThumbCell = o.style().appearance() == SliderThumbVerticalPart
1542 ? sliderThumbVertical()
1543 : sliderThumbHorizontal();
1545 LocalCurrentGraphicsContext localContext(paintInfo.context());
1547 // Update the various states we respond to.
1548 updateEnabledState(sliderThumbCell, o);
1549 Element* focusDelegate = is<Element>(o.node()) ? downcast<Element>(*o.node()).focusDelegate() : nullptr;
1551 updateFocusedState(sliderThumbCell, *focusDelegate->renderer());
1553 // Update the pressed state using the NSCell tracking methods, since that's how NSSliderCell keeps track of it.
1555 if (o.style().appearance() == SliderThumbVerticalPart)
1556 oldPressed = m_isSliderThumbVerticalPressed;
1558 oldPressed = m_isSliderThumbHorizontalPressed;
1560 bool pressed = isPressed(o);
1562 if (o.style().appearance() == SliderThumbVerticalPart)
1563 m_isSliderThumbVerticalPressed = pressed;
1565 m_isSliderThumbHorizontalPressed = pressed;
1567 NSView *view = documentViewFor(o);
1569 if (pressed != oldPressed) {
1571 [sliderThumbCell startTrackingAt:NSPoint() inView:view];
1573 [sliderThumbCell stopTracking:NSPoint() at:NSPoint() inView:view mouseIsUp:YES];
1576 FloatRect bounds = r;
1577 // Make the height of the vertical slider slightly larger so NSSliderCell will draw a vertical slider.
1578 if (o.style().appearance() == SliderThumbVerticalPart)
1579 bounds.setHeight(bounds.height() + verticalSliderHeightPadding * o.style().effectiveZoom());
1581 GraphicsContextStateSaver stateSaver(paintInfo.context());
1582 float zoomLevel = o.style().effectiveZoom();
1584 FloatRect unzoomedRect = bounds;
1585 if (zoomLevel != 1.0f) {
1586 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
1587 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
1588 paintInfo.context().translate(unzoomedRect.x(), unzoomedRect.y());
1589 paintInfo.context().scale(FloatSize(zoomLevel, zoomLevel));
1590 paintInfo.context().translate(-unzoomedRect.x(), -unzoomedRect.y());
1593 bool shouldDrawCell = true;
1594 bool shouldDrawFocusRing = false;
1595 float deviceScaleFactor = o.document().page()->deviceScaleFactor();
1596 bool shouldUseImageBuffer = deviceScaleFactor != 1 || zoomLevel != 1;
1597 ThemeMac::drawCellOrFocusRingWithViewIntoContext(sliderThumbCell, paintInfo.context(), unzoomedRect, view, shouldDrawCell, shouldDrawFocusRing, shouldUseImageBuffer, deviceScaleFactor);
1598 [sliderThumbCell setControlView:nil];
1603 bool RenderThemeMac::paintSearchField(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1605 LocalCurrentGraphicsContext localContext(paintInfo.context());
1606 NSSearchFieldCell* search = this->search();
1608 setSearchCellState(o, r);
1610 GraphicsContextStateSaver stateSaver(paintInfo.context());
1612 float zoomLevel = o.style().effectiveZoom();
1614 IntRect unzoomedRect = r;
1616 if (zoomLevel != 1.0f) {
1617 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
1618 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
1619 paintInfo.context().translate(unzoomedRect.x(), unzoomedRect.y());
1620 paintInfo.context().scale(FloatSize(zoomLevel, zoomLevel));
1621 paintInfo.context().translate(-unzoomedRect.x(), -unzoomedRect.y());
1624 // Set the search button to nil before drawing. Then reset it so we can draw it later.
1625 [search setSearchButtonCell:nil];
1627 paintCellAndSetFocusedElementNeedsRepaintIfNecessary(search, o, paintInfo, unzoomedRect);
1628 [search setControlView:nil];
1629 [search resetSearchButtonCell];
1634 void RenderThemeMac::setSearchCellState(const RenderObject& o, const IntRect&)
1636 NSSearchFieldCell* search = this->search();
1638 [search setPlaceholderString:@""];
1639 [search setControlSize:controlSizeForFont(o.style())];
1641 // Update the various states we respond to.
1642 updateEnabledState(search, o);
1643 updateFocusedState(search, o);
1646 const IntSize* RenderThemeMac::searchFieldSizes() const
1648 static const IntSize sizes[3] = { IntSize(0, 22), IntSize(0, 19), IntSize(0, 17) };
1652 void RenderThemeMac::setSearchFieldSize(RenderStyle& style) const
1654 // If the width and height are both specified, then we have nothing to do.
1655 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
1658 // Use the font size to determine the intrinsic width of the control.
1659 setSizeFromFont(style, searchFieldSizes());
1662 void RenderThemeMac::adjustSearchFieldStyle(StyleResolver& styleResolver, RenderStyle& style, const Element*) const
1665 style.resetBorder();
1666 const short borderWidth = 2 * style.effectiveZoom();
1667 style.setBorderLeftWidth(borderWidth);
1668 style.setBorderLeftStyle(INSET);
1669 style.setBorderRightWidth(borderWidth);
1670 style.setBorderRightStyle(INSET);
1671 style.setBorderBottomWidth(borderWidth);
1672 style.setBorderBottomStyle(INSET);
1673 style.setBorderTopWidth(borderWidth);
1674 style.setBorderTopStyle(INSET);
1677 style.setHeight(Length(Auto));
1678 setSearchFieldSize(style);
1680 // Override padding size to match AppKit text positioning.
1681 const int padding = 1 * style.effectiveZoom();
1682 style.setPaddingLeft(Length(padding, Fixed));
1683 style.setPaddingRight(Length(padding, Fixed));
1684 style.setPaddingTop(Length(padding, Fixed));
1685 style.setPaddingBottom(Length(padding, Fixed));
1687 NSControlSize controlSize = controlSizeForFont(style);
1688 setFontFromControlSize(styleResolver, style, controlSize);
1690 style.setBoxShadow(nullptr);
1693 bool RenderThemeMac::paintSearchFieldCancelButton(const RenderBox& box, const PaintInfo& paintInfo, const IntRect& r)
1695 auto adjustedCancelButtonRect = [this, &box] (const FloatRect& localBoundsForCancelButton) -> FloatRect
1697 IntSize cancelButtonSizeBasedOnFontSize = sizeForSystemFont(box.style(), cancelButtonSizes());
1698 FloatSize diff = localBoundsForCancelButton.size() - FloatSize(cancelButtonSizeBasedOnFontSize);
1699 if (!diff.width() && !diff.height())
1700 return localBoundsForCancelButton;
1701 // Vertically centered and right aligned.
1702 FloatRect adjustedLocalBoundsForCancelButton = localBoundsForCancelButton;
1703 adjustedLocalBoundsForCancelButton.move(diff.width(), floorToDevicePixel(diff.height() / 2, box.document().deviceScaleFactor()));
1704 adjustedLocalBoundsForCancelButton.setSize(cancelButtonSizeBasedOnFontSize);
1705 return adjustedLocalBoundsForCancelButton;
1710 Element* input = box.element()->shadowHost();
1712 input = box.element();
1714 if (!is<RenderBox>(input->renderer()))
1717 const RenderBox& inputBox = downcast<RenderBox>(*input->renderer());
1718 LocalCurrentGraphicsContext localContext(paintInfo.context());
1719 setSearchCellState(inputBox, r);
1721 NSSearchFieldCell* search = this->search();
1723 if (!input->isDisabledFormControl() && (is<HTMLTextFormControlElement>(*input) && !downcast<HTMLTextFormControlElement>(*input).isReadOnly()))
1724 updatePressedState([search cancelButtonCell], box);
1725 else if ([[search cancelButtonCell] isHighlighted])
1726 [[search cancelButtonCell] setHighlighted:NO];
1728 GraphicsContextStateSaver stateSaver(paintInfo.context());
1730 float zoomLevel = box.style().effectiveZoom();
1732 FloatRect localBounds = adjustedCancelButtonRect([search cancelButtonRectForBounds:NSRect(snappedIntRect(inputBox.contentBoxRect()))]);
1733 // Adjust position based on the content direction.
1734 float adjustedXPosition;
1735 if (box.style().direction() == RTL)
1736 adjustedXPosition = inputBox.contentBoxRect().x();
1738 adjustedXPosition = inputBox.contentBoxRect().maxX() - localBounds.size().width();
1740 localBounds.setX(adjustedXPosition);
1741 FloatPoint paintingPos = convertToPaintingPosition(inputBox, box, localBounds.location(), r.location());
1743 FloatRect unzoomedRect(paintingPos, localBounds.size());
1744 if (zoomLevel != 1.0f) {
1745 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
1746 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
1747 paintInfo.context().translate(unzoomedRect.x(), unzoomedRect.y());
1748 paintInfo.context().scale(FloatSize(zoomLevel, zoomLevel));
1749 paintInfo.context().translate(-unzoomedRect.x(), -unzoomedRect.y());
1751 [[search cancelButtonCell] drawWithFrame:unzoomedRect inView:documentViewFor(box)];
1752 [[search cancelButtonCell] setControlView:nil];
1756 const IntSize* RenderThemeMac::cancelButtonSizes() const
1758 static const IntSize sizes[3] = { IntSize(22, 22), IntSize(19, 19), IntSize(15, 15) };
1762 void RenderThemeMac::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1764 IntSize size = sizeForSystemFont(style, cancelButtonSizes());
1765 style.setWidth(Length(size.width(), Fixed));
1766 style.setHeight(Length(size.height(), Fixed));
1767 style.setBoxShadow(nullptr);
1770 const int resultsArrowWidth = 5;
1771 const IntSize* RenderThemeMac::resultsButtonSizes() const
1773 static const IntSize sizes[3] = { IntSize(19, 22), IntSize(17, 19), IntSize(17, 15) };
1777 const int emptyResultsOffset = 9;
1778 void RenderThemeMac::adjustSearchFieldDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
1780 IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1781 style.setWidth(Length(size.width() - emptyResultsOffset, Fixed));
1782 style.setHeight(Length(size.height(), Fixed));
1783 style.setBoxShadow(nullptr);
1786 bool RenderThemeMac::paintSearchFieldDecorationPart(const RenderObject&, const PaintInfo&, const IntRect&)
1791 void RenderThemeMac::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
1793 IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1794 style.setWidth(Length(size.width(), Fixed));
1795 style.setHeight(Length(size.height(), Fixed));
1796 style.setBoxShadow(nullptr);
1799 bool RenderThemeMac::paintSearchFieldResultsDecorationPart(const RenderBox& box, const PaintInfo& paintInfo, const IntRect& r)
1803 Element* input = box.element()->shadowHost();
1805 input = box.element();
1806 if (!is<RenderBox>(input->renderer()))
1809 const RenderBox& inputBox = downcast<RenderBox>(*input->renderer());
1810 LocalCurrentGraphicsContext localContext(paintInfo.context());
1811 setSearchCellState(inputBox, r);
1813 NSSearchFieldCell* search = this->search();
1815 if ([search searchMenuTemplate] != nil)
1816 [search setSearchMenuTemplate:nil];
1818 FloatRect localBounds = [search searchButtonRectForBounds:NSRect(snappedIntRect(inputBox.borderBoxRect()))];
1819 FloatPoint paintingPos = convertToPaintingPosition(inputBox, box, localBounds.location(), r.location());
1820 localBounds.setLocation(paintingPos);
1822 [[search searchButtonCell] drawWithFrame:localBounds inView:documentViewFor(box)];
1823 [[search searchButtonCell] setControlView:nil];
1827 void RenderThemeMac::adjustSearchFieldResultsButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1829 IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1830 style.setWidth(Length(size.width() + resultsArrowWidth, Fixed));
1831 style.setHeight(Length(size.height(), Fixed));
1832 style.setBoxShadow(nullptr);
1835 bool RenderThemeMac::paintSearchFieldResultsButton(const RenderBox& box, const PaintInfo& paintInfo, const IntRect& r)
1837 auto adjustedResultButtonRect = [this, &box] (const FloatRect& localBounds) -> FloatRect
1839 IntSize buttonSize = sizeForSystemFont(box.style(), resultsButtonSizes());
1840 buttonSize.expand(resultsArrowWidth, 0);
1841 FloatSize diff = localBounds.size() - FloatSize(buttonSize);
1844 // Vertically centered and left aligned.
1845 FloatRect adjustedLocalBounds = localBounds;
1846 adjustedLocalBounds.move(0, floorToDevicePixel(diff.height() / 2, box.document().deviceScaleFactor()));
1847 adjustedLocalBounds.setSize(buttonSize);
1848 return adjustedLocalBounds;
1851 Element* input = box.element()->shadowHost();
1853 input = box.element();
1854 if (!is<RenderBox>(input->renderer()))
1857 const RenderBox& inputBox = downcast<RenderBox>(*input->renderer());
1858 LocalCurrentGraphicsContext localContext(paintInfo.context());
1859 setSearchCellState(inputBox, r);
1861 NSSearchFieldCell* search = this->search();
1863 if (![search searchMenuTemplate])
1864 [search setSearchMenuTemplate:searchMenuTemplate()];
1866 GraphicsContextStateSaver stateSaver(paintInfo.context());
1867 float zoomLevel = box.style().effectiveZoom();
1869 FloatRect localBounds = adjustedResultButtonRect([search searchButtonRectForBounds:NSRect(snappedIntRect(inputBox.contentBoxRect()))]);
1870 // Adjust position based on the content direction.
1871 float adjustedXPosition;
1872 if (box.style().direction() == RTL)
1873 adjustedXPosition = inputBox.contentBoxRect().maxX() - localBounds.size().width();
1875 adjustedXPosition = inputBox.contentBoxRect().x();
1876 localBounds.setX(adjustedXPosition);
1877 FloatPoint paintingPos = convertToPaintingPosition(inputBox, box, localBounds.location(), r.location());
1879 FloatRect unzoomedRect(paintingPos, localBounds.size());
1880 if (zoomLevel != 1.0f) {
1881 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
1882 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
1883 paintInfo.context().translate(unzoomedRect.x(), unzoomedRect.y());
1884 paintInfo.context().scale(FloatSize(zoomLevel, zoomLevel));
1885 paintInfo.context().translate(-unzoomedRect.x(), -unzoomedRect.y());
1888 [[search searchButtonCell] drawWithFrame:unzoomedRect inView:documentViewFor(box)];
1889 [[search searchButtonCell] setControlView:nil];
1894 bool RenderThemeMac::paintSnapshottedPluginOverlay(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect&)
1896 if (paintInfo.phase != PaintPhaseBlockBackground)
1899 if (!is<RenderBlock>(renderer))
1902 const RenderBlock& renderBlock = downcast<RenderBlock>(renderer);
1904 LayoutUnit contentWidth = renderBlock.contentWidth();
1905 LayoutUnit contentHeight = renderBlock.contentHeight();
1906 if (!contentWidth || !contentHeight)
1909 GraphicsContext& context = paintInfo.context();
1911 LayoutSize contentSize(contentWidth, contentHeight);
1912 LayoutPoint contentLocation = renderBlock.location();
1913 contentLocation.move(renderBlock.borderLeft() + renderBlock.paddingLeft(), renderBlock.borderTop() + renderBlock.paddingTop());
1915 LayoutRect rect(contentLocation, contentSize);
1916 IntRect alignedRect = snappedIntRect(rect);
1917 if (alignedRect.width() <= 0 || alignedRect.height() <= 0)
1920 // We need to get the snapshot image from the plugin element, which should be available
1921 // from our node. Assuming this node is the plugin overlay element, we should get to the
1922 // plugin itself by asking for the shadow root parent, and then its parent.
1924 if (!is<HTMLElement>(*renderBlock.element()))
1927 HTMLElement& plugInOverlay = downcast<HTMLElement>(*renderBlock.element());
1928 Element* parent = plugInOverlay.parentOrShadowHostElement();
1929 while (parent && !is<HTMLPlugInElement>(*parent))
1930 parent = parent->parentOrShadowHostElement();
1935 HTMLPlugInElement& plugInElement = downcast<HTMLPlugInElement>(*parent);
1936 if (!is<HTMLPlugInImageElement>(plugInElement))
1939 HTMLPlugInImageElement& plugInImageElement = downcast<HTMLPlugInImageElement>(plugInElement);
1941 Image* snapshot = plugInImageElement.snapshotImage();
1945 RenderSnapshottedPlugIn& plugInRenderer = downcast<RenderSnapshottedPlugIn>(*plugInImageElement.renderer());
1946 FloatPoint snapshotAbsPos = plugInRenderer.localToAbsolute();
1947 snapshotAbsPos.move(plugInRenderer.borderLeft() + plugInRenderer.paddingLeft(), plugInRenderer.borderTop() + plugInRenderer.paddingTop());
1949 // We could draw the snapshot with that coordinates, but we need to make sure there
1950 // isn't a composited layer between us and the plugInRenderer.
1951 for (auto* renderBox = &downcast<RenderBox>(renderer); renderBox != &plugInRenderer; renderBox = renderBox->parentBox()) {
1952 if (renderBox->isComposited()) {
1953 snapshotAbsPos = -renderBox->location();
1958 LayoutSize pluginSize(plugInRenderer.contentWidth(), plugInRenderer.contentHeight());
1959 LayoutRect pluginRect(snapshotAbsPos, pluginSize);
1960 IntRect alignedPluginRect = snappedIntRect(pluginRect);
1962 if (alignedPluginRect.width() <= 0 || alignedPluginRect.height() <= 0)
1965 context.drawImage(*snapshot, alignedPluginRect, CompositeSourceOver);
1969 #if ENABLE(DATALIST_ELEMENT)
1970 IntSize RenderThemeMac::sliderTickSize() const
1972 return IntSize(1, 3);
1975 int RenderThemeMac::sliderTickOffsetFromTrackCenter() const
1981 const int sliderThumbWidth = 15;
1982 const int sliderThumbHeight = 15;
1984 void RenderThemeMac::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1986 float zoomLevel = style.effectiveZoom();
1987 if (style.appearance() == SliderThumbHorizontalPart || style.appearance() == SliderThumbVerticalPart) {
1988 style.setWidth(Length(static_cast<int>(sliderThumbWidth * zoomLevel), Fixed));
1989 style.setHeight(Length(static_cast<int>(sliderThumbHeight * zoomLevel), Fixed));
1993 bool RenderThemeMac::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const
1995 return element.isPasswordField();
1998 NSPopUpButtonCell* RenderThemeMac::popupButton() const
2000 if (!m_popupButton) {
2001 m_popupButton = adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
2002 [m_popupButton.get() setUsesItemFromMenu:NO];
2003 [m_popupButton.get() setFocusRingType:NSFocusRingTypeExterior];
2004 [m_popupButton setUserInterfaceLayoutDirection:NSUserInterfaceLayoutDirectionLeftToRight];
2007 return m_popupButton.get();
2010 NSSearchFieldCell* RenderThemeMac::search() const
2013 m_search = adoptNS([[NSSearchFieldCell alloc] initTextCell:@""]);
2014 [m_search.get() setBezelStyle:NSTextFieldRoundedBezel];
2015 [m_search.get() setBezeled:YES];
2016 [m_search.get() setEditable:YES];
2017 [m_search.get() setFocusRingType:NSFocusRingTypeExterior];
2018 [m_search.get() setCenteredLook:NO];
2021 return m_search.get();
2024 NSMenu* RenderThemeMac::searchMenuTemplate() const
2026 if (!m_searchMenuTemplate)
2027 m_searchMenuTemplate = adoptNS([[NSMenu alloc] initWithTitle:@""]);
2029 return m_searchMenuTemplate.get();
2032 NSSliderCell* RenderThemeMac::sliderThumbHorizontal() const
2034 if (!m_sliderThumbHorizontal) {
2035 m_sliderThumbHorizontal = adoptNS([[NSSliderCell alloc] init]);
2036 #pragma clang diagnostic push
2037 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
2038 [m_sliderThumbHorizontal.get() setSliderType:NSLinearSlider];
2039 [m_sliderThumbHorizontal.get() setControlSize:NSSmallControlSize];
2040 [m_sliderThumbHorizontal.get() setFocusRingType:NSFocusRingTypeExterior];
2041 #pragma clang diagnostic pop
2044 return m_sliderThumbHorizontal.get();
2047 NSSliderCell* RenderThemeMac::sliderThumbVertical() const
2049 if (!m_sliderThumbVertical) {
2050 m_sliderThumbVertical = adoptNS([[NSSliderCell alloc] init]);
2051 #pragma clang diagnostic push
2052 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
2053 [m_sliderThumbVertical.get() setSliderType:NSLinearSlider];
2054 [m_sliderThumbVertical.get() setControlSize:NSSmallControlSize];
2055 #pragma clang diagnostic pop
2056 [m_sliderThumbVertical.get() setFocusRingType:NSFocusRingTypeExterior];
2059 return m_sliderThumbVertical.get();
2062 NSTextFieldCell* RenderThemeMac::textField() const
2065 m_textField = adoptNS([[WebCoreTextFieldCell alloc] initTextCell:@""]);
2066 [m_textField.get() setBezeled:YES];
2067 [m_textField.get() setEditable:YES];
2068 [m_textField.get() setFocusRingType:NSFocusRingTypeExterior];
2069 // Post-Lion, WebCore can be in charge of paintinng the background thanks to
2070 // the workaround in place for <rdar://problem/11385461>, which is implemented
2071 // above as _coreUIDrawOptionsWithFrame.
2072 [m_textField.get() setDrawsBackground:NO];
2075 return m_textField.get();
2078 String RenderThemeMac::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
2083 String strToTruncate;
2084 if (fileList->isEmpty())
2085 strToTruncate = fileListDefaultLabel(multipleFilesAllowed);
2086 else if (fileList->length() == 1)
2087 strToTruncate = [[NSFileManager defaultManager] displayNameAtPath:(fileList->item(0)->path())];
2089 return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
2091 return StringTruncator::centerTruncate(strToTruncate, width, font);
2094 #if ENABLE(SERVICE_CONTROLS)
2095 NSServicesRolloverButtonCell* RenderThemeMac::servicesRolloverButtonCell() const
2097 #if HAVE(APPKIT_SERVICE_CONTROLS_SUPPORT)
2098 if (!m_servicesRolloverButton) {
2099 m_servicesRolloverButton = [NSServicesRolloverButtonCell serviceRolloverButtonCellForStyle:NSSharingServicePickerStyleRollover];
2100 [m_servicesRolloverButton setBezelStyle:NSRoundedDisclosureBezelStyle];
2101 [m_servicesRolloverButton setButtonType:NSPushOnPushOffButton];
2102 [m_servicesRolloverButton setImagePosition:NSImageOnly];
2103 [m_servicesRolloverButton setState:NO];
2106 return m_servicesRolloverButton.get();
2112 bool RenderThemeMac::paintImageControlsButton(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& rect)
2114 if (paintInfo.phase != PaintPhaseBlockBackground)
2117 #if HAVE(APPKIT_SERVICE_CONTROLS_SUPPORT)
2118 NSServicesRolloverButtonCell *cell = servicesRolloverButtonCell();
2120 LocalCurrentGraphicsContext localContext(paintInfo.context());
2121 GraphicsContextStateSaver stateSaver(paintInfo.context());
2123 paintInfo.context().translate(rect.x(), rect.y());
2125 IntRect innerFrame(IntPoint(), rect.size());
2126 [cell drawWithFrame:innerFrame inView:documentViewFor(renderer)];
2127 [cell setControlView:nil];
2129 UNUSED_PARAM(renderer);
2136 IntSize RenderThemeMac::imageControlsButtonSize(const RenderObject&) const
2138 #if HAVE(APPKIT_SERVICE_CONTROLS_SUPPORT)
2139 return IntSize(servicesRolloverButtonCell().cellSize);
2145 IntSize RenderThemeMac::imageControlsButtonPositionOffset() const
2147 #if HAVE(APPKIT_SERVICE_CONTROLS_SUPPORT)
2148 // FIXME: Currently the offsets will always be the same no matter what image rect you try with.
2149 // This may not always be true in the future.
2150 static const int dummyDimension = 100;
2151 IntRect dummyImageRect(0, 0, dummyDimension, dummyDimension);
2152 NSRect bounds = [servicesRolloverButtonCell() rectForBounds:dummyImageRect preferredEdge:NSMinYEdge];
2154 return IntSize(dummyDimension - bounds.origin.x, bounds.origin.y);
2161 #if ENABLE(ATTACHMENT_ELEMENT)
2162 const CGFloat attachmentIconSize = 48;
2163 const CGFloat attachmentIconBackgroundPadding = 6;
2164 const CGFloat attachmentIconBackgroundSize = attachmentIconSize + attachmentIconBackgroundPadding;
2165 const CGFloat attachmentIconSelectionBorderThickness = 1;
2166 const CGFloat attachmentIconBackgroundRadius = 3;
2167 const CGFloat attachmentIconToTitleMargin = 2;
2169 static Color attachmentIconBackgroundColor() { return Color(0, 0, 0, 30); }
2170 static Color attachmentIconBorderColor() { return Color(255, 255, 255, 125); }
2172 const CGFloat attachmentTitleFontSize = 12;
2173 const CGFloat attachmentTitleBackgroundRadius = 3;
2174 const CGFloat attachmentTitleBackgroundPadding = 3;
2175 const CGFloat attachmentTitleMaximumWidth = 100 - (attachmentTitleBackgroundPadding * 2);
2176 const CFIndex attachmentTitleMaximumLineCount = 2;
2178 static Color attachmentTitleInactiveBackgroundColor() { return Color(204, 204, 204, 255); }
2179 static Color attachmentTitleInactiveTextColor() { return Color(100, 100, 100, 255); }
2181 const CGFloat attachmentSubtitleFontSize = 10;
2182 const int attachmentSubtitleWidthIncrement = 10;
2183 static Color attachmentSubtitleTextColor() { return Color(82, 145, 214, 255); }
2185 const CGFloat attachmentProgressBarWidth = 30;
2186 const CGFloat attachmentProgressBarHeight = 5;
2187 const CGFloat attachmentProgressBarOffset = -9;
2188 const CGFloat attachmentProgressBarBorderWidth = 1;
2189 static Color attachmentProgressBarBackgroundColor() { return Color(0, 0, 0, 89); }
2190 static Color attachmentProgressBarFillColor() { return Color(Color::white); }
2191 static Color attachmentProgressBarBorderColor() { return Color(0, 0, 0, 128); }
2193 const CGFloat attachmentPlaceholderBorderRadius = 5;
2194 static Color attachmentPlaceholderBorderColor() { return Color(0, 0, 0, 56); }
2195 const CGFloat attachmentPlaceholderBorderWidth = 2;
2196 const CGFloat attachmentPlaceholderBorderDashLength = 6;
2198 const CGFloat attachmentMargin = 3;
2200 struct AttachmentLayout {
2201 explicit AttachmentLayout(const RenderAttachment&);
2204 FloatRect backgroundRect;
2206 RetainPtr<CTLineRef> line;
2209 Vector<LabelLine> lines;
2212 FloatRect iconBackgroundRect;
2213 FloatRect attachmentRect;
2217 RetainPtr<CTLineRef> subtitleLine;
2218 FloatRect subtitleTextRect;
2221 void layOutTitle(const RenderAttachment&);
2222 void layOutSubtitle(const RenderAttachment&);
2224 void addTitleLine(CTLineRef, CGFloat& yOffset, Vector<CGPoint> origins, CFIndex lineIndex, const RenderAttachment&);
2227 static NSColor *titleTextColorForAttachment(const RenderAttachment& attachment)
2229 if (attachment.selectionState() != RenderObject::SelectionNone) {
2230 if (attachment.frame().selection().isFocusedAndActive())
2231 return [NSColor alternateSelectedControlTextColor];
2232 return (NSColor *)cachedCGColor(attachmentTitleInactiveTextColor());
2235 return [NSColor blackColor];
2238 void AttachmentLayout::addTitleLine(CTLineRef line, CGFloat& yOffset, Vector<CGPoint> origins, CFIndex lineIndex, const RenderAttachment& attachment)
2240 CGRect lineBounds = CTLineGetBoundsWithOptions(line, 0);
2241 CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
2242 CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
2243 CGFloat lineHeight = CGCeiling(lineBounds.size.height);
2245 // Center the line relative to the icon.
2246 CGFloat xOffset = (attachmentIconBackgroundSize / 2) - (lineWidthIgnoringTrailingWhitespace / 2);
2249 yOffset += origins[lineIndex - 1].y - origins[lineIndex].y;
2251 LabelLine labelLine;
2252 labelLine.origin = FloatPoint(xOffset, yOffset + lineHeight - origins.last().y);
2253 labelLine.line = line;
2254 labelLine.backgroundRect = FloatRect(xOffset, yOffset, lineWidthIgnoringTrailingWhitespace, lineHeight);
2255 labelLine.backgroundRect.inflateX(attachmentTitleBackgroundPadding);
2256 labelLine.backgroundRect = encloseRectToDevicePixels(labelLine.backgroundRect, attachment.document().deviceScaleFactor());
2258 // If the text rects are close in size, the curved enclosing background won't
2259 // look right, so make them the same exact size.
2260 if (!lines.isEmpty()) {
2261 float previousBackgroundRectWidth = lines.last().backgroundRect.width();
2262 if (fabs(labelLine.backgroundRect.width() - previousBackgroundRectWidth) < attachmentTitleBackgroundRadius * 4) {
2263 float newBackgroundRectWidth = std::max(previousBackgroundRectWidth, labelLine.backgroundRect.width());
2264 labelLine.backgroundRect.inflateX((newBackgroundRectWidth - labelLine.backgroundRect.width()) / 2);
2265 lines.last().backgroundRect.inflateX((newBackgroundRectWidth - previousBackgroundRectWidth) / 2);
2269 lines.append(labelLine);
2272 void AttachmentLayout::layOutTitle(const RenderAttachment& attachment)
2274 CFStringRef language = 0; // By not specifying a language we use the system language.
2275 RetainPtr<CTFontRef> font = adoptCF(CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, attachmentTitleFontSize, language));
2276 baseline = CGRound(attachmentIconBackgroundSize + attachmentIconToTitleMargin + CTFontGetAscent(font.get()));
2278 String title = attachment.attachmentElement().attachmentTitle();
2279 if (title.isEmpty())
2282 NSDictionary *textAttributes = @{
2283 (id)kCTFontAttributeName: (id)font.get(),
2284 (id)kCTForegroundColorAttributeName: titleTextColorForAttachment(attachment)
2286 RetainPtr<NSAttributedString> attributedTitle = adoptNS([[NSAttributedString alloc] initWithString:title attributes:textAttributes]);
2287 RetainPtr<CTFramesetterRef> titleFramesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedTitle.get()));
2290 CGSize titleTextSize = CTFramesetterSuggestFrameSizeWithConstraints(titleFramesetter.get(), CFRangeMake(0, 0), nullptr, CGSizeMake(attachmentTitleMaximumWidth, CGFLOAT_MAX), &fitRange);
2292 RetainPtr<CGPathRef> titlePath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, titleTextSize.width, titleTextSize.height), nullptr));
2293 RetainPtr<CTFrameRef> titleFrame = adoptCF(CTFramesetterCreateFrame(titleFramesetter.get(), fitRange, titlePath.get(), nullptr));
2295 CFArrayRef ctLines = CTFrameGetLines(titleFrame.get());
2296 CFIndex lineCount = CFArrayGetCount(ctLines);
2300 Vector<CGPoint> origins(lineCount);
2301 CTFrameGetLineOrigins(titleFrame.get(), CFRangeMake(0, 0), origins.data());
2303 // Lay out and record the first (attachmentTitleMaximumLineCount - 1) lines.
2304 CFIndex lineIndex = 0;
2305 CGFloat yOffset = attachmentIconBackgroundSize + attachmentIconToTitleMargin;
2306 for (; lineIndex < std::min(attachmentTitleMaximumLineCount - 1, lineCount); ++lineIndex) {
2307 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
2308 addTitleLine(line, yOffset, origins, lineIndex, attachment);
2311 if (lineIndex == lineCount)
2314 // We had text that didn't fit in the first (attachmentTitleMaximumLineCount - 1) lines.
2315 // Combine it into one last line, and center-truncate it.
2316 CTLineRef firstRemainingLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
2317 CFIndex remainingRangeStart = CTLineGetStringRange(firstRemainingLine).location;
2318 NSRange remainingRange = NSMakeRange(remainingRangeStart, [attributedTitle length] - remainingRangeStart);
2319 NSAttributedString *remainingString = [attributedTitle attributedSubstringFromRange:remainingRange];
2320 RetainPtr<CTLineRef> remainingLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)remainingString));
2321 RetainPtr<NSAttributedString> ellipsisString = adoptNS([[NSAttributedString alloc] initWithString:@"\u2026" attributes:textAttributes]);
2322 RetainPtr<CTLineRef> ellipsisLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)ellipsisString.get()));
2323 RetainPtr<CTLineRef> truncatedLine = adoptCF(CTLineCreateTruncatedLine(remainingLine.get(), attachmentTitleMaximumWidth, kCTLineTruncationMiddle, ellipsisLine.get()));
2326 truncatedLine = remainingLine;
2328 addTitleLine(truncatedLine.get(), yOffset, origins, lineIndex, attachment);
2331 void AttachmentLayout::layOutSubtitle(const RenderAttachment& attachment)
2333 String subtitleText = attachment.attachmentElement().attributeWithoutSynchronization(subtitleAttr);
2335 if (subtitleText.isEmpty())
2338 CFStringRef language = 0; // By not specifying a language we use the system language.
2339 RetainPtr<CTFontRef> font = adoptCF(CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, attachmentSubtitleFontSize, language));
2340 NSDictionary *textAttributes = @{
2341 (id)kCTFontAttributeName: (id)font.get(),
2342 (id)kCTForegroundColorAttributeName: (NSColor *)cachedCGColor(attachmentSubtitleTextColor())
2344 RetainPtr<NSAttributedString> attributedSubtitleText = adoptNS([[NSAttributedString alloc] initWithString:subtitleText attributes:textAttributes]);
2345 subtitleLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)attributedSubtitleText.get()));
2347 CGRect lineBounds = CTLineGetBoundsWithOptions(subtitleLine.get(), 0);
2349 // Center the line relative to the icon.
2350 CGFloat xOffset = (attachmentIconBackgroundSize / 2) - (lineBounds.size.width / 2);
2351 CGFloat yOffset = 0;
2353 if (!lines.isEmpty())
2354 yOffset = lines.last().backgroundRect.maxY();
2356 yOffset = attachmentIconBackgroundSize + attachmentIconToTitleMargin;
2358 LabelLine labelLine;
2359 subtitleTextRect = FloatRect(xOffset, yOffset, lineBounds.size.width, lineBounds.size.height);
2362 AttachmentLayout::AttachmentLayout(const RenderAttachment& attachment)
2364 layOutTitle(attachment);
2365 layOutSubtitle(attachment);
2367 iconBackgroundRect = FloatRect(0, 0, attachmentIconBackgroundSize, attachmentIconBackgroundSize);
2369 iconRect = iconBackgroundRect;
2370 iconRect.setSize(FloatSize(attachmentIconSize, attachmentIconSize));
2371 iconRect.move(attachmentIconBackgroundPadding / 2, attachmentIconBackgroundPadding / 2);
2373 attachmentRect = iconBackgroundRect;
2374 for (const auto& line : lines)
2375 attachmentRect.unite(line.backgroundRect);
2376 if (!subtitleTextRect.isEmpty()) {
2377 FloatRect roundedSubtitleTextRect = subtitleTextRect;
2378 roundedSubtitleTextRect.inflateX(attachmentSubtitleWidthIncrement - clampToInteger(ceilf(subtitleTextRect.width())) % attachmentSubtitleWidthIncrement);
2379 attachmentRect.unite(roundedSubtitleTextRect);
2381 attachmentRect.inflate(attachmentMargin);
2382 attachmentRect = encloseRectToDevicePixels(attachmentRect, attachment.document().deviceScaleFactor());
2385 LayoutSize RenderThemeMac::attachmentIntrinsicSize(const RenderAttachment& attachment) const
2387 AttachmentLayout layout(attachment);
2388 return LayoutSize(layout.attachmentRect.size());
2391 int RenderThemeMac::attachmentBaseline(const RenderAttachment& attachment) const
2393 AttachmentLayout layout(attachment);
2394 return layout.baseline;
2397 static void paintAttachmentIconBackground(const RenderAttachment&, GraphicsContext& context, AttachmentLayout& layout)
2399 // FIXME: Finder has a discontinuous behavior here when you have a background color other than white,
2400 // where it switches into 'bordered mode' and the border pops in on top of the background.
2401 bool paintBorder = true;
2403 FloatRect backgroundRect = layout.iconBackgroundRect;
2405 backgroundRect.inflate(-attachmentIconSelectionBorderThickness);
2407 context.fillRoundedRect(FloatRoundedRect(backgroundRect, FloatRoundedRect::Radii(attachmentIconBackgroundRadius)), attachmentIconBackgroundColor());
2410 FloatRect borderRect = layout.iconBackgroundRect;
2411 borderRect.inflate(-attachmentIconSelectionBorderThickness / 2);
2413 FloatSize iconBackgroundRadiusSize(attachmentIconBackgroundRadius, attachmentIconBackgroundRadius);
2415 borderPath.addRoundedRect(borderRect, iconBackgroundRadiusSize);
2416 context.setStrokeColor(attachmentIconBorderColor());
2417 context.setStrokeThickness(attachmentIconSelectionBorderThickness);
2418 context.strokePath(borderPath);
2422 static RefPtr<Icon> iconForAttachment(const RenderAttachment& attachment)
2424 String attachmentType = attachment.attachmentElement().attachmentType();
2426 if (!attachmentType.isEmpty()) {
2427 if (equalIgnoringASCIICase(attachmentType, "multipart/x-folder") || equalIgnoringASCIICase(attachmentType, "application/vnd.apple.folder")) {
2428 if (auto icon = Icon::createIconForUTI("public.directory"))
2431 auto attachmentTypeCF = attachmentType.createCFString();
2432 RetainPtr<CFStringRef> UTI;
2433 if (isDeclaredUTI(attachmentTypeCF.get()))
2434 UTI = attachmentTypeCF;
2436 UTI = UTIFromMIMEType(attachmentTypeCF.get());
2438 if (auto icon = Icon::createIconForUTI(UTI.get()))
2443 if (File* file = attachment.attachmentElement().file()) {
2444 if (auto icon = Icon::createIconForFiles({ file->path() }))
2448 NSString *fileExtension = [static_cast<NSString *>(attachment.attachmentElement().attachmentTitle()) pathExtension];
2449 if (fileExtension.length) {
2450 if (auto icon = Icon::createIconForFileExtension(fileExtension))
2454 return Icon::createIconForUTI("public.data");
2457 static void paintAttachmentIcon(const RenderAttachment& attachment, GraphicsContext& context, AttachmentLayout& layout)
2459 auto icon = iconForAttachment(attachment);
2462 icon->paint(context, layout.iconRect);
2465 static void paintAttachmentIconPlaceholder(const RenderAttachment& attachment, GraphicsContext& context, AttachmentLayout& layout)
2467 RefPtr<Image> placeholderImage;
2468 float imageScale = 1;
2469 if (attachment.document().deviceScaleFactor() >= 2) {
2470 placeholderImage = Image::loadPlatformResource("AttachmentPlaceholder@2x");
2473 placeholderImage = Image::loadPlatformResource("AttachmentPlaceholder");
2475 // Center the placeholder image where the icon would usually be.
2476 FloatRect placeholderRect(0, 0, placeholderImage->width() / imageScale, placeholderImage->height() / imageScale);
2477 placeholderRect.setX(layout.iconRect.x() + (layout.iconRect.width() - placeholderRect.width()) / 2);
2478 placeholderRect.setY(layout.iconRect.y() + (layout.iconRect.height() - placeholderRect.height()) / 2);
2480 context.drawImage(*placeholderImage, placeholderRect);
2483 static void paintAttachmentTitleBackground(const RenderAttachment& attachment, GraphicsContext& context, AttachmentLayout& layout)
2485 if (layout.lines.isEmpty())
2488 Vector<FloatRect> backgroundRects;
2490 for (size_t i = 0; i < layout.lines.size(); ++i)
2491 backgroundRects.append(layout.lines[i].backgroundRect);
2493 Color backgroundColor;
2494 if (attachment.frame().selection().isFocusedAndActive())
2495 backgroundColor = convertNSColorToColor([NSColor alternateSelectedControlColor]);
2497 backgroundColor = attachmentTitleInactiveBackgroundColor();
2499 context.setFillColor(backgroundColor);
2501 Path backgroundPath = PathUtilities::pathWithShrinkWrappedRects(backgroundRects, attachmentTitleBackgroundRadius);
2502 context.fillPath(backgroundPath);
2505 static void paintAttachmentTitle(const RenderAttachment&, GraphicsContext& context, AttachmentLayout& layout)
2507 for (const auto& line : layout.lines) {
2508 GraphicsContextStateSaver saver(context);
2510 context.translate(toFloatSize(line.origin));
2511 context.scale(FloatSize(1, -1));
2513 CGContextSetTextPosition(context.platformContext(), 0, 0);
2514 CTLineDraw(line.line.get(), context.platformContext());
2518 static void paintAttachmentSubtitle(const RenderAttachment&, GraphicsContext& context, AttachmentLayout& layout)
2520 GraphicsContextStateSaver saver(context);
2522 context.translate(toFloatSize(layout.subtitleTextRect.minXMaxYCorner()));
2523 context.scale(FloatSize(1, -1));
2525 CGContextSetTextPosition(context.platformContext(), 0, 0);
2526 CTLineDraw(layout.subtitleLine.get(), context.platformContext());
2529 static void paintAttachmentProgress(const RenderAttachment& attachment, GraphicsContext& context, AttachmentLayout& layout, float progress)
2531 GraphicsContextStateSaver saver(context);
2533 FloatRect progressBounds((attachmentIconBackgroundSize - attachmentProgressBarWidth) / 2, layout.iconBackgroundRect.maxY() + attachmentProgressBarOffset - attachmentProgressBarHeight, attachmentProgressBarWidth, attachmentProgressBarHeight);
2535 FloatRect borderRect = progressBounds;
2536 borderRect.inflate(-0.5);
2537 FloatRect backgroundRect = borderRect;
2538 backgroundRect.inflate(-attachmentProgressBarBorderWidth / 2);
2540 FloatRoundedRect backgroundRoundedRect(backgroundRect, FloatRoundedRect::Radii(backgroundRect.height() / 2));
2541 context.fillRoundedRect(backgroundRoundedRect, attachmentProgressBarBackgroundColor());
2544 GraphicsContextStateSaver clipSaver(context);
2545 context.clipRoundedRect(backgroundRoundedRect);
2547 FloatRect progressRect = progressBounds;
2548 progressRect.setWidth(progressRect.width() * progress);
2549 progressRect = encloseRectToDevicePixels(progressRect, attachment.document().deviceScaleFactor());
2551 context.fillRect(progressRect, attachmentProgressBarFillColor());
2555 float borderRadius = borderRect.height() / 2;
2556 borderPath.addRoundedRect(borderRect, FloatSize(borderRadius, borderRadius));
2557 context.setStrokeColor(attachmentProgressBarBorderColor());
2558 context.setStrokeThickness(attachmentProgressBarBorderWidth);
2559 context.strokePath(borderPath);
2562 static void paintAttachmentPlaceholderBorder(const RenderAttachment&, GraphicsContext& context, AttachmentLayout& layout)
2565 borderPath.addRoundedRect(layout.attachmentRect, FloatSize(attachmentPlaceholderBorderRadius, attachmentPlaceholderBorderRadius));
2566 context.setStrokeColor(attachmentPlaceholderBorderColor());
2567 context.setStrokeThickness(attachmentPlaceholderBorderWidth);
2568 context.setStrokeStyle(DashedStroke);
2569 context.setLineDash({attachmentPlaceholderBorderDashLength}, 0);
2570 context.strokePath(borderPath);
2573 bool RenderThemeMac::paintAttachment(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& paintRect)
2575 if (!is<RenderAttachment>(renderer))
2578 const RenderAttachment& attachment = downcast<RenderAttachment>(renderer);
2580 AttachmentLayout layout(attachment);
2582 String progressString = attachment.attachmentElement().attributeWithoutSynchronization(progressAttr);
2583 bool validProgress = false;
2585 if (!progressString.isEmpty())
2586 progress = progressString.toFloat(&validProgress);
2588 GraphicsContext& context = paintInfo.context();
2589 LocalCurrentGraphicsContext localContext(context);
2590 GraphicsContextStateSaver saver(context);
2592 context.translate(toFloatSize(paintRect.location()));
2593 context.translate(floorSizeToDevicePixels(LayoutSize((paintRect.width() - attachmentIconBackgroundSize) / 2, 0), renderer.document().deviceScaleFactor()));
2595 bool useSelectedStyle = attachment.selectionState() != RenderObject::SelectionNone;
2596 bool usePlaceholder = validProgress && !progress;
2598 if (useSelectedStyle)
2599 paintAttachmentIconBackground(attachment, context, layout);
2601 paintAttachmentIconPlaceholder(attachment, context, layout);
2603 paintAttachmentIcon(attachment, context, layout);
2605 if (useSelectedStyle)
2606 paintAttachmentTitleBackground(attachment, context, layout);
2607 paintAttachmentTitle(attachment, context, layout);
2608 paintAttachmentSubtitle(attachment, context, layout);
2610 if (validProgress && progress)
2611 paintAttachmentProgress(attachment, context, layout, progress);
2614 paintAttachmentPlaceholderBorder(attachment, context, layout);
2619 #endif // ENABLE(ATTACHMENT_ELEMENT)
2621 } // namespace WebCore
2623 #endif // !PLATFORM(IOS)