Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / platform / mac / ThemeMac.mm
1 /*
2  * Copyright (C) 2008, 2010, 2011, 2012 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "ThemeMac.h"
28
29 #import "AXObjectCache.h"
30 #import "BlockExceptions.h"
31 #import "GraphicsContext.h"
32 #import "ImageBuffer.h"
33 #import "LocalCurrentGraphicsContext.h"
34 #import "NSButtonCellSPI.h"
35 #import "ScrollView.h"
36 #import "WebCoreSystemInterface.h"
37 #import <Carbon/Carbon.h>
38 #import <wtf/StdLibExtras.h>
39
40 static NSRect focusRingClipRect;
41 static BOOL themeWindowHasKeyAppearance;
42
43 @interface WebCoreThemeWindow : NSWindow
44 @end
45
46 @implementation WebCoreThemeWindow
47
48 - (BOOL)hasKeyAppearance
49 {
50     return themeWindowHasKeyAppearance;
51 }
52
53 - (BOOL)isKeyWindow
54 {
55     return themeWindowHasKeyAppearance;
56 }
57
58 @end
59
60 @interface WebCoreThemeView : NSControl
61 @end
62
63 @implementation WebCoreThemeView
64
65 - (NSWindow *)window
66 {
67     // Using defer:YES prevents us from wasting any window server resources for this window, since we're not actually
68     // going to draw into it. The other arguments match what you get when calling -[NSWindow init].
69     static WebCoreThemeWindow *window = [[WebCoreThemeWindow alloc] initWithContentRect:NSMakeRect(100, 100, 100, 100)
70         styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:YES];
71     return window;
72 }
73
74 - (BOOL)isFlipped
75 {
76     return YES;
77 }
78
79 - (NSText *)currentEditor
80 {
81     return nil;
82 }
83
84 - (BOOL)_automaticFocusRingDisabled
85 {
86     return YES;
87 }
88
89 - (NSRect)_focusRingVisibleRect
90 {
91     if (NSIsEmptyRect(focusRingClipRect))
92         return [self visibleRect];
93     return focusRingClipRect;
94 }
95
96 - (NSView *)_focusRingClipAncestor
97 {
98     return self;
99 }
100
101 - (void)addSubview:(NSView*)subview
102 {
103     // By doing nothing in this method we forbid controls from adding subviews.
104     // This tells AppKit to not use Layer-backed animation for control rendering.
105     UNUSED_PARAM(subview);
106 }
107
108 @end
109
110 @implementation NSFont (WebCoreTheme)
111
112 - (NSString*)webCoreFamilyName
113 {
114     if ([[self familyName] hasPrefix:@"."])
115         return [self fontName];
116
117     return [self familyName];
118 }
119
120 @end
121
122 // FIXME: Default buttons really should be more like push buttons and not like buttons.
123
124 namespace WebCore {
125
126 enum {
127     topMargin,
128     rightMargin,
129     bottomMargin,
130     leftMargin
131 };
132
133 Theme* platformTheme()
134 {
135     DEPRECATED_DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ());
136     return &themeMac;
137 }
138
139 // Helper functions used by a bunch of different control parts.
140
141 static NSControlSize controlSizeForFont(const FontCascade& font)
142 {
143     int fontSize = font.pixelSize();
144     if (fontSize >= 16)
145         return NSRegularControlSize;
146     if (fontSize >= 11)
147         return NSSmallControlSize;
148     return NSMiniControlSize;
149 }
150
151 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
152 {
153     IntSize controlSize = sizes[nsControlSize];
154     if (zoomFactor != 1.0f)
155         controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
156     LengthSize result = zoomedSize;
157     if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
158         result.setWidth(Length(controlSize.width(), Fixed));
159     if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
160         result.setHeight(Length(controlSize.height(), Fixed));
161     return result;
162 }
163
164 static LengthSize sizeFromFont(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
165 {
166     return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
167 }
168
169 static ControlSize controlSizeFromPixelSize(const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
170 {
171     if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
172         minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
173         return NSRegularControlSize;
174     if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
175         minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
176         return NSSmallControlSize;
177     return NSMiniControlSize;
178 }
179
180 static void setControlSize(NSCell* cell, const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
181 {
182     ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
183     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
184         [cell setControlSize:(NSControlSize)size];
185 }
186
187 static void updateStates(NSCell* cell, const ControlStates& controlStates, bool useAnimation = false)
188 {
189     // The animated state cause this thread to start and stop repeatedly on CoreAnimation synchronize calls.
190     // This short burts of activity in between are not long enough for VoiceOver to retrieve accessibility attributes and makes the process appear unresponsive.
191     if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled())
192         useAnimation = false;
193     
194     ControlStates::States states = controlStates.states();
195
196     // Hover state is not supported by Aqua.
197     
198     // Pressed state
199     bool oldPressed = [cell isHighlighted];
200     bool pressed = states & ControlStates::PressedState;
201     if (pressed != oldPressed) {
202         [(NSButtonCell*)cell _setHighlighted:pressed animated:useAnimation];
203     }
204     
205     // Enabled state
206     bool oldEnabled = [cell isEnabled];
207     bool enabled = states & ControlStates::EnabledState;
208     if (enabled != oldEnabled)
209         [cell setEnabled:enabled];
210
211     // Checked and Indeterminate
212     bool oldIndeterminate = [cell state] == NSMixedState;
213     bool indeterminate = (states & ControlStates::IndeterminateState);
214     bool checked = states & ControlStates::CheckedState;
215     bool oldChecked = [cell state] == NSOnState;
216     if (oldIndeterminate != indeterminate || checked != oldChecked) {
217         NSCellStateValue newState = indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState);
218         [(NSButtonCell*)cell _setState:newState animated:useAnimation];
219     }
220
221     // Window inactive state does not need to be checked explicitly, since we paint parented to 
222     // a view in a window whose key state can be detected.
223 }
224
225 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, const ControlStates& controlStates)
226 {
227     ControlStates::States states = controlStates.states();
228
229     if (!(states & ControlStates::EnabledState))
230         return kThemeStateUnavailableInactive;
231
232     // Do not process PressedState if !EnabledState.
233     if (states & ControlStates::PressedState) {
234         if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
235             return states & ControlStates::SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
236         return kThemeStatePressed;
237     }
238     return kThemeStateActive;
239 }
240
241 static FloatRect inflateRect(const FloatRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
242 {
243     // Only do the inflation if the available width/height are too small.  Otherwise try to
244     // fit the glow/check space into the available box's width/height.
245     int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
246     int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
247     FloatRect result(zoomedRect);
248     if (widthDelta < 0) {
249         result.setX(result.x() - margins[leftMargin] * zoomFactor);
250         result.setWidth(result.width() - widthDelta);
251     }
252     if (heightDelta < 0) {
253         result.setY(result.y() - margins[topMargin] * zoomFactor);
254         result.setHeight(result.height() - heightDelta);
255     }
256     return result;
257 }
258
259 // Checkboxes and radio buttons
260
261 static const std::array<IntSize, 3>& checkboxSizes()
262 {
263     static const std::array<IntSize, 3> sizes = { { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) } };
264     return sizes;
265 }
266
267 static const int* checkboxMargins(NSControlSize controlSize)
268 {
269     static const int margins[3][4] =
270     {
271         // top right bottom left
272         { 2, 2, 2, 2 },
273         { 2, 1, 2, 1 },
274         { 0, 0, 1, 0 },
275     };
276     return margins[controlSize];
277 }
278
279 static LengthSize checkboxSize(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor)
280 {
281     // If the width and height are both specified, then we have nothing to do.
282     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
283         return zoomedSize;
284
285     // Use the font size to determine the intrinsic width of the control.
286     return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
287 }
288
289 // Radio Buttons
290
291 static const std::array<IntSize, 3>& radioSizes()
292 {
293     static const std::array<IntSize, 3> sizes = { { IntSize(16, 16), IntSize(12, 12), IntSize(10, 10) } };
294     return sizes;
295 }
296
297 static const int* radioMargins(NSControlSize controlSize)
298 {
299     static const int margins[3][4] =
300     {
301         // top right bottom left
302         { 1, 0, 1, 2 },
303         { 1, 1, 2, 1 },
304         { 0, 0, 1, 1 },
305     };
306     return margins[controlSize];
307 }
308
309 static LengthSize radioSize(const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor)
310 {
311     // If the width and height are both specified, then we have nothing to do.
312     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
313         return zoomedSize;
314
315     // Use the font size to determine the intrinsic width of the control.
316     return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
317 }
318     
319 static void configureToggleButton(NSCell* cell, ControlPart buttonType, const ControlStates& states, const IntSize& zoomedSize, float zoomFactor, bool isStateChange)
320 {
321     // Set the control size based off the rectangle we're painting into.
322     setControlSize(cell, buttonType == CheckboxPart ? checkboxSizes() : radioSizes(), zoomedSize, zoomFactor);
323
324     // Update the various states we respond to.
325     updateStates(cell, states, isStateChange);
326 }
327     
328 static RetainPtr<NSButtonCell> createToggleButtonCell(ControlPart buttonType)
329 {
330     RetainPtr<NSButtonCell> toggleButtonCell = adoptNS([[NSButtonCell alloc] init]);
331     
332     if (buttonType == CheckboxPart) {
333         [toggleButtonCell setButtonType:NSSwitchButton];
334         [toggleButtonCell setAllowsMixedState:YES];
335     } else {
336         ASSERT(buttonType == RadioPart);
337         [toggleButtonCell setButtonType:NSRadioButton];
338     }
339     
340     [toggleButtonCell setTitle:nil];
341     [toggleButtonCell setFocusRingType:NSFocusRingTypeExterior];
342     return toggleButtonCell;
343 }
344     
345 static NSButtonCell *sharedRadioCell(const ControlStates& states, const IntSize& zoomedSize, float zoomFactor)
346 {
347     static NSButtonCell *radioCell = createToggleButtonCell(RadioPart).leakRef();
348
349     configureToggleButton(radioCell, RadioPart, states, zoomedSize, zoomFactor, false);
350     return radioCell;
351 }
352     
353 static NSButtonCell *sharedCheckboxCell(const ControlStates& states, const IntSize& zoomedSize, float zoomFactor)
354 {
355     static NSButtonCell *checkboxCell = createToggleButtonCell(CheckboxPart).leakRef();
356
357     configureToggleButton(checkboxCell, CheckboxPart, states, zoomedSize, zoomFactor, false);
358     return checkboxCell;
359 }
360
361 static bool drawCellFocusRing(NSCell *cell, NSRect cellFrame, NSView *controlView)
362 {
363     wkDrawCellFocusRingWithFrameAtTime(cell, cellFrame, controlView, std::numeric_limits<double>::max());
364     return false;
365 }
366
367 static void paintToggleButton(ControlPart buttonType, ControlStates& controlStates, GraphicsContext& context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView, float deviceScaleFactor, float pageScaleFactor)
368 {
369     BEGIN_BLOCK_OBJC_EXCEPTIONS
370
371     RetainPtr<NSButtonCell> toggleButtonCell = static_cast<NSButtonCell *>(controlStates.platformControl());
372     IntSize zoomedRectSize = IntSize(zoomedRect.size());
373
374     if (controlStates.isDirty()) {
375         if (!toggleButtonCell)
376             toggleButtonCell = createToggleButtonCell(buttonType);
377         configureToggleButton(toggleButtonCell.get(), buttonType, controlStates, zoomedRectSize, zoomFactor, true);
378     } else {
379         if (!toggleButtonCell) {
380             if (buttonType == CheckboxPart)
381                 toggleButtonCell = sharedCheckboxCell(controlStates, zoomedRectSize, zoomFactor);
382             else {
383                 ASSERT(buttonType == RadioPart);
384                 toggleButtonCell = sharedRadioCell(controlStates, zoomedRectSize, zoomFactor);
385             }
386         }
387         configureToggleButton(toggleButtonCell.get(), buttonType, controlStates, zoomedRectSize, zoomFactor, false);
388     }
389     controlStates.setDirty(false);
390
391     GraphicsContextStateSaver stateSaver(context);
392
393     NSControlSize controlSize = [toggleButtonCell controlSize];
394     IntSize zoomedSize = buttonType == CheckboxPart ? checkboxSizes()[controlSize] : radioSizes()[controlSize];
395     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
396     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
397     const int* controlMargins = buttonType == CheckboxPart ? checkboxMargins(controlSize) : radioMargins(controlSize);
398     FloatRect inflatedRect = inflateRect(zoomedRect, zoomedSize, controlMargins, zoomFactor);
399
400     if (zoomFactor != 1.0f) {
401         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
402         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
403         context.translate(inflatedRect.x(), inflatedRect.y());
404         context.scale(FloatSize(zoomFactor, zoomFactor));
405         context.translate(-inflatedRect.x(), -inflatedRect.y());
406     }
407
408     LocalCurrentGraphicsContext localContext(context);
409
410     NSView *view = ThemeMac::ensuredView(scrollView, controlStates, true /* useUnparentedView */);
411
412     bool needsRepaint = false;
413     bool useImageBuffer = pageScaleFactor != 1.0f || zoomFactor != 1.0f;
414     bool isCellFocused = controlStates.states() & ControlStates::FocusState;
415
416     if ([toggleButtonCell _stateAnimationRunning]) {
417         context.translate(inflatedRect.x(), inflatedRect.y());
418         context.scale(FloatSize(1, -1));
419         context.translate(0, -inflatedRect.height());
420
421         [toggleButtonCell _renderCurrentAnimationFrameInContext:context.platformContext() atLocation:NSMakePoint(0, 0)];
422         if (![toggleButtonCell _stateAnimationRunning] && isCellFocused)
423             needsRepaint = ThemeMac::drawCellOrFocusRingWithViewIntoContext(toggleButtonCell.get(), context, inflatedRect, view, false, true, useImageBuffer, deviceScaleFactor);
424     } else
425         needsRepaint = ThemeMac::drawCellOrFocusRingWithViewIntoContext(toggleButtonCell.get(), context, inflatedRect, view, true, isCellFocused, useImageBuffer, deviceScaleFactor);
426
427     [toggleButtonCell setControlView:nil];
428
429     needsRepaint |= [toggleButtonCell _stateAnimationRunning];
430     controlStates.setNeedsRepaint(needsRepaint);
431     if (needsRepaint)
432         controlStates.setPlatformControl(toggleButtonCell.get());
433
434     END_BLOCK_OBJC_EXCEPTIONS
435 }
436
437 // Buttons
438
439 // Buttons really only constrain height. They respect width.
440 static const std::array<IntSize, 3>& buttonSizes()
441 {
442     static const std::array<IntSize, 3> sizes = { { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) } };
443     return sizes;
444 }
445
446 static const int* buttonMargins(NSControlSize controlSize)
447 {
448     static const int margins[3][4] =
449     {
450         { 4, 6, 7, 6 },
451         { 4, 5, 6, 5 },
452         { 0, 1, 1, 1 },
453     };
454     return margins[controlSize];
455 }
456
457 enum ButtonCellType { NormalButtonCell, DefaultButtonCell };
458
459 static NSButtonCell *leakButtonCell(ButtonCellType type)
460 {
461     NSButtonCell *cell = [[NSButtonCell alloc] init];
462     [cell setTitle:nil];
463     [cell setButtonType:NSMomentaryPushInButton];
464     if (type == DefaultButtonCell)
465         [cell setKeyEquivalent:@"\r"];
466     return cell;
467 }
468
469 static void setUpButtonCell(NSButtonCell *cell, ControlPart part, const ControlStates& states, const IntSize& zoomedSize, float zoomFactor)
470 {
471     // Set the control size based off the rectangle we're painting into.
472     const std::array<IntSize, 3>& sizes = buttonSizes();
473     if (part == SquareButtonPart || zoomedSize.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
474         // Use the square button
475         if ([cell bezelStyle] != NSShadowlessSquareBezelStyle)
476             [cell setBezelStyle:NSShadowlessSquareBezelStyle];
477     } else if ([cell bezelStyle] != NSRoundedBezelStyle)
478         [cell setBezelStyle:NSRoundedBezelStyle];
479
480     setControlSize(cell, sizes, zoomedSize, zoomFactor);
481
482     // Update the various states we respond to.
483     updateStates(cell, states);
484 }
485
486 static NSButtonCell *button(ControlPart part, const ControlStates& controlStates, const IntSize& zoomedSize, float zoomFactor)
487 {
488     ControlStates::States states = controlStates.states();
489     NSButtonCell *cell;
490     if (states & ControlStates::DefaultState) {
491         static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell);
492         cell = defaultCell;
493     } else {
494         static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell);
495         cell = normalCell;
496     }
497     setUpButtonCell(cell, part, controlStates, zoomedSize, zoomFactor);
498     return cell;
499 }
500     
501 static void paintButton(ControlPart part, ControlStates& controlStates, GraphicsContext& context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView, float deviceScaleFactor, float pageScaleFactor)
502 {
503     BEGIN_BLOCK_OBJC_EXCEPTIONS
504     
505     // Determine the width and height needed for the control and prepare the cell for painting.
506     ControlStates::States states = controlStates.states();
507     NSButtonCell *buttonCell = button(part, controlStates, IntSize(zoomedRect.size()), zoomFactor);
508     GraphicsContextStateSaver stateSaver(context);
509
510     NSControlSize controlSize = [buttonCell controlSize];
511     IntSize zoomedSize = buttonSizes()[controlSize];
512     zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
513     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
514     FloatRect inflatedRect = zoomedRect;
515     if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
516         // Center the button within the available space.
517         if (inflatedRect.height() > zoomedSize.height()) {
518             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
519             inflatedRect.setHeight(zoomedSize.height());
520         }
521
522         // Now inflate it to account for the shadow.
523         inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
524
525         if (zoomFactor != 1.0f) {
526             inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
527             inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
528             context.translate(inflatedRect.x(), inflatedRect.y());
529             context.scale(FloatSize(zoomFactor, zoomFactor));
530             context.translate(-inflatedRect.x(), -inflatedRect.y());
531         }
532     }
533     
534     LocalCurrentGraphicsContext localContext(context);
535     
536     NSView *view = ThemeMac::ensuredView(scrollView, controlStates);
537     NSWindow *window = [view window];
538     NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell];
539
540     bool useImageBuffer = pageScaleFactor != 1.0f || zoomFactor != 1.0f;
541     bool needsRepaint = ThemeMac::drawCellOrFocusRingWithViewIntoContext(buttonCell, context, inflatedRect, view, true, states & ControlStates::FocusState, useImageBuffer, deviceScaleFactor);
542     if (states & ControlStates::DefaultState)
543         [window setDefaultButtonCell:buttonCell];
544     else if ([previousDefaultButtonCell isEqual:buttonCell])
545         [window setDefaultButtonCell:nil];
546     
547     controlStates.setNeedsRepaint(needsRepaint);
548
549     [buttonCell setControlView:nil];
550
551     if (![previousDefaultButtonCell isEqual:buttonCell])
552         [window setDefaultButtonCell:previousDefaultButtonCell];
553
554     END_BLOCK_OBJC_EXCEPTIONS
555 }
556
557 // Stepper
558
559 static const std::array<IntSize, 3>& stepperSizes()
560 {
561     static const std::array<IntSize, 3> sizes = { { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) } };
562     return sizes;
563 }
564
565 // We don't use controlSizeForFont() for steppers because the stepper height
566 // should be equal to or less than the corresponding text field height,
567 static NSControlSize stepperControlSizeForFont(const FontCascade& font)
568 {
569     int fontSize = font.pixelSize();
570     if (fontSize >= 18)
571         return NSRegularControlSize;
572     if (fontSize >= 13)
573         return NSSmallControlSize;
574     return NSMiniControlSize;
575 }
576
577 static void paintStepper(ControlStates& states, GraphicsContext& context, const FloatRect& zoomedRect, float zoomFactor, ScrollView*)
578 {
579     // We don't use NSStepperCell because there are no ways to draw an
580     // NSStepperCell with the up button highlighted.
581
582     HIThemeButtonDrawInfo drawInfo;
583     drawInfo.version = 0;
584     drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
585     drawInfo.adornment = kThemeAdornmentDefault;
586     ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), IntSize(zoomedRect.size()), zoomFactor);
587     if (controlSize == NSSmallControlSize)
588         drawInfo.kind = kThemeIncDecButtonSmall;
589     else if (controlSize == NSMiniControlSize)
590         drawInfo.kind = kThemeIncDecButtonMini;
591     else
592         drawInfo.kind = kThemeIncDecButton;
593
594     IntRect rect(zoomedRect);
595     GraphicsContextStateSaver stateSaver(context);
596     if (zoomFactor != 1.0f) {
597         rect.setWidth(rect.width() / zoomFactor);
598         rect.setHeight(rect.height() / zoomFactor);
599         context.translate(rect.x(), rect.y());
600         context.scale(FloatSize(zoomFactor, zoomFactor));
601         context.translate(-rect.x(), -rect.y());
602     }
603     CGRect bounds(rect);
604     CGRect backgroundBounds;
605     HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
606     // Center the stepper rectangle in the specified area.
607     backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2;
608     if (backgroundBounds.size.height < bounds.size.height) {
609         int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height);
610         backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1;
611     }
612
613     LocalCurrentGraphicsContext localContext(context);
614     HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0);
615 }
616
617 // This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView.
618 // If the ScrollView doesn't have an NSView, we will return a fake NSView set up in the way AppKit expects.
619 NSView *ThemeMac::ensuredView(ScrollView* scrollView, const ControlStates& controlStates, bool useUnparentedView)
620 {
621     if (!useUnparentedView) {
622         if (NSView *documentView = scrollView->documentView())
623             return documentView;
624     }
625
626     // Use a fake view.
627     static WebCoreThemeView *themeView = [[WebCoreThemeView alloc] init];
628     [themeView setFrameSize:NSSizeFromCGSize(scrollView->totalContentsSize())];
629
630     themeWindowHasKeyAppearance = !(controlStates.states() & ControlStates::WindowInactiveState);
631
632     return themeView;
633 }
634
635 void ThemeMac::setFocusRingClipRect(const FloatRect& rect)
636 {
637     focusRingClipRect = rect;
638 }
639
640 const float buttonFocusRectOutlineWidth = 3.0f;
641
642 static inline bool drawCellOrFocusRingIntoRectWithView(NSCell *cell, NSRect rect, NSView *view, bool drawButtonCell, bool drawFocusRing)
643 {
644     if (drawButtonCell) {
645         if ([cell isKindOfClass:[NSSliderCell class]]) {
646             // For slider cells, draw only the knob.
647             [(NSSliderCell *)cell drawKnob:rect];
648         } else
649             [cell drawWithFrame:rect inView:view];
650     }
651     if (drawFocusRing)
652         return drawCellFocusRing(cell, rect, view);
653
654     return false;
655 }
656
657 bool ThemeMac::drawCellOrFocusRingWithViewIntoContext(NSCell *cell, GraphicsContext& context, const FloatRect& rect, NSView *view, bool drawButtonCell, bool drawFocusRing, bool useImageBuffer, float deviceScaleFactor)
658 {
659     ASSERT(drawButtonCell || drawFocusRing);
660     bool needsRepaint = false;
661     if (useImageBuffer) {
662         NSRect imageBufferDrawRect = NSRect(FloatRect(buttonFocusRectOutlineWidth, buttonFocusRectOutlineWidth, rect.width(), rect.height()));
663         auto imageBuffer = ImageBuffer::createCompatibleBuffer(rect.size() + 2 * FloatSize(buttonFocusRectOutlineWidth, buttonFocusRectOutlineWidth), deviceScaleFactor, ColorSpaceSRGB, context, false);
664         if (!imageBuffer)
665             return needsRepaint;
666         {
667             LocalCurrentGraphicsContext localContext(imageBuffer->context());
668             needsRepaint = drawCellOrFocusRingIntoRectWithView(cell, imageBufferDrawRect, view, drawButtonCell, drawFocusRing);
669         }
670         context.drawConsumingImageBuffer(WTFMove(imageBuffer), rect.location() - FloatSize(buttonFocusRectOutlineWidth, buttonFocusRectOutlineWidth));
671         return needsRepaint;
672     }
673     if (drawButtonCell)
674         needsRepaint = drawCellOrFocusRingIntoRectWithView(cell, NSRect(rect), view, drawButtonCell, drawFocusRing);
675     
676     return needsRepaint;
677 }
678
679 // Theme overrides
680
681 int ThemeMac::baselinePositionAdjustment(ControlPart part) const
682 {
683     if (part == CheckboxPart || part == RadioPart)
684         return -2;
685     return Theme::baselinePositionAdjustment(part);
686 }
687
688 Optional<FontCascadeDescription> ThemeMac::controlFont(ControlPart part, const FontCascade& font, float zoomFactor) const
689 {
690     switch (part) {
691         case PushButtonPart: {
692             FontCascadeDescription fontDescription;
693             fontDescription.setIsAbsoluteSize(true);
694
695             NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
696             fontDescription.setOneFamily(AtomicString("-apple-system", AtomicString::ConstructFromLiteral));
697             fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
698             fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
699             return fontDescription;
700         }
701         default:
702             return Theme::controlFont(part, font, zoomFactor);
703     }
704 }
705
706 LengthSize ThemeMac::controlSize(ControlPart part, const FontCascade& font, const LengthSize& zoomedSize, float zoomFactor) const
707 {
708     switch (part) {
709         case CheckboxPart:
710             return checkboxSize(font, zoomedSize, zoomFactor);
711         case RadioPart:
712             return radioSize(font, zoomedSize, zoomFactor);
713         case PushButtonPart:
714             // Height is reset to auto so that specified heights can be ignored.
715             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
716         case InnerSpinButtonPart:
717             if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
718                 return zoomedSize;
719             return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
720         default:
721             return zoomedSize;
722     }
723 }
724
725 LengthSize ThemeMac::minimumControlSize(ControlPart part, const FontCascade& font, float zoomFactor) const
726 {
727     switch (part) {
728         case SquareButtonPart:
729         case DefaultButtonPart:
730         case ButtonPart:
731             return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
732         case InnerSpinButtonPart:{
733             IntSize base = stepperSizes()[NSMiniControlSize];
734             return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
735                               Length(static_cast<int>(base.height() * zoomFactor), Fixed));
736         }
737         default:
738             return Theme::minimumControlSize(part, font, zoomFactor);
739     }
740 }
741
742 LengthBox ThemeMac::controlBorder(ControlPart part, const FontCascade& font, const LengthBox& zoomedBox, float zoomFactor) const
743 {
744     switch (part) {
745         case SquareButtonPart:
746         case DefaultButtonPart:
747         case ButtonPart:
748             return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
749         default:
750             return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
751     }
752 }
753
754 LengthBox ThemeMac::controlPadding(ControlPart part, const FontCascade& font, const LengthBox& zoomedBox, float zoomFactor) const
755 {
756     switch (part) {
757         case PushButtonPart: {
758             // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
759             // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
760             // by definition constrained, since we select mini only for small cramped environments.
761             // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
762             // padding.
763             const int padding = 8 * zoomFactor;
764             return LengthBox(2, padding, 3, padding);
765         }
766         default:
767             return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
768     }
769 }
770
771 void ThemeMac::inflateControlPaintRect(ControlPart part, const ControlStates& states, FloatRect& zoomedRect, float zoomFactor) const
772 {
773     BEGIN_BLOCK_OBJC_EXCEPTIONS
774     IntSize zoomRectSize = IntSize(zoomedRect.size());
775     switch (part) {
776         case CheckboxPart: {
777             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
778             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
779             NSCell *cell = sharedCheckboxCell(states, zoomRectSize, zoomFactor);
780             NSControlSize controlSize = [cell controlSize];
781             IntSize zoomedSize = checkboxSizes()[controlSize];
782             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
783             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
784             zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
785             break;
786         }
787         case RadioPart: {
788             // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
789             // shadow".  We don't consider this part of the bounds of the control in WebKit.
790             NSCell *cell = sharedRadioCell(states, zoomRectSize, zoomFactor);
791             NSControlSize controlSize = [cell controlSize];
792             IntSize zoomedSize = radioSizes()[controlSize];
793             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
794             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
795             zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
796             break;
797         }
798         case PushButtonPart:
799         case DefaultButtonPart:
800         case ButtonPart: {
801             NSButtonCell *cell = button(part, states, zoomRectSize, zoomFactor);
802             NSControlSize controlSize = [cell controlSize];
803
804             // We inflate the rect as needed to account for the Aqua button's shadow.
805             if ([cell bezelStyle] == NSRoundedBezelStyle) {
806                 IntSize zoomedSize = buttonSizes()[controlSize];
807                 zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
808                 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
809                 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
810             }
811             break;
812         }
813         case InnerSpinButtonPart: {
814             static const int stepperMargin[4] = { 0, 0, 0, 0 };
815             ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomRectSize, zoomFactor);
816             IntSize zoomedSize = stepperSizes()[controlSize];
817             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
818             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
819             zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
820             break;
821         }
822         default:
823             break;
824     }
825     END_BLOCK_OBJC_EXCEPTIONS
826 }
827
828 void ThemeMac::paint(ControlPart part, ControlStates& states, GraphicsContext& context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView, float deviceScaleFactor, float pageScaleFactor)
829 {
830     switch (part) {
831         case CheckboxPart:
832             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView, deviceScaleFactor, pageScaleFactor);
833             break;
834         case RadioPart:
835             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView, deviceScaleFactor, pageScaleFactor);
836             break;
837         case PushButtonPart:
838         case DefaultButtonPart:
839         case ButtonPart:
840         case SquareButtonPart:
841             paintButton(part, states, context, zoomedRect, zoomFactor, scrollView, deviceScaleFactor, pageScaleFactor);
842             break;
843         case InnerSpinButtonPart:
844             paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
845             break;
846         default:
847             break;
848     }
849 }
850
851 }