Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2011 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 "base/memory/scoped_nsobject.h"
      8 #include "grit/generated_resources.h"
      9 #import "third_party/molokocacao/NSBezierPath+MCAdditions.h"
     10 #include "ui/base/l10n/l10n_util.h"
     11 
     12 namespace  {
     13 const CGFloat kCircleRadius = 0.415 * 16;
     14 const CGFloat kCircleHoverWhite = 0.565;
     15 const CGFloat kCircleClickWhite = 0.396;
     16 const CGFloat kXShadowAlpha = 0.75;
     17 const CGFloat kXShadowCircleAlpha = 0.1;
     18 }  // namespace
     19 
     20 @interface HoverCloseButton(Private)
     21 - (void)updatePaths;
     22 - (void)setUpDrawingPaths;
     23 @end
     24 
     25 @implementation HoverCloseButton
     26 
     27 - (id)initWithFrame:(NSRect)frameRect {
     28   if ((self = [super initWithFrame:frameRect])) {
     29     [self commonInit];
     30   }
     31   return self;
     32 }
     33 
     34 - (void)awakeFromNib {
     35   [super awakeFromNib];
     36   [self commonInit];
     37 }
     38 
     39 - (void)drawRect:(NSRect)rect {
     40   if (!circlePath_.get() || !xPath_.get())
     41     [self setUpDrawingPaths];
     42 
     43   // Only call updatePaths if the size changed.
     44   if (!NSEqualSizes(oldSize_, [self bounds].size))
     45     [self updatePaths];
     46 
     47   // If the user is hovering over the button, a light/dark gray circle is drawn
     48   // behind the 'x'.
     49   if (hoverState_ != kHoverStateNone) {
     50     // Adjust the darkness of the circle depending on whether it is being
     51     // clicked.
     52     CGFloat white = (hoverState_ == kHoverStateMouseOver) ?
     53         kCircleHoverWhite : kCircleClickWhite;
     54     [[NSColor colorWithCalibratedWhite:white alpha:1.0] set];
     55     [circlePath_ fill];
     56   }
     57 
     58   [[NSColor whiteColor] set];
     59   [xPath_ fill];
     60 
     61   // Give the 'x' an inner shadow for depth. If the button is in a hover state
     62   // (circle behind it), then adjust the shadow accordingly (not as harsh).
     63   NSShadow* shadow = [[[NSShadow alloc] init] autorelease];
     64   CGFloat alpha = (hoverState_ != kHoverStateNone) ?
     65       kXShadowCircleAlpha : kXShadowAlpha;
     66   [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.15
     67                                                      alpha:alpha]];
     68   [shadow setShadowOffset:NSMakeSize(0.0, 0.0)];
     69   [shadow setShadowBlurRadius:2.5];
     70   [xPath_ fillWithInnerShadow:shadow];
     71 }
     72 
     73 - (void)commonInit {
     74   // Set accessibility description.
     75   NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE);
     76   [[self cell]
     77       accessibilitySetOverrideValue:description
     78                        forAttribute:NSAccessibilityDescriptionAttribute];
     79 
     80   // Add a tooltip.
     81   [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB)];
     82 }
     83 
     84 - (void)setUpDrawingPaths {
     85   // Keep the paths centered around the origin in this function. It is then
     86   // translated in -updatePaths.
     87   NSPoint xCenter = NSZeroPoint;
     88 
     89   circlePath_.reset([[NSBezierPath bezierPath] retain]);
     90   [circlePath_ moveToPoint:xCenter];
     91   CGFloat radius = kCircleRadius;
     92   [circlePath_ appendBezierPathWithArcWithCenter:xCenter
     93                                           radius:radius
     94                                       startAngle:0.0
     95                                         endAngle:365.0];
     96 
     97   // Construct an 'x' by drawing two intersecting rectangles in the shape of a
     98   // cross and then rotating the path by 45 degrees.
     99   xPath_.reset([[NSBezierPath bezierPath] retain]);
    100   [xPath_ appendBezierPathWithRect:NSMakeRect(3.5, 7.0, 9.0, 2.0)];
    101   [xPath_ appendBezierPathWithRect:NSMakeRect(7.0, 3.5, 2.0, 9.0)];
    102 
    103   NSRect pathBounds = [xPath_ bounds];
    104   NSPoint pathCenter = NSMakePoint(NSMidX(pathBounds), NSMidY(pathBounds));
    105 
    106   NSAffineTransform* transform = [NSAffineTransform transform];
    107   [transform translateXBy:xCenter.x yBy:xCenter.y];
    108   [transform rotateByDegrees:45.0];
    109   [transform translateXBy:-pathCenter.x yBy:-pathCenter.y];
    110 
    111   [xPath_ transformUsingAffineTransform:transform];
    112 }
    113 
    114 - (void)updatePaths {
    115   oldSize_ = [self bounds].size;
    116 
    117   // Revert the current transform for the two points.
    118   if (transform_.get()) {
    119     [transform_.get() invert];
    120     [circlePath_.get() transformUsingAffineTransform:transform_.get()];
    121     [xPath_.get() transformUsingAffineTransform:transform_.get()];
    122   }
    123 
    124   // Create the new transform. [self bounds] is prefered in case aRect wasn't
    125   // literally taken as bounds (e.g. cropped).
    126   NSPoint xCenter = NSMakePoint(8, oldSize_.height / 2.0f);
    127 
    128   // Retain here, as scoped_* don't retain.
    129   transform_.reset([[NSAffineTransform transform] retain]);
    130   [transform_.get() translateXBy:xCenter.x yBy:xCenter.y];
    131   [circlePath_.get() transformUsingAffineTransform:transform_.get()];
    132   [xPath_.get() transformUsingAffineTransform:transform_.get()];
    133 }
    134 
    135 @end
    136