Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / ui / message_center / cocoa / popup_controller.mm
blob4991f3bbe22d8efab374beefb470c21ddb45e2f1
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 : NSWindow {
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                                          backing:NSBackingStoreBuffered
105                                            defer:NO]);
106   if ((self = [super initWithWindow:window])) {
107     messageCenter_ = messageCenter;
108     popupCollection_ = popupCollection;
109     notificationController_.reset(
110         [[MCNotificationController alloc] initWithNotification:notification
111                                                  messageCenter:messageCenter_]);
112     isClosing_ = NO;
113     bounds_ = [[notificationController_ view] frame];
115     [window setReleasedWhenClosed:NO];
117     [window setLevel:NSFloatingWindowLevel];
118     [window setExcludedFromWindowsMenu:YES];
119     [window setCollectionBehavior:
120         NSWindowCollectionBehaviorIgnoresCycle |
121         NSWindowCollectionBehaviorFullScreenAuxiliary];
123     [window setHasShadow:YES];
124     [window setContentView:[notificationController_ view]];
126     trackingArea_.reset(
127         [[CrTrackingArea alloc] initWithRect:NSZeroRect
128                                      options:NSTrackingInVisibleRect |
129                                              NSTrackingMouseEnteredAndExited |
130                                              NSTrackingActiveAlways
131                                        owner:self
132                                     userInfo:nil]);
133     [[window contentView] addTrackingArea:trackingArea_.get()];
134   }
135   return self;
138 #ifndef NDEBUG
139 - (void)dealloc {
140   DCHECK(hasBeenClosed_);
141   [super dealloc];
143 #endif
145 - (void)close {
146 #ifndef NDEBUG
147   hasBeenClosed_ = YES;
148 #endif
149   [self setBoundsAnimation:nil];
150   if (trackingArea_.get())
151     [[[self window] contentView] removeTrackingArea:trackingArea_.get()];
152   [super close];
153   [self performSelectorOnMainThread:@selector(release)
154                          withObject:nil
155                       waitUntilDone:NO
156                               modes:@[ NSDefaultRunLoopMode ]];
159 - (MCNotificationController*)notificationController {
160   return notificationController_.get();
163 - (const message_center::Notification*)notification {
164   return [notificationController_ notification];
167 - (const std::string&)notificationID {
168   return [notificationController_ notificationID];
171 // Private /////////////////////////////////////////////////////////////////////
173 - (void)notificationSwipeStarted {
174   originalFrame_ = [[self window] frame];
175   swipeGestureEnded_ = NO;
178 - (void)notificationSwipeMoved:(CGFloat)amount {
179   NSWindow* window = [self window];
181   [window setAlphaValue:1.0 - std::abs(amount)];
182   NSRect frame = [window frame];
183   CGFloat originalMin = NSMinX(originalFrame_);
184   frame.origin.x = originalMin + (NSMidX(originalFrame_) - originalMin) *
185                    -amount;
186   [window setFrame:frame display:YES];
189 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete {
190   swipeGestureEnded_ |= ended;
191   if (swipeGestureEnded_ && isComplete) {
192     messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
193     [popupCollection_ onPopupAnimationEnded:[self notificationID]];
194   }
197 - (void)setBoundsAnimation:(NSViewAnimation*)animation {
198   [boundsAnimation_ stopAnimation];
199   [boundsAnimation_ setDelegate:nil];
200   boundsAnimation_.reset([animation retain]);
203 - (NSViewAnimation*)animationWithDictionary:(NSDictionary*)dictionary {
204   return [[[NSViewAnimation alloc]
205       initWithViewAnimations:@[ dictionary ]] autorelease];
208 - (void)animationDidEnd:(NSAnimation*)animation {
209   DCHECK_EQ(animation, boundsAnimation_.get());
210   [self setBoundsAnimation:nil];
212   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
214   if (isClosing_)
215     [self close];
218 - (void)showWithAnimation:(NSRect)newBounds {
219   bounds_ = newBounds;
220   NSRect startBounds = newBounds;
221   startBounds.origin.x += startBounds.size.width;
222   [[self window] setFrame:startBounds display:NO];
223   [[self window] setAlphaValue:0];
224   [self showWindow:nil];
226   // Slide-in and fade-in simultaneously.
227   NSDictionary* animationDict = @{
228     NSViewAnimationTargetKey : [self window],
229     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds],
230     NSViewAnimationEffectKey : NSViewAnimationFadeInEffect
231   };
232   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
233   [self setBoundsAnimation:animation];
234   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
235   [boundsAnimation_ setDelegate:self];
236   [boundsAnimation_ startAnimation];
239 - (void)closeWithAnimation {
240   if (isClosing_)
241     return;
243 #ifndef NDEBUG
244   hasBeenClosed_ = YES;
245 #endif
246   isClosing_ = YES;
248   // If the notification was swiped closed, do not animate it as the
249   // notification has already faded out.
250   if (swipeGestureEnded_) {
251     [self close];
252     return;
253   }
255   NSDictionary* animationDict = @{
256     NSViewAnimationTargetKey : [self window],
257     NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect
258   };
259   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
260   [self setBoundsAnimation:animation];
261   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
262   [boundsAnimation_ setDelegate:self];
263   [boundsAnimation_ startAnimation];
266 - (void)markPopupCollectionGone {
267   popupCollection_ = nil;
270 - (NSRect)bounds {
271   return bounds_;
274 - (void)setBounds:(NSRect)newBounds {
275   if (isClosing_ || NSEqualRects(bounds_ , newBounds))
276     return;
277   bounds_ = newBounds;
279   NSDictionary* animationDict = @{
280     NSViewAnimationTargetKey :   [self window],
281     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds]
282   };
283   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
284   [self setBoundsAnimation:animation];
285   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
286   [boundsAnimation_ setDelegate:self];
287   [boundsAnimation_ startAnimation];
290 - (void)mouseEntered:(NSEvent*)event {
291   messageCenter_->PausePopupTimers();
294 - (void)mouseExited:(NSEvent*)event {
295   messageCenter_->RestartPopupTimers();
298 @end