| /* |
| * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2008, 2009 Google, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #import "third_party/blink/renderer/core/layout/layout_theme_mac.h" |
| |
| #import <AvailabilityMacros.h> |
| #import <Carbon/Carbon.h> |
| #import <Cocoa/Cocoa.h> |
| #import <math.h> |
| |
| #import "base/mac/mac_util.h" |
| #import "third_party/blink/public/platform/mac/web_sandbox_support.h" |
| #import "third_party/blink/public/platform/platform.h" |
| #import "third_party/blink/renderer/core/css_value_keywords.h" |
| #import "third_party/blink/renderer/core/fileapi/file_list.h" |
| #import "third_party/blink/renderer/core/html_names.h" |
| #import "third_party/blink/renderer/core/layout/layout_progress.h" |
| #import "third_party/blink/renderer/core/layout/layout_theme_default.h" |
| #import "third_party/blink/renderer/core/layout/layout_view.h" |
| #import "third_party/blink/renderer/core/style/shadow_list.h" |
| #import "third_party/blink/renderer/platform/data_resource_helper.h" |
| #import "third_party/blink/renderer/platform/fonts/string_truncator.h" |
| #import "third_party/blink/renderer/platform/graphics/bitmap_image.h" |
| #import "third_party/blink/renderer/platform/mac/block_exceptions.h" |
| #import "third_party/blink/renderer/platform/mac/color_mac.h" |
| #import "third_party/blink/renderer/platform/mac/web_core_ns_cell_extras.h" |
| #import "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #import "third_party/blink/renderer/platform/text/platform_locale.h" |
| #import "third_party/blink/renderer/platform/web_test_support.h" |
| |
| // This is a view whose sole purpose is to tell AppKit that it's flipped. |
| @interface BlinkFlippedControl : NSControl |
| @end |
| |
| @implementation BlinkFlippedControl |
| |
| - (BOOL)isFlipped { |
| return YES; |
| } |
| |
| - (NSText*)currentEditor { |
| return nil; |
| } |
| |
| - (BOOL)_automaticFocusRingDisabled { |
| return YES; |
| } |
| |
| @end |
| // The methods in this file are specific to the Mac OS X platform. |
| |
| @interface BlinkLayoutThemeNotificationObserver : NSObject { |
| blink::LayoutTheme* _theme; |
| } |
| |
| - (id)initWithTheme:(blink::LayoutTheme*)theme; |
| - (void)systemColorsDidChange:(NSNotification*)notification; |
| |
| @end |
| |
| @implementation BlinkLayoutThemeNotificationObserver |
| |
| - (id)initWithTheme:(blink::LayoutTheme*)theme { |
| if (!(self = [super init])) |
| return nil; |
| |
| _theme = theme; |
| return self; |
| } |
| |
| - (void)systemColorsDidChange:(NSNotification*)unusedNotification { |
| DCHECK([[unusedNotification name] |
| isEqualToString:NSSystemColorsDidChangeNotification]); |
| _theme->PlatformColorsDidChange(); |
| } |
| |
| @end |
| |
| @interface NSTextFieldCell (WKDetails) |
| - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame |
| inView:(NSView*)controlView |
| includeFocus:(BOOL)includeFocus; |
| @end |
| |
| @interface BlinkTextFieldCell : NSTextFieldCell |
| - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame |
| inView:(NSView*)controlView |
| includeFocus:(BOOL)includeFocus; |
| @end |
| |
| @implementation BlinkTextFieldCell |
| - (CFDictionaryRef)_coreUIDrawOptionsWithFrame:(NSRect)cellFrame |
| inView:(NSView*)controlView |
| includeFocus:(BOOL)includeFocus { |
| // FIXME: This is a post-Lion-only workaround for <rdar://problem/11385461>. |
| // When that bug is resolved, we should remove this code. |
| CFMutableDictionaryRef coreUIDrawOptions = CFDictionaryCreateMutableCopy( |
| NULL, 0, [super _coreUIDrawOptionsWithFrame:cellFrame |
| inView:controlView |
| includeFocus:includeFocus]); |
| CFDictionarySetValue(coreUIDrawOptions, @"borders only", kCFBooleanTrue); |
| return (CFDictionaryRef)[NSMakeCollectable(coreUIDrawOptions) autorelease]; |
| } |
| @end |
| |
| @interface BlinkFlippedView : NSView |
| @end |
| |
| @implementation BlinkFlippedView |
| |
| - (BOOL)isFlipped { |
| return YES; |
| } |
| |
| - (NSText*)currentEditor { |
| return nil; |
| } |
| |
| @end |
| |
| namespace blink { |
| |
| namespace { |
| |
| class LayoutThemeMacRefresh final : public LayoutThemeDefault { |
| public: |
| static scoped_refptr<LayoutTheme> Create() { |
| return base::AdoptRef(new LayoutThemeMacRefresh()); |
| } |
| }; |
| |
| // Inflate an IntRect to account for specific padding around margins. |
| enum { kTopMargin = 0, kRightMargin = 1, kBottomMargin = 2, kLeftMargin = 3 }; |
| |
| bool FontSizeMatchesToControlSize(const ComputedStyle& style) { |
| int font_size = style.FontSize(); |
| if (font_size == [NSFont systemFontSizeForControlSize:NSRegularControlSize]) |
| return true; |
| if (font_size == [NSFont systemFontSizeForControlSize:NSSmallControlSize]) |
| return true; |
| if (font_size == [NSFont systemFontSizeForControlSize:NSMiniControlSize]) |
| return true; |
| return false; |
| } |
| |
| Color GetSystemColor(MacSystemColorID color_id) { |
| // In tests, a WebSandboxSupport may not be set up. Just return a dummy |
| // color, in this case, black. |
| auto* sandbox_support = Platform::Current()->GetSandboxSupport(); |
| if (!sandbox_support) |
| return Color(); |
| return sandbox_support->GetSystemColor(color_id); |
| } |
| |
| // Helper functions used by a bunch of different control parts. |
| NSControlSize ControlSizeForFont(const FontDescription& font_description) { |
| int font_size = font_description.ComputedPixelSize(); |
| if (font_size >= 16) |
| return NSRegularControlSize; |
| if (font_size >= 11) |
| return NSSmallControlSize; |
| return NSMiniControlSize; |
| } |
| |
| LengthSize SizeFromNSControlSize(NSControlSize ns_control_size, |
| const LengthSize& zoomed_size, |
| float zoom_factor, |
| const IntSize* sizes) { |
| IntSize control_size = sizes[ns_control_size]; |
| if (zoom_factor != 1.0f) |
| control_size = IntSize(control_size.Width() * zoom_factor, |
| control_size.Height() * zoom_factor); |
| LengthSize result = zoomed_size; |
| if (zoomed_size.Width().IsIntrinsicOrAuto() && control_size.Width() > 0) |
| result.SetWidth(Length::Fixed(control_size.Width())); |
| if (zoomed_size.Height().IsIntrinsicOrAuto() && control_size.Height() > 0) |
| result.SetHeight(Length::Fixed(control_size.Height())); |
| return result; |
| } |
| |
| LengthSize SizeFromFont(const FontDescription& font_description, |
| const LengthSize& zoomed_size, |
| float zoom_factor, |
| const IntSize* sizes) { |
| return SizeFromNSControlSize(ControlSizeForFont(font_description), |
| zoomed_size, zoom_factor, sizes); |
| } |
| |
| } // namespace |
| |
| LayoutThemeMac::LayoutThemeMac() |
| : LayoutTheme(), |
| notification_observer_( |
| [[BlinkLayoutThemeNotificationObserver alloc] initWithTheme:this]), |
| painter_(*this) { |
| [[NSNotificationCenter defaultCenter] |
| addObserver:notification_observer_ |
| selector:@selector(systemColorsDidChange:) |
| name:NSSystemColorsDidChangeNotification |
| object:nil]; |
| } |
| |
| LayoutThemeMac::~LayoutThemeMac() { |
| [[NSNotificationCenter defaultCenter] removeObserver:notification_observer_]; |
| } |
| |
| Color LayoutThemeMac::PlatformActiveSelectionBackgroundColor() const { |
| return GetSystemColor(MacSystemColorID::kSelectedTextBackground); |
| } |
| |
| Color LayoutThemeMac::PlatformInactiveSelectionBackgroundColor() const { |
| return GetSystemColor(MacSystemColorID::kSecondarySelectedControl); |
| } |
| |
| Color LayoutThemeMac::PlatformActiveSelectionForegroundColor() const { |
| return Color::kBlack; |
| } |
| |
| Color LayoutThemeMac::PlatformActiveListBoxSelectionBackgroundColor() const { |
| return GetSystemColor(MacSystemColorID::kAlternateSelectedControl); |
| } |
| |
| Color LayoutThemeMac::PlatformActiveListBoxSelectionForegroundColor() const { |
| return Color::kWhite; |
| } |
| |
| Color LayoutThemeMac::PlatformInactiveListBoxSelectionForegroundColor() const { |
| return Color::kBlack; |
| } |
| |
| Color LayoutThemeMac::PlatformSpellingMarkerUnderlineColor() const { |
| return Color(251, 45, 29); |
| } |
| |
| Color LayoutThemeMac::PlatformGrammarMarkerUnderlineColor() const { |
| return Color(107, 107, 107); |
| } |
| |
| Color LayoutThemeMac::PlatformFocusRingColor() const { |
| static const RGBA32 kOldAquaFocusRingColor = 0xFF7DADD9; |
| if (UsesTestModeFocusRingColor()) |
| return kOldAquaFocusRingColor; |
| |
| return SystemColor(CSSValueID::kWebkitFocusRingColor); |
| } |
| |
| Color LayoutThemeMac::PlatformInactiveListBoxSelectionBackgroundColor() const { |
| return PlatformInactiveSelectionBackgroundColor(); |
| } |
| |
| static FontSelectionValue ToFontWeight(NSInteger app_kit_font_weight) { |
| DCHECK_GT(app_kit_font_weight, 0); |
| DCHECK_LT(app_kit_font_weight, 15); |
| if (app_kit_font_weight > 14) |
| app_kit_font_weight = 14; |
| else if (app_kit_font_weight < 1) |
| app_kit_font_weight = 1; |
| |
| static FontSelectionValue font_weights[] = { |
| FontSelectionValue(100), FontSelectionValue(100), FontSelectionValue(200), |
| FontSelectionValue(300), FontSelectionValue(400), FontSelectionValue(500), |
| FontSelectionValue(600), FontSelectionValue(600), FontSelectionValue(700), |
| FontSelectionValue(800), FontSelectionValue(800), FontSelectionValue(900), |
| FontSelectionValue(900), FontSelectionValue(900)}; |
| return font_weights[app_kit_font_weight - 1]; |
| } |
| |
| static inline NSFont* SystemNSFont(CSSValueID system_font_id) { |
| switch (system_font_id) { |
| case CSSValueID::kSmallCaption: |
| return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; |
| case CSSValueID::kMenu: |
| return [NSFont menuFontOfSize:[NSFont systemFontSize]]; |
| case CSSValueID::kStatusBar: |
| return [NSFont labelFontOfSize:[NSFont labelFontSize]]; |
| case CSSValueID::kWebkitMiniControl: |
| return [NSFont |
| systemFontOfSize:[NSFont |
| systemFontSizeForControlSize:NSMiniControlSize]]; |
| case CSSValueID::kWebkitSmallControl: |
| return [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize: |
| NSSmallControlSize]]; |
| case CSSValueID::kWebkitControl: |
| return [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize: |
| NSRegularControlSize]]; |
| default: |
| return [NSFont systemFontOfSize:[NSFont systemFontSize]]; |
| } |
| } |
| |
| void LayoutThemeMac::SystemFont(CSSValueID system_font_id, |
| FontSelectionValue& font_slope, |
| FontSelectionValue& font_weight, |
| float& font_size, |
| AtomicString& font_family) const { |
| NSFont* font = SystemNSFont(system_font_id); |
| if (!font) |
| return; |
| |
| NSFontManager* font_manager = [NSFontManager sharedFontManager]; |
| font_slope = ([font_manager traitsOfFont:font] & NSItalicFontMask) |
| ? ItalicSlopeValue() |
| : NormalSlopeValue(); |
| font_weight = ToFontWeight([font_manager weightOfFont:font]); |
| font_size = [font pointSize]; |
| font_family = font_family_names::kSystemUi; |
| } |
| |
| void LayoutThemeMac::PlatformColorsDidChange() { |
| system_color_cache_.clear(); |
| LayoutTheme::PlatformColorsDidChange(); |
| } |
| |
| Color LayoutThemeMac::SystemColor(CSSValueID css_value_id) const { |
| { |
| HashMap<CSSValueID, RGBA32>::iterator it = |
| system_color_cache_.find(css_value_id); |
| if (it != system_color_cache_.end()) |
| return it->value; |
| } |
| |
| Color color; |
| bool needs_fallback = false; |
| switch (css_value_id) { |
| case CSSValueID::kActiveborder: |
| color = GetSystemColor(MacSystemColorID::kKeyboardFocusIndicator); |
| break; |
| case CSSValueID::kActivecaption: |
| color = GetSystemColor(MacSystemColorID::kWindowFrameText); |
| break; |
| case CSSValueID::kAppworkspace: |
| color = GetSystemColor(MacSystemColorID::kHeader); |
| break; |
| case CSSValueID::kBackground: |
| // Use theme independent default |
| needs_fallback = true; |
| break; |
| case CSSValueID::kButtonface: |
| color = GetSystemColor(MacSystemColorID::kControlBackground); |
| break; |
| case CSSValueID::kButtonhighlight: |
| color = GetSystemColor(MacSystemColorID::kControlHighlight); |
| break; |
| case CSSValueID::kButtonshadow: |
| color = GetSystemColor(MacSystemColorID::kControlShadow); |
| break; |
| case CSSValueID::kButtontext: |
| color = GetSystemColor(MacSystemColorID::kControlText); |
| break; |
| case CSSValueID::kCaptiontext: |
| color = GetSystemColor(MacSystemColorID::kText); |
| break; |
| case CSSValueID::kGraytext: |
| color = GetSystemColor(MacSystemColorID::kDisabledControlText); |
| break; |
| case CSSValueID::kHighlight: |
| color = GetSystemColor(MacSystemColorID::kSelectedTextBackground); |
| break; |
| case CSSValueID::kHighlighttext: |
| color = GetSystemColor(MacSystemColorID::kSelectedText); |
| break; |
| case CSSValueID::kInactiveborder: |
| color = GetSystemColor(MacSystemColorID::kControlBackground); |
| break; |
| case CSSValueID::kInactivecaption: |
| color = GetSystemColor(MacSystemColorID::kControlBackground); |
| break; |
| case CSSValueID::kInactivecaptiontext: |
| color = GetSystemColor(MacSystemColorID::kText); |
| break; |
| case CSSValueID::kInfobackground: |
| // There is no corresponding NSColor for this so we use a hard coded |
| // value. |
| color = 0xFFFBFCC5; |
| break; |
| case CSSValueID::kInfotext: |
| color = GetSystemColor(MacSystemColorID::kText); |
| break; |
| case CSSValueID::kMenu: |
| color = GetSystemColor(MacSystemColorID::kMenuBackground); |
| break; |
| case CSSValueID::kMenutext: |
| color = GetSystemColor(MacSystemColorID::kSelectedMenuItemText); |
| break; |
| case CSSValueID::kScrollbar: |
| color = GetSystemColor(MacSystemColorID::kScrollBar); |
| break; |
| case CSSValueID::kText: |
| color = GetSystemColor(MacSystemColorID::kText); |
| break; |
| case CSSValueID::kThreeddarkshadow: |
| color = GetSystemColor(MacSystemColorID::kControlDarkShadow); |
| break; |
| case CSSValueID::kThreedshadow: |
| color = GetSystemColor(MacSystemColorID::kShadow); |
| break; |
| case CSSValueID::kThreedface: |
| // We use this value instead of NSColor's controlColor to avoid website |
| // incompatibilities. We may want to change this to use the NSColor in |
| // future. |
| color = 0xFFC0C0C0; |
| break; |
| case CSSValueID::kThreedhighlight: |
| color = GetSystemColor(MacSystemColorID::kHighlight); |
| break; |
| case CSSValueID::kThreedlightshadow: |
| color = GetSystemColor(MacSystemColorID::kControlLightHighlight); |
| break; |
| case CSSValueID::kWebkitFocusRingColor: |
| color = GetSystemColor(MacSystemColorID::kKeyboardFocusIndicator); |
| break; |
| case CSSValueID::kWindow: |
| color = GetSystemColor(MacSystemColorID::kWindowBackground); |
| break; |
| case CSSValueID::kWindowframe: |
| color = GetSystemColor(MacSystemColorID::kWindowFrame); |
| break; |
| case CSSValueID::kWindowtext: |
| color = GetSystemColor(MacSystemColorID::kWindowFrameText); |
| break; |
| default: |
| needs_fallback = true; |
| break; |
| } |
| |
| if (needs_fallback) |
| color = LayoutTheme::SystemColor(css_value_id); |
| |
| system_color_cache_.Set(css_value_id, color.Rgb()); |
| |
| return color; |
| } |
| |
| bool LayoutThemeMac::IsControlStyled(const ComputedStyle& style) const { |
| if (style.Appearance() == kTextFieldPart || |
| style.Appearance() == kTextAreaPart) |
| return style.HasAuthorBorder() || style.BoxShadow(); |
| |
| if (style.Appearance() == kMenulistPart) { |
| // FIXME: This is horrible, but there is not much else that can be done. |
| // Menu lists cannot draw properly when scaled. They can't really draw |
| // properly when transformed either. We can't detect the transform case |
| // at style adjustment time so that will just have to stay broken. We |
| // can however detect that we're zooming. If zooming is in effect we |
| // treat it like the control is styled. |
| if (style.EffectiveZoom() != 1.0f) |
| return true; |
| if (!FontSizeMatchesToControlSize(style)) |
| return true; |
| if (style.GetFontDescription().Family().Family() != |
| font_family_names::kSystemUi) |
| return true; |
| if (!style.Height().IsIntrinsicOrAuto()) |
| return true; |
| } |
| // Some other cells don't work well when scaled. |
| if (style.EffectiveZoom() != 1) { |
| switch (style.Appearance()) { |
| case kButtonPart: |
| case kPushButtonPart: |
| case kSearchFieldPart: |
| case kSquareButtonPart: |
| return true; |
| default: |
| break; |
| } |
| } |
| return LayoutTheme::IsControlStyled(style); |
| } |
| |
| void LayoutThemeMac::AddVisualOverflow(const Node* node, |
| const ComputedStyle& style, |
| IntRect& rect) { |
| ControlPart part = style.Appearance(); |
| switch (part) { |
| case kCheckboxPart: |
| case kRadioPart: |
| case kPushButtonPart: |
| case kSquareButtonPart: |
| case kButtonPart: |
| case kInnerSpinButtonPart: |
| return AddVisualOverflowHelper(style.Appearance(), |
| ControlStatesForNode(node, style), |
| style.EffectiveZoom(), rect); |
| default: |
| break; |
| } |
| |
| float zoom_level = style.EffectiveZoom(); |
| |
| if (part == kMenulistPart) { |
| SetPopupButtonCellState(node, style, rect); |
| IntSize size = PopupButtonSizes()[[PopupButton() controlSize]]; |
| size.SetHeight(size.Height() * zoom_level); |
| size.SetWidth(rect.Width()); |
| rect = InflateRect(rect, size, PopupButtonMargins(), zoom_level); |
| } else if (part == kSliderThumbHorizontalPart || |
| part == kSliderThumbVerticalPart) { |
| rect.SetHeight(rect.Height() + kSliderThumbShadowBlur); |
| } |
| } |
| |
| void LayoutThemeMac::UpdateCheckedState(NSCell* cell, const Node* node) { |
| bool old_indeterminate = [cell state] == NSMixedState; |
| bool indeterminate = IsIndeterminate(node); |
| bool checked = IsChecked(node); |
| |
| if (old_indeterminate != indeterminate) { |
| [cell setState:indeterminate ? NSMixedState |
| : (checked ? NSOnState : NSOffState)]; |
| return; |
| } |
| |
| bool old_checked = [cell state] == NSOnState; |
| if (checked != old_checked) |
| [cell setState:checked ? NSOnState : NSOffState]; |
| } |
| |
| void LayoutThemeMac::UpdateEnabledState(NSCell* cell, const Node* node) { |
| bool old_enabled = [cell isEnabled]; |
| bool enabled = IsEnabled(node); |
| if (enabled != old_enabled) |
| [cell setEnabled:enabled]; |
| } |
| |
| void LayoutThemeMac::UpdateFocusedState(NSCell* cell, |
| const Node* node, |
| const ComputedStyle& style) { |
| bool old_focused = [cell showsFirstResponder]; |
| bool focused = IsFocused(node) && style.OutlineStyleIsAuto(); |
| if (focused != old_focused) |
| [cell setShowsFirstResponder:focused]; |
| } |
| |
| void LayoutThemeMac::UpdatePressedState(NSCell* cell, const Node* node) { |
| bool old_pressed = [cell isHighlighted]; |
| bool pressed = node && node->IsActive(); |
| if (pressed != old_pressed) |
| [cell setHighlighted:pressed]; |
| } |
| |
| NSControlSize LayoutThemeMac::ControlSizeForFont( |
| const ComputedStyle& style) const { |
| int font_size = style.FontSize(); |
| if (font_size >= 16) |
| return NSRegularControlSize; |
| if (font_size >= 11) |
| return NSSmallControlSize; |
| return NSMiniControlSize; |
| } |
| |
| void LayoutThemeMac::SetControlSize(NSCell* cell, |
| const IntSize* sizes, |
| const IntSize& min_size, |
| float zoom_level) { |
| NSControlSize size; |
| if (min_size.Width() >= |
| static_cast<int>(sizes[NSRegularControlSize].Width() * zoom_level) && |
| min_size.Height() >= |
| static_cast<int>(sizes[NSRegularControlSize].Height() * zoom_level)) |
| size = NSRegularControlSize; |
| else if (min_size.Width() >= |
| static_cast<int>(sizes[NSSmallControlSize].Width() * |
| zoom_level) && |
| min_size.Height() >= |
| static_cast<int>(sizes[NSSmallControlSize].Height() * |
| zoom_level)) |
| size = NSSmallControlSize; |
| else |
| size = NSMiniControlSize; |
| // Only update if we have to, since AppKit does work even if the size is the |
| // same. |
| if (size != [cell controlSize]) |
| [cell setControlSize:size]; |
| } |
| |
| IntSize LayoutThemeMac::SizeForFont(const ComputedStyle& style, |
| const IntSize* sizes) const { |
| if (style.EffectiveZoom() != 1.0f) { |
| IntSize result = sizes[ControlSizeForFont(style)]; |
| return IntSize(result.Width() * style.EffectiveZoom(), |
| result.Height() * style.EffectiveZoom()); |
| } |
| return sizes[ControlSizeForFont(style)]; |
| } |
| |
| IntSize LayoutThemeMac::SizeForSystemFont(const ComputedStyle& style, |
| const IntSize* sizes) const { |
| if (style.EffectiveZoom() != 1.0f) { |
| IntSize result = sizes[ControlSizeForSystemFont(style)]; |
| return IntSize(result.Width() * style.EffectiveZoom(), |
| result.Height() * style.EffectiveZoom()); |
| } |
| return sizes[ControlSizeForSystemFont(style)]; |
| } |
| |
| void LayoutThemeMac::SetSizeFromFont(ComputedStyle& style, |
| const IntSize* sizes) const { |
| // FIXME: Check is flawed, since it doesn't take min-width/max-width into |
| // account. |
| IntSize size = SizeForFont(style, sizes); |
| if (style.Width().IsIntrinsicOrAuto() && size.Width() > 0) |
| style.SetWidth(Length::Fixed(size.Width())); |
| if (style.Height().IsAuto() && size.Height() > 0) |
| style.SetHeight(Length::Fixed(size.Height())); |
| } |
| |
| void LayoutThemeMac::SetFontFromControlSize(ComputedStyle& style, |
| NSControlSize control_size) const { |
| FontDescription font_description; |
| font_description.SetIsAbsoluteSize(true); |
| font_description.SetGenericFamily(FontDescription::kSerifFamily); |
| |
| NSFont* font = [NSFont |
| systemFontOfSize:[NSFont systemFontSizeForControlSize:control_size]]; |
| font_description.FirstFamily().SetFamily(font_family_names::kSystemUi); |
| font_description.SetComputedSize([font pointSize] * style.EffectiveZoom()); |
| font_description.SetSpecifiedSize([font pointSize] * style.EffectiveZoom()); |
| |
| // Reset line height. |
| style.SetLineHeight(ComputedStyleInitialValues::InitialLineHeight()); |
| |
| // TODO(esprehn): The fontSelector manual management is buggy and error prone. |
| FontSelector* font_selector = style.GetFont().GetFontSelector(); |
| if (style.SetFontDescription(font_description)) |
| style.GetFont().Update(font_selector); |
| } |
| |
| NSControlSize LayoutThemeMac::ControlSizeForSystemFont( |
| const ComputedStyle& style) const { |
| float font_size = style.FontSize(); |
| float zoom_level = style.EffectiveZoom(); |
| if (zoom_level != 1) |
| font_size /= zoom_level; |
| if (font_size >= [NSFont systemFontSizeForControlSize:NSRegularControlSize]) |
| return NSRegularControlSize; |
| if (font_size >= [NSFont systemFontSizeForControlSize:NSSmallControlSize]) |
| return NSSmallControlSize; |
| return NSMiniControlSize; |
| } |
| |
| const int* LayoutThemeMac::PopupButtonMargins() const { |
| static const int kMargins[3][4] = {{0, 3, 1, 3}, {0, 3, 2, 3}, {0, 1, 0, 1}}; |
| return kMargins[[PopupButton() controlSize]]; |
| } |
| |
| const IntSize* LayoutThemeMac::PopupButtonSizes() const { |
| static const IntSize kSizes[3] = {IntSize(0, 21), IntSize(0, 18), |
| IntSize(0, 15)}; |
| return kSizes; |
| } |
| |
| const int* LayoutThemeMac::PopupButtonPadding(NSControlSize size) const { |
| static const int kPadding[3][4] = { |
| {2, 26, 3, 8}, {2, 23, 3, 8}, {2, 22, 3, 10}}; |
| return kPadding[size]; |
| } |
| |
| const int* LayoutThemeMac::ProgressBarHeights() const { |
| static const int kSizes[3] = {20, 12, 12}; |
| return kSizes; |
| } |
| |
| constexpr TimeDelta LayoutThemeMac::kProgressAnimationFrameRate; |
| |
| TimeDelta LayoutThemeMac::AnimationRepeatIntervalForProgressBar() const { |
| return kProgressAnimationFrameRate; |
| } |
| |
| TimeDelta LayoutThemeMac::AnimationDurationForProgressBar() const { |
| return kProgressAnimationNumFrames * kProgressAnimationFrameRate; |
| } |
| |
| static const IntSize* MenuListButtonSizes() { |
| static const IntSize kSizes[3] = {IntSize(0, 21), IntSize(0, 18), |
| IntSize(0, 15)}; |
| return kSizes; |
| } |
| |
| void LayoutThemeMac::AdjustMenuListStyle(ComputedStyle& style, |
| Element* e) const { |
| NSControlSize control_size = ControlSizeForFont(style); |
| |
| style.ResetBorder(); |
| style.ResetPadding(); |
| |
| // Height is locked to auto. |
| style.SetHeight(Length::Auto()); |
| |
| // White-space is locked to pre. |
| style.SetWhiteSpace(EWhiteSpace::kPre); |
| |
| // Set the foreground color to black or gray when we have the aqua look. |
| // Cast to RGB32 is to work around a compiler bug. |
| style.SetColor(e && !e->IsDisabledFormControl() |
| ? static_cast<RGBA32>(Color::kBlack) |
| : Color::kDarkGray); |
| |
| // Set the button's vertical size. |
| SetSizeFromFont(style, MenuListButtonSizes()); |
| |
| // 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 a |
| // reasonable control size, but once that control size is determined, we |
| // throw that font away and use the appropriate system font for the control |
| // size instead. |
| SetFontFromControlSize(style, control_size); |
| } |
| |
| static const int kBaseBorderRadius = 5; |
| static const int kStyledPopupPaddingStart = 8; |
| static const int kStyledPopupPaddingTop = 1; |
| static const int kStyledPopupPaddingBottom = 2; |
| |
| // These functions are called with MenuListPart or MenulistButtonPart appearance |
| // by LayoutMenuList. |
| int LayoutThemeMac::PopupInternalPaddingStart( |
| const ComputedStyle& style) const { |
| if (style.Appearance() == kMenulistPart) |
| return PopupButtonPadding(ControlSizeForFont(style))[kLeftMargin] * |
| style.EffectiveZoom(); |
| if (style.Appearance() == kMenulistButtonPart) |
| return kStyledPopupPaddingStart * style.EffectiveZoom(); |
| return 0; |
| } |
| |
| int LayoutThemeMac::PopupInternalPaddingEnd(const ChromeClient*, |
| const ComputedStyle& style) const { |
| if (style.Appearance() == kMenulistPart) |
| return PopupButtonPadding(ControlSizeForFont(style))[kRightMargin] * |
| style.EffectiveZoom(); |
| if (style.Appearance() != kMenulistButtonPart) |
| return 0; |
| float font_scale = style.FontSize() / kBaseFontSize; |
| float arrow_width = kMenuListBaseArrowWidth * font_scale; |
| return static_cast<int>(ceilf( |
| arrow_width + (kMenuListArrowPaddingStart + kMenuListArrowPaddingEnd) * |
| style.EffectiveZoom())); |
| } |
| |
| int LayoutThemeMac::PopupInternalPaddingTop(const ComputedStyle& style) const { |
| if (style.Appearance() == kMenulistPart) |
| return PopupButtonPadding(ControlSizeForFont(style))[kTopMargin] * |
| style.EffectiveZoom(); |
| if (style.Appearance() == kMenulistButtonPart) |
| return kStyledPopupPaddingTop * style.EffectiveZoom(); |
| return 0; |
| } |
| |
| int LayoutThemeMac::PopupInternalPaddingBottom( |
| const ComputedStyle& style) const { |
| if (style.Appearance() == kMenulistPart) |
| return PopupButtonPadding(ControlSizeForFont(style))[kBottomMargin] * |
| style.EffectiveZoom(); |
| if (style.Appearance() == kMenulistButtonPart) |
| return kStyledPopupPaddingBottom * style.EffectiveZoom(); |
| return 0; |
| } |
| |
| void LayoutThemeMac::AdjustMenuListButtonStyle(ComputedStyle& style, |
| Element*) const { |
| float font_scale = style.FontSize() / kBaseFontSize; |
| |
| style.ResetPadding(); |
| style.SetBorderRadius( |
| IntSize(int(kBaseBorderRadius + font_scale - 1), |
| int(kBaseBorderRadius + font_scale - 1))); // FIXME: Round up? |
| |
| const int kMinHeight = 15; |
| style.SetMinHeight(Length::Fixed(kMinHeight)); |
| |
| style.SetLineHeight(ComputedStyleInitialValues::InitialLineHeight()); |
| } |
| |
| void LayoutThemeMac::SetPopupButtonCellState(const Node* node, |
| const ComputedStyle& style, |
| const IntRect& rect) { |
| NSPopUpButtonCell* popup_button = this->PopupButton(); |
| |
| // Set the control size based off the rectangle we're painting into. |
| SetControlSize(popup_button, PopupButtonSizes(), rect.Size(), |
| style.EffectiveZoom()); |
| |
| // Update the various states we respond to. |
| UpdateActiveState(popup_button, node); |
| UpdateCheckedState(popup_button, node); |
| UpdateEnabledState(popup_button, node); |
| UpdatePressedState(popup_button, node); |
| |
| popup_button.userInterfaceLayoutDirection = |
| style.Direction() == TextDirection::kLtr |
| ? NSUserInterfaceLayoutDirectionLeftToRight |
| : NSUserInterfaceLayoutDirectionRightToLeft; |
| } |
| |
| const IntSize* LayoutThemeMac::MenuListSizes() const { |
| static const IntSize kSizes[3] = {IntSize(9, 0), IntSize(5, 0), |
| IntSize(0, 0)}; |
| return kSizes; |
| } |
| |
| int LayoutThemeMac::MinimumMenuListSize(const ComputedStyle& style) const { |
| return SizeForSystemFont(style, MenuListSizes()).Width(); |
| } |
| |
| void LayoutThemeMac::SetSearchCellState(const Node* node, |
| const ComputedStyle& style, |
| const IntRect&) { |
| NSSearchFieldCell* search = this->Search(); |
| |
| // Update the various states we respond to. |
| UpdateActiveState(search, node); |
| UpdateEnabledState(search, node); |
| UpdateFocusedState(search, node, style); |
| } |
| |
| const IntSize* LayoutThemeMac::SearchFieldSizes() const { |
| static const IntSize kSizes[3] = {IntSize(0, 22), IntSize(0, 19), |
| IntSize(0, 15)}; |
| return kSizes; |
| } |
| |
| static const int* SearchFieldHorizontalPaddings() { |
| static const int kSizes[3] = {3, 2, 1}; |
| return kSizes; |
| } |
| |
| void LayoutThemeMac::SetSearchFieldSize(ComputedStyle& style) const { |
| // If the width and height are both specified, then we have nothing to do. |
| if (!style.Width().IsIntrinsicOrAuto() && !style.Height().IsAuto()) |
| return; |
| |
| // Use the font size to determine the intrinsic width of the control. |
| SetSizeFromFont(style, SearchFieldSizes()); |
| } |
| |
| const uint8_t kSearchFieldBorderWidth = 2; |
| void LayoutThemeMac::AdjustSearchFieldStyle(ComputedStyle& style) const { |
| // Override border. |
| style.ResetBorder(); |
| const float border_width = kSearchFieldBorderWidth * style.EffectiveZoom(); |
| style.SetBorderLeftWidth(border_width); |
| style.SetBorderLeftStyle(EBorderStyle::kInset); |
| style.SetBorderRightWidth(border_width); |
| style.SetBorderRightStyle(EBorderStyle::kInset); |
| style.SetBorderBottomWidth(border_width); |
| style.SetBorderBottomStyle(EBorderStyle::kInset); |
| style.SetBorderTopWidth(border_width); |
| style.SetBorderTopStyle(EBorderStyle::kInset); |
| |
| // Override height. |
| style.SetHeight(Length::Auto()); |
| SetSearchFieldSize(style); |
| |
| NSControlSize control_size = ControlSizeForFont(style); |
| |
| // Override padding size to match AppKit text positioning. |
| const int vertical_padding = 1 * style.EffectiveZoom(); |
| const int horizontal_padding = |
| SearchFieldHorizontalPaddings()[control_size] * style.EffectiveZoom(); |
| style.SetPaddingLeft(Length::Fixed(horizontal_padding)); |
| style.SetPaddingRight(Length::Fixed(horizontal_padding)); |
| style.SetPaddingTop(Length::Fixed(vertical_padding)); |
| style.SetPaddingBottom(Length::Fixed(vertical_padding)); |
| |
| SetFontFromControlSize(style, control_size); |
| |
| style.SetBoxShadow(nullptr); |
| } |
| |
| const IntSize* LayoutThemeMac::CancelButtonSizes() const { |
| static const IntSize kSizes[3] = {IntSize(14, 14), IntSize(11, 11), |
| IntSize(9, 9)}; |
| return kSizes; |
| } |
| |
| void LayoutThemeMac::AdjustSearchFieldCancelButtonStyle( |
| ComputedStyle& style) const { |
| IntSize size = SizeForSystemFont(style, CancelButtonSizes()); |
| style.SetWidth(Length::Fixed(size.Width())); |
| style.SetHeight(Length::Fixed(size.Height())); |
| style.SetBoxShadow(nullptr); |
| } |
| |
| IntSize LayoutThemeMac::SliderTickSize() const { |
| return IntSize(1, 3); |
| } |
| |
| int LayoutThemeMac::SliderTickOffsetFromTrackCenter() const { |
| return -9; |
| } |
| |
| void LayoutThemeMac::AdjustProgressBarBounds(ComputedStyle& style) const { |
| float zoom_level = style.EffectiveZoom(); |
| NSControlSize control_size = ControlSizeForFont(style); |
| int height = ProgressBarHeights()[control_size] * zoom_level; |
| |
| // Now inflate it to account for the shadow. |
| style.SetMinHeight(Length::Fixed(height + zoom_level)); |
| } |
| |
| void LayoutThemeMac::AdjustSliderThumbSize(ComputedStyle& style) const { |
| float zoom_level = style.EffectiveZoom(); |
| if (style.Appearance() == kSliderThumbHorizontalPart || |
| style.Appearance() == kSliderThumbVerticalPart) { |
| style.SetWidth( |
| Length::Fixed(static_cast<int>(kSliderThumbWidth * zoom_level))); |
| style.SetHeight( |
| Length::Fixed(static_cast<int>(kSliderThumbHeight * zoom_level))); |
| } |
| } |
| |
| NSPopUpButtonCell* LayoutThemeMac::PopupButton() const { |
| if (!popup_button_) { |
| popup_button_.reset([[NSPopUpButtonCell alloc] initTextCell:@"" |
| pullsDown:NO]); |
| [popup_button_ setUsesItemFromMenu:NO]; |
| [popup_button_ setFocusRingType:NSFocusRingTypeExterior]; |
| } |
| |
| return popup_button_; |
| } |
| |
| NSSearchFieldCell* LayoutThemeMac::Search() const { |
| if (!search_) { |
| search_.reset([[NSSearchFieldCell alloc] initTextCell:@""]); |
| [search_ setBezelStyle:NSTextFieldRoundedBezel]; |
| [search_ setBezeled:YES]; |
| [search_ setEditable:YES]; |
| [search_ setFocusRingType:NSFocusRingTypeExterior]; |
| |
| // Suppress NSSearchFieldCell's default placeholder text. Prior to OS10.11, |
| // this is achieved by calling |setCenteredLook| with NO. In OS10.11 and |
| // later, instead call |setPlaceholderString| with an empty string. |
| // See https://crbug.com/752362. |
| if (base::mac::IsAtMostOS10_10()) { |
| SEL sel = @selector(setCenteredLook:); |
| if ([search_ respondsToSelector:sel]) { |
| BOOL bool_value = NO; |
| NSMethodSignature* signature = |
| [NSSearchFieldCell instanceMethodSignatureForSelector:sel]; |
| NSInvocation* invocation = |
| [NSInvocation invocationWithMethodSignature:signature]; |
| [invocation setTarget:search_]; |
| [invocation setSelector:sel]; |
| [invocation setArgument:&bool_value atIndex:2]; |
| [invocation invoke]; |
| } |
| } else { |
| [search_ setPlaceholderString:@""]; |
| } |
| } |
| |
| return search_; |
| } |
| |
| NSTextFieldCell* LayoutThemeMac::TextField() const { |
| if (!text_field_) { |
| text_field_.reset([[BlinkTextFieldCell alloc] initTextCell:@""]); |
| [text_field_ setBezeled:YES]; |
| [text_field_ setEditable:YES]; |
| [text_field_ setFocusRingType:NSFocusRingTypeExterior]; |
| [text_field_ setDrawsBackground:YES]; |
| [text_field_ setBackgroundColor:[NSColor whiteColor]]; |
| } |
| |
| return text_field_; |
| } |
| |
| String LayoutThemeMac::FileListNameForWidth(Locale& locale, |
| const FileList* file_list, |
| const Font& font, |
| int width) const { |
| if (width <= 0) |
| return String(); |
| |
| String str_to_truncate; |
| if (file_list->IsEmpty()) { |
| str_to_truncate = |
| locale.QueryString(WebLocalizedString::kFileButtonNoFileSelectedLabel); |
| } else if (file_list->length() == 1) { |
| File* file = file_list->item(0); |
| if (file->GetUserVisibility() == File::kIsUserVisible) |
| str_to_truncate = [[NSFileManager defaultManager] |
| displayNameAtPath:(file_list->item(0)->GetPath())]; |
| else |
| str_to_truncate = file->name(); |
| } else { |
| return StringTruncator::RightTruncate( |
| locale.QueryString(WebLocalizedString::kMultipleFileUploadText, |
| locale.ConvertToLocalizedNumber( |
| String::Number(file_list->length()))), |
| width, font); |
| } |
| |
| return StringTruncator::CenterTruncate(str_to_truncate, width, font); |
| } |
| |
| NSView* FlippedView() { |
| static NSView* view = [[BlinkFlippedView alloc] init]; |
| return view; |
| } |
| |
| LayoutTheme& LayoutTheme::NativeTheme() { |
| if (RuntimeEnabledFeatures::FormControlsRefreshEnabled()) { |
| DEFINE_STATIC_REF(LayoutTheme, layout_theme, |
| (LayoutThemeMacRefresh::Create())); |
| return *layout_theme; |
| } else { |
| DEFINE_STATIC_REF(LayoutTheme, layout_theme, (LayoutThemeMac::Create())); |
| return *layout_theme; |
| } |
| } |
| |
| scoped_refptr<LayoutTheme> LayoutThemeMac::Create() { |
| return base::AdoptRef(new LayoutThemeMac); |
| } |
| |
| bool LayoutThemeMac::UsesTestModeFocusRingColor() const { |
| return WebTestSupport::IsRunningWebTest(); |
| } |
| |
| NSView* LayoutThemeMac::DocumentView() const { |
| return FlippedView(); |
| } |
| |
| // Updates the control tint (a.k.a. active state) of |cell| (from |o|). In the |
| // Chromium port, the layoutObject runs as a background process and controls' |
| // NSCell(s) lack a parent NSView. Therefore controls don't have their tint |
| // color updated correctly when the application is activated/deactivated. |
| // FocusController's setActive() is called when the application is |
| // activated/deactivated, which causes a paint invalidation at which time this |
| // code is called. |
| // This function should be called before drawing any NSCell-derived controls, |
| // unless you're sure it isn't needed. |
| void LayoutThemeMac::UpdateActiveState(NSCell* cell, const Node* node) { |
| NSControlTint old_tint = [cell controlTint]; |
| NSControlTint tint = IsActive(node) |
| ? [NSColor currentControlTint] |
| : static_cast<NSControlTint>(NSClearControlTint); |
| |
| if (tint != old_tint) |
| [cell setControlTint:tint]; |
| } |
| |
| String LayoutThemeMac::ExtraFullscreenStyleSheet() { |
| // FIXME: Chromium may wish to style its default media controls differently in |
| // fullscreen. |
| return String(); |
| } |
| |
| String LayoutThemeMac::ExtraDefaultStyleSheet() { |
| return LayoutTheme::ExtraDefaultStyleSheet() + |
| GetDataResourceAsASCIIString("input_multiple_fields.css") + |
| GetDataResourceAsASCIIString("mac.css"); |
| } |
| |
| bool LayoutThemeMac::ThemeDrawsFocusRing(const ComputedStyle& style) const { |
| if (ShouldUseFallbackTheme(style)) |
| return false; |
| switch (style.Appearance()) { |
| case kCheckboxPart: |
| case kRadioPart: |
| case kPushButtonPart: |
| case kSquareButtonPart: |
| case kButtonPart: |
| case kMenulistPart: |
| case kSliderThumbHorizontalPart: |
| case kSliderThumbVerticalPart: |
| return true; |
| |
| // Actually, they don't support native focus rings, but this function |
| // returns true for them in order to prevent Blink from drawing focus rings. |
| // SliderThumb*Part have focus rings, and we don't need to draw two focus |
| // rings for single slider. |
| case kSliderHorizontalPart: |
| case kSliderVerticalPart: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| bool LayoutThemeMac::ShouldUseFallbackTheme(const ComputedStyle& style) const { |
| ControlPart part = style.Appearance(); |
| if (part == kCheckboxPart || part == kRadioPart) |
| return style.EffectiveZoom() != 1; |
| return false; |
| } |
| |
| // We don't use controlSizeForFont() for steppers because the stepper height |
| // should be equal to or less than the corresponding text field height, |
| static NSControlSize StepperControlSizeForFont( |
| const FontDescription& font_description) { |
| int font_size = font_description.ComputedPixelSize(); |
| if (font_size >= 27) |
| return NSRegularControlSize; |
| if (font_size >= 22) |
| return NSSmallControlSize; |
| return NSMiniControlSize; |
| } |
| |
| NSControlSize LayoutThemeMac::ControlSizeFromPixelSize( |
| const IntSize* sizes, |
| const IntSize& min_zoomed_size, |
| float zoom_factor) { |
| if (min_zoomed_size.Width() >= |
| static_cast<int>(sizes[NSRegularControlSize].Width() * zoom_factor) && |
| min_zoomed_size.Height() >= |
| static_cast<int>(sizes[NSRegularControlSize].Height() * zoom_factor)) |
| return NSRegularControlSize; |
| if (min_zoomed_size.Width() >= |
| static_cast<int>(sizes[NSSmallControlSize].Width() * zoom_factor) && |
| min_zoomed_size.Height() >= |
| static_cast<int>(sizes[NSSmallControlSize].Height() * zoom_factor)) |
| return NSSmallControlSize; |
| return NSMiniControlSize; |
| } |
| |
| static void SetControlSizeThemeMac(NSCell* cell, |
| const IntSize* sizes, |
| const IntSize& min_zoomed_size, |
| float zoom_factor) { |
| ControlSize size = LayoutThemeMac::ControlSizeFromPixelSize( |
| sizes, min_zoomed_size, zoom_factor); |
| // Only update if we have to, since AppKit does work even if the size is the |
| // same. |
| if (size != [cell controlSize]) |
| [cell setControlSize:(NSControlSize)size]; |
| } |
| |
| static void UpdateStates(NSCell* cell, ControlStates states) { |
| // Hover state is not supported by Aqua. |
| |
| // Pressed state |
| bool old_pressed = [cell isHighlighted]; |
| bool pressed = states & kPressedControlState; |
| if (pressed != old_pressed) |
| [cell setHighlighted:pressed]; |
| |
| // Enabled state |
| bool old_enabled = [cell isEnabled]; |
| bool enabled = states & kEnabledControlState; |
| if (enabled != old_enabled) |
| [cell setEnabled:enabled]; |
| |
| // Checked and Indeterminate |
| bool old_indeterminate = [cell state] == NSMixedState; |
| bool indeterminate = (states & kIndeterminateControlState); |
| bool checked = states & kCheckedControlState; |
| bool old_checked = [cell state] == NSOnState; |
| if (old_indeterminate != indeterminate || checked != old_checked) |
| [cell setState:indeterminate ? NSMixedState |
| : (checked ? NSOnState : NSOffState)]; |
| |
| // Window inactive state does not need to be checked explicitly, since we |
| // paint parented to a view in a window whose key state can be detected. |
| } |
| |
| // Return a fake NSView whose sole purpose is to tell AppKit that it's flipped. |
| NSView* LayoutThemeMac::EnsuredView(const IntSize& size) { |
| // Use a fake flipped view. |
| static NSView* flipped_view = [[BlinkFlippedControl alloc] init]; |
| [flipped_view setFrameSize:NSSizeFromCGSize(CGSize(size))]; |
| |
| return flipped_view; |
| } |
| |
| LayoutUnit LayoutThemeMac::BaselinePositionAdjustment( |
| const ComputedStyle& style) const { |
| ControlPart part = style.Appearance(); |
| if (part == kCheckboxPart || part == kRadioPart) |
| return LayoutUnit(style.EffectiveZoom() * -2); |
| return LayoutTheme::BaselinePositionAdjustment(style); |
| } |
| |
| FontDescription LayoutThemeMac::ControlFont( |
| ControlPart part, |
| const FontDescription& font_description, |
| float zoom_factor) const { |
| using ::blink::ControlSizeForFont; |
| switch (part) { |
| case kPushButtonPart: { |
| FontDescription result; |
| result.SetIsAbsoluteSize(true); |
| result.SetGenericFamily(FontDescription::kSerifFamily); |
| |
| NSFont* ns_font = [NSFont |
| systemFontOfSize:[NSFont systemFontSizeForControlSize: |
| ControlSizeForFont(font_description)]]; |
| result.FirstFamily().SetFamily(font_family_names::kSystemUi); |
| result.SetComputedSize([ns_font pointSize] * zoom_factor); |
| result.SetSpecifiedSize([ns_font pointSize] * zoom_factor); |
| return result; |
| } |
| default: |
| return LayoutTheme::ControlFont(part, font_description, zoom_factor); |
| } |
| } |
| |
| LengthSize LayoutThemeMac::GetControlSize( |
| ControlPart part, |
| const FontDescription& font_description, |
| const LengthSize& zoomed_size, |
| float zoom_factor) const { |
| switch (part) { |
| case kCheckboxPart: |
| return CheckboxSize(font_description, zoomed_size, zoom_factor); |
| case kRadioPart: |
| return RadioSize(font_description, zoomed_size, zoom_factor); |
| case kPushButtonPart: |
| // Height is reset to auto so that specified heights can be ignored. |
| return SizeFromFont(font_description, |
| LengthSize(zoomed_size.Width(), Length()), |
| zoom_factor, ButtonSizes()); |
| case kInnerSpinButtonPart: |
| if (!zoomed_size.Width().IsIntrinsicOrAuto() && |
| !zoomed_size.Height().IsIntrinsicOrAuto()) |
| return zoomed_size; |
| return SizeFromNSControlSize(StepperControlSizeForFont(font_description), |
| zoomed_size, zoom_factor, StepperSizes()); |
| default: |
| return zoomed_size; |
| } |
| } |
| |
| LengthSize LayoutThemeMac::MinimumControlSize( |
| ControlPart part, |
| const FontDescription& font_description, |
| float zoom_factor, |
| const ComputedStyle& style) const { |
| switch (part) { |
| case kSquareButtonPart: |
| case kButtonPart: |
| return LengthSize(style.MinWidth().Zoom(zoom_factor), |
| Length::Fixed(static_cast<int>(15 * zoom_factor))); |
| case kInnerSpinButtonPart: { |
| IntSize base = StepperSizes()[NSMiniControlSize]; |
| return LengthSize( |
| Length::Fixed(static_cast<int>(base.Width() * zoom_factor)), |
| Length::Fixed(static_cast<int>(base.Height() * zoom_factor))); |
| } |
| default: |
| return LayoutTheme::MinimumControlSize(part, font_description, |
| zoom_factor, style); |
| } |
| } |
| |
| LengthBox LayoutThemeMac::ControlPadding( |
| ControlPart part, |
| const FontDescription& font_description, |
| const Length& zoomed_box_top, |
| const Length& zoomed_box_right, |
| const Length& zoomed_box_bottom, |
| const Length& zoomed_box_left, |
| float zoom_factor) const { |
| switch (part) { |
| case kPushButtonPart: { |
| // Just use 8px. AppKit wants to use 11px for mini buttons, but that |
| // padding is just too large for real-world Web sites (creating a huge |
| // necessary minimum width for buttons whose space is by definition |
| // constrained, since we select mini only for small cramped environments. |
| // This also guarantees the HTML <button> will match our rendering by |
| // default, since we're using a consistent padding. |
| const int padding = 8 * zoom_factor; |
| return LengthBox(2, padding, 3, padding); |
| } |
| default: |
| return LayoutTheme::ControlPadding(part, font_description, zoomed_box_top, |
| zoomed_box_right, zoomed_box_bottom, |
| zoomed_box_left, zoom_factor); |
| } |
| } |
| |
| LengthBox LayoutThemeMac::ControlBorder(ControlPart part, |
| const FontDescription& font_description, |
| const LengthBox& zoomed_box, |
| float zoom_factor) const { |
| switch (part) { |
| case kSquareButtonPart: |
| return LengthBox(0, zoomed_box.Right().Value(), 0, |
| zoomed_box.Left().Value()); |
| default: |
| return LayoutTheme::ControlBorder(part, font_description, zoomed_box, |
| zoom_factor); |
| } |
| } |
| |
| void LayoutThemeMac::AddVisualOverflowHelper(ControlPart part, |
| ControlStates states, |
| float zoom_factor, |
| IntRect& zoomed_rect) const { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| switch (part) { |
| case kCheckboxPart: { |
| // We inflate the rect as needed to account for padding included in the |
| // cell to accommodate the checkbox shadow" and the check. We don't |
| // consider this part of the bounds of the control in WebKit. |
| NSCell* cell = Checkbox(states, zoomed_rect, zoom_factor); |
| NSControlSize control_size = [cell controlSize]; |
| IntSize zoomed_size = CheckboxSizes()[control_size]; |
| zoomed_size.SetHeight(zoomed_size.Height() * zoom_factor); |
| zoomed_size.SetWidth(zoomed_size.Width() * zoom_factor); |
| zoomed_rect = InflateRect(zoomed_rect, zoomed_size, |
| CheckboxMargins(control_size), zoom_factor); |
| break; |
| } |
| case kRadioPart: { |
| // We inflate the rect as needed to account for padding included in the |
| // cell to accommodate the radio button shadow". We don't consider this |
| // part of the bounds of the control in WebKit. |
| NSCell* cell = Radio(states, zoomed_rect, zoom_factor); |
| NSControlSize control_size = [cell controlSize]; |
| IntSize zoomed_size = RadioSizes()[control_size]; |
| zoomed_size.SetHeight(zoomed_size.Height() * zoom_factor); |
| zoomed_size.SetWidth(zoomed_size.Width() * zoom_factor); |
| zoomed_rect = InflateRect(zoomed_rect, zoomed_size, |
| RadioMargins(control_size), zoom_factor); |
| break; |
| } |
| case kPushButtonPart: |
| case kButtonPart: { |
| NSButtonCell* cell = Button(part, states, zoomed_rect, zoom_factor); |
| NSControlSize control_size = [cell controlSize]; |
| |
| // We inflate the rect as needed to account for the Aqua button's shadow. |
| if ([cell bezelStyle] == NSRoundedBezelStyle) { |
| IntSize zoomed_size = ButtonSizes()[control_size]; |
| zoomed_size.SetHeight(zoomed_size.Height() * zoom_factor); |
| // Buttons don't ever constrain width, so the zoomed width can just be |
| // honored. |
| zoomed_size.SetWidth(zoomed_rect.Width()); |
| zoomed_rect = InflateRect(zoomed_rect, zoomed_size, |
| ButtonMargins(control_size), zoom_factor); |
| } |
| break; |
| } |
| case kInnerSpinButtonPart: { |
| static const int kStepperMargin[4] = {0, 0, 0, 0}; |
| ControlSize control_size = ControlSizeFromPixelSize( |
| StepperSizes(), zoomed_rect.Size(), zoom_factor); |
| IntSize zoomed_size = StepperSizes()[control_size]; |
| zoomed_size.SetHeight(zoomed_size.Height() * zoom_factor); |
| zoomed_size.SetWidth(zoomed_size.Width() * zoom_factor); |
| zoomed_rect = |
| InflateRect(zoomed_rect, zoomed_size, kStepperMargin, zoom_factor); |
| break; |
| } |
| default: |
| break; |
| } |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void LayoutThemeMac::AdjustControlPartStyle(ComputedStyle& style) { |
| ControlPart part = style.Appearance(); |
| switch (part) { |
| case kCheckboxPart: |
| case kInnerSpinButtonPart: |
| case kRadioPart: |
| case kPushButtonPart: |
| case kSquareButtonPart: |
| case kButtonPart: { |
| // Border |
| LengthBox border_box(style.BorderTopWidth(), style.BorderRightWidth(), |
| style.BorderBottomWidth(), style.BorderLeftWidth()); |
| border_box = ControlBorder(part, style.GetFont().GetFontDescription(), |
| border_box, style.EffectiveZoom()); |
| if (border_box.Top().Value() != |
| static_cast<int>(style.BorderTopWidth())) { |
| if (border_box.Top().Value()) |
| style.SetBorderTopWidth(border_box.Top().Value()); |
| else |
| style.ResetBorderTop(); |
| } |
| if (border_box.Right().Value() != |
| static_cast<int>(style.BorderRightWidth())) { |
| if (border_box.Right().Value()) |
| style.SetBorderRightWidth(border_box.Right().Value()); |
| else |
| style.ResetBorderRight(); |
| } |
| if (border_box.Bottom().Value() != |
| static_cast<int>(style.BorderBottomWidth())) { |
| style.SetBorderBottomWidth(border_box.Bottom().Value()); |
| if (border_box.Bottom().Value()) |
| style.SetBorderBottomWidth(border_box.Bottom().Value()); |
| else |
| style.ResetBorderBottom(); |
| } |
| if (border_box.Left().Value() != |
| static_cast<int>(style.BorderLeftWidth())) { |
| style.SetBorderLeftWidth(border_box.Left().Value()); |
| if (border_box.Left().Value()) |
| style.SetBorderLeftWidth(border_box.Left().Value()); |
| else |
| style.ResetBorderLeft(); |
| } |
| |
| // Padding |
| LengthBox padding_box = ControlPadding( |
| part, style.GetFont().GetFontDescription(), style.PaddingTop(), |
| style.PaddingRight(), style.PaddingBottom(), style.PaddingLeft(), |
| style.EffectiveZoom()); |
| if (!style.PaddingEqual(padding_box)) |
| style.SetPadding(padding_box); |
| |
| // Whitespace |
| if (ControlRequiresPreWhiteSpace(part)) |
| style.SetWhiteSpace(EWhiteSpace::kPre); |
| |
| // Width / Height |
| // The width and height here are affected by the zoom. |
| LengthSize control_size = GetControlSize( |
| part, style.GetFont().GetFontDescription(), |
| LengthSize(style.Width(), style.Height()), style.EffectiveZoom()); |
| |
| LengthSize min_control_size = |
| MinimumControlSize(part, style.GetFont().GetFontDescription(), |
| style.EffectiveZoom(), style); |
| |
| // Only potentially set min-size to |control_size| for these parts. |
| if (part == kCheckboxPart || part == kRadioPart) |
| SetMinimumSize(style, &control_size, &min_control_size); |
| else |
| SetMinimumSize(style, nullptr, &min_control_size); |
| |
| if (control_size.Width() != style.Width()) |
| style.SetWidth(control_size.Width()); |
| if (control_size.Height() != style.Height()) |
| style.SetHeight(control_size.Height()); |
| |
| // Font |
| FontDescription control_font = ControlFont( |
| part, style.GetFont().GetFontDescription(), style.EffectiveZoom()); |
| if (control_font != style.GetFont().GetFontDescription()) { |
| // Reset our line-height |
| style.SetLineHeight(ComputedStyleInitialValues::InitialLineHeight()); |
| |
| // Now update our font. |
| if (style.SetFontDescription(control_font)) |
| style.GetFont().Update(nullptr); |
| } |
| break; |
| } |
| case kProgressBarPart: |
| AdjustProgressBarBounds(style); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // static |
| IntRect LayoutThemeMac::InflateRect(const IntRect& zoomed_rect, |
| const IntSize& zoomed_size, |
| const int* margins, |
| float zoom_factor) { |
| // Only do the inflation if the available width/height are too small. |
| // Otherwise try to fit the glow/check space into the available box's |
| // width/height. |
| int width_delta = zoomed_rect.Width() - |
| (zoomed_size.Width() + margins[kLeftMargin] * zoom_factor + |
| margins[kRightMargin] * zoom_factor); |
| int height_delta = zoomed_rect.Height() - |
| (zoomed_size.Height() + margins[kTopMargin] * zoom_factor + |
| margins[kBottomMargin] * zoom_factor); |
| IntRect result(zoomed_rect); |
| if (width_delta < 0) { |
| result.SetX(result.X() - margins[kLeftMargin] * zoom_factor); |
| result.SetWidth(result.Width() - width_delta); |
| } |
| if (height_delta < 0) { |
| result.SetY(result.Y() - margins[kTopMargin] * zoom_factor); |
| result.SetHeight(result.Height() - height_delta); |
| } |
| return result; |
| } |
| |
| // static |
| IntRect LayoutThemeMac::InflateRectForFocusRing(const IntRect& rect) { |
| // Just put a margin of 16 units around the rect. The UI elements that use |
| // this don't appropriately scale their focus rings appropriately (e.g, paint |
| // pickers), or switch to non-native widgets when scaled (e.g, check boxes |
| // and radio buttons). |
| const int kMargin = 16; |
| IntRect result; |
| result.SetX(rect.X() - kMargin); |
| result.SetY(rect.Y() - kMargin); |
| result.SetWidth(rect.Width() + 2 * kMargin); |
| result.SetHeight(rect.Height() + 2 * kMargin); |
| return result; |
| } |
| |
| // Checkboxes |
| |
| const IntSize* LayoutThemeMac::CheckboxSizes() { |
| static const IntSize kSizes[3] = {IntSize(14, 14), IntSize(12, 12), |
| IntSize(10, 10)}; |
| return kSizes; |
| } |
| |
| const int* LayoutThemeMac::CheckboxMargins(NSControlSize control_size) { |
| static const int kMargins[3][4] = { |
| {3, 4, 4, 2}, |
| {4, 3, 3, 3}, |
| {4, 3, 3, 3}, |
| }; |
| return kMargins[control_size]; |
| } |
| |
| LengthSize LayoutThemeMac::CheckboxSize(const FontDescription& font_description, |
| const LengthSize& zoomed_size, |
| float zoom_factor) { |
| // If the width and height are both specified, then we have nothing to do. |
| if (!zoomed_size.Width().IsIntrinsicOrAuto() && |
| !zoomed_size.Height().IsIntrinsicOrAuto()) |
| return zoomed_size; |
| |
| // Use the font size to determine the intrinsic width of the control. |
| return SizeFromFont(font_description, zoomed_size, zoom_factor, |
| CheckboxSizes()); |
| } |
| |
| NSButtonCell* LayoutThemeMac::Checkbox(ControlStates states, |
| const IntRect& zoomed_rect, |
| float zoom_factor) { |
| static NSButtonCell* checkbox_cell; |
| if (!checkbox_cell) { |
| checkbox_cell = [[NSButtonCell alloc] init]; |
| [checkbox_cell setButtonType:NSSwitchButton]; |
| [checkbox_cell setTitle:nil]; |
| [checkbox_cell setAllowsMixedState:YES]; |
| [checkbox_cell setFocusRingType:NSFocusRingTypeExterior]; |
| } |
| |
| // Set the control size based off the rectangle we're painting into. |
| SetControlSizeThemeMac(checkbox_cell, CheckboxSizes(), zoomed_rect.Size(), |
| zoom_factor); |
| |
| // Update the various states we respond to. |
| UpdateStates(checkbox_cell, states); |
| |
| return checkbox_cell; |
| } |
| |
| const IntSize* LayoutThemeMac::RadioSizes() { |
| static const IntSize kSizes[3] = {IntSize(14, 15), IntSize(12, 13), |
| IntSize(10, 10)}; |
| return kSizes; |
| } |
| |
| const int* LayoutThemeMac::RadioMargins(NSControlSize control_size) { |
| static const int kMargins[3][4] = { |
| {2, 2, 4, 2}, |
| {3, 2, 3, 2}, |
| {1, 0, 2, 0}, |
| }; |
| return kMargins[control_size]; |
| } |
| |
| LengthSize LayoutThemeMac::RadioSize(const FontDescription& font_description, |
| const LengthSize& zoomed_size, |
| float zoom_factor) { |
| // If the width and height are both specified, then we have nothing to do. |
| if (!zoomed_size.Width().IsIntrinsicOrAuto() && |
| !zoomed_size.Height().IsIntrinsicOrAuto()) |
| return zoomed_size; |
| |
| // Use the font size to determine the intrinsic width of the control. |
| return SizeFromFont(font_description, zoomed_size, zoom_factor, RadioSizes()); |
| } |
| |
| NSButtonCell* LayoutThemeMac::Radio(ControlStates states, |
| const IntRect& zoomed_rect, |
| float zoom_factor) { |
| static NSButtonCell* radio_cell; |
| if (!radio_cell) { |
| radio_cell = [[NSButtonCell alloc] init]; |
| [radio_cell setButtonType:NSRadioButton]; |
| [radio_cell setTitle:nil]; |
| [radio_cell setFocusRingType:NSFocusRingTypeExterior]; |
| } |
| |
| // Set the control size based off the rectangle we're painting into. |
| SetControlSizeThemeMac(radio_cell, RadioSizes(), zoomed_rect.Size(), |
| zoom_factor); |
| |
| // Update the various states we respond to. |
| // Cocoa draws NSMixedState NSRadioButton as NSOnState so we don't want that. |
| states &= ~kIndeterminateControlState; |
| UpdateStates(radio_cell, states); |
| |
| return radio_cell; |
| } |
| |
| // Buttons really only constrain height. They respect width. |
| const IntSize* LayoutThemeMac::ButtonSizes() { |
| static const IntSize kSizes[3] = {IntSize(0, 21), IntSize(0, 18), |
| IntSize(0, 15)}; |
| return kSizes; |
| } |
| |
| const int* LayoutThemeMac::ButtonMargins(NSControlSize control_size) { |
| static const int kMargins[3][4] = { |
| {4, 6, 7, 6}, |
| {4, 5, 6, 5}, |
| {0, 1, 1, 1}, |
| }; |
| return kMargins[control_size]; |
| } |
| |
| static void SetUpButtonCell(NSButtonCell* cell, |
| ControlPart part, |
| ControlStates states, |
| const IntRect& zoomed_rect, |
| float zoom_factor) { |
| // Set the control size based off the rectangle we're painting into. |
| const IntSize* sizes = LayoutThemeMac::ButtonSizes(); |
| if (part == kSquareButtonPart || |
| zoomed_rect.Height() > |
| LayoutThemeMac::ButtonSizes()[NSRegularControlSize].Height() * |
| zoom_factor) { |
| // Use the square button |
| if ([cell bezelStyle] != NSShadowlessSquareBezelStyle) |
| [cell setBezelStyle:NSShadowlessSquareBezelStyle]; |
| } else if ([cell bezelStyle] != NSRoundedBezelStyle) |
| [cell setBezelStyle:NSRoundedBezelStyle]; |
| |
| SetControlSizeThemeMac(cell, sizes, zoomed_rect.Size(), zoom_factor); |
| |
| // Update the various states we respond to. |
| UpdateStates(cell, states); |
| } |
| |
| NSButtonCell* LayoutThemeMac::Button(ControlPart part, |
| ControlStates states, |
| const IntRect& zoomed_rect, |
| float zoom_factor) { |
| static NSButtonCell* cell = nil; |
| if (!cell) { |
| cell = [[NSButtonCell alloc] init]; |
| [cell setTitle:nil]; |
| [cell setButtonType:NSMomentaryPushInButton]; |
| } |
| SetUpButtonCell(cell, part, states, zoomed_rect, zoom_factor); |
| return cell; |
| } |
| |
| const IntSize* LayoutThemeMac::StepperSizes() { |
| static const IntSize kSizes[3] = {IntSize(19, 27), IntSize(15, 22), |
| IntSize(13, 15)}; |
| return kSizes; |
| } |
| |
| } // namespace blink |