summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/hover_close_button.mm
blob: c885ae67c8c573f373e7553ab6a99d500e36ad34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/cocoa/hover_close_button.h"

#include "app/l10n_util.h"
#include "base/scoped_nsobject.h"
#import "chrome/browser/cocoa/third_party/NSBezierPath+MCAdditions.h"
#include "grit/generated_resources.h"

namespace  {
// Convenience function to return the middle point of the given |rect|.
static NSPoint MidRect(NSRect rect) {
  return NSMakePoint(NSMidX(rect), NSMidY(rect));
}

const CGFloat kCircleRadiusPercentage = 0.45;
const CGFloat kCircleHoverWhite = 0.565;
const CGFloat kCircleClickWhite = 0.396;
const CGFloat kXShadowAlpha = 0.6;
const CGFloat kXShadowCircleAlpha = 0.1;
}  // namespace

@interface HoverCloseButton(Private)
- (void)setUpDrawingPaths;
@end

@implementation HoverCloseButton

- (id)initWithFrame:(NSRect)frameRect {
  if ((self = [super initWithFrame:frameRect])) {
    [self commonInit];
  }
  return self;
}

- (void)awakeFromNib {
  [self commonInit];
}

- (void)drawRect:(NSRect)rect {
  if (!circlePath_.get() || !xPath_.get())
    [self setUpDrawingPaths];

  // If the user is hovering over the button, a light/dark gray circle is drawn
  // behind the 'x'.
  if (hoverState_ != kHoverStateNone) {
    // Adjust the darkness of the circle depending on whether it is being
    // clicked.
    CGFloat white = (hoverState_ == kHoverStateMouseOver) ?
        kCircleHoverWhite : kCircleClickWhite;
    [[NSColor colorWithCalibratedWhite:white alpha:1.0] set];
    [circlePath_ fill];
  }

  [[NSColor whiteColor] set];
  [xPath_ fill];

  // Give the 'x' an inner shadow for depth. If the button is in a hover state
  // (circle behind it), then adjust the shadow accordingly (not as harsh).
  NSShadow* shadow = [[NSShadow alloc] init];
  CGFloat alpha = (hoverState_ != kHoverStateNone) ?
      kXShadowCircleAlpha : kXShadowAlpha;
  [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0
                                                     alpha:alpha]];
  [shadow setShadowOffset:NSMakeSize(0.0, -1.0)];
  [shadow setShadowBlurRadius:2.0];
  [xPath_ fillWithInnerShadow:shadow];
}

- (void)commonInit {
  [self setTrackingEnabled:YES];
  hoverState_ = kHoverStateNone;
  [self updateTrackingAreas];

  // Set accessibility description.
  NSString* description = l10n_util::GetNSStringWithFixup(IDS_ACCNAME_CLOSE);
  [[self cell]
      accessibilitySetOverrideValue:description
                       forAttribute:NSAccessibilityDescriptionAttribute];
}

- (void)setUpDrawingPaths {
  NSPoint viewCenter = MidRect([self bounds]);

  circlePath_.reset([[NSBezierPath bezierPath] retain]);
  [circlePath_ moveToPoint:viewCenter];
  CGFloat radius = kCircleRadiusPercentage * NSWidth([self bounds]);
  [circlePath_ appendBezierPathWithArcWithCenter:viewCenter
                                          radius:radius
                                      startAngle:0.0
                                        endAngle:365.0];

  // Construct an 'x' by drawing two intersecting rectangles in the shape of a
  // cross and then rotating the path by 45 degrees.
  xPath_.reset([[NSBezierPath bezierPath] retain]);
  [xPath_ appendBezierPathWithRect:NSMakeRect(3.5, 7.0, 9.0, 2.0)];
  [xPath_ appendBezierPathWithRect:NSMakeRect(7.0, 3.5, 2.0, 9.0)];

  NSPoint pathCenter = MidRect([xPath_ bounds]);

  NSAffineTransform* transform = [NSAffineTransform transform];
  [transform translateXBy:viewCenter.x yBy:viewCenter.y];
  [transform rotateByDegrees:45.0];
  [transform translateXBy:-pathCenter.x yBy:-pathCenter.y];

  [xPath_ transformUsingAffineTransform:transform];
}

- (void)dealloc {
  [self setTrackingEnabled:NO];
  [super dealloc];
}

- (void)mouseEntered:(NSEvent*)theEvent {
  hoverState_ = kHoverStateMouseOver;
  [self setNeedsDisplay:YES];
}

- (void)mouseExited:(NSEvent*)theEvent {
  hoverState_ = kHoverStateNone;
  [self setNeedsDisplay:YES];
}

- (void)mouseDown:(NSEvent*)theEvent {
  hoverState_ = kHoverStateMouseDown;
  [self setNeedsDisplay:YES];
  // The hover button needs to hold onto itself here for a bit.  Otherwise,
  // it can be freed while |super mouseDown:| is in it's loop, and the
  // |checkImageState| call will crash.
  // http://crbug.com/28220
  scoped_nsobject<HoverCloseButton> myself([self retain]);

  [super mouseDown:theEvent];
  // We need to check the image state after the mouseDown event loop finishes.
  // It's possible that we won't get a mouseExited event if the button was
  // moved under the mouse during tab resize, instead of the mouse moving over
  // the button.
  // http://crbug.com/31279
  [self checkImageState];
}

- (void)setTrackingEnabled:(BOOL)enabled {
  if (enabled) {
    closeTrackingArea_.reset(
        [[NSTrackingArea alloc] initWithRect:[self bounds]
                                     options:NSTrackingMouseEnteredAndExited |
                                             NSTrackingActiveAlways
                                       owner:self
                                    userInfo:nil]);
    [self addTrackingArea:closeTrackingArea_.get()];

    // If you have a separate window that overlaps the close button, and you
    // move the mouse directly over the close button without entering another
    // part of the tab strip, we don't get any mouseEntered event since the
    // tracking area was disabled when we entered.
    [self checkImageState];
  } else {
    if (closeTrackingArea_.get()) {
      [self removeTrackingArea:closeTrackingArea_.get()];
      closeTrackingArea_.reset(nil);
    }
  }
}

- (void)updateTrackingAreas {
  [super updateTrackingAreas];
  [self checkImageState];
}

- (void)checkImageState {
  if (!closeTrackingArea_.get())
    return;

  // Update the close buttons if the tab has moved.
  NSPoint mouseLoc = [[self window] mouseLocationOutsideOfEventStream];
  mouseLoc = [self convertPoint:mouseLoc fromView:nil];
  hoverState_ = NSPointInRect(mouseLoc, [self bounds]) ?
      kHoverStateMouseOver : kHoverStateNone;
  [self setNeedsDisplay:YES];
}

@end