1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/browser/profiles/profiles_state.h"
10 #include "chrome/browser/themes/theme_service.h"
11 #include "chrome/browser/themes/theme_service_factory.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_window.h"
14 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
15 #include "grit/generated_resources.h"
16 #include "grit/theme_resources.h"
17 #import "ui/base/cocoa/appkit_utils.h"
18 #import "ui/base/cocoa/hover_image_button.h"
19 #include "ui/base/l10n/l10n_util_mac.h"
20 #include "ui/base/nine_image_painter_factory.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/text_elider.h"
26 const CGFloat kButtonPadding = 12;
27 const CGFloat kButtonDefaultPadding = 5;
28 const CGFloat kButtonHeight = 27;
29 const CGFloat kButtonTitleImageSpacing = 10;
30 const CGFloat kMaxButtonWidth = 120;
32 const ui::NinePartImageIds kNormalBorderImageIds =
33 IMAGE_GRID(IDR_AVATAR_MAC_BUTTON_NORMAL);
34 const ui::NinePartImageIds kHoverBorderImageIds =
35 IMAGE_GRID(IDR_AVATAR_MAC_BUTTON_HOVER);
36 const ui::NinePartImageIds kPressedBorderImageIds =
37 IMAGE_GRID(IDR_AVATAR_MAC_BUTTON_PRESSED);
38 const ui::NinePartImageIds kThemedBorderImageIds =
39 IMAGE_GRID(IDR_AVATAR_THEMED_MAC_BUTTON_NORMAL);
41 NSImage* GetImageFromResourceID(int resourceId) {
42 return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
43 resourceId).ToNSImage();
48 // Button cell with a custom border given by a set of nine-patch image grids.
49 @interface CustomThemeButtonCell : NSButtonCell {
53 - (void)setIsThemedWindow:(BOOL)isThemedWindow;
56 @implementation CustomThemeButtonCell
57 - (id)initWithThemedWindow:(BOOL)isThemedWindow {
58 if ((self = [super init])) {
59 isThemedWindow_ = isThemedWindow;
65 NSSize buttonSize = [super cellSize];
66 buttonSize.width += 2 * kButtonPadding - 2 * kButtonDefaultPadding;
67 buttonSize.height = kButtonHeight;
71 - (NSRect)drawTitle:(NSAttributedString*)title
72 withFrame:(NSRect)frame
73 inView:(NSView*)controlView {
74 frame.origin.x = kButtonPadding;
75 // Ensure there's always a padding between the text and the image.
76 frame.size.width -= kButtonTitleImageSpacing;
77 return [super drawTitle:title withFrame:frame inView:controlView];
80 - (void)drawImage:(NSImage*)image
81 withFrame:(NSRect)frame
82 inView:(NSView*)controlView {
83 // For the x-offset, we need to undo the default padding and apply the
84 // new one. For the y-offset, increasing the button height means we need
85 // to move the image a little down to align it nicely with the text; this
86 // was chosen by visual inspection.
87 frame = NSOffsetRect(frame, kButtonDefaultPadding - kButtonPadding, 2);
88 [super drawImage:image withFrame:frame inView:controlView];
91 - (void)drawBezelWithFrame:(NSRect)frame
92 inView:(NSView*)controlView {
93 HoverState hoverState =
94 [base::mac::ObjCCastStrict<HoverImageButton>(controlView) hoverState];
95 ui::NinePartImageIds imageIds = kNormalBorderImageIds;
97 imageIds = kThemedBorderImageIds;
99 if (hoverState == kHoverStateMouseDown)
100 imageIds = kPressedBorderImageIds;
101 else if (hoverState == kHoverStateMouseOver)
102 imageIds = kHoverBorderImageIds;
103 ui::DrawNinePartImage(frame, imageIds, NSCompositeSourceOver, 1.0, true);
106 - (void)setIsThemedWindow:(BOOL)isThemedWindow {
107 isThemedWindow_ = isThemedWindow;
111 @interface AvatarButtonController (Private)
112 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent;
114 - (void)themeDidChangeNotification:(NSNotification*)aNotification;
117 @implementation AvatarButtonController
119 - (id)initWithBrowser:(Browser*)browser {
120 if ((self = [super initWithBrowser:browser])) {
121 ThemeService* themeService =
122 ThemeServiceFactory::GetForProfile(browser->profile());
123 isThemedWindow_ = !themeService->UsingSystemTheme();
125 HoverImageButton* hoverButton =
126 [[HoverImageButton alloc] initWithFrame:NSZeroRect];
127 [hoverButton setDefaultImage:GetImageFromResourceID(
128 IDR_AVATAR_MAC_BUTTON_DROPARROW)];
129 [hoverButton setHoverImage:GetImageFromResourceID(
130 IDR_AVATAR_MAC_BUTTON_DROPARROW_HOVER)];
131 [hoverButton setPressedImage:GetImageFromResourceID(
132 IDR_AVATAR_MAC_BUTTON_DROPARROW_PRESSED)];
134 button_.reset(hoverButton);
135 base::scoped_nsobject<CustomThemeButtonCell> cell(
136 [[CustomThemeButtonCell alloc] initWithThemedWindow:isThemedWindow_]);
137 [button_ setCell:cell.get()];
138 [self setView:button_];
140 [button_ setBezelStyle:NSShadowlessSquareBezelStyle];
141 [button_ setButtonType:NSMomentaryChangeButton];
142 [button_ setBordered:YES];
143 // This is a workaround for an issue in the HoverImageButton where the
144 // button is initially sized incorrectly unless a default image is provided.
145 [button_ setImage:GetImageFromResourceID(IDR_AVATAR_MAC_BUTTON_DROPARROW)];
146 [button_ setImagePosition:NSImageRight];
147 [button_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
148 [button_ setTarget:self];
149 [button_ setAction:@selector(buttonClicked:)];
151 [self updateAvatarButtonAndLayoutParent:NO];
153 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
154 [center addObserver:self
155 selector:@selector(themeDidChangeNotification:)
156 name:kBrowserThemeDidChangeNotification
164 [[NSNotificationCenter defaultCenter] removeObserver:self];
168 - (void)themeDidChangeNotification:(NSNotification*)aNotification {
169 // Redraw the button if the window has switched between themed and native.
170 ThemeService* themeService =
171 ThemeServiceFactory::GetForProfile(browser_->profile());
172 BOOL updatedIsThemedWindow = !themeService->UsingSystemTheme();
173 if (isThemedWindow_ != updatedIsThemedWindow) {
174 isThemedWindow_ = updatedIsThemedWindow;
175 [[button_ cell] setIsThemedWindow:isThemedWindow_];
176 [self updateAvatarButtonAndLayoutParent:YES];
180 - (void)updateAvatarButtonAndLayoutParent:(BOOL)layoutParent {
181 // The button text has a black foreground and a white drop shadow for regular
182 // windows, and a light text with a dark drop shadow for guest windows
183 // which are themed with a dark background.
184 // TODO(noms): Figure out something similar for themed windows, if possible.
185 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
186 [shadow setShadowOffset:NSMakeSize(0, -1)];
187 [shadow setShadowBlurRadius:0];
189 NSColor* foregroundColor;
190 if (browser_->profile()->IsGuestSession()) {
191 foregroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.9];
192 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.4]];
193 } else if (!isThemedWindow_) {
194 foregroundColor = [NSColor blackColor];
195 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.7]];
197 foregroundColor = [NSColor blackColor];
198 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.4]];
201 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
202 [[NSMutableParagraphStyle alloc] init]);
203 [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
204 [paragraphStyle setAlignment:NSLeftTextAlignment];
206 NSString* buttonTitle = base::SysUTF16ToNSString(
207 profiles::GetAvatarNameForProfile(browser_->profile()));
209 base::scoped_nsobject<NSAttributedString> attributedTitle(
210 [[NSAttributedString alloc]
211 initWithString:buttonTitle
212 attributes:@{ NSShadowAttributeName : shadow.get(),
213 NSForegroundColorAttributeName : foregroundColor,
214 NSParagraphStyleAttributeName : paragraphStyle }]);
215 [button_ setAttributedTitle:attributedTitle];
218 // Truncate the title if needed.
219 if (NSWidth([button_ bounds]) > kMaxButtonWidth)
220 [button_ setFrameSize:NSMakeSize(kMaxButtonWidth, kButtonHeight)];
223 // Because the width of the button might have changed, the parent browser
224 // frame needs to recalculate the button bounds and redraw it.
225 [[BrowserWindowController
226 browserWindowControllerForWindow:browser_->window()->GetNativeWindow()]