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.
5 #import "chrome/browser/ui/cocoa/hover_close_button.h"
7 #include "chrome/grit/generated_resources.h"
8 #include "grit/theme_resources.h"
9 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMKeyValueAnimation.h"
10 #include "ui/base/cocoa/animation_utils.h"
11 #include "ui/base/l10n/l10n_util.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/resources/grit/ui_resources.h"
16 const CGFloat kFramesPerSecond = 16; // Determined experimentally to look good.
17 const CGFloat kCloseAnimationDuration = 0.1;
19 // Strings that are used for all close buttons. Set up in +initialize.
20 NSString* gTooltip = nil;
21 NSString* gDescription = nil;
23 // If this string is changed, the setter (currently setFadeOutValue:) must
24 // be changed as well to match.
25 NSString* const kFadeOutValueKeyPath = @"fadeOutValue";
28 @interface HoverCloseButton ()
30 // Common initialization routine called from initWithFrame and awakeFromNib.
33 // Called by |fadeOutAnimation_| when animated value changes.
34 - (void)setFadeOutValue:(CGFloat)value;
36 // Gets the image for the given hover state.
37 - (NSImage*)imageForHoverState:(HoverState)hoverState;
41 @implementation HoverCloseButton
44 // Grab some strings that are used by all close buttons.
46 gDescription = [l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE) copy];
48 gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy];
51 - (id)initWithFrame:(NSRect)frameRect {
52 if ((self = [super initWithFrame:frameRect])) {
58 - (void)awakeFromNib {
63 - (void)removeFromSuperview {
64 // -stopAnimation will call the animationDidStop: delegate method
65 // which will release our animation.
66 [fadeOutAnimation_ stopAnimation];
67 [super removeFromSuperview];
70 - (void)animationDidStop:(NSAnimation*)animation {
71 DCHECK(animation == fadeOutAnimation_);
72 [fadeOutAnimation_ setDelegate:nil];
73 [fadeOutAnimation_ release];
74 fadeOutAnimation_ = nil;
77 - (void)animationDidEnd:(NSAnimation*)animation {
78 [self animationDidStop:animation];
81 - (void)drawRect:(NSRect)dirtyRect {
82 NSImage* image = [self imageForHoverState:[self hoverState]];
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];
89 NSRect destRect = [self bounds];
90 destRect.origin.y = floor((NSHeight(destRect) / 2)
91 - (NSHeight(imageRect) / 2));
92 destRect.size = imageRect.size;
94 switch(self.hoverState) {
95 case kHoverStateMouseOver:
96 [image drawInRect:destRect
98 operation:NSCompositeSourceOver
104 case kHoverStateMouseDown:
105 [image drawInRect:destRect
107 operation:NSCompositeSourceOver
113 case kHoverStateNone: {
115 if (fadeOutAnimation_) {
116 value = [fadeOutAnimation_ currentValue];
117 NSImage* previousImage = nil;
118 if (previousState_ == kHoverStateMouseOver)
119 previousImage = [self imageForHoverState:kHoverStateMouseOver];
121 previousImage = [self imageForHoverState:kHoverStateMouseDown];
122 [previousImage drawInRect:destRect
124 operation:NSCompositeSourceOver
129 [image drawInRect:destRect
131 operation:NSCompositeSourceOver
140 - (void)setFadeOutValue:(CGFloat)value {
141 [self setNeedsDisplay];
144 - (NSImage*)imageForHoverState:(HoverState)hoverState {
145 int imageID = IDR_CLOSE_1;
146 switch (hoverState) {
147 case kHoverStateNone:
148 imageID = IDR_CLOSE_1;
150 case kHoverStateMouseOver:
151 imageID = IDR_CLOSE_1_H;
153 case kHoverStateMouseDown:
154 imageID = IDR_CLOSE_1_P;
157 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
158 return bundle.GetNativeImageNamed(imageID).ToNSImage();
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);
169 [[GTMKeyValueAnimation alloc] initWithTarget:self
170 keyPath:kFadeOutValueKeyPath];
171 [fadeOutAnimation_ setDuration:kCloseAnimationDuration];
172 [fadeOutAnimation_ setFrameRate:kFramesPerSecond];
173 [fadeOutAnimation_ setDelegate:self];
174 [fadeOutAnimation_ startAnimation];
176 // -stopAnimation will call the animationDidStop: delegate method
177 // which will clean up the animation.
178 [fadeOutAnimation_ stopAnimation];
184 // Set accessibility description.
185 NSCell* cell = [self cell];
186 [cell accessibilitySetOverrideValue:gDescription
187 forAttribute:NSAccessibilityDescriptionAttribute];
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];
194 // Initialize previousState.
195 previousState_ = kHoverStateNone;
198 // Called each time a tooltip is about to be shown.
199 - (NSString*)view:(NSView*)view
200 stringForToolTip:(NSToolTipTag)tag
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))
213 return nil; // Do not show the tooltip.
218 @implementation WebUIHoverCloseButton
220 - (NSImage*)imageForHoverState:(HoverState)hoverState {
221 int imageID = IDR_CLOSE_DIALOG;
222 switch (hoverState) {
223 case kHoverStateNone:
224 imageID = IDR_CLOSE_DIALOG;
226 case kHoverStateMouseOver:
227 imageID = IDR_CLOSE_DIALOG_H;
229 case kHoverStateMouseDown:
230 imageID = IDR_CLOSE_DIALOG_P;
233 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
234 return bundle.GetNativeImageNamed(imageID).ToNSImage();