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 "core/platform/mac/ThemeMac.h" 28 29 #import <Carbon/Carbon.h> 30 #import "core/platform/ScrollView.h" 31 #include "core/platform/graphics/GraphicsContextStateSaver.h" 32 #import "core/platform/mac/BlockExceptions.h" 33 #import "core/platform/mac/LocalCurrentGraphicsContext.h" 34 #import "core/platform/mac/WebCoreNSCellExtras.h" 35 #include "wtf/StdLibExtras.h" 36 37 using namespace std; 38 39 NSRect focusRingClipRect; 40 41 // This is a view whose sole purpose is to tell AppKit that it's flipped. 42 @interface WebCoreFlippedView : NSControl 43 @end 44 45 @implementation WebCoreFlippedView 46 47 - (BOOL)isFlipped 48 { 49 return YES; 50 } 51 52 - (NSText *)currentEditor 53 { 54 return nil; 55 } 56 57 - (BOOL)_automaticFocusRingDisabled 58 { 59 return YES; 60 } 61 62 - (NSRect)_focusRingVisibleRect 63 { 64 if (NSIsEmptyRect(focusRingClipRect)) 65 return [self visibleRect]; 66 67 NSRect rect = focusRingClipRect; 68 rect.origin.y = [self bounds].size.height - NSMaxY(rect); 69 70 return rect; 71 } 72 73 - (NSView *)_focusRingClipAncestor 74 { 75 return self; 76 } 77 78 @end 79 80 @implementation NSFont (WebCoreTheme) 81 82 - (NSString*)webCoreFamilyName 83 { 84 if ([[self familyName] hasPrefix:@"."]) 85 return [self fontName]; 86 87 return [self familyName]; 88 } 89 90 @end 91 92 namespace WebCore { 93 94 enum { 95 topMargin, 96 rightMargin, 97 bottomMargin, 98 leftMargin 99 }; 100 101 Theme* platformTheme() 102 { 103 DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ()); 104 return &themeMac; 105 } 106 107 // Helper functions used by a bunch of different control parts. 108 109 static NSControlSize controlSizeForFont(const Font& font) 110 { 111 int fontSize = font.pixelSize(); 112 if (fontSize >= 16) 113 return NSRegularControlSize; 114 if (fontSize >= 11) 115 return NSSmallControlSize; 116 return NSMiniControlSize; 117 } 118 119 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 120 { 121 IntSize controlSize = sizes[nsControlSize]; 122 if (zoomFactor != 1.0f) 123 controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor); 124 LengthSize result = zoomedSize; 125 if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0) 126 result.setWidth(Length(controlSize.width(), Fixed)); 127 if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0) 128 result.setHeight(Length(controlSize.height(), Fixed)); 129 return result; 130 } 131 132 static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 133 { 134 return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes); 135 } 136 137 static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 138 { 139 if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) && 140 minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor)) 141 return NSRegularControlSize; 142 if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) && 143 minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor)) 144 return NSSmallControlSize; 145 return NSMiniControlSize; 146 } 147 148 static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 149 { 150 ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor); 151 if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. 152 [cell setControlSize:(NSControlSize)size]; 153 } 154 155 static void updateStates(NSCell* cell, ControlStates states) 156 { 157 // Hover state is not supported by Aqua. 158 159 // Pressed state 160 bool oldPressed = [cell isHighlighted]; 161 bool pressed = states & PressedState; 162 if (pressed != oldPressed) 163 [cell setHighlighted:pressed]; 164 165 // Enabled state 166 bool oldEnabled = [cell isEnabled]; 167 bool enabled = states & EnabledState; 168 if (enabled != oldEnabled) 169 [cell setEnabled:enabled]; 170 171 #if BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 172 // Focused state 173 bool oldFocused = [cell showsFirstResponder]; 174 bool focused = states & FocusState; 175 if (focused != oldFocused) 176 [cell setShowsFirstResponder:focused]; 177 #endif 178 179 // Checked and Indeterminate 180 bool oldIndeterminate = [cell state] == NSMixedState; 181 bool indeterminate = (states & IndeterminateState); 182 bool checked = states & CheckedState; 183 bool oldChecked = [cell state] == NSOnState; 184 if (oldIndeterminate != indeterminate || checked != oldChecked) 185 [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)]; 186 187 // Window inactive state does not need to be checked explicitly, since we paint parented to 188 // a view in a window whose key state can be detected. 189 } 190 191 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states) 192 { 193 if (states & ReadOnlyState) 194 return kThemeStateUnavailableInactive; 195 if (!(states & EnabledState)) 196 return kThemeStateUnavailableInactive; 197 198 // Do not process PressedState if !EnabledState or ReadOnlyState. 199 if (states & PressedState) { 200 if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini) 201 return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown; 202 return kThemeStatePressed; 203 } 204 return kThemeStateActive; 205 } 206 207 static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor) 208 { 209 // Only do the inflation if the available width/height are too small. Otherwise try to 210 // fit the glow/check space into the available box's width/height. 211 int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor); 212 int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor); 213 IntRect result(zoomedRect); 214 if (widthDelta < 0) { 215 result.setX(result.x() - margins[leftMargin] * zoomFactor); 216 result.setWidth(result.width() - widthDelta); 217 } 218 if (heightDelta < 0) { 219 result.setY(result.y() - margins[topMargin] * zoomFactor); 220 result.setHeight(result.height() - heightDelta); 221 } 222 return result; 223 } 224 225 // Checkboxes 226 227 static const IntSize* checkboxSizes() 228 { 229 static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; 230 return sizes; 231 } 232 233 static const int* checkboxMargins(NSControlSize controlSize) 234 { 235 static const int margins[3][4] = 236 { 237 { 3, 4, 4, 2 }, 238 { 4, 3, 3, 3 }, 239 { 4, 3, 3, 3 }, 240 }; 241 return margins[controlSize]; 242 } 243 244 static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 245 { 246 // If the width and height are both specified, then we have nothing to do. 247 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 248 return zoomedSize; 249 250 // Use the font size to determine the intrinsic width of the control. 251 return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes()); 252 } 253 254 static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 255 { 256 static NSButtonCell *checkboxCell; 257 if (!checkboxCell) { 258 checkboxCell = [[NSButtonCell alloc] init]; 259 [checkboxCell setButtonType:NSSwitchButton]; 260 [checkboxCell setTitle:nil]; 261 [checkboxCell setAllowsMixedState:YES]; 262 [checkboxCell setFocusRingType:NSFocusRingTypeExterior]; 263 } 264 265 // Set the control size based off the rectangle we're painting into. 266 setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor); 267 268 // Update the various states we respond to. 269 updateStates(checkboxCell, states); 270 271 return checkboxCell; 272 } 273 274 // FIXME: Share more code with radio buttons. 275 static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 276 { 277 BEGIN_BLOCK_OBJC_EXCEPTIONS 278 279 // Determine the width and height needed for the control and prepare the cell for painting. 280 NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor); 281 GraphicsContextStateSaver stateSaver(*context); 282 283 NSControlSize controlSize = [checkboxCell controlSize]; 284 IntSize zoomedSize = checkboxSizes()[controlSize]; 285 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 286 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 287 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 288 289 if (zoomFactor != 1.0f) { 290 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 291 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 292 context->translate(inflatedRect.x(), inflatedRect.y()); 293 context->scale(FloatSize(zoomFactor, zoomFactor)); 294 context->translate(-inflatedRect.x(), -inflatedRect.y()); 295 } 296 297 LocalCurrentGraphicsContext localContext(context); 298 NSView *view = ThemeMac::ensuredView(scrollView); 299 [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:view]; 300 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 301 if (states & FocusState) 302 [checkboxCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 303 #endif 304 [checkboxCell setControlView:nil]; 305 306 END_BLOCK_OBJC_EXCEPTIONS 307 } 308 309 // Radio Buttons 310 311 static const IntSize* radioSizes() 312 { 313 static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; 314 return sizes; 315 } 316 317 static const int* radioMargins(NSControlSize controlSize) 318 { 319 static const int margins[3][4] = 320 { 321 { 2, 2, 4, 2 }, 322 { 3, 2, 3, 2 }, 323 { 1, 0, 2, 0 }, 324 }; 325 return margins[controlSize]; 326 } 327 328 static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 329 { 330 // If the width and height are both specified, then we have nothing to do. 331 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 332 return zoomedSize; 333 334 // Use the font size to determine the intrinsic width of the control. 335 return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes()); 336 } 337 338 static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 339 { 340 static NSButtonCell *radioCell; 341 if (!radioCell) { 342 radioCell = [[NSButtonCell alloc] init]; 343 [radioCell setButtonType:NSRadioButton]; 344 [radioCell setTitle:nil]; 345 [radioCell setFocusRingType:NSFocusRingTypeExterior]; 346 } 347 348 // Set the control size based off the rectangle we're painting into. 349 setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor); 350 351 // Update the various states we respond to. 352 updateStates(radioCell, states); 353 354 return radioCell; 355 } 356 357 static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 358 { 359 // Determine the width and height needed for the control and prepare the cell for painting. 360 NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor); 361 GraphicsContextStateSaver stateSaver(*context); 362 363 NSControlSize controlSize = [radioCell controlSize]; 364 IntSize zoomedSize = radioSizes()[controlSize]; 365 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 366 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 367 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 368 369 if (zoomFactor != 1.0f) { 370 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 371 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 372 context->translate(inflatedRect.x(), inflatedRect.y()); 373 context->scale(FloatSize(zoomFactor, zoomFactor)); 374 context->translate(-inflatedRect.x(), -inflatedRect.y()); 375 } 376 377 LocalCurrentGraphicsContext localContext(context); 378 BEGIN_BLOCK_OBJC_EXCEPTIONS 379 NSView *view = ThemeMac::ensuredView(scrollView); 380 [radioCell drawWithFrame:NSRect(inflatedRect) inView:view]; 381 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 382 if (states & FocusState) 383 [radioCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 384 #endif 385 [radioCell setControlView:nil]; 386 END_BLOCK_OBJC_EXCEPTIONS 387 } 388 389 // Buttons 390 391 // Buttons really only constrain height. They respect width. 392 static const IntSize* buttonSizes() 393 { 394 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; 395 return sizes; 396 } 397 398 static const int* buttonMargins(NSControlSize controlSize) 399 { 400 static const int margins[3][4] = 401 { 402 { 4, 6, 7, 6 }, 403 { 4, 5, 6, 5 }, 404 { 0, 1, 1, 1 }, 405 }; 406 return margins[controlSize]; 407 } 408 409 static void setUpButtonCell(NSButtonCell *cell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 410 { 411 // Set the control size based off the rectangle we're painting into. 412 const IntSize* sizes = buttonSizes(); 413 if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) { 414 // Use the square button 415 if ([cell bezelStyle] != NSShadowlessSquareBezelStyle) 416 [cell setBezelStyle:NSShadowlessSquareBezelStyle]; 417 } else if ([cell bezelStyle] != NSRoundedBezelStyle) 418 [cell setBezelStyle:NSRoundedBezelStyle]; 419 420 setControlSize(cell, sizes, zoomedRect.size(), zoomFactor); 421 422 // Update the various states we respond to. 423 updateStates(cell, states); 424 } 425 426 static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 427 { 428 static NSButtonCell *cell = nil; 429 if (!cell) { 430 cell = [[NSButtonCell alloc] init]; 431 [cell setTitle:nil]; 432 [cell setButtonType:NSMomentaryPushInButton]; 433 } 434 setUpButtonCell(cell, part, states, zoomedRect, zoomFactor); 435 return cell; 436 } 437 438 static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 439 { 440 BEGIN_BLOCK_OBJC_EXCEPTIONS 441 442 // Determine the width and height needed for the control and prepare the cell for painting. 443 NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor); 444 GraphicsContextStateSaver stateSaver(*context); 445 446 NSControlSize controlSize = [buttonCell controlSize]; 447 IntSize zoomedSize = buttonSizes()[controlSize]; 448 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 449 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 450 IntRect inflatedRect = zoomedRect; 451 if ([buttonCell bezelStyle] == NSRoundedBezelStyle) { 452 // Center the button within the available space. 453 if (inflatedRect.height() > zoomedSize.height()) { 454 inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2); 455 inflatedRect.setHeight(zoomedSize.height()); 456 } 457 458 // Now inflate it to account for the shadow. 459 inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 460 461 if (zoomFactor != 1.0f) { 462 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 463 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 464 context->translate(inflatedRect.x(), inflatedRect.y()); 465 context->scale(FloatSize(zoomFactor, zoomFactor)); 466 context->translate(-inflatedRect.x(), -inflatedRect.y()); 467 } 468 } 469 470 LocalCurrentGraphicsContext localContext(context); 471 NSView *view = ThemeMac::ensuredView(scrollView); 472 NSWindow *window = [view window]; 473 474 [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view]; 475 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 476 if (states & FocusState) 477 [buttonCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 478 #endif 479 [buttonCell setControlView:nil]; 480 481 END_BLOCK_OBJC_EXCEPTIONS 482 } 483 484 // Stepper 485 486 static const IntSize* stepperSizes() 487 { 488 static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) }; 489 return sizes; 490 } 491 492 // We don't use controlSizeForFont() for steppers because the stepper height 493 // should be equal to or less than the corresponding text field height, 494 static NSControlSize stepperControlSizeForFont(const Font& font) 495 { 496 int fontSize = font.pixelSize(); 497 if (fontSize >= 18) 498 return NSRegularControlSize; 499 if (fontSize >= 13) 500 return NSSmallControlSize; 501 return NSMiniControlSize; 502 } 503 504 static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*) 505 { 506 // We don't use NSStepperCell because there are no ways to draw an 507 // NSStepperCell with the up button highlighted. 508 509 HIThemeButtonDrawInfo drawInfo; 510 drawInfo.version = 0; 511 drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states); 512 drawInfo.adornment = kThemeAdornmentDefault; 513 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 514 if (controlSize == NSSmallControlSize) 515 drawInfo.kind = kThemeIncDecButtonSmall; 516 else if (controlSize == NSMiniControlSize) 517 drawInfo.kind = kThemeIncDecButtonMini; 518 else 519 drawInfo.kind = kThemeIncDecButton; 520 521 IntRect rect(zoomedRect); 522 GraphicsContextStateSaver stateSaver(*context); 523 if (zoomFactor != 1.0f) { 524 rect.setWidth(rect.width() / zoomFactor); 525 rect.setHeight(rect.height() / zoomFactor); 526 context->translate(rect.x(), rect.y()); 527 context->scale(FloatSize(zoomFactor, zoomFactor)); 528 context->translate(-rect.x(), -rect.y()); 529 } 530 CGRect bounds(rect); 531 CGRect backgroundBounds; 532 HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds); 533 // Center the stepper rectangle in the specified area. 534 backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2; 535 if (backgroundBounds.size.height < bounds.size.height) { 536 int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height); 537 backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1; 538 } 539 540 LocalCurrentGraphicsContext localContext(context); 541 HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0); 542 } 543 544 // This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView. 545 // If the ScrollView doesn't have an NSView, we will return a fake NSView whose sole purpose is to tell AppKit that it's flipped. 546 NSView *ThemeMac::ensuredView(ScrollView* scrollView) 547 { 548 549 // Use a fake flipped view. 550 static NSView *flippedView = [[WebCoreFlippedView alloc] init]; 551 [flippedView setFrameSize:NSSizeFromCGSize(scrollView->contentsSize())]; 552 553 return flippedView; 554 } 555 556 void ThemeMac::setFocusRingClipRect(const FloatRect& rect) 557 { 558 focusRingClipRect = rect; 559 } 560 561 // Theme overrides 562 563 int ThemeMac::baselinePositionAdjustment(ControlPart part) const 564 { 565 if (part == CheckboxPart || part == RadioPart) 566 return -2; 567 return Theme::baselinePositionAdjustment(part); 568 } 569 570 FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const 571 { 572 switch (part) { 573 case PushButtonPart: { 574 FontDescription fontDescription; 575 fontDescription.setIsAbsoluteSize(true); 576 fontDescription.setGenericFamily(FontDescription::SerifFamily); 577 578 NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]]; 579 fontDescription.firstFamily().setFamily([nsFont webCoreFamilyName]); 580 fontDescription.setComputedSize([nsFont pointSize] * zoomFactor); 581 fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor); 582 return fontDescription; 583 } 584 default: 585 return Theme::controlFont(part, font, zoomFactor); 586 } 587 } 588 589 LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const 590 { 591 switch (part) { 592 case CheckboxPart: 593 return checkboxSize(font, zoomedSize, zoomFactor); 594 case RadioPart: 595 return radioSize(font, zoomedSize, zoomFactor); 596 case PushButtonPart: 597 // Height is reset to auto so that specified heights can be ignored. 598 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes()); 599 case InnerSpinButtonPart: 600 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 601 return zoomedSize; 602 return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes()); 603 default: 604 return zoomedSize; 605 } 606 } 607 608 LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const 609 { 610 switch (part) { 611 case SquareButtonPart: 612 case ButtonPart: 613 return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed)); 614 case InnerSpinButtonPart:{ 615 IntSize base = stepperSizes()[NSMiniControlSize]; 616 return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed), 617 Length(static_cast<int>(base.height() * zoomFactor), Fixed)); 618 } 619 default: 620 return Theme::minimumControlSize(part, font, zoomFactor); 621 } 622 } 623 624 LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 625 { 626 switch (part) { 627 case SquareButtonPart: 628 case ButtonPart: 629 return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value()); 630 default: 631 return Theme::controlBorder(part, font, zoomedBox, zoomFactor); 632 } 633 } 634 635 LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 636 { 637 switch (part) { 638 case PushButtonPart: { 639 // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large 640 // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is 641 // by definition constrained, since we select mini only for small cramped environments. 642 // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent 643 // padding. 644 const int padding = 8 * zoomFactor; 645 return LengthBox(0, padding, 0, padding); 646 } 647 default: 648 return Theme::controlPadding(part, font, zoomedBox, zoomFactor); 649 } 650 } 651 652 void ThemeMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const 653 { 654 BEGIN_BLOCK_OBJC_EXCEPTIONS 655 switch (part) { 656 case CheckboxPart: { 657 // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox 658 // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. 659 NSCell *cell = checkbox(states, zoomedRect, zoomFactor); 660 NSControlSize controlSize = [cell controlSize]; 661 IntSize zoomedSize = checkboxSizes()[controlSize]; 662 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 663 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 664 zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 665 break; 666 } 667 case RadioPart: { 668 // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button 669 // shadow". We don't consider this part of the bounds of the control in WebKit. 670 NSCell *cell = radio(states, zoomedRect, zoomFactor); 671 NSControlSize controlSize = [cell controlSize]; 672 IntSize zoomedSize = radioSizes()[controlSize]; 673 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 674 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 675 zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 676 break; 677 } 678 case PushButtonPart: 679 case ButtonPart: { 680 NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor); 681 NSControlSize controlSize = [cell controlSize]; 682 683 // We inflate the rect as needed to account for the Aqua button's shadow. 684 if ([cell bezelStyle] == NSRoundedBezelStyle) { 685 IntSize zoomedSize = buttonSizes()[controlSize]; 686 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 687 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 688 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 689 } 690 break; 691 } 692 case InnerSpinButtonPart: { 693 static const int stepperMargin[4] = { 0, 0, 0, 0 }; 694 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 695 IntSize zoomedSize = stepperSizes()[controlSize]; 696 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 697 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 698 zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor); 699 break; 700 } 701 default: 702 break; 703 } 704 END_BLOCK_OBJC_EXCEPTIONS 705 } 706 707 void ThemeMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const 708 { 709 switch (part) { 710 case CheckboxPart: 711 paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView); 712 break; 713 case RadioPart: 714 paintRadio(states, context, zoomedRect, zoomFactor, scrollView); 715 break; 716 case PushButtonPart: 717 case ButtonPart: 718 case SquareButtonPart: 719 paintButton(part, states, context, zoomedRect, zoomFactor, scrollView); 720 break; 721 case InnerSpinButtonPart: 722 paintStepper(states, context, zoomedRect, zoomFactor, scrollView); 723 break; 724 default: 725 break; 726 } 727 } 728 729 } 730