1 // Copyright (c) 2011 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/image_button_cell.h"
7 #include "base/logging.h"
8 #include "base/mac/mac_util.h"
9 #import "chrome/browser/themes/theme_service.h"
10 #import "chrome/browser/ui/cocoa/rect_path_utils.h"
11 #import "chrome/browser/ui/cocoa/themed_window.h"
12 #import "ui/base/cocoa/nsview_additions.h"
13 #include "ui/gfx/image/image.h"
14 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
16 // When the window doesn't have focus then we want to draw the button with a
17 // slightly lighter color. We do this by just reducing the alpha.
18 const CGFloat kImageNoFocusAlpha = 0.65;
20 @interface ImageButtonCell (Private)
22 - (image_button_cell::ButtonState)currentButtonState;
23 - (NSImage*)imageForID:(NSInteger)imageID
24 controlView:(NSView*)controlView;
27 @implementation ImageButtonCell
29 @synthesize isMouseInside = isMouseInside_;
31 // For nib instantiations
32 - (id)initWithCoder:(NSCoder*)decoder {
33 if ((self = [super initWithCoder:decoder])) {
39 // For programmatic instantiations
40 - (id)initTextCell:(NSString*)string {
41 if ((self = [super initTextCell:string])) {
48 [self setHighlightsBy:NSNoCellMask];
50 // We need to set this so that we can override |-mouseEntered:| and
51 // |-mouseExited:| to change the button image on hover states.
52 [self setShowsBorderOnlyWhileMouseInside:YES];
55 - (NSImage*)imageForState:(image_button_cell::ButtonState)state
56 view:(NSView*)controlView{
57 if (image_[state].imageId)
58 return [self imageForID:image_[state].imageId controlView:controlView];
59 return image_[state].image;
62 - (void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
63 image_button_cell::ButtonState state = [self currentButtonState];
64 BOOL windowHasFocus = [[controlView window] isMainWindow] ||
65 [[controlView window] isKeyWindow];
66 CGFloat alpha = [self imageAlphaForWindowState:[controlView window]];
67 NSImage* image = [self imageForState:state view:controlView];
69 if (!windowHasFocus) {
70 NSImage* defaultImage = [self
71 imageForState:image_button_cell::kDefaultStateBackground
73 NSImage* hoverImage = [self
74 imageForState:image_button_cell::kHoverStateBackground
76 if ([self currentButtonState] == image_button_cell::kDefaultState &&
80 } else if ([self currentButtonState] == image_button_cell::kHoverState &&
88 imageRect.size = [image size];
89 imageRect.origin.x = cellFrame.origin.x +
90 roundf((NSWidth(cellFrame) - NSWidth(imageRect)) / 2.0);
91 imageRect.origin.y = cellFrame.origin.y +
92 roundf((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0);
94 [image drawInRect:imageRect
96 operation:NSCompositeSourceOver
102 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
103 [self drawImageWithFrame:cellFrame inView:controlView];
104 [self drawFocusRingWithFrame:cellFrame inView:controlView];
107 - (void)setImageID:(NSInteger)imageID
108 forButtonState:(image_button_cell::ButtonState)state {
110 DCHECK_LT(state, image_button_cell::kButtonStateCount);
112 image_[state].image.reset();
113 image_[state].imageId = imageID;
114 [[self controlView] setNeedsDisplay:YES];
117 // Sets the image for the given button state using an image.
118 - (void)setImage:(NSImage*)image
119 forButtonState:(image_button_cell::ButtonState)state {
121 DCHECK_LT(state, image_button_cell::kButtonStateCount);
123 image_[state].image.reset([image retain]);
124 image_[state].imageId = 0;
125 [[self controlView] setNeedsDisplay:YES];
128 - (CGFloat)imageAlphaForWindowState:(NSWindow*)window {
129 BOOL windowHasFocus = [window isMainWindow] || [window isKeyWindow];
130 return windowHasFocus ? 1.0 : kImageNoFocusAlpha;
133 - (ui::ThemeProvider*)themeProviderForWindow:(NSWindow*)window {
134 return [window themeProvider];
137 - (void)drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
138 // Draw custom focus ring only if AppKit won't draw one automatically.
139 // The new focus ring APIs became available with 10.7, but did not get
140 // applied to buttons (only editable text fields) until 10.8.
141 if (base::mac::IsOSMountainLionOrLater())
144 if (![self showsFirstResponder])
146 gfx::ScopedNSGraphicsContextSaveGState scoped_state;
147 const CGFloat lineWidth = [controlView cr_lineWidth];
148 rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll,
149 NSInsetRect(cellFrame, 0, lineWidth),
153 lineWidth * 2, // lineWidth
155 cr_keyboardFocusIndicatorColor]);
158 - (image_button_cell::ButtonState)currentButtonState {
159 bool (^has)(image_button_cell::ButtonState) =
160 ^(image_button_cell::ButtonState state) {
161 return image_[state].image || image_[state].imageId;
163 if (![self isEnabled] && has(image_button_cell::kDisabledState))
164 return image_button_cell::kDisabledState;
165 if ([self isHighlighted] && has(image_button_cell::kPressedState))
166 return image_button_cell::kPressedState;
167 if ([self isMouseInside] && has(image_button_cell::kHoverState))
168 return image_button_cell::kHoverState;
169 return image_button_cell::kDefaultState;
172 - (NSImage*)imageForID:(NSInteger)imageID
173 controlView:(NSView*)controlView {
177 ui::ThemeProvider* themeProvider =
178 [self themeProviderForWindow:[controlView window]];
182 return themeProvider->GetNSImageNamed(imageID);
185 - (void)setIsMouseInside:(BOOL)isMouseInside {
186 if (isMouseInside_ != isMouseInside) {
187 isMouseInside_ = isMouseInside;
188 NSView<ImageButton>* control =
189 static_cast<NSView<ImageButton>*>([self controlView]);
190 if ([control respondsToSelector:@selector(mouseInsideStateDidChange:)]) {
191 [control mouseInsideStateDidChange:isMouseInside];
193 [control setNeedsDisplay:YES];
197 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)show {
198 VLOG_IF(1, !show) << "setShowsBorderOnlyWhileMouseInside:NO ignored";
201 - (BOOL)showsBorderOnlyWhileMouseInside {
202 // Always returns YES so that buttons always get mouse tracking even when
203 // disabled. The reload button (and possibly others) depend on this.
207 - (void)mouseEntered:(NSEvent*)theEvent {
208 [self setIsMouseInside:YES];
211 - (void)mouseExited:(NSEvent*)theEvent {
212 [self setIsMouseInside:NO];