Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / ash / frame / caption_buttons / alternate_frame_size_button.cc
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 #include "ash/frame/caption_buttons/alternate_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 AlternateFrameSizeButton::AlternateFrameSizeButton(
48     views::ButtonListener* listener,
49     views::Widget* frame,
50     AlternateFrameSizeButtonDelegate* 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 AlternateFrameSizeButton::~AlternateFrameSizeButton() {
60 }
61
62 bool AlternateFrameSizeButton::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 AlternateFrameSizeButton::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 AlternateFrameSizeButton::OnMouseReleased(const ui::MouseEvent& event) {
86   if (!IsTriggerableEvent(event) || !CommitSnap(event))
87     FrameCaptionButton::OnMouseReleased(event);
88 }
89
90 void AlternateFrameSizeButton::OnMouseCaptureLost() {
91   SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
92   FrameCaptionButton::OnMouseCaptureLost();
93 }
94
95 void AlternateFrameSizeButton::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 AlternateFrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
102   if (event->details().touch_points() > 1) {
103     SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::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 AlternateFrameSizeButton::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         &AlternateFrameSizeButton::AnimateButtonsToSnapMode);
149   }
150 }
151
152 void AlternateFrameSizeButton::AnimateButtonsToSnapMode() {
153   SetButtonsToSnapMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
154 }
155
156 void AlternateFrameSizeButton::SetButtonsToSnapMode(
157     AlternateFrameSizeButtonDelegate::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 AlternateFrameSizeButton::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(AlternateFrameSizeButtonDelegate::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(
227           new internal::PhantomWindowController(window));
228     }
229     gfx::Rect phantom_bounds_in_parent = (snap_type_ == SNAP_LEFT) ?
230         wm::GetDefaultLeftSnappedWindowBoundsInParent(window) :
231         wm::GetDefaultRightSnappedWindowBoundsInParent(window);
232     phantom_window_controller_->Show(ScreenUtil::ConvertRectToScreen(
233           window->parent(), phantom_bounds_in_parent));
234   } else {
235     phantom_window_controller_.reset();
236   }
237 }
238
239 const FrameCaptionButton* AlternateFrameSizeButton::GetButtonToHover(
240     const gfx::Point& event_location_in_screen) const {
241   const FrameCaptionButton* closest_button = delegate_->GetButtonClosestTo(
242       event_location_in_screen);
243   if ((closest_button->icon() == CAPTION_BUTTON_ICON_LEFT_SNAPPED ||
244        closest_button->icon() == CAPTION_BUTTON_ICON_RIGHT_SNAPPED) &&
245       HitTestButton(closest_button, event_location_in_screen)) {
246     return closest_button;
247   }
248   return NULL;
249 }
250
251 bool AlternateFrameSizeButton::CommitSnap(const ui::LocatedEvent& event) {
252   // The position of |event| may be different than the position of the previous
253   // event.
254   UpdateSnapType(event);
255
256   if (in_snap_mode_ &&
257       (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
258     wm::WindowState* window_state =
259         wm::GetWindowState(frame_->GetNativeWindow());
260     UserMetricsRecorder* metrics = Shell::GetInstance()->metrics();
261     const wm::WMEvent snap_event(
262         snap_type_ == SNAP_LEFT ?
263         wm::WM_EVENT_SNAP_LEFT : wm::WM_EVENT_SNAP_RIGHT);
264     window_state->OnWMEvent(&snap_event);
265     metrics->RecordUserMetricsAction(
266         snap_type_ == SNAP_LEFT ?
267         UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
268         UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
269     SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_NO);
270     return true;
271   }
272   SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
273   return false;
274 }
275
276 void AlternateFrameSizeButton::SetButtonsToNormalMode(
277     AlternateFrameSizeButtonDelegate::Animate animate) {
278   in_snap_mode_ = false;
279   snap_type_ = SNAP_NONE;
280   set_buttons_to_snap_mode_timer_.Stop();
281   delegate_->SetButtonsToNormal(animate);
282   phantom_window_controller_.reset();
283 }
284
285 }  // namespace ash