Home | History | Annotate | Download | only in cocoa
      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