1 // Copyright (c) 2012 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/hover_close_button.h" 6 7 #include "grit/generated_resources.h" 8 #include "grit/theme_resources.h" 9 #include "grit/ui_resources.h" 10 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMKeyValueAnimation.h" 11 #include "ui/base/cocoa/animation_utils.h" 12 #include "ui/base/l10n/l10n_util.h" 13 #include "ui/base/resource/resource_bundle.h" 14 15 namespace { 16 const CGFloat kFramesPerSecond = 16; // Determined experimentally to look good. 17 const CGFloat kCloseAnimationDuration = 0.1; 18 19 // Strings that are used for all close buttons. Set up in +initialize. 20 NSString* gTooltip = nil; 21 NSString* gDescription = nil; 22 23 // If this string is changed, the setter (currently setFadeOutValue:) must 24 // be changed as well to match. 25 NSString* const kFadeOutValueKeyPath = @"fadeOutValue"; 26 } // namespace 27 28 @interface HoverCloseButton () 29 30 // Common initialization routine called from initWithFrame and awakeFromNib. 31 - (void)commonInit; 32 33 // Called by |fadeOutAnimation_| when animated value changes. 34 - (void)setFadeOutValue:(CGFloat)value; 35 36 // Gets the image for the given hover state. 37 - (NSImage*)imageForHoverState:(HoverState)hoverState; 38 39 @end 40 41 @implementation HoverCloseButton 42 43 + (void)initialize { 44 // Grab some strings that are used by all close buttons. 45 if (!gDescription) 46 gDescription = [l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE) copy]; 47 if (!gTooltip) 48 gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy]; 49 } 50 51 - (id)initWithFrame:(NSRect)frameRect { 52 if ((self = [super initWithFrame:frameRect])) { 53 [self commonInit]; 54 } 55 return self; 56 } 57 58 - (void)awakeFromNib { 59 [super awakeFromNib]; 60 [self commonInit]; 61 } 62 63 - (void)removeFromSuperview { 64 // -stopAnimation will call the animationDidStop: delegate method 65 // which will release our animation. 66 [fadeOutAnimation_ stopAnimation]; 67 [super removeFromSuperview]; 68 } 69 70 - (void)animationDidStop:(NSAnimation*)animation { 71 DCHECK(animation == fadeOutAnimation_); 72 [fadeOutAnimation_ setDelegate:nil]; 73 [fadeOutAnimation_ release]; 74 fadeOutAnimation_ = nil; 75 } 76 77 - (void)animationDidEnd:(NSAnimation*)animation { 78 [self animationDidStop:animation]; 79 } 80 81 - (void)drawRect:(NSRect)dirtyRect { 82 NSImage* image = [self imageForHoverState:[self hoverState]]; 83 84 // Close boxes align left horizontally, and align center vertically. 85 // http:crbug.com/14739 requires this. 86 NSRect imageRect = NSZeroRect; 87 imageRect.size = [image size]; 88 89 NSRect destRect = [self bounds]; 90 destRect.origin.y = floor((NSHeight(destRect) / 2) 91 - (NSHeight(imageRect) / 2)); 92 destRect.size = imageRect.size; 93 94 switch(self.hoverState) { 95 case kHoverStateMouseOver: 96 [image drawInRect:destRect 97 fromRect:imageRect 98 operation:NSCompositeSourceOver 99 fraction:1.0 100 respectFlipped:YES 101 hints:nil]; 102 break; 103 104 case kHoverStateMouseDown: 105 [image drawInRect:destRect 106 fromRect:imageRect 107 operation:NSCompositeSourceOver 108 fraction:1.0 109 respectFlipped:YES 110 hints:nil]; 111 break; 112 113 case kHoverStateNone: { 114 CGFloat value = 1.0; 115 if (fadeOutAnimation_) { 116 value = [fadeOutAnimation_ currentValue]; 117 NSImage* previousImage = nil; 118 if (previousState_ == kHoverStateMouseOver) 119 previousImage = [self imageForHoverState:kHoverStateMouseOver]; 120 else 121 previousImage = [self imageForHoverState:kHoverStateMouseDown]; 122 [previousImage drawInRect:destRect 123 fromRect:imageRect 124 operation:NSCompositeSourceOver 125 fraction:1.0 - value 126 respectFlipped:YES 127 hints:nil]; 128 } 129 [image drawInRect:destRect 130 fromRect:imageRect 131 operation:NSCompositeSourceOver 132 fraction:value 133 respectFlipped:YES 134 hints:nil]; 135 break; 136 } 137 } 138 } 139 140 - (void)setFadeOutValue:(CGFloat)value { 141 [self setNeedsDisplay]; 142 } 143 144 - (NSImage*)imageForHoverState:(HoverState)hoverState { 145 int imageID = IDR_CLOSE_1; 146 switch (hoverState) { 147 case kHoverStateNone: 148 imageID = IDR_CLOSE_1; 149 break; 150 case kHoverStateMouseOver: 151 imageID = IDR_CLOSE_1_H; 152 break; 153 case kHoverStateMouseDown: 154 imageID = IDR_CLOSE_1_P; 155 break; 156 } 157 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 158 return bundle.GetNativeImageNamed(imageID).ToNSImage(); 159 } 160 161 - (void)setHoverState:(HoverState)state { 162 if (state != self.hoverState) { 163 previousState_ = self.hoverState; 164 [super setHoverState:state]; 165 // Only animate the HoverStateNone case. 166 if (state == kHoverStateNone) { 167 DCHECK(fadeOutAnimation_ == nil); 168 fadeOutAnimation_ = 169 [[GTMKeyValueAnimation alloc] initWithTarget:self 170 keyPath:kFadeOutValueKeyPath]; 171 [fadeOutAnimation_ setDuration:kCloseAnimationDuration]; 172 [fadeOutAnimation_ setFrameRate:kFramesPerSecond]; 173 [fadeOutAnimation_ setDelegate:self]; 174 [fadeOutAnimation_ startAnimation]; 175 } else { 176 // -stopAnimation will call the animationDidStop: delegate method 177 // which will clean up the animation. 178 [fadeOutAnimation_ stopAnimation]; 179 } 180 } 181 } 182 183 - (void)commonInit { 184 // Set accessibility description. 185 NSCell* cell = [self cell]; 186 [cell accessibilitySetOverrideValue:gDescription 187 forAttribute:NSAccessibilityDescriptionAttribute]; 188 189 // Add a tooltip. Using 'owner:self' means that 190 // -view:stringForToolTip:point:userData: will be called to provide the 191 // tooltip contents immediately before showing it. 192 [self addToolTipRect:[self bounds] owner:self userData:NULL]; 193 194 // Initialize previousState. 195 previousState_ = kHoverStateNone; 196 } 197 198 // Called each time a tooltip is about to be shown. 199 - (NSString*)view:(NSView*)view 200 stringForToolTip:(NSToolTipTag)tag 201 point:(NSPoint)point 202 userData:(void*)userData { 203 if (self.hoverState == kHoverStateMouseOver) { 204 // In some cases (e.g. the download tray), the button is still in the 205 // hover state, but is outside the bounds of its parent and not visible. 206 // Don't show the tooltip in that case. 207 NSRect buttonRect = [self frame]; 208 NSRect parentRect = [[self superview] bounds]; 209 if (NSIntersectsRect(buttonRect, parentRect)) 210 return gTooltip; 211 } 212 213 return nil; // Do not show the tooltip. 214 } 215 216 @end 217 218 @implementation WebUIHoverCloseButton 219 220 - (NSImage*)imageForHoverState:(HoverState)hoverState { 221 int imageID = IDR_CLOSE_DIALOG; 222 switch (hoverState) { 223 case kHoverStateNone: 224 imageID = IDR_CLOSE_DIALOG; 225 break; 226 case kHoverStateMouseOver: 227 imageID = IDR_CLOSE_DIALOG_H; 228 break; 229 case kHoverStateMouseDown: 230 imageID = IDR_CLOSE_DIALOG_P; 231 break; 232 } 233 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 234 return bundle.GetNativeImageNamed(imageID).ToNSImage(); 235 } 236 237 @end 238