Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 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. |
| 4 | |
| 5 | #import "chrome/browser/ui/cocoa/image_button_cell.h" |
| 6 | |
| 7 | #include "base/logging.h" |
| 8 | #import "chrome/browser/themes/theme_service.h" |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 9 | #import "chrome/browser/ui/cocoa/nsview_additions.h" |
| 10 | #import "chrome/browser/ui/cocoa/rect_path_utils.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 11 | #import "chrome/browser/ui/cocoa/themed_window.h" |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 12 | #include "ui/base/resource/resource_bundle.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 13 | #include "ui/gfx/image/image.h" |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 14 | #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 15 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 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; |
| 19 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 20 | @interface ImageButtonCell (Private) |
| 21 | - (void)sharedInit; |
| 22 | - (image_button_cell::ButtonState)currentButtonState; |
| 23 | - (NSImage*)imageForID:(NSInteger)imageID |
| 24 | controlView:(NSView*)controlView; |
| 25 | @end |
| 26 | |
| 27 | @implementation ImageButtonCell |
| 28 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 29 | @synthesize isMouseInside = isMouseInside_; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 30 | |
| 31 | // For nib instantiations |
| 32 | - (id)initWithCoder:(NSCoder*)decoder { |
| 33 | if ((self = [super initWithCoder:decoder])) { |
| 34 | [self sharedInit]; |
| 35 | } |
| 36 | return self; |
| 37 | } |
| 38 | |
| 39 | // For programmatic instantiations |
| 40 | - (id)initTextCell:(NSString*)string { |
| 41 | if ((self = [super initTextCell:string])) { |
| 42 | [self sharedInit]; |
| 43 | } |
| 44 | return self; |
| 45 | } |
| 46 | |
| 47 | - (void)sharedInit { |
| 48 | [self setHighlightsBy:NSNoCellMask]; |
| 49 | |
| 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]; |
| 53 | } |
| 54 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 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; |
| 60 | } |
| 61 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 62 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 63 | image_button_cell::ButtonState state = [self currentButtonState]; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 64 | BOOL windowHasFocus = [[controlView window] isMainWindow] || |
| 65 | [[controlView window] isKeyWindow]; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 66 | CGFloat alpha = [self imageAlphaForWindowState:[controlView window]]; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 67 | NSImage* image = [self imageForState:state view:controlView]; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 68 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 69 | if (!windowHasFocus) { |
| 70 | NSImage* defaultImage = [self |
| 71 | imageForState:image_button_cell::kDefaultStateBackground |
| 72 | view:controlView]; |
| 73 | NSImage* hoverImage = [self |
| 74 | imageForState:image_button_cell::kHoverStateBackground |
| 75 | view:controlView]; |
| 76 | if ([self currentButtonState] == image_button_cell::kDefaultState && |
| 77 | defaultImage) { |
| 78 | image = defaultImage; |
| 79 | alpha = 1.0; |
| 80 | } else if ([self currentButtonState] == image_button_cell::kHoverState && |
| 81 | hoverImage) { |
| 82 | image = hoverImage; |
| 83 | alpha = 1.0; |
| 84 | } |
| 85 | } |
| 86 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 87 | NSRect imageRect; |
| 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); |
| 93 | |
| 94 | [image drawInRect:imageRect |
| 95 | fromRect:NSZeroRect |
| 96 | operation:NSCompositeSourceOver |
| 97 | fraction:alpha |
| 98 | respectFlipped:YES |
| 99 | hints:nil]; |
| 100 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 101 | [self drawFocusRingWithFrame:cellFrame inView:controlView]; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | - (void)setImageID:(NSInteger)imageID |
| 105 | forButtonState:(image_button_cell::ButtonState)state { |
| 106 | DCHECK_GE(state, 0); |
| 107 | DCHECK_LT(state, image_button_cell::kButtonStateCount); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 108 | |
| 109 | image_[state].image.reset(); |
| 110 | image_[state].imageId = imageID; |
| 111 | [[self controlView] setNeedsDisplay:YES]; |
| 112 | } |
| 113 | |
| 114 | // Sets the image for the given button state using an image. |
| 115 | - (void)setImage:(NSImage*)image |
| 116 | forButtonState:(image_button_cell::ButtonState)state { |
| 117 | DCHECK_GE(state, 0); |
| 118 | DCHECK_LT(state, image_button_cell::kButtonStateCount); |
| 119 | |
| 120 | image_[state].image.reset([image retain]); |
| 121 | image_[state].imageId = 0; |
| 122 | [[self controlView] setNeedsDisplay:YES]; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 123 | } |
| 124 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 125 | - (CGFloat)imageAlphaForWindowState:(NSWindow*)window { |
| 126 | BOOL windowHasFocus = [window isMainWindow] || [window isKeyWindow]; |
| 127 | return windowHasFocus ? 1.0 : kImageNoFocusAlpha; |
| 128 | } |
| 129 | |
| 130 | - (void)drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 131 | if (![self showsFirstResponder]) |
| 132 | return; |
| 133 | gfx::ScopedNSGraphicsContextSaveGState scoped_state; |
| 134 | const CGFloat lineWidth = [controlView cr_lineWidth]; |
| 135 | rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll, |
| 136 | NSInsetRect(cellFrame, 0, lineWidth), |
| 137 | 0.0, // insetX |
| 138 | 0.0, // insetY |
| 139 | 3.0, // outerRadius |
| 140 | lineWidth * 2, // lineWidth |
| 141 | [controlView |
| 142 | cr_keyboardFocusIndicatorColor]); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | - (image_button_cell::ButtonState)currentButtonState { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 146 | bool (^has)(image_button_cell::ButtonState) = |
| 147 | ^(image_button_cell::ButtonState state) { |
| 148 | return image_[state].image || image_[state].imageId; |
| 149 | }; |
| 150 | if (![self isEnabled] && has(image_button_cell::kDisabledState)) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 151 | return image_button_cell::kDisabledState; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 152 | if ([self isHighlighted] && has(image_button_cell::kPressedState)) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 153 | return image_button_cell::kPressedState; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 154 | if ([self isMouseInside] && has(image_button_cell::kHoverState)) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 155 | return image_button_cell::kHoverState; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 156 | return image_button_cell::kDefaultState; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | - (NSImage*)imageForID:(NSInteger)imageID |
| 160 | controlView:(NSView*)controlView { |
| 161 | if (!imageID) |
| 162 | return nil; |
| 163 | |
| 164 | ui::ThemeProvider* themeProvider = [[controlView window] themeProvider]; |
| 165 | if (!themeProvider) |
| 166 | return nil; |
| 167 | |
| 168 | return themeProvider->GetNSImageNamed(imageID, true); |
| 169 | } |
| 170 | |
| 171 | - (void)setIsMouseInside:(BOOL)isMouseInside { |
| 172 | if (isMouseInside_ != isMouseInside) { |
| 173 | isMouseInside_ = isMouseInside; |
| 174 | NSView<ImageButton>* control = |
| 175 | static_cast<NSView<ImageButton>*>([self controlView]); |
| 176 | if ([control respondsToSelector:@selector(mouseInsideStateDidChange:)]) { |
| 177 | [control mouseInsideStateDidChange:isMouseInside]; |
| 178 | } |
| 179 | [control setNeedsDisplay:YES]; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)show { |
| 184 | VLOG_IF(1, !show) << "setShowsBorderOnlyWhileMouseInside:NO ignored"; |
| 185 | } |
| 186 | |
| 187 | - (BOOL)showsBorderOnlyWhileMouseInside { |
| 188 | // Always returns YES so that buttons always get mouse tracking even when |
| 189 | // disabled. The reload button (and possibly others) depend on this. |
| 190 | return YES; |
| 191 | } |
| 192 | |
| 193 | - (void)mouseEntered:(NSEvent*)theEvent { |
| 194 | [self setIsMouseInside:YES]; |
| 195 | } |
| 196 | |
| 197 | - (void)mouseExited:(NSEvent*)theEvent { |
| 198 | [self setIsMouseInside:NO]; |
| 199 | } |
| 200 | |
| 201 | @end |