Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / hover_close_button.mm
blobbe82dc52a8a71f22519de5974abb0d40df08b6f1
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"
16 namespace  {
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";
27 }  // namespace
29 @interface HoverCloseButton ()
31 // Common initialization routine called from initWithFrame and awakeFromNib.
32 - (void)commonInit;
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;
40 @end
42 @implementation HoverCloseButton
44 + (void)initialize {
45   // Grab some strings that are used by all close buttons.
46   if (!gBasicAccessibilityTitle) {
47     gBasicAccessibilityTitle = [l10n_util::GetNSStringWithFixup(
48         IDS_ACCNAME_CLOSE) copy];
49   }
50   if (!gTooltip)
51     gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy];
54 - (id)initWithFrame:(NSRect)frameRect {
55   if ((self = [super initWithFrame:frameRect])) {
56     [self commonInit];
57   }
58   return self;
61 - (void)awakeFromNib {
62   [super awakeFromNib];
63   [self commonInit];
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
101                fromRect:imageRect
102               operation:NSCompositeSourceOver
103                fraction:1.0
104          respectFlipped:YES
105                   hints:nil];
106       break;
108     case kHoverStateNone: {
109       CGFloat value = 1.0;
110       if (fadeOutAnimation_) {
111         value = [fadeOutAnimation_ currentValue];
112         NSImage* previousImage = nil;
113         if (previousState_ == kHoverStateMouseOver)
114           previousImage = [self imageForHoverState:kHoverStateMouseOver];
115         else
116           previousImage = [self imageForHoverState:kHoverStateMouseDown];
117         [previousImage drawInRect:destRect
118                          fromRect:imageRect
119                         operation:NSCompositeSourceOver
120                          fraction:1.0 - value
121                    respectFlipped:YES
122                             hints:nil];
123       }
124       [image drawInRect:destRect
125                fromRect:imageRect
126               operation:NSCompositeSourceOver
127                fraction:value
128          respectFlipped:YES
129                   hints:nil];
130       break;
131     }
132   }
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;
150       break;
151     case kHoverStateMouseOver:
152       imageID = IDR_CLOSE_1_H;
153       break;
154     case kHoverStateMouseDown:
155       imageID = IDR_CLOSE_1_P;
156       break;
157   }
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
167     // view hierarchy.
168     if (state == kHoverStateNone && [self superview] != nil) {
169       DCHECK(fadeOutAnimation_ == nil);
170       fadeOutAnimation_ =
171           [[GTMKeyValueAnimation alloc] initWithTarget:self
172                                                keyPath:kFadeOutValueKeyPath];
173       [fadeOutAnimation_ setDuration:kCloseAnimationDuration];
174       [fadeOutAnimation_ setFrameRate:kFramesPerSecond];
175       [fadeOutAnimation_ setDelegate:self];
176       [fadeOutAnimation_ startAnimation];
177     } else {
178       // -stopAnimation will call the animationDidStop: delegate method
179       // which will clean up the animation.
180       [fadeOutAnimation_ stopAnimation];
181     }
182   }
185 - (void)commonInit {
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
200             point:(NSPoint)point
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))
209       return gTooltip;
210   }
212   return nil;  // Do not show the tooltip.
215 - (void)setAccessibilityTitle:(NSString*)accessibilityTitle {
216   if (!accessibilityTitle) {
217     [super setAccessibilityTitle:gBasicAccessibilityTitle];
218     return;
219   }
221   NSString* extendedTitle = l10n_util::GetNSStringFWithFixup(
222       IDS_ACCNAME_CLOSE_TAB,
223       base::SysNSStringToUTF16(accessibilityTitle));
224   [super setAccessibilityTitle:extendedTitle];
227 @end
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;
236       break;
237     case kHoverStateMouseOver:
238       imageID = IDR_CLOSE_DIALOG_H;
239       break;
240     case kHoverStateMouseDown:
241       imageID = IDR_CLOSE_DIALOG_P;
242       break;
243   }
244   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
245   return bundle.GetNativeImageNamed(imageID).ToNSImage();
248 @end