1 // Copyright (c) 2012 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.
5 #include "ash/wm/gestures/long_press_affordance_handler.h"
7 #include "ash/display/display_controller.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/shell_window_ids.h"
11 #include "ash/wm/property_util.h"
12 #include "third_party/skia/include/core/SkColor.h"
13 #include "third_party/skia/include/core/SkPaint.h"
14 #include "third_party/skia/include/core/SkPath.h"
15 #include "third_party/skia/include/core/SkRect.h"
16 #include "third_party/skia/include/effects/SkGradientShader.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/root_window.h"
19 #include "ui/aura/window.h"
20 #include "ui/base/gestures/gesture_configuration.h"
21 #include "ui/base/gestures/gesture_util.h"
22 #include "ui/compositor/layer.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/screen.h"
25 #include "ui/gfx/transform.h"
26 #include "ui/views/view.h"
27 #include "ui/views/widget/widget.h"
31 const int kAffordanceOuterRadius
= 60;
32 const int kAffordanceInnerRadius
= 50;
34 // Angles from x-axis at which the outer and inner circles start.
35 const int kAffordanceOuterStartAngle
= -109;
36 const int kAffordanceInnerStartAngle
= -65;
38 const int kAffordanceGlowWidth
= 20;
39 // The following is half width to avoid division by 2.
40 const int kAffordanceArcWidth
= 3;
42 // Start and end values for various animations.
43 const double kAffordanceScaleStartValue
= 0.8;
44 const double kAffordanceScaleEndValue
= 1.0;
45 const double kAffordanceShrinkScaleEndValue
= 0.5;
46 const double kAffordanceOpacityStartValue
= 0.1;
47 const double kAffordanceOpacityEndValue
= 0.5;
48 const int kAffordanceAngleStartValue
= 0;
49 // The end angle is a bit greater than 360 to make sure the circle completes at
50 // the end of the animation.
51 const int kAffordanceAngleEndValue
= 380;
52 const int kAffordanceDelayBeforeShrinkMs
= 200;
53 const int kAffordanceShrinkAnimationDurationMs
= 100;
56 const SkColor kAffordanceGlowStartColor
= SkColorSetARGB(24, 255, 255, 255);
57 const SkColor kAffordanceGlowEndColor
= SkColorSetARGB(0, 255, 255, 255);
58 const SkColor kAffordanceArcColor
= SkColorSetARGB(80, 0, 0, 0);
59 const int kAffordanceFrameRateHz
= 60;
61 views::Widget
* CreateAffordanceWidget(aura::RootWindow
* root_window
) {
62 views::Widget
* widget
= new views::Widget
;
63 views::Widget::InitParams params
;
64 params
.type
= views::Widget::InitParams::TYPE_WINDOW_FRAMELESS
;
65 params
.keep_on_top
= true;
66 params
.accept_events
= false;
67 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
68 params
.context
= root_window
;
69 params
.transparent
= true;
71 widget
->SetOpacity(0xFF);
72 ash::GetRootWindowController(root_window
)->GetContainer(
73 ash::internal::kShellWindowId_OverlayContainer
)->AddChild(
74 widget
->GetNativeWindow());
78 void PaintAffordanceArc(gfx::Canvas
* canvas
,
84 paint
.setStyle(SkPaint::kStroke_Style
);
85 paint
.setStrokeWidth(2 * kAffordanceArcWidth
);
86 paint
.setColor(kAffordanceArcColor
);
87 paint
.setAntiAlias(true);
90 arc_path
.addArc(SkRect::MakeXYWH(center
.x() - radius
,
94 start_angle
, end_angle
);
95 canvas
->DrawPath(arc_path
, paint
);
98 void PaintAffordanceGlow(gfx::Canvas
* canvas
,
106 int radius
= (end_radius
+ start_radius
) / 2;
107 int glow_width
= end_radius
- start_radius
;
108 sk_center
.iset(center
.x(), center
.y());
109 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
110 SkGradientShader::CreateTwoPointRadial(
112 SkIntToScalar(start_radius
),
114 SkIntToScalar(end_radius
),
118 SkShader::kClamp_TileMode
));
121 paint
.setStyle(SkPaint::kStroke_Style
);
122 paint
.setStrokeWidth(glow_width
);
123 paint
.setShader(shader
.get());
124 paint
.setAntiAlias(true);
126 arc_path
.addArc(SkRect::MakeXYWH(center
.x() - radius
,
131 canvas
->DrawPath(arc_path
, paint
);
139 // View of the LongPressAffordanceHandler. Draws the actual contents and
140 // updates as the animation proceeds. It also maintains the views::Widget that
141 // the animation is shown in.
142 class LongPressAffordanceHandler::LongPressAffordanceView
143 : public views::View
{
145 LongPressAffordanceView(const gfx::Point
& event_location
,
146 aura::RootWindow
* root_window
)
148 widget_(CreateAffordanceWidget(root_window
)),
149 current_angle_(kAffordanceAngleStartValue
),
150 current_scale_(kAffordanceScaleStartValue
) {
151 widget_
->SetContentsView(this);
152 widget_
->SetAlwaysOnTop(true);
154 // We are owned by the LongPressAffordance.
155 set_owned_by_client();
156 gfx::Point point
= event_location
;
157 aura::client::GetScreenPositionClient(root_window
)->ConvertPointToScreen(
158 root_window
, &point
);
159 widget_
->SetBounds(gfx::Rect(
160 point
.x() - (kAffordanceOuterRadius
+ kAffordanceGlowWidth
),
161 point
.y() - (kAffordanceOuterRadius
+ kAffordanceGlowWidth
),
162 GetPreferredSize().width(),
163 GetPreferredSize().height()));
165 widget_
->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue
);
168 virtual ~LongPressAffordanceView() {
171 void UpdateWithGrowAnimation(ui::Animation
* animation
) {
172 // Update the portion of the circle filled so far and re-draw.
173 current_angle_
= animation
->CurrentValueBetween(kAffordanceAngleStartValue
,
174 kAffordanceAngleEndValue
);
175 current_scale_
= animation
->CurrentValueBetween(kAffordanceScaleStartValue
,
176 kAffordanceScaleEndValue
);
177 widget_
->GetNativeView()->layer()->SetOpacity(
178 animation
->CurrentValueBetween(kAffordanceOpacityStartValue
,
179 kAffordanceOpacityEndValue
));
183 void UpdateWithShrinkAnimation(ui::Animation
* animation
) {
184 current_scale_
= animation
->CurrentValueBetween(kAffordanceScaleEndValue
,
185 kAffordanceShrinkScaleEndValue
);
186 widget_
->GetNativeView()->layer()->SetOpacity(
187 animation
->CurrentValueBetween(kAffordanceOpacityEndValue
,
188 kAffordanceOpacityStartValue
));
193 // Overridden from views::View.
194 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
195 return gfx::Size(2 * (kAffordanceOuterRadius
+ kAffordanceGlowWidth
),
196 2 * (kAffordanceOuterRadius
+ kAffordanceGlowWidth
));
199 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
200 gfx::Point
center(GetPreferredSize().width() / 2,
201 GetPreferredSize().height() / 2);
204 gfx::Transform scale
;
205 scale
.Scale(current_scale_
, current_scale_
);
206 // We want to scale from the center.
207 canvas
->Translate(center
.OffsetFromOrigin());
208 canvas
->Transform(scale
);
209 canvas
->Translate(-center
.OffsetFromOrigin());
211 // Paint affordance glow
212 int start_radius
= kAffordanceInnerRadius
- kAffordanceGlowWidth
;
213 int end_radius
= kAffordanceOuterRadius
+ kAffordanceGlowWidth
;
214 const int num_colors
= 3;
215 SkScalar pos
[num_colors
] = {0, 0.5, 1};
216 SkColor colors
[num_colors
] = {kAffordanceGlowEndColor
,
217 kAffordanceGlowStartColor
, kAffordanceGlowEndColor
};
218 PaintAffordanceGlow(canvas
, center
, start_radius
, end_radius
, colors
, pos
,
221 // Paint inner circle.
222 PaintAffordanceArc(canvas
, center
, kAffordanceInnerRadius
,
223 kAffordanceInnerStartAngle
, -current_angle_
);
224 // Paint outer circle.
225 PaintAffordanceArc(canvas
, center
, kAffordanceOuterRadius
,
226 kAffordanceOuterStartAngle
, current_angle_
);
231 scoped_ptr
<views::Widget
> widget_
;
233 double current_scale_
;
235 DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView
);
238 ////////////////////////////////////////////////////////////////////////////////
239 // LongPressAffordanceHandler, public
241 LongPressAffordanceHandler::LongPressAffordanceHandler()
242 : ui::LinearAnimation(kAffordanceFrameRateHz
, this),
244 tap_down_touch_id_(-1),
245 tap_down_display_id_(0),
246 current_animation_type_(NONE
) {
249 LongPressAffordanceHandler::~LongPressAffordanceHandler() {}
251 void LongPressAffordanceHandler::ProcessEvent(aura::Window
* target
,
252 ui::LocatedEvent
* event
,
254 // Once we have a touch id, we are only interested in event of that touch id.
255 if (tap_down_touch_id_
!= -1 && tap_down_touch_id_
!= touch_id
)
257 int64 timer_start_time_ms
=
258 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
259 switch (event
->type()) {
260 case ui::ET_GESTURE_TAP_DOWN
:
262 tap_down_location_
= event
->root_location();
263 tap_down_touch_id_
= touch_id
;
264 current_animation_type_
= GROW_ANIMATION
;
265 tap_down_display_id_
=
266 Shell::GetScreen()->GetDisplayNearestWindow(target
).id();
267 timer_
.Start(FROM_HERE
,
268 base::TimeDelta::FromMilliseconds(timer_start_time_ms
),
270 &LongPressAffordanceHandler::StartAnimation
);
272 case ui::ET_TOUCH_MOVED
:
273 // If animation is running, We want it to be robust to small finger
274 // movements. So we stop the animation only when the finger moves a
276 if (!ui::gestures::IsInsideManhattanSquare(
277 event
->root_location(), tap_down_location_
))
280 case ui::ET_TOUCH_CANCELLED
:
281 case ui::ET_GESTURE_END
:
282 // We will stop the animation on TOUCH_RELEASED.
284 case ui::ET_GESTURE_LONG_PRESS
:
289 // On all other touch and gesture events, we hide the animation.
295 ////////////////////////////////////////////////////////////////////////////////
296 // LongPressAffordanceHandler, private
298 void LongPressAffordanceHandler::StartAnimation() {
299 aura::RootWindow
* root_window
= NULL
;
300 switch (current_animation_type_
) {
302 root_window
= ash::Shell::GetInstance()->display_controller()->
303 GetRootWindowForDisplayId(tap_down_display_id_
);
308 view_
.reset(new LongPressAffordanceView(tap_down_location_
, root_window
));
310 ui::GestureConfiguration::long_press_time_in_seconds() * 1000 -
311 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 -
312 kAffordanceDelayBeforeShrinkMs
);
315 case SHRINK_ANIMATION
:
316 SetDuration(kAffordanceShrinkAnimationDurationMs
);
325 void LongPressAffordanceHandler::StopAnimation() {
326 if (timer_
.IsRunning())
328 // Since, Animation::Stop() calls AnimationEnded(), we need to reset the
329 // |current_animation_type_| before Stop(), otherwise AnimationEnded() may
330 // start the timer again.
331 current_animation_type_
= NONE
;
335 tap_down_touch_id_
= -1;
336 tap_down_display_id_
= 0;
339 void LongPressAffordanceHandler::AnimateToState(double state
) {
341 switch (current_animation_type_
) {
343 view_
->UpdateWithGrowAnimation(this);
345 case SHRINK_ANIMATION
:
346 view_
->UpdateWithShrinkAnimation(this);
354 bool LongPressAffordanceHandler::ShouldSendCanceledFromStop() {
358 void LongPressAffordanceHandler::AnimationEnded(
359 const ui::Animation
* animation
) {
360 switch (current_animation_type_
) {
362 current_animation_type_
= SHRINK_ANIMATION
;
363 timer_
.Start(FROM_HERE
,
364 base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs
),
365 this, &LongPressAffordanceHandler::StartAnimation
);
367 case SHRINK_ANIMATION
:
368 current_animation_type_
= NONE
;
369 // fall through to reset the view.
372 tap_down_touch_id_
= -1;
373 tap_down_display_id_
= 0;
378 } // namespace internal