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/shell.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" 28 29 namespace { 30 31 const int kAffordanceOuterRadius = 60; 32 const int kAffordanceInnerRadius = 50; 33 34 // Angles from x-axis at which the outer and inner circles start. 35 const int kAffordanceOuterStartAngle = -109; 36 const int kAffordanceInnerStartAngle = -65; 37 38 const int kAffordanceGlowWidth = 20; 39 // The following is half width to avoid division by 2. 40 const int kAffordanceArcWidth = 3; 41 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; 54 55 // Visual constants. 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; 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.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 70 widget->Init(params); 71 widget->SetOpacity(0xFF); 72 ash::GetRootWindowController(root_window)->GetContainer( 73 ash::internal::kShellWindowId_OverlayContainer)->AddChild( 74 widget->GetNativeWindow()); 75 return widget; 76 } 77 78 void PaintAffordanceArc(gfx::Canvas* canvas, 79 gfx::Point& center, 80 int radius, 81 int start_angle, 82 int end_angle) { 83 SkPaint paint; 84 paint.setStyle(SkPaint::kStroke_Style); 85 paint.setStrokeWidth(2 * kAffordanceArcWidth); 86 paint.setColor(kAffordanceArcColor); 87 paint.setAntiAlias(true); 88 89 SkPath arc_path; 90 arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, 91 center.y() - radius, 92 2 * radius, 93 2 * radius), 94 start_angle, end_angle); 95 canvas->DrawPath(arc_path, paint); 96 } 97 98 void PaintAffordanceGlow(gfx::Canvas* canvas, 99 gfx::Point& center, 100 int start_radius, 101 int end_radius, 102 SkColor* colors, 103 SkScalar* pos, 104 int num_colors) { 105 SkPoint sk_center; 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( 111 sk_center, 112 SkIntToScalar(start_radius), 113 sk_center, 114 SkIntToScalar(end_radius), 115 colors, 116 pos, 117 num_colors, 118 SkShader::kClamp_TileMode)); 119 DCHECK(shader); 120 SkPaint paint; 121 paint.setStyle(SkPaint::kStroke_Style); 122 paint.setStrokeWidth(glow_width); 123 paint.setShader(shader.get()); 124 paint.setAntiAlias(true); 125 SkPath arc_path; 126 arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, 127 center.y() - radius, 128 2 * radius, 129 2 * radius), 130 0, 360); 131 canvas->DrawPath(arc_path, paint); 132 } 133 134 } // namespace 135 136 namespace ash { 137 namespace internal { 138 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 { 144 public: 145 LongPressAffordanceView(const gfx::Point& event_location, 146 aura::RootWindow* root_window) 147 : views::View(), 148 widget_(CreateAffordanceWidget(root_window)), 149 current_angle_(kAffordanceAngleStartValue), 150 current_scale_(kAffordanceScaleStartValue) { 151 widget_->SetContentsView(this); 152 widget_->SetAlwaysOnTop(true); 153 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())); 164 widget_->Show(); 165 widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue); 166 } 167 168 virtual ~LongPressAffordanceView() { 169 } 170 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)); 180 SchedulePaint(); 181 } 182 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)); 189 SchedulePaint(); 190 } 191 192 private: 193 // Overridden from views::View. 194 virtual gfx::Size GetPreferredSize() OVERRIDE { 195 return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth), 196 2 * (kAffordanceOuterRadius + kAffordanceGlowWidth)); 197 } 198 199 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 200 gfx::Point center(GetPreferredSize().width() / 2, 201 GetPreferredSize().height() / 2); 202 canvas->Save(); 203 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()); 210 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, 219 num_colors); 220 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_); 227 228 canvas->Restore(); 229 } 230 231 scoped_ptr<views::Widget> widget_; 232 int current_angle_; 233 double current_scale_; 234 235 DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView); 236 }; 237 238 //////////////////////////////////////////////////////////////////////////////// 239 // LongPressAffordanceHandler, public 240 241 LongPressAffordanceHandler::LongPressAffordanceHandler() 242 : ui::LinearAnimation(kAffordanceFrameRateHz, this), 243 tap_down_touch_id_(-1), 244 tap_down_display_id_(0), 245 current_animation_type_(NONE) {} 246 247 LongPressAffordanceHandler::~LongPressAffordanceHandler() {} 248 249 void LongPressAffordanceHandler::ProcessEvent(aura::Window* target, 250 ui::LocatedEvent* event, 251 int touch_id) { 252 // Once we have a touch id, we are only interested in event of that touch id. 253 if (tap_down_touch_id_ != -1 && tap_down_touch_id_ != touch_id) 254 return; 255 int64 timer_start_time_ms = 256 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000; 257 switch (event->type()) { 258 case ui::ET_GESTURE_TAP_DOWN: 259 // Start animation. 260 tap_down_location_ = event->root_location(); 261 tap_down_touch_id_ = touch_id; 262 current_animation_type_ = GROW_ANIMATION; 263 tap_down_display_id_ = 264 Shell::GetScreen()->GetDisplayNearestWindow(target).id(); 265 timer_.Start(FROM_HERE, 266 base::TimeDelta::FromMilliseconds(timer_start_time_ms), 267 this, 268 &LongPressAffordanceHandler::StartAnimation); 269 break; 270 case ui::ET_TOUCH_MOVED: 271 // If animation is running, We want it to be robust to small finger 272 // movements. So we stop the animation only when the finger moves a 273 // certain distance. 274 if (!ui::gestures::IsInsideManhattanSquare( 275 event->root_location(), tap_down_location_)) 276 StopAnimation(); 277 break; 278 case ui::ET_TOUCH_CANCELLED: 279 case ui::ET_GESTURE_END: 280 // We will stop the animation on TOUCH_RELEASED. 281 break; 282 case ui::ET_GESTURE_LONG_PRESS: 283 if (is_animating()) 284 End(); 285 break; 286 default: 287 // On all other touch and gesture events, we hide the animation. 288 StopAnimation(); 289 break; 290 } 291 } 292 293 //////////////////////////////////////////////////////////////////////////////// 294 // LongPressAffordanceHandler, private 295 296 void LongPressAffordanceHandler::StartAnimation() { 297 aura::RootWindow* root_window = NULL; 298 switch (current_animation_type_) { 299 case GROW_ANIMATION: 300 root_window = ash::Shell::GetInstance()->display_controller()-> 301 GetRootWindowForDisplayId(tap_down_display_id_); 302 if (!root_window) { 303 StopAnimation(); 304 return; 305 } 306 view_.reset(new LongPressAffordanceView(tap_down_location_, root_window)); 307 SetDuration( 308 ui::GestureConfiguration::long_press_time_in_seconds() * 1000 - 309 ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 - 310 kAffordanceDelayBeforeShrinkMs); 311 Start(); 312 break; 313 case SHRINK_ANIMATION: 314 SetDuration(kAffordanceShrinkAnimationDurationMs); 315 Start(); 316 break; 317 default: 318 NOTREACHED(); 319 break; 320 } 321 } 322 323 void LongPressAffordanceHandler::StopAnimation() { 324 if (timer_.IsRunning()) 325 timer_.Stop(); 326 // Since, Animation::Stop() calls AnimationEnded(), we need to reset the 327 // |current_animation_type_| before Stop(), otherwise AnimationEnded() may 328 // start the timer again. 329 current_animation_type_ = NONE; 330 if (is_animating()) 331 Stop(); 332 view_.reset(); 333 tap_down_touch_id_ = -1; 334 tap_down_display_id_ = 0; 335 } 336 337 void LongPressAffordanceHandler::AnimateToState(double state) { 338 DCHECK(view_.get()); 339 switch (current_animation_type_) { 340 case GROW_ANIMATION: 341 view_->UpdateWithGrowAnimation(this); 342 break; 343 case SHRINK_ANIMATION: 344 view_->UpdateWithShrinkAnimation(this); 345 break; 346 default: 347 NOTREACHED(); 348 break; 349 } 350 } 351 352 bool LongPressAffordanceHandler::ShouldSendCanceledFromStop() { 353 return false; 354 } 355 356 void LongPressAffordanceHandler::AnimationEnded( 357 const ui::Animation* animation) { 358 switch (current_animation_type_) { 359 case GROW_ANIMATION: 360 current_animation_type_ = SHRINK_ANIMATION; 361 timer_.Start(FROM_HERE, 362 base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs), 363 this, &LongPressAffordanceHandler::StartAnimation); 364 break; 365 case SHRINK_ANIMATION: 366 current_animation_type_ = NONE; 367 // fall through to reset the view. 368 default: 369 view_.reset(); 370 tap_down_touch_id_ = -1; 371 tap_down_display_id_ = 0; 372 break; 373 } 374 } 375 376 } // namespace internal 377 } // namespace ash 378