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