Home | History | Annotate | Download | only in cocoa
      1 // Copyright 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.
      4 
      5 #import "chrome/browser/ui/cocoa/fullscreen_mode_controller.h"
      6 
      7 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
      8 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
      9 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
     10 
     11 @interface FullscreenModeController (Private)
     12 - (void)startAnimationToState:(FullscreenToolbarState)state;
     13 - (void)setMenuBarRevealProgress:(CGFloat)progress;
     14 - (void)setRevealAnimationProgress:(NSAnimationProgress)progress;
     15 @end
     16 
     17 namespace {
     18 
     19 OSStatus MenuBarRevealHandler(EventHandlerCallRef handler,
     20                               EventRef event,
     21                               void* context) {
     22   FullscreenModeController* self =
     23       static_cast<FullscreenModeController*>(context);
     24   CGFloat revealFraction = 0;
     25   GetEventParameter(event, FOUR_CHAR_CODE('rvlf'), typeCGFloat, NULL,
     26       sizeof(CGFloat), NULL, &revealFraction);
     27   [self setMenuBarRevealProgress:revealFraction];
     28   return CallNextEventHandler(handler, event);
     29 }
     30 
     31 // The duration of the animation to bring down the tabstrip.
     32 const NSTimeInterval kAnimationDuration = 0.35;
     33 
     34 // The height of the tracking area in which mouse entered/exit events will
     35 // reveal the tabstrip.
     36 const NSUInteger kTrackingAreaHeight = 100;
     37 
     38 // There is a tiny gap between the window's max Y and the top of the screen,
     39 // which will produce a mouse exit event when the menu bar should be revealed.
     40 // This factor is the size of that gap plus padding.
     41 const CGFloat kTrackingAreaMaxYEpsilon = 15;
     42 
     43 }  // namespace
     44 
     45 // Animation ///////////////////////////////////////////////////////////////////
     46 
     47 @interface FullscreenModeDropDownAnimation : NSAnimation {
     48   FullscreenModeController* controller_;
     49 }
     50 @end
     51 
     52 @implementation FullscreenModeDropDownAnimation
     53 
     54 - (id)initWithFullscreenModeController:(FullscreenModeController*)controller {
     55   if ((self = [super initWithDuration:kAnimationDuration
     56                        animationCurve:NSAnimationEaseInOut])) {
     57     controller_ = controller;
     58     [self setAnimationBlockingMode:NSAnimationNonblocking];
     59     [self setDelegate:controller_];
     60   }
     61   return self;
     62 }
     63 
     64 - (void)setCurrentProgress:(NSAnimationProgress)progress {
     65   [controller_ setRevealAnimationProgress:progress];
     66 }
     67 
     68 @end
     69 
     70 // Implementation //////////////////////////////////////////////////////////////
     71 
     72 @implementation FullscreenModeController
     73 
     74 - (id)initWithBrowserWindowController:(BrowserWindowController*)bwc {
     75   if ((self = [super init])) {
     76     controller_ = bwc;
     77 
     78     currentState_ = kFullscreenToolbarOnly;
     79     destinationState_ = kFullscreenToolbarOnly;
     80 
     81     // Create the tracking area at the top of the window.
     82     NSRect windowFrame = [[bwc window] frame];
     83     NSRect trackingRect = NSMakeRect(
     84         0, NSHeight(windowFrame) - kTrackingAreaHeight,
     85         NSWidth(windowFrame), kTrackingAreaHeight);
     86     trackingArea_.reset(
     87         [[CrTrackingArea alloc] initWithRect:trackingRect
     88                                      options:NSTrackingMouseEnteredAndExited |
     89                                              NSTrackingActiveAlways
     90                                        owner:self
     91                                     userInfo:nil]);
     92     [[[bwc window] contentView] addTrackingArea:trackingArea_.get()];
     93 
     94     // Install the Carbon event handler for the undocumented menu bar show/hide
     95     // event.
     96     EventTypeSpec eventSpec = { kEventClassMenu, 2004 };
     97     InstallApplicationEventHandler(NewEventHandlerUPP(&MenuBarRevealHandler),
     98                                    1, &eventSpec,
     99                                    self, &menuBarTrackingHandler_);
    100   }
    101   return self;
    102 }
    103 
    104 - (void)dealloc {
    105   RemoveEventHandler(menuBarTrackingHandler_);
    106   [[[controller_ window] contentView] removeTrackingArea:trackingArea_.get()];
    107   [super dealloc];
    108 }
    109 
    110 - (CGFloat)menuBarHeight {
    111   // -[NSMenu menuBarHeight] will return 0 when the menu bar is hidden, so
    112   // use the status bar API instead, which always returns a constant value.
    113   return [[NSStatusBar systemStatusBar] thickness] * menuBarRevealFraction_;
    114 }
    115 
    116 - (void)mouseEntered:(NSEvent*)event {
    117   if (animation_ || currentState_ == kFullscreenToolbarAndTabstrip)
    118     return;
    119 
    120   [self startAnimationToState:kFullscreenToolbarAndTabstrip];
    121 }
    122 
    123 - (void)mouseExited:(NSEvent*)event {
    124   if (animation_ || currentState_ == kFullscreenToolbarOnly)
    125     return;
    126 
    127   // Take allowance for the small gap between the window max Y and top edge of
    128   // the screen.
    129   NSPoint mousePoint = [NSEvent mouseLocation];
    130   NSRect screenRect = [[[controller_ window] screen] frame];
    131   if (mousePoint.y >= NSMaxY(screenRect) - kTrackingAreaMaxYEpsilon)
    132     return;
    133 
    134   [self startAnimationToState:kFullscreenToolbarOnly];
    135 }
    136 
    137 - (void)startAnimationToState:(FullscreenToolbarState)state {
    138   DCHECK_NE(currentState_, state);
    139 
    140   destinationState_ = state;
    141 
    142   // Turn on fast resize mode to ensure a smooth web contents animation.
    143   // TODO(rsesek): This makes the animation jump at the end.
    144   [[controller_ tabContentArea] setFastResizeMode:YES];
    145 
    146   animation_.reset([[FullscreenModeDropDownAnimation alloc]
    147       initWithFullscreenModeController:self]);
    148   [animation_ startAnimation];
    149 }
    150 
    151 - (void)setMenuBarRevealProgress:(CGFloat)progress {
    152   menuBarRevealFraction_ = progress;
    153 
    154   // If an animation is not running, then -layoutSubviews will not be called
    155   // for each tick of the menu bar reveal. Do that manually.
    156   // TODO(rsesek): This is kind of hacky and janky.
    157   if (!animation_)
    158     [controller_ layoutSubviews];
    159 }
    160 
    161 - (void)setRevealAnimationProgress:(NSAnimationProgress)progress {
    162   // When hiding the tabstrip, invert the fraction.
    163   if (destinationState_ == kFullscreenToolbarOnly)
    164     progress = 1.0 - progress;
    165   [controller_ setFloatingBarShownFraction:progress];
    166 }
    167 
    168 - (void)animationDidEnd:(NSAnimation*)animation {
    169   DCHECK_EQ(animation_.get(), animation);
    170 
    171   currentState_ = destinationState_;
    172 
    173   [animation_ setDelegate:nil];
    174   animation_.reset();
    175 
    176   [[controller_ tabContentArea] setFastResizeMode:NO];
    177 }
    178 
    179 @end
    180