Home | History | Annotate | Download | only in caption_buttons
      1 // Copyright 2014 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 #include "ash/frame/caption_buttons/frame_size_button.h"
      6 
      7 #include "ash/metrics/user_metrics_recorder.h"
      8 #include "ash/screen_util.h"
      9 #include "ash/shell.h"
     10 #include "ash/touch/touch_uma.h"
     11 #include "ash/wm/window_state.h"
     12 #include "ash/wm/window_util.h"
     13 #include "ash/wm/wm_event.h"
     14 #include "ash/wm/workspace/phantom_window_controller.h"
     15 #include "base/i18n/rtl.h"
     16 #include "ui/gfx/vector2d.h"
     17 #include "ui/views/widget/widget.h"
     18 
     19 namespace {
     20 
     21 // The default delay between the user pressing the size button and the buttons
     22 // adjacent to the size button morphing into buttons for snapping left and
     23 // right.
     24 const int kSetButtonsToSnapModeDelayMs = 150;
     25 
     26 // The amount that a user can overshoot one of the caption buttons while in
     27 // "snap mode" and keep the button hovered/pressed.
     28 const int kMaxOvershootX = 200;
     29 const int kMaxOvershootY = 50;
     30 
     31 // Returns true if a mouse drag while in "snap mode" at |location_in_screen|
     32 // would hover/press |button| or keep it hovered/pressed.
     33 bool HitTestButton(const ash::FrameCaptionButton* button,
     34                    const gfx::Point& location_in_screen) {
     35   gfx::Rect expanded_bounds_in_screen = button->GetBoundsInScreen();
     36   if (button->state() == views::Button::STATE_HOVERED ||
     37       button->state() == views::Button::STATE_PRESSED) {
     38     expanded_bounds_in_screen.Inset(-kMaxOvershootX, -kMaxOvershootY);
     39   }
     40   return expanded_bounds_in_screen.Contains(location_in_screen);
     41 }
     42 
     43 }  // namespace
     44 
     45 namespace ash {
     46 
     47 FrameSizeButton::FrameSizeButton(
     48     views::ButtonListener* listener,
     49     views::Widget* frame,
     50     FrameSizeButtonDelegate* delegate)
     51     : FrameCaptionButton(listener, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
     52       frame_(frame),
     53       delegate_(delegate),
     54       set_buttons_to_snap_mode_delay_ms_(kSetButtonsToSnapModeDelayMs),
     55       in_snap_mode_(false),
     56       snap_type_(SNAP_NONE) {
     57 }
     58 
     59 FrameSizeButton::~FrameSizeButton() {
     60 }
     61 
     62 bool FrameSizeButton::OnMousePressed(const ui::MouseEvent& event) {
     63   // The minimize and close buttons are set to snap left and right when snapping
     64   // is enabled. Do not enable snapping if the minimize button is not visible.
     65   // The close button is always visible.
     66   if (IsTriggerableEvent(event) &&
     67       !in_snap_mode_ &&
     68       delegate_->IsMinimizeButtonVisible()) {
     69     StartSetButtonsToSnapModeTimer(event);
     70   }
     71   FrameCaptionButton::OnMousePressed(event);
     72   return true;
     73 }
     74 
     75 bool FrameSizeButton::OnMouseDragged(const ui::MouseEvent& event) {
     76   UpdateSnapType(event);
     77   // By default a FrameCaptionButton reverts to STATE_NORMAL once the mouse
     78   // leaves its bounds. Skip FrameCaptionButton's handling when
     79   // |in_snap_mode_| == true because we want different behavior.
     80   if (!in_snap_mode_)
     81     FrameCaptionButton::OnMouseDragged(event);
     82   return true;
     83 }
     84 
     85 void FrameSizeButton::OnMouseReleased(const ui::MouseEvent& event) {
     86   if (!IsTriggerableEvent(event) || !CommitSnap(event))
     87     FrameCaptionButton::OnMouseReleased(event);
     88 }
     89 
     90 void FrameSizeButton::OnMouseCaptureLost() {
     91   SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES);
     92   FrameCaptionButton::OnMouseCaptureLost();
     93 }
     94 
     95 void FrameSizeButton::OnMouseMoved(const ui::MouseEvent& event) {
     96   // Ignore any synthetic mouse moves during a drag.
     97   if (!in_snap_mode_)
     98     FrameCaptionButton::OnMouseMoved(event);
     99 }
    100 
    101 void FrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
    102   if (event->details().touch_points() > 1) {
    103     SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES);
    104     return;
    105   }
    106 
    107   if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
    108     StartSetButtonsToSnapModeTimer(*event);
    109     // Go through FrameCaptionButton's handling so that the button gets pressed.
    110     FrameCaptionButton::OnGestureEvent(event);
    111     return;
    112   }
    113 
    114   if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
    115       event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
    116     UpdateSnapType(*event);
    117     event->SetHandled();
    118     return;
    119   }
    120 
    121   if (event->type() == ui::ET_GESTURE_TAP ||
    122       event->type() == ui::ET_GESTURE_SCROLL_END ||
    123       event->type() == ui::ET_SCROLL_FLING_START ||
    124       event->type() == ui::ET_GESTURE_END) {
    125     if (CommitSnap(*event)) {
    126       if (event->type() == ui::ET_GESTURE_TAP) {
    127         TouchUMA::GetInstance()->RecordGestureAction(
    128             TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP);
    129       }
    130       event->SetHandled();
    131       return;
    132     }
    133   }
    134 
    135   FrameCaptionButton::OnGestureEvent(event);
    136 }
    137 
    138 void FrameSizeButton::StartSetButtonsToSnapModeTimer(
    139     const ui::LocatedEvent& event) {
    140   set_buttons_to_snap_mode_timer_event_location_ = event.location();
    141   if (set_buttons_to_snap_mode_delay_ms_ == 0) {
    142     AnimateButtonsToSnapMode();
    143   } else {
    144     set_buttons_to_snap_mode_timer_.Start(
    145         FROM_HERE,
    146         base::TimeDelta::FromMilliseconds(set_buttons_to_snap_mode_delay_ms_),
    147         this,
    148         &FrameSizeButton::AnimateButtonsToSnapMode);
    149   }
    150 }
    151 
    152 void FrameSizeButton::AnimateButtonsToSnapMode() {
    153   SetButtonsToSnapMode(FrameSizeButtonDelegate::ANIMATE_YES);
    154 }
    155 
    156 void FrameSizeButton::SetButtonsToSnapMode(
    157     FrameSizeButtonDelegate::Animate animate) {
    158   in_snap_mode_ = true;
    159 
    160   // When using a right-to-left layout the close button is left of the size
    161   // button and the minimize button is right of the size button.
    162   if (base::i18n::IsRTL()) {
    163     delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
    164                               CAPTION_BUTTON_ICON_LEFT_SNAPPED,
    165                               animate);
    166   } else {
    167     delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_LEFT_SNAPPED,
    168                               CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
    169                               animate);
    170   }
    171 }
    172 
    173 void FrameSizeButton::UpdateSnapType(const ui::LocatedEvent& event) {
    174   if (!in_snap_mode_) {
    175     // Set the buttons adjacent to the size button to snap left and right early
    176     // if the user drags past the drag threshold.
    177     // |set_buttons_to_snap_mode_timer_| is checked to avoid entering the snap
    178     // mode as a result of an unsupported drag type (e.g. only the right mouse
    179     // button is pressed).
    180     gfx::Vector2d delta(
    181         event.location() - set_buttons_to_snap_mode_timer_event_location_);
    182     if (!set_buttons_to_snap_mode_timer_.IsRunning() ||
    183         !views::View::ExceededDragThreshold(delta)) {
    184       return;
    185     }
    186     AnimateButtonsToSnapMode();
    187   }
    188 
    189   gfx::Point event_location_in_screen(event.location());
    190   views::View::ConvertPointToScreen(this, &event_location_in_screen);
    191   const FrameCaptionButton* to_hover =
    192       GetButtonToHover(event_location_in_screen);
    193   bool press_size_button =
    194       to_hover || HitTestButton(this, event_location_in_screen);
    195 
    196   if (to_hover) {
    197     // Progress the minimize and close icon morph animations to the end if they
    198     // are in progress.
    199     SetButtonsToSnapMode(FrameSizeButtonDelegate::ANIMATE_NO);
    200   }
    201 
    202   delegate_->SetHoveredAndPressedButtons(
    203       to_hover, press_size_button ? this : NULL);
    204 
    205   snap_type_ = SNAP_NONE;
    206   if (to_hover) {
    207     switch (to_hover->icon()) {
    208       case CAPTION_BUTTON_ICON_LEFT_SNAPPED:
    209         snap_type_ = SNAP_LEFT;
    210         break;
    211       case CAPTION_BUTTON_ICON_RIGHT_SNAPPED:
    212         snap_type_ = SNAP_RIGHT;
    213         break;
    214       case CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
    215       case CAPTION_BUTTON_ICON_MINIMIZE:
    216       case CAPTION_BUTTON_ICON_CLOSE:
    217       case CAPTION_BUTTON_ICON_COUNT:
    218         NOTREACHED();
    219         break;
    220     }
    221   }
    222 
    223   if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) {
    224     aura::Window* window = frame_->GetNativeWindow();
    225     if (!phantom_window_controller_.get()) {
    226       phantom_window_controller_.reset(new PhantomWindowController(window));
    227     }
    228     gfx::Rect phantom_bounds_in_parent = (snap_type_ == SNAP_LEFT) ?
    229         wm::GetDefaultLeftSnappedWindowBoundsInParent(window) :
    230         wm::GetDefaultRightSnappedWindowBoundsInParent(window);
    231     phantom_window_controller_->Show(ScreenUtil::ConvertRectToScreen(
    232           window->parent(), phantom_bounds_in_parent));
    233   } else {
    234     phantom_window_controller_.reset();
    235   }
    236 }
    237 
    238 const FrameCaptionButton* FrameSizeButton::GetButtonToHover(
    239     const gfx::Point& event_location_in_screen) const {
    240   const FrameCaptionButton* closest_button = delegate_->GetButtonClosestTo(
    241       event_location_in_screen);
    242   if ((closest_button->icon() == CAPTION_BUTTON_ICON_LEFT_SNAPPED ||
    243        closest_button->icon() == CAPTION_BUTTON_ICON_RIGHT_SNAPPED) &&
    244       HitTestButton(closest_button, event_location_in_screen)) {
    245     return closest_button;
    246   }
    247   return NULL;
    248 }
    249 
    250 bool FrameSizeButton::CommitSnap(const ui::LocatedEvent& event) {
    251   // The position of |event| may be different than the position of the previous
    252   // event.
    253   UpdateSnapType(event);
    254 
    255   if (in_snap_mode_ &&
    256       (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
    257     wm::WindowState* window_state =
    258         wm::GetWindowState(frame_->GetNativeWindow());
    259     UserMetricsRecorder* metrics = Shell::GetInstance()->metrics();
    260     const wm::WMEvent snap_event(
    261         snap_type_ == SNAP_LEFT ?
    262         wm::WM_EVENT_SNAP_LEFT : wm::WM_EVENT_SNAP_RIGHT);
    263     window_state->OnWMEvent(&snap_event);
    264     metrics->RecordUserMetricsAction(
    265         snap_type_ == SNAP_LEFT ?
    266         UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
    267         UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
    268     SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_NO);
    269     return true;
    270   }
    271   SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES);
    272   return false;
    273 }
    274 
    275 void FrameSizeButton::SetButtonsToNormalMode(
    276     FrameSizeButtonDelegate::Animate animate) {
    277   in_snap_mode_ = false;
    278   snap_type_ = SNAP_NONE;
    279   set_buttons_to_snap_mode_timer_.Stop();
    280   delegate_->SetButtonsToNormal(animate);
    281   phantom_window_controller_.reset();
    282 }
    283 
    284 }  // namespace ash
    285