Fix search results being clipped in app list.
[chromium-blink-merge.git] / ui / message_center / cocoa / popup_controller.mm
blobb301b9c7b2536120cdb1666df47d5f86546a21e8
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 #ifndef NDEBUG
140 - (void)dealloc {
141   DCHECK(hasBeenClosed_);
142   [super dealloc];
144 #endif
146 - (void)close {
147 #ifndef NDEBUG
148   hasBeenClosed_ = YES;
149 #endif
150   [self setBoundsAnimation:nil];
151   if (trackingArea_.get())
152     [[[self window] contentView] removeTrackingArea:trackingArea_.get()];
153   [super close];
154   [self performSelectorOnMainThread:@selector(release)
155                          withObject:nil
156                       waitUntilDone:NO
157                               modes:@[ NSDefaultRunLoopMode ]];
160 - (MCNotificationController*)notificationController {
161   return notificationController_.get();
164 - (const message_center::Notification*)notification {
165   return [notificationController_ notification];
168 - (const std::string&)notificationID {
169   return [notificationController_ notificationID];
172 // Private /////////////////////////////////////////////////////////////////////
174 - (void)notificationSwipeStarted {
175   originalFrame_ = [[self window] frame];
176   swipeGestureEnded_ = NO;
179 - (void)notificationSwipeMoved:(CGFloat)amount {
180   NSWindow* window = [self window];
182   [window setAlphaValue:1.0 - std::abs(amount)];
183   NSRect frame = [window frame];
184   CGFloat originalMin = NSMinX(originalFrame_);
185   frame.origin.x = originalMin + (NSMidX(originalFrame_) - originalMin) *
186                    -amount;
187   [window setFrame:frame display:YES];
190 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete {
191   swipeGestureEnded_ |= ended;
192   if (swipeGestureEnded_ && isComplete) {
193     messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
194     [popupCollection_ onPopupAnimationEnded:[self notificationID]];
195   }
198 - (void)setBoundsAnimation:(NSViewAnimation*)animation {
199   [boundsAnimation_ stopAnimation];
200   [boundsAnimation_ setDelegate:nil];
201   boundsAnimation_.reset([animation retain]);
204 - (NSViewAnimation*)animationWithDictionary:(NSDictionary*)dictionary {
205   return [[[NSViewAnimation alloc]
206       initWithViewAnimations:@[ dictionary ]] autorelease];
209 - (void)animationDidEnd:(NSAnimation*)animation {
210   DCHECK_EQ(animation, boundsAnimation_.get());
211   [self setBoundsAnimation:nil];
213   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
215   if (isClosing_)
216     [self close];
219 - (void)showWithAnimation:(NSRect)newBounds {
220   bounds_ = newBounds;
221   NSRect startBounds = newBounds;
222   startBounds.origin.x += startBounds.size.width;
223   [[self window] setFrame:startBounds display:NO];
224   [[self window] setAlphaValue:0];
225   [self showWindow:nil];
227   // Slide-in and fade-in simultaneously.
228   NSDictionary* animationDict = @{
229     NSViewAnimationTargetKey : [self window],
230     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds],
231     NSViewAnimationEffectKey : NSViewAnimationFadeInEffect
232   };
233   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
234   [self setBoundsAnimation:animation];
235   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
236   [boundsAnimation_ setDelegate:self];
237   [boundsAnimation_ startAnimation];
240 - (void)closeWithAnimation {
241   if (isClosing_)
242     return;
244 #ifndef NDEBUG
245   hasBeenClosed_ = YES;
246 #endif
247   isClosing_ = YES;
249   // If the notification was swiped closed, do not animate it as the
250   // notification has already faded out.
251   if (swipeGestureEnded_) {
252     [self close];
253     return;
254   }
256   NSDictionary* animationDict = @{
257     NSViewAnimationTargetKey : [self window],
258     NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect
259   };
260   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
261   [self setBoundsAnimation:animation];
262   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
263   [boundsAnimation_ setDelegate:self];
264   [boundsAnimation_ startAnimation];
267 - (void)markPopupCollectionGone {
268   popupCollection_ = nil;
271 - (NSRect)bounds {
272   return bounds_;
275 - (void)setBounds:(NSRect)newBounds {
276   if (isClosing_ || NSEqualRects(bounds_ , newBounds))
277     return;
278   bounds_ = newBounds;
280   NSDictionary* animationDict = @{
281     NSViewAnimationTargetKey :   [self window],
282     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds]
283   };
284   NSViewAnimation* animation = [self animationWithDictionary:animationDict];
285   [self setBoundsAnimation:animation];
286   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
287   [boundsAnimation_ setDelegate:self];
288   [boundsAnimation_ startAnimation];
291 - (void)mouseEntered:(NSEvent*)event {
292   messageCenter_->PausePopupTimers();
295 - (void)mouseExited:(NSEvent*)event {
296   messageCenter_->RestartPopupTimers();
299 @end