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. 4 5#include "ash/wm/gestures/long_press_affordance_handler.h" 6 7#include "ash/display/display_controller.h" 8#include "ash/root_window_controller.h" 9#include "ash/shell.h" 10#include "ash/shell_window_ids.h" 11#include "third_party/skia/include/core/SkColor.h" 12#include "third_party/skia/include/core/SkPaint.h" 13#include "third_party/skia/include/core/SkPath.h" 14#include "third_party/skia/include/core/SkRect.h" 15#include "third_party/skia/include/effects/SkGradientShader.h" 16#include "ui/aura/client/screen_position_client.h" 17#include "ui/aura/root_window.h" 18#include "ui/aura/window.h" 19#include "ui/compositor/layer.h" 20#include "ui/events/gestures/gesture_configuration.h" 21#include "ui/events/gestures/gesture_util.h" 22#include "ui/gfx/canvas.h" 23#include "ui/gfx/screen.h" 24#include "ui/gfx/transform.h" 25#include "ui/views/view.h" 26#include "ui/views/widget/widget.h" 27 28namespace ash { 29namespace internal { 30namespace { 31 32const int kAffordanceOuterRadius = 60; 33const int kAffordanceInnerRadius = 50; 34 35// Angles from x-axis at which the outer and inner circles start. 36const int kAffordanceOuterStartAngle = -109; 37const int kAffordanceInnerStartAngle = -65; 38 39const int kAffordanceGlowWidth = 20; 40// The following is half width to avoid division by 2. 41const int kAffordanceArcWidth = 3; 42 43// Start and end values for various animations. 44const double kAffordanceScaleStartValue = 0.8; 45const double kAffordanceScaleEndValue = 1.0; 46const double kAffordanceShrinkScaleEndValue = 0.5; 47const double kAffordanceOpacityStartValue = 0.1; 48const double kAffordanceOpacityEndValue = 0.5; 49const int kAffordanceAngleStartValue = 0; 50// The end angle is a bit greater than 360 to make sure the circle completes at 51// the end of the animation. 52const int kAffordanceAngleEndValue = 380; 53const int kAffordanceDelayBeforeShrinkMs = 200; 54const int kAffordanceShrinkAnimationDurationMs = 100; 55 56// Visual constants. 57const SkColor kAffordanceGlowStartColor = SkColorSetARGB(24, 255, 255, 255); 58const SkColor kAffordanceGlowEndColor = SkColorSetARGB(0, 255, 255, 255); 59const SkColor kAffordanceArcColor = SkColorSetARGB(80, 0, 0, 0); 60const int kAffordanceFrameRateHz = 60; 61 62views::Widget* CreateAffordanceWidget(aura::Window* root_window) { 63 views::Widget* widget = new views::Widget; 64 views::Widget::InitParams params; 65 params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; 66 params.keep_on_top = true; 67 params.accept_events = false; 68 params.can_activate = false; 69 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 70 params.context = root_window; 71 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 72 widget->Init(params); 73 widget->SetOpacity(0xFF); 74 GetRootWindowController(root_window)->GetContainer( 75 kShellWindowId_OverlayContainer)->AddChild(widget->GetNativeWindow()); 76 return widget; 77} 78 79void PaintAffordanceArc(gfx::Canvas* canvas, 80 gfx::Point& center, 81 int radius, 82 int start_angle, 83 int end_angle) { 84 SkPaint paint; 85 paint.setStyle(SkPaint::kStroke_Style); 86 paint.setStrokeWidth(2 * kAffordanceArcWidth); 87 paint.setColor(kAffordanceArcColor); 88 paint.setAntiAlias(true); 89 90 SkPath arc_path; 91 arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, 92 center.y() - radius, 93 2 * radius, 94 2 * radius), 95 start_angle, end_angle); 96 canvas->DrawPath(arc_path, paint); 97} 98 99void PaintAffordanceGlow(gfx::Canvas* canvas, 100 gfx::Point& center, 101 int start_radius, 102 int end_radius, 103 SkColor* colors, 104 SkScalar* pos, 105 int num_colors) { 106 SkPoint sk_center; 107 int radius = (end_radius + start_radius) / 2; 108 int glow_width = end_radius - start_radius; 109 sk_center.iset(center.x(), center.y()); 110 skia::RefPtr<SkShader> shader = skia::AdoptRef( 111 SkGradientShader::CreateTwoPointRadial( 112 sk_center, 113 SkIntToScalar(start_radius), 114 sk_center, 115 SkIntToScalar(end_radius), 116 colors, 117 pos, 118 num_colors, 119 SkShader::kClamp_TileMode)); 120 DCHECK(shader); 121 SkPaint paint; 122 paint.setStyle(SkPaint::kStroke_Style); 123 paint.setStrokeWidth(glow_width); 124 paint.setShader(shader.get()); 125 paint.setAntiAlias(true); 126 SkPath arc_path; 127 arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, 128 center.y() - radius, 129 2 * radius, 130 2 * radius), 131 0, 360); 132 canvas->DrawPath(arc_path, paint); 133} 134 135} // namespace 136 137// View of the LongPressAffordanceHandler. Draws the actual contents and 138// updates as the animation proceeds. It also maintains the views::Widget that 139// the animation is shown in. 140class LongPressAffordanceHandler::LongPressAffordanceView 141 : public views::View { 142 public: 143 LongPressAffordanceView(const gfx::Point& event_location, 144 aura::Window* root_window) 145 : views::View(), 146 widget_(CreateAffordanceWidget(root_window)), 147 current_angle_(kAffordanceAngleStartValue), 148 current_scale_(kAffordanceScaleStartValue) { 149 widget_->SetContentsView(this); 150 widget_->SetAlwaysOnTop(true); 151 152 // We are owned by the LongPressAffordance. 153 set_owned_by_client(); 154 gfx::Point point = event_location; 155 aura::client::GetScreenPositionClient(root_window)->ConvertPointToScreen( 156 root_window, &point); 157 widget_->SetBounds(gfx::Rect( 158 point.x() - (kAffordanceOuterRadius + kAffordanceGlowWidth), 159 point.y() - (kAffordanceOuterRadius + kAffordanceGlowWidth), 160 GetPreferredSize().width(), 161 GetPreferredSize().height())); 162 widget_->Show(); 163 widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue); 164 } 165 166 virtual ~LongPressAffordanceView() { 167 } 168 169 void UpdateWithGrowAnimation(gfx::Animation* animation) { 170 // Update the portion of the circle filled so far and re-draw. 171 current_angle_ = animation->CurrentValueBetween(kAffordanceAngleStartValue, 172 kAffordanceAngleEndValue); 173 current_scale_ = animation->CurrentValueBetween(kAffordanceScaleStartValue, 174 kAffordanceScaleEndValue); 175 widget_->GetNativeView()->layer()->SetOpacity( 176 animation->CurrentValueBetween(kAffordanceOpacityStartValue, 177 kAffordanceOpacityEndValue)); 178 SchedulePaint(); 179 } 180 181 void UpdateWithShrinkAnimation(gfx::Animation* animation) { 182 current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue, 183 kAffordanceShrinkScaleEndValue); 184 widget_->GetNativeView()->layer()->SetOpacity( 185 animation->CurrentValueBetween(kAffordanceOpacityEndValue, 186 kAffordanceOpacityStartValue)); 187 SchedulePaint(); 188 } 189 190 private: 191 // Overridden from views::View. 192 virtual gfx::Size GetPreferredSize() OVERRIDE { 193 return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth), 194 2 * (kAffordanceOuterRadius + kAffordanceGlowWidth)); 195 } 196 197 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 198 gfx::Point center(GetPreferredSize().width() / 2, 199 GetPreferredSize().height() / 2); 200 canvas->Save(); 201 202 gfx::Transform scale; 203 scale.Scale(current_scale_, current_scale_); 204 // We want to scale from the center. 205 canvas->Translate(center.OffsetFromOrigin()); 206 canvas->Transform(scale); 207 canvas->Translate(-center.OffsetFromOrigin()); 208 209 // Paint affordance glow 210 int start_radius = kAffordanceInnerRadius - kAffordanceGlowWidth; 211 int end_radius = kAffordanceOuterRadius + kAffordanceGlowWidth; 212 const int num_colors = 3; 213 SkScalar pos[num_colors] = {0, 0.5, 1}; 214 SkColor colors[num_colors] = {kAffordanceGlowEndColor, 215 kAffordanceGlowStartColor, kAffordanceGlowEndColor}; 216 PaintAffordanceGlow(canvas, center, start_radius, end_radius, colors, pos, 217 num_colors); 218 219 // Paint inner circle. 220 PaintAffordanceArc(canvas, center, kAffordanceInnerRadius, 221 kAffordanceInnerStartAngle, -current_angle_); 222 // Paint outer circle. 223 PaintAffordanceArc(canvas, center, kAffordanceOuterRadius, 224 kAffordanceOuterStartAngle, current_angle_); 225 226 canvas->Restore(); 227 } 228 229 scoped_ptr<views::Widget> widget_; 230 int current_angle_; 231 double current_scale_; 232 233 DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView); 234}; 235 236//////////////////////////////////////////////////////////////////////////////// 237// LongPressAffordanceHandler, public 238 239LongPressAffordanceHandler::LongPressAffordanceHandler() 240 : gfx::LinearAnimation(kAffordanceFrameRateHz, NULL), 241 tap_down_target_(NULL), 242 current_animation_type_(NONE) {} 243 244LongPressAffordanceHandler::~LongPressAffordanceHandler() { 245 StopAffordance(); 246} 247 248void LongPressAffordanceHandler::ProcessEvent(aura::Window* target, 249 ui::GestureEvent* event) { 250 // Once we have a target, we are only interested in events with that target. 251 if (tap_down_target_ && tap_down_target_ != target) 252 return; 253 switch (event->type()) { 254 case ui::ET_GESTURE_TAP_DOWN: { 255 // Start timer that will start animation on "semi-long-press". 256 tap_down_location_ = event->root_location(); 257 SetTapDownTarget(target); 258 current_animation_type_ = GROW_ANIMATION; 259 int64 timer_start_time_ms = 260 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000; 261 timer_.Start(FROM_HERE, 262 base::TimeDelta::FromMilliseconds(timer_start_time_ms), 263 this, 264 &LongPressAffordanceHandler::StartAnimation); 265 break; 266 } 267 case ui::ET_GESTURE_TAP: 268 case ui::ET_GESTURE_TAP_CANCEL: 269 StopAffordance(); 270 break; 271 case ui::ET_GESTURE_LONG_PRESS: 272 End(); 273 break; 274 default: 275 break; 276 } 277} 278 279//////////////////////////////////////////////////////////////////////////////// 280// LongPressAffordanceHandler, private 281 282void LongPressAffordanceHandler::StartAnimation() { 283 switch (current_animation_type_) { 284 case GROW_ANIMATION: { 285 aura::Window* root_window = tap_down_target_->GetRootWindow(); 286 if (!root_window) { 287 StopAffordance(); 288 return; 289 } 290 view_.reset(new LongPressAffordanceView(tap_down_location_, root_window)); 291 SetDuration( 292 ui::GestureConfiguration::long_press_time_in_seconds() * 1000 - 293 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 - 294 kAffordanceDelayBeforeShrinkMs); 295 Start(); 296 break; 297 } 298 case SHRINK_ANIMATION: 299 SetDuration(kAffordanceShrinkAnimationDurationMs); 300 Start(); 301 break; 302 default: 303 NOTREACHED(); 304 break; 305 } 306} 307 308void LongPressAffordanceHandler::StopAffordance() { 309 if (timer_.IsRunning()) 310 timer_.Stop(); 311 // Since, Animation::Stop() calls AnimationStopped(), we need to reset the 312 // |current_animation_type_| before Stop(), otherwise AnimationStopped() may 313 // start the timer again. 314 current_animation_type_ = NONE; 315 Stop(); 316 view_.reset(); 317 SetTapDownTarget(NULL); 318} 319 320void LongPressAffordanceHandler::SetTapDownTarget(aura::Window* target) { 321 if (tap_down_target_ == target) 322 return; 323 324 if (tap_down_target_) 325 tap_down_target_->RemoveObserver(this); 326 tap_down_target_ = target; 327 if (tap_down_target_) 328 tap_down_target_->AddObserver(this); 329} 330 331void LongPressAffordanceHandler::AnimateToState(double state) { 332 DCHECK(view_.get()); 333 switch (current_animation_type_) { 334 case GROW_ANIMATION: 335 view_->UpdateWithGrowAnimation(this); 336 break; 337 case SHRINK_ANIMATION: 338 view_->UpdateWithShrinkAnimation(this); 339 break; 340 default: 341 NOTREACHED(); 342 break; 343 } 344} 345 346void LongPressAffordanceHandler::AnimationStopped() { 347 switch (current_animation_type_) { 348 case GROW_ANIMATION: 349 current_animation_type_ = SHRINK_ANIMATION; 350 timer_.Start(FROM_HERE, 351 base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs), 352 this, &LongPressAffordanceHandler::StartAnimation); 353 break; 354 case SHRINK_ANIMATION: 355 current_animation_type_ = NONE; 356 // fall through to reset the view. 357 default: 358 view_.reset(); 359 SetTapDownTarget(NULL); 360 break; 361 } 362} 363 364void LongPressAffordanceHandler::OnWindowDestroying(aura::Window* window) { 365 DCHECK_EQ(tap_down_target_, window); 366 StopAffordance(); 367} 368 369} // namespace internal 370} // namespace ash 371