base: Change DCHECK_IS_ON to a macro DCHECK_IS_ON().
[chromium-blink-merge.git] / ui / message_center / cocoa / popup_controller.mm
blob889d8dea6c590c78395fa032143ef54b77359899
1 // Copyright (c) 2013 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 "ui/message_center/cocoa/popup_controller.h"
7 #include <cmath>
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/sdk_forward_declarations.h"
11 #import "ui/base/cocoa/window_size_constants.h"
12 #import "ui/message_center/cocoa/notification_controller.h"
13 #import "ui/message_center/cocoa/popup_collection.h"
14 #include "ui/message_center/message_center.h"
16 ////////////////////////////////////////////////////////////////////////////////
18 @interface MCPopupController (Private)
19 - (void)notificationSwipeStarted;
20 - (void)notificationSwipeMoved:(CGFloat)amount;
21 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete;
23 // This setter for |boundsAnimation_| also cleans up the state of the previous
24 // |boundsAnimation_|.
25 - (void)setBoundsAnimation:(NSViewAnimation*)animation;
27 // Constructs an NSViewAnimation from |dictionary|, which should be a view
28 // animation dictionary.
29 - (NSViewAnimation*)animationWithDictionary:(NSDictionary*)dictionary;
30 @end
32 // Window Subclass /////////////////////////////////////////////////////////////
34 @interface MCPopupWindow : NSPanel {
35   // The cumulative X and Y scrollingDeltas since the -scrollWheel: event began.
36   NSPoint totalScrollDelta_;
38 @end
40 @implementation MCPopupWindow
42 - (void)scrollWheel:(NSEvent*)event {
43   // Gesture swiping only exists on 10.7+.
44   if (![event respondsToSelector:@selector(phase)])
45     return;
47   NSEventPhase phase = [event phase];
48   BOOL shouldTrackSwipe = NO;
50   if (phase == NSEventPhaseBegan) {
51     totalScrollDelta_ = NSZeroPoint;
52   } else if (phase == NSEventPhaseChanged) {
53     shouldTrackSwipe = YES;
54     totalScrollDelta_.x += [event scrollingDeltaX];
55     totalScrollDelta_.y += [event scrollingDeltaY];
56   }
58   // Only allow horizontal scrolling.
59   if (std::abs(totalScrollDelta_.x) < std::abs(totalScrollDelta_.y))
60     return;
62   if (shouldTrackSwipe) {
63     MCPopupController* controller =
64         base::mac::ObjCCastStrict<MCPopupController>([self windowController]);
65     BOOL directionInverted = [event isDirectionInvertedFromDevice];
67     auto handler = ^(CGFloat gestureAmount, NSEventPhase phase,
68                      BOOL isComplete, BOOL* stop) {
69         // The swipe direction should match the direction the user's fingers
70         // are moving, not the interpreted scroll direction.
71         if (directionInverted)
72           gestureAmount *= -1;
74         if (phase == NSEventPhaseBegan) {
75           [controller notificationSwipeStarted];
76           return;
77         }
79         [controller notificationSwipeMoved:gestureAmount];
81         BOOL ended = phase == NSEventPhaseEnded;
82         if (ended || isComplete)
83           [controller notificationSwipeEnded:ended complete:isComplete];
84     };
85     [event trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection
86              dampenAmountThresholdMin:-1
87                                   max:1
88                          usingHandler:handler];
89   }
92 @end
94 ////////////////////////////////////////////////////////////////////////////////
96 @implementation MCPopupController
98 - (id)initWithNotification:(const message_center::Notification*)notification
99              messageCenter:(message_center::MessageCenter*)messageCenter
100            popupCollection:(MCPopupCollection*)popupCollection {
101   base::scoped_nsobject<MCPopupWindow> window(
102       [[MCPopupWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
103                                        styleMask:NSBorderlessWindowMask |
104                                                  NSNonactivatingPanelMask
105                                          backing:NSBackingStoreBuffered
106                                            defer:YES]);
107   if ((self = [super initWithWindow:window])) {
108     messageCenter_ = messageCenter;
109     popupCollection_ = popupCollection;
110     notificationController_.reset(
111         [[MCNotificationController alloc] initWithNotification:notification
112                                                  messageCenter:messageCenter_]);
113     isClosing_ = NO;
114     bounds_ = [[notificationController_ view] frame];
116     [window setReleasedWhenClosed:NO];
118     [window setLevel:NSFloatingWindowLevel];
119     [window setExcludedFromWindowsMenu:YES];
120     [window setCollectionBehavior:
121         NSWindowCollectionBehaviorIgnoresCycle |
122         NSWindowCollectionBehaviorFullScreenAuxiliary];
124     [window setHasShadow:YES];
125     [window setContentView:[notificationController_ view]];
127     trackingArea_.reset(
128         [[CrTrackingArea alloc] initWithRect:NSZeroRect
129                                      options:NSTrackingInVisibleRect |
130                                              NSTrackingMouseEnteredAndExited |
131                                              NSTrackingActiveAlways
132                                        owner:self
133                                     userInfo:nil]);
134     [[window contentView] addTrackingArea:trackingArea_.get()];
135   }
136   return self;
139 - (void)close {
140   [self setBoundsAnimation:nil];
141   if (trackingArea_.get())
142     [[[self window] contentView] removeTrackingArea:trackingArea_.get()];
143   [super close];
144   [self performSelectorOnMainThread:@selector(release)
145                          withObject:nil
146                       waitUntilDone:NO
147                               modes:@[ NSDefaultRunLoopMode ]];
150 - (MCNotificationController*)notificationController {
151   return notificationController_.get();
154 - (const message_center::Notification*)notification {
155   return [notificationController_ notification];
158 - (const std::string&)notificationID {
159   return [notificationController_ notificationID];
162 // Private /////////////////////////////////////////////////////////////////////
164 - (void)notificationSwipeStarted {
165   originalFrame_ = [[self window] frame];
166   swipeGestureEnded_ = NO;
169 - (void)notificationSwipeMoved:(CGFloat)amount {
170   NSWindow* window = [self window];
172   [window setAlphaValue:1.0 - std::abs(amount)];
173   NSRect frame = [window frame];
174   CGFloat originalMin = NSMinX(originalFrame_);
175   frame.origin.x = originalMin + (NSMidX(originalFrame_) - originalMin) *
176                    -amount;
177   [window setFrame:frame display:YES];
180 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete {
181   swipeGestureEnded_ |= ended;
182   if (swipeGestureEnded_ && isComplete) {
183     messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
184     [popupCollection_ onPopupAnimationEnded:[self notificationID]];
185   }
188 - (void)setBoundsAnimation:(NSViewAnimation*)animation {
189   [boundsAnimation_ stopAnimation];
190   [boundsAnimation_ setDelegate:nil];
191   boundsAnimation_.reset([animation retain]);
194 - (NSViewAnimation*)animationWithDictionary:(NSDictionary*)dictionary {
195   return [[[NSViewAnimation alloc]
196       initWithViewAnimations:@[ dictionary ]] autorelease];
199 - (void)animationDidEnd:(NSAnimation*)animation {
200   DCHECK_EQ(animation, boundsAnimation_.get());
201   [self setBoundsAnimation:nil];
203   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
205   if (isClosing_)
206     [self close];
209 - (void)animationDidStop:(NSAnimation*)animation {
210   // We can arrive here if animation was stopped in [self close] call.
211   DCHECK_EQ(animation, boundsAnimation_.get());
212   [self setBoundsAnimation:nil];
214   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
217 - (void)showWithAnimation:(NSRect)newBounds {
218   bounds_ = newBounds;
219   NSRect startBounds = newBounds;
220   startBounds.origin.x += startBounds.size.width;
221   [[self window] setFrame:startBounds display:NO];
222   [[self window] setAlphaValue:0];
223   [self showWindow:nil];
225   // Slide-in and fade-in simultaneously.
226   NSDictionary* animationDict = @{
227     NSViewAnimationTargetKey : [self window],
228     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds],
229     NSViewAnimationEffectKey : NSViewAnimationFadeInEffect
230   };
231   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
232   [self setBoundsAnimation:animation];
233   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
234   [boundsAnimation_ setDelegate:self];
235   [boundsAnimation_ startAnimation];
238 - (void)closeWithAnimation {
239   if (isClosing_)
240     return;
242   isClosing_ = YES;
244   // If the notification was swiped closed, do not animate it as the
245   // notification has already faded out.
246   if (swipeGestureEnded_) {
247     [self close];
248     return;
249   }
251   NSDictionary* animationDict = @{
252     NSViewAnimationTargetKey : [self window],
253     NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect
254   };
255   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
256   [self setBoundsAnimation:animation];
257   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
258   [boundsAnimation_ setDelegate:self];
259   [boundsAnimation_ startAnimation];
262 - (void)markPopupCollectionGone {
263   popupCollection_ = nil;
266 - (NSRect)bounds {
267   return bounds_;
270 - (void)setBounds:(NSRect)newBounds {
271   if (isClosing_ || NSEqualRects(bounds_ , newBounds))
272     return;
273   bounds_ = newBounds;
275   NSDictionary* animationDict = @{
276     NSViewAnimationTargetKey :   [self window],
277     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds]
278   };
279   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
280   [self setBoundsAnimation:animation];
281   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
282   [boundsAnimation_ setDelegate:self];
283   [boundsAnimation_ startAnimation];
286 - (void)mouseEntered:(NSEvent*)event {
287   messageCenter_->PausePopupTimers();
290 - (void)mouseExited:(NSEvent*)event {
291   messageCenter_->RestartPopupTimers();
294 @end