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 "base/strings/sys_string_conversions.h"
8 #include "chrome/grit/generated_resources.h"
9 #include "grit/theme_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 #include "ui/resources/grit/ui_resources.h"
17 const CGFloat kFramesPerSecond = 16; // Determined experimentally to look good.
18 const CGFloat kCloseAnimationDuration = 0.1;
20 // Strings that are used for all close buttons. Set up in +initialize.
21 NSString* gBasicAccessibilityTitle = nil;
22 NSString* gTooltip = nil;
24 // If this string is changed, the setter (currently setFadeOutValue:) must
25 // be changed as well to match.
26 NSString* const kFadeOutValueKeyPath = @"fadeOutValue";
29 @interface HoverCloseButton ()
31 // Common initialization routine called from initWithFrame and awakeFromNib.
34 // Called by |fadeOutAnimation_| when animated value changes.
35 - (void)setFadeOutValue:(CGFloat)value;
37 // Gets the image for the given hover state.
38 - (NSImage*)imageForHoverState:(HoverState)hoverState;
42 @implementation HoverCloseButton
45 // Grab some strings that are used by all close buttons.
46 if (!gBasicAccessibilityTitle) {
47 gBasicAccessibilityTitle = [l10n_util::GetNSStringWithFixup(
48 IDS_ACCNAME_CLOSE) copy];
51 gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy];
54 - (id)initWithFrame:(NSRect)frameRect {
55 if ((self = [super initWithFrame:frameRect])) {
61 - (void)awakeFromNib {
66 - (void)removeFromSuperview {
67 // -stopAnimation will call the animationDidStop: delegate method
68 // which will release our animation.
69 [fadeOutAnimation_ stopAnimation];
70 [super removeFromSuperview];
73 - (void)animationDidStop:(NSAnimation*)animation {
74 DCHECK(animation == fadeOutAnimation_);
75 [fadeOutAnimation_ setDelegate:nil];
76 [fadeOutAnimation_ release];
77 fadeOutAnimation_ = nil;
80 - (void)animationDidEnd:(NSAnimation*)animation {
81 [self animationDidStop:animation];
84 - (void)drawRect:(NSRect)dirtyRect {
85 NSImage* image = [self imageForHoverState:[self hoverState]];
87 // Close boxes align left horizontally, and align center vertically.
88 // http:crbug.com/14739 requires this.
89 NSRect imageRect = NSZeroRect;
90 imageRect.size = [image size];
92 NSRect destRect = [self bounds];
93 destRect.origin.y = floor((NSHeight(destRect) / 2)
94 - (NSHeight(imageRect) / 2));
95 destRect.size = imageRect.size;
97 switch(self.hoverState) {
98 case kHoverStateMouseOver:
99 case kHoverStateMouseDown:
100 [image drawInRect:destRect
102 operation:NSCompositeSourceOver
108 case kHoverStateNone: {
110 if (fadeOutAnimation_) {
111 value = [fadeOutAnimation_ currentValue];
112 NSImage* previousImage = nil;
113 if (previousState_ == kHoverStateMouseOver)
114 previousImage = [self imageForHoverState:kHoverStateMouseOver];
116 previousImage = [self imageForHoverState:kHoverStateMouseDown];
117 [previousImage drawInRect:destRect
119 operation:NSCompositeSourceOver
124 [image drawInRect:destRect
126 operation:NSCompositeSourceOver
135 - (void)drawFocusRingMask {
136 // Match the hover image's shape.
137 NSRect circleRect = NSInsetRect([self bounds], 2, 2);
138 [[NSBezierPath bezierPathWithOvalInRect:circleRect] fill];
141 - (void)setFadeOutValue:(CGFloat)value {
142 [self setNeedsDisplay];
145 - (NSImage*)imageForHoverState:(HoverState)hoverState {
146 int imageID = IDR_CLOSE_1;
147 switch (hoverState) {
148 case kHoverStateNone:
149 imageID = IDR_CLOSE_1;
151 case kHoverStateMouseOver:
152 imageID = IDR_CLOSE_1_H;
154 case kHoverStateMouseDown:
155 imageID = IDR_CLOSE_1_P;
158 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
159 return bundle.GetNativeImageNamed(imageID).ToNSImage();
162 - (void)setHoverState:(HoverState)state {
163 if (state != self.hoverState) {
164 previousState_ = self.hoverState;
165 [super setHoverState:state];
166 // Only animate the HoverStateNone case and only if this is still in the
168 if (state == kHoverStateNone && [self superview] != nil) {
169 DCHECK(fadeOutAnimation_ == nil);
171 [[GTMKeyValueAnimation alloc] initWithTarget:self
172 keyPath:kFadeOutValueKeyPath];
173 [fadeOutAnimation_ setDuration:kCloseAnimationDuration];
174 [fadeOutAnimation_ setFrameRate:kFramesPerSecond];
175 [fadeOutAnimation_ setDelegate:self];
176 [fadeOutAnimation_ startAnimation];
178 // -stopAnimation will call the animationDidStop: delegate method
179 // which will clean up the animation.
180 [fadeOutAnimation_ stopAnimation];
186 [self setAccessibilityTitle:nil];
188 // Add a tooltip. Using 'owner:self' means that
189 // -view:stringForToolTip:point:userData: will be called to provide the
190 // tooltip contents immediately before showing it.
191 [self addToolTipRect:[self bounds] owner:self userData:NULL];
193 // Initialize previousState.
194 previousState_ = kHoverStateNone;
197 // Called each time a tooltip is about to be shown.
198 - (NSString*)view:(NSView*)view
199 stringForToolTip:(NSToolTipTag)tag
201 userData:(void*)userData {
202 if (self.hoverState == kHoverStateMouseOver) {
203 // In some cases (e.g. the download tray), the button is still in the
204 // hover state, but is outside the bounds of its parent and not visible.
205 // Don't show the tooltip in that case.
206 NSRect buttonRect = [self frame];
207 NSRect parentRect = [[self superview] bounds];
208 if (NSIntersectsRect(buttonRect, parentRect))
212 return nil; // Do not show the tooltip.
215 - (void)setAccessibilityTitle:(NSString*)accessibilityTitle {
216 if (!accessibilityTitle) {
217 [super setAccessibilityTitle:gBasicAccessibilityTitle];
221 NSString* extendedTitle = l10n_util::GetNSStringFWithFixup(
222 IDS_ACCNAME_CLOSE_TAB,
223 base::SysNSStringToUTF16(accessibilityTitle));
224 [super setAccessibilityTitle:extendedTitle];
229 @implementation WebUIHoverCloseButton
231 - (NSImage*)imageForHoverState:(HoverState)hoverState {
232 int imageID = IDR_CLOSE_DIALOG;
233 switch (hoverState) {
234 case kHoverStateNone:
235 imageID = IDR_CLOSE_DIALOG;
237 case kHoverStateMouseOver:
238 imageID = IDR_CLOSE_DIALOG_H;
240 case kHoverStateMouseDown:
241 imageID = IDR_CLOSE_DIALOG_P;
244 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
245 return bundle.GetNativeImageNamed(imageID).ToNSImage();