// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/frame/caption_buttons/frame_size_button.h" #include "ash/metrics/user_metrics_recorder.h" #include "ash/screen_util.h" #include "ash/shell.h" #include "ash/touch/touch_uma.h" #include "ash/wm/window_state.h" #include "ash/wm/window_util.h" #include "ash/wm/wm_event.h" #include "ash/wm/workspace/phantom_window_controller.h" #include "base/i18n/rtl.h" #include "ui/gfx/vector2d.h" #include "ui/views/widget/widget.h" namespace { // The default delay between the user pressing the size button and the buttons // adjacent to the size button morphing into buttons for snapping left and // right. const int kSetButtonsToSnapModeDelayMs = 150; // The amount that a user can overshoot one of the caption buttons while in // "snap mode" and keep the button hovered/pressed. const int kMaxOvershootX = 200; const int kMaxOvershootY = 50; // Returns true if a mouse drag while in "snap mode" at |location_in_screen| // would hover/press |button| or keep it hovered/pressed. bool HitTestButton(const ash::FrameCaptionButton* button, const gfx::Point& location_in_screen) { gfx::Rect expanded_bounds_in_screen = button->GetBoundsInScreen(); if (button->state() == views::Button::STATE_HOVERED || button->state() == views::Button::STATE_PRESSED) { expanded_bounds_in_screen.Inset(-kMaxOvershootX, -kMaxOvershootY); } return expanded_bounds_in_screen.Contains(location_in_screen); } } // namespace namespace ash { FrameSizeButton::FrameSizeButton( views::ButtonListener* listener, views::Widget* frame, FrameSizeButtonDelegate* delegate) : FrameCaptionButton(listener, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE), frame_(frame), delegate_(delegate), set_buttons_to_snap_mode_delay_ms_(kSetButtonsToSnapModeDelayMs), in_snap_mode_(false), snap_type_(SNAP_NONE) { } FrameSizeButton::~FrameSizeButton() { } bool FrameSizeButton::OnMousePressed(const ui::MouseEvent& event) { // The minimize and close buttons are set to snap left and right when snapping // is enabled. Do not enable snapping if the minimize button is not visible. // The close button is always visible. if (IsTriggerableEvent(event) && !in_snap_mode_ && delegate_->IsMinimizeButtonVisible()) { StartSetButtonsToSnapModeTimer(event); } FrameCaptionButton::OnMousePressed(event); return true; } bool FrameSizeButton::OnMouseDragged(const ui::MouseEvent& event) { UpdateSnapType(event); // By default a FrameCaptionButton reverts to STATE_NORMAL once the mouse // leaves its bounds. Skip FrameCaptionButton's handling when // |in_snap_mode_| == true because we want different behavior. if (!in_snap_mode_) FrameCaptionButton::OnMouseDragged(event); return true; } void FrameSizeButton::OnMouseReleased(const ui::MouseEvent& event) { if (!IsTriggerableEvent(event) || !CommitSnap(event)) FrameCaptionButton::OnMouseReleased(event); } void FrameSizeButton::OnMouseCaptureLost() { SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES); FrameCaptionButton::OnMouseCaptureLost(); } void FrameSizeButton::OnMouseMoved(const ui::MouseEvent& event) { // Ignore any synthetic mouse moves during a drag. if (!in_snap_mode_) FrameCaptionButton::OnMouseMoved(event); } void FrameSizeButton::OnGestureEvent(ui::GestureEvent* event) { if (event->details().touch_points() > 1) { SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES); return; } if (event->type() == ui::ET_GESTURE_TAP_DOWN) { StartSetButtonsToSnapModeTimer(*event); // Go through FrameCaptionButton's handling so that the button gets pressed. FrameCaptionButton::OnGestureEvent(event); return; } if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN || event->type() == ui::ET_GESTURE_SCROLL_UPDATE) { UpdateSnapType(*event); event->SetHandled(); return; } if (event->type() == ui::ET_GESTURE_TAP || event->type() == ui::ET_GESTURE_SCROLL_END || event->type() == ui::ET_SCROLL_FLING_START || event->type() == ui::ET_GESTURE_END) { if (CommitSnap(*event)) { if (event->type() == ui::ET_GESTURE_TAP) { TouchUMA::GetInstance()->RecordGestureAction( TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP); } event->SetHandled(); return; } } FrameCaptionButton::OnGestureEvent(event); } void FrameSizeButton::StartSetButtonsToSnapModeTimer( const ui::LocatedEvent& event) { set_buttons_to_snap_mode_timer_event_location_ = event.location(); if (set_buttons_to_snap_mode_delay_ms_ == 0) { AnimateButtonsToSnapMode(); } else { set_buttons_to_snap_mode_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(set_buttons_to_snap_mode_delay_ms_), this, &FrameSizeButton::AnimateButtonsToSnapMode); } } void FrameSizeButton::AnimateButtonsToSnapMode() { SetButtonsToSnapMode(FrameSizeButtonDelegate::ANIMATE_YES); } void FrameSizeButton::SetButtonsToSnapMode( FrameSizeButtonDelegate::Animate animate) { in_snap_mode_ = true; // When using a right-to-left layout the close button is left of the size // button and the minimize button is right of the size button. if (base::i18n::IsRTL()) { delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, CAPTION_BUTTON_ICON_LEFT_SNAPPED, animate); } else { delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_LEFT_SNAPPED, CAPTION_BUTTON_ICON_RIGHT_SNAPPED, animate); } } void FrameSizeButton::UpdateSnapType(const ui::LocatedEvent& event) { if (!in_snap_mode_) { // Set the buttons adjacent to the size button to snap left and right early // if the user drags past the drag threshold. // |set_buttons_to_snap_mode_timer_| is checked to avoid entering the snap // mode as a result of an unsupported drag type (e.g. only the right mouse // button is pressed). gfx::Vector2d delta( event.location() - set_buttons_to_snap_mode_timer_event_location_); if (!set_buttons_to_snap_mode_timer_.IsRunning() || !views::View::ExceededDragThreshold(delta)) { return; } AnimateButtonsToSnapMode(); } gfx::Point event_location_in_screen(event.location()); views::View::ConvertPointToScreen(this, &event_location_in_screen); const FrameCaptionButton* to_hover = GetButtonToHover(event_location_in_screen); bool press_size_button = to_hover || HitTestButton(this, event_location_in_screen); if (to_hover) { // Progress the minimize and close icon morph animations to the end if they // are in progress. SetButtonsToSnapMode(FrameSizeButtonDelegate::ANIMATE_NO); } delegate_->SetHoveredAndPressedButtons( to_hover, press_size_button ? this : NULL); snap_type_ = SNAP_NONE; if (to_hover) { switch (to_hover->icon()) { case CAPTION_BUTTON_ICON_LEFT_SNAPPED: snap_type_ = SNAP_LEFT; break; case CAPTION_BUTTON_ICON_RIGHT_SNAPPED: snap_type_ = SNAP_RIGHT; break; case CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE: case CAPTION_BUTTON_ICON_MINIMIZE: case CAPTION_BUTTON_ICON_CLOSE: case CAPTION_BUTTON_ICON_BACK: case CAPTION_BUTTON_ICON_COUNT: NOTREACHED(); break; } } if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) { aura::Window* window = frame_->GetNativeWindow(); if (!phantom_window_controller_.get()) { phantom_window_controller_.reset(new PhantomWindowController(window)); } gfx::Rect phantom_bounds_in_parent = (snap_type_ == SNAP_LEFT) ? wm::GetDefaultLeftSnappedWindowBoundsInParent(window) : wm::GetDefaultRightSnappedWindowBoundsInParent(window); phantom_window_controller_->Show(ScreenUtil::ConvertRectToScreen( window->parent(), phantom_bounds_in_parent)); } else { phantom_window_controller_.reset(); } } const FrameCaptionButton* FrameSizeButton::GetButtonToHover( const gfx::Point& event_location_in_screen) const { const FrameCaptionButton* closest_button = delegate_->GetButtonClosestTo( event_location_in_screen); if ((closest_button->icon() == CAPTION_BUTTON_ICON_LEFT_SNAPPED || closest_button->icon() == CAPTION_BUTTON_ICON_RIGHT_SNAPPED) && HitTestButton(closest_button, event_location_in_screen)) { return closest_button; } return NULL; } bool FrameSizeButton::CommitSnap(const ui::LocatedEvent& event) { // The position of |event| may be different than the position of the previous // event. UpdateSnapType(event); if (in_snap_mode_ && (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) { wm::WindowState* window_state = wm::GetWindowState(frame_->GetNativeWindow()); UserMetricsRecorder* metrics = Shell::GetInstance()->metrics(); const wm::WMEvent snap_event( snap_type_ == SNAP_LEFT ? wm::WM_EVENT_SNAP_LEFT : wm::WM_EVENT_SNAP_RIGHT); window_state->OnWMEvent(&snap_event); metrics->RecordUserMetricsAction( snap_type_ == SNAP_LEFT ? UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT : UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT); SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_NO); return true; } SetButtonsToNormalMode(FrameSizeButtonDelegate::ANIMATE_YES); return false; } void FrameSizeButton::SetButtonsToNormalMode( FrameSizeButtonDelegate::Animate animate) { in_snap_mode_ = false; snap_type_ = SNAP_NONE; set_buttons_to_snap_mode_timer_.Stop(); delegate_->SetButtonsToNormal(animate); phantom_window_controller_.reset(); } } // namespace ash