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/wm/caption_buttons/alternate_frame_size_button.h"
6
7 #include "ash/metrics/user_metrics_recorder.h"
8 #include "ash/shell.h"
9 #include "ash/touch/touch_uma.h"
10 #include "ash/wm/window_state.h"
11 #include "ash/wm/workspace/snap_sizer.h"
12 #include "ui/gfx/vector2d.h"
13 #include "ui/views/widget/widget.h"
14
15 namespace {
16
17 // The default delay between the user pressing the size button and the buttons
18 // adjacent to the size button morphing into buttons for snapping left and
19 // right.
20 const int kSetButtonsToSnapModeDelayMs = 150;
21
22 // The amount that a user can overshoot the snap left / snap right button and
23 // keep the snap left / snap right button pressed.
24 const int kPressedHitBoundsExpandX = 200;
25 const int kPressedHitBoundsExpandY = 50;
26
27 } // namespace
28
29 namespace ash {
30
AlternateFrameSizeButton(views::ButtonListener * listener,views::Widget * frame,AlternateFrameSizeButtonDelegate * delegate)31 AlternateFrameSizeButton::AlternateFrameSizeButton(
32 views::ButtonListener* listener,
33 views::Widget* frame,
34 AlternateFrameSizeButtonDelegate* delegate)
35 : FrameCaptionButton(listener, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
36 frame_(frame),
37 delegate_(delegate),
38 set_buttons_to_snap_mode_delay_ms_(kSetButtonsToSnapModeDelayMs),
39 in_snap_mode_(false),
40 snap_type_(SNAP_NONE) {
41 }
42
~AlternateFrameSizeButton()43 AlternateFrameSizeButton::~AlternateFrameSizeButton() {
44 }
45
OnMousePressed(const ui::MouseEvent & event)46 bool AlternateFrameSizeButton::OnMousePressed(const ui::MouseEvent& event) {
47 // The minimize and close buttons are set to snap left and right when snapping
48 // is enabled. Do not enable snapping if the minimize button is not visible.
49 // The close button is always visible.
50 if (IsTriggerableEvent(event) &&
51 !in_snap_mode_ &&
52 delegate_->IsMinimizeButtonVisible()) {
53 StartSetButtonsToSnapModeTimer(event);
54 }
55 FrameCaptionButton::OnMousePressed(event);
56 return true;
57 }
58
OnMouseDragged(const ui::MouseEvent & event)59 bool AlternateFrameSizeButton::OnMouseDragged(const ui::MouseEvent& event) {
60 UpdatePressedButton(event);
61 FrameCaptionButton::OnMouseDragged(event);
62 return true;
63 }
64
OnMouseReleased(const ui::MouseEvent & event)65 void AlternateFrameSizeButton::OnMouseReleased(const ui::MouseEvent& event) {
66 if (!IsTriggerableEvent(event) || !CommitSnap(event))
67 FrameCaptionButton::OnMouseReleased(event);
68 }
69
OnMouseCaptureLost()70 void AlternateFrameSizeButton::OnMouseCaptureLost() {
71 SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
72 FrameCaptionButton::OnMouseCaptureLost();
73 }
74
OnGestureEvent(ui::GestureEvent * event)75 void AlternateFrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
76 if (event->details().touch_points() > 1) {
77 SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
78 return;
79 }
80
81 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
82 StartSetButtonsToSnapModeTimer(*event);
83 // Go through FrameCaptionButton's handling so that the button gets pressed.
84 FrameCaptionButton::OnGestureEvent(event);
85 return;
86 }
87
88 if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
89 event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
90 UpdatePressedButton(*event);
91 event->SetHandled();
92 return;
93 }
94
95 if (event->type() == ui::ET_GESTURE_TAP ||
96 event->type() == ui::ET_GESTURE_SCROLL_END ||
97 event->type() == ui::ET_SCROLL_FLING_START ||
98 event->type() == ui::ET_GESTURE_END) {
99 if (CommitSnap(*event)) {
100 if (event->type() == ui::ET_GESTURE_TAP) {
101 TouchUMA::GetInstance()->RecordGestureAction(
102 TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP);
103 }
104 event->SetHandled();
105 return;
106 }
107 }
108
109 FrameCaptionButton::OnGestureEvent(event);
110 }
111
StartSetButtonsToSnapModeTimer(const ui::LocatedEvent & event)112 void AlternateFrameSizeButton::StartSetButtonsToSnapModeTimer(
113 const ui::LocatedEvent& event) {
114 set_buttons_to_snap_mode_timer_event_location_ = event.location();
115 if (set_buttons_to_snap_mode_delay_ms_ == 0) {
116 SetButtonsToSnapMode();
117 } else {
118 set_buttons_to_snap_mode_timer_.Start(
119 FROM_HERE,
120 base::TimeDelta::FromMilliseconds(set_buttons_to_snap_mode_delay_ms_),
121 this,
122 &AlternateFrameSizeButton::SetButtonsToSnapMode);
123 }
124 }
125
SetButtonsToSnapMode()126 void AlternateFrameSizeButton::SetButtonsToSnapMode() {
127 if (in_snap_mode_)
128 return;
129 in_snap_mode_ = true;
130 delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_LEFT_SNAPPED,
131 CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
132 AlternateFrameSizeButtonDelegate::ANIMATE_YES);
133 }
134
UpdatePressedButton(const ui::LocatedEvent & event)135 void AlternateFrameSizeButton::UpdatePressedButton(
136 const ui::LocatedEvent& event) {
137 if (!in_snap_mode_) {
138 // Set the buttons adjacent to the size button to snap left and right early
139 // if the user drags past the drag threshold.
140 // |set_buttons_to_snap_mode_timer_| is checked to avoid entering the snap
141 // mode as a result of an unsupported drag type (e.g. only the right mouse
142 // button is pressed).
143 gfx::Vector2d delta(
144 event.location() - set_buttons_to_snap_mode_timer_event_location_);
145 if (!set_buttons_to_snap_mode_timer_.IsRunning() ||
146 !views::View::ExceededDragThreshold(delta)) {
147 return;
148 }
149 SetButtonsToSnapMode();
150 }
151
152 gfx::Point event_location_in_screen(event.location());
153 views::View::ConvertPointToScreen(this, &event_location_in_screen);
154
155 gfx::Insets pressed_button_hittest_insets(-kPressedHitBoundsExpandY,
156 -kPressedHitBoundsExpandX,
157 -kPressedHitBoundsExpandY,
158 -kPressedHitBoundsExpandX);
159 const FrameCaptionButton* pressed_button = delegate_->PressButtonAt(
160 event_location_in_screen, pressed_button_hittest_insets);
161 snap_type_ = SNAP_NONE;
162 if (pressed_button) {
163 switch (pressed_button->icon()) {
164 case CAPTION_BUTTON_ICON_LEFT_SNAPPED:
165 snap_type_ = SNAP_LEFT;
166 break;
167 case CAPTION_BUTTON_ICON_RIGHT_SNAPPED:
168 snap_type_ = SNAP_RIGHT;
169 break;
170 case CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
171 // snap_type_ = SNAP_NONE
172 break;
173 case CAPTION_BUTTON_ICON_MINIMIZE:
174 case CAPTION_BUTTON_ICON_CLOSE:
175 NOTREACHED();
176 break;
177 }
178 }
179 }
180
CommitSnap(const ui::LocatedEvent & event)181 bool AlternateFrameSizeButton::CommitSnap(const ui::LocatedEvent& event) {
182 // The position of |event| may be different than the position of the previous
183 // event.
184 UpdatePressedButton(event);
185
186 if (in_snap_mode_ &&
187 (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
188 using internal::SnapSizer;
189 SnapSizer::SnapWindow(ash::wm::GetWindowState(frame_->GetNativeWindow()),
190 snap_type_ == SNAP_LEFT ?
191 SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE);
192 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
193 snap_type_ == SNAP_LEFT ?
194 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
195 ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
196 SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_NO);
197 return true;
198 }
199 SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
200 return false;
201 }
202
SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::Animate animate)203 void AlternateFrameSizeButton::SetButtonsToNormalMode(
204 AlternateFrameSizeButtonDelegate::Animate animate) {
205 in_snap_mode_ = false;
206 snap_type_ = SNAP_NONE;
207 set_buttons_to_snap_mode_timer_.Stop();
208 delegate_->SetButtonsToNormal(animate);
209 }
210
211 } // namespace ash
212