diff options
Diffstat (limited to 'chromium/ash/wm/gestures/long_press_affordance_handler.cc')
-rw-r--r-- | chromium/ash/wm/gestures/long_press_affordance_handler.cc | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/chromium/ash/wm/gestures/long_press_affordance_handler.cc b/chromium/ash/wm/gestures/long_press_affordance_handler.cc new file mode 100644 index 00000000000..5450e97c27f --- /dev/null +++ b/chromium/ash/wm/gestures/long_press_affordance_handler.cc @@ -0,0 +1,377 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/wm/gestures/long_press_affordance_handler.h" + +#include "ash/display/display_controller.h" +#include "ash/shell.h" +#include "ash/root_window_controller.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/property_util.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#include "ui/base/gestures/gesture_configuration.h" +#include "ui/base/gestures/gesture_util.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/transform.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace { + +const int kAffordanceOuterRadius = 60; +const int kAffordanceInnerRadius = 50; + +// Angles from x-axis at which the outer and inner circles start. +const int kAffordanceOuterStartAngle = -109; +const int kAffordanceInnerStartAngle = -65; + +const int kAffordanceGlowWidth = 20; +// The following is half width to avoid division by 2. +const int kAffordanceArcWidth = 3; + +// Start and end values for various animations. +const double kAffordanceScaleStartValue = 0.8; +const double kAffordanceScaleEndValue = 1.0; +const double kAffordanceShrinkScaleEndValue = 0.5; +const double kAffordanceOpacityStartValue = 0.1; +const double kAffordanceOpacityEndValue = 0.5; +const int kAffordanceAngleStartValue = 0; +// The end angle is a bit greater than 360 to make sure the circle completes at +// the end of the animation. +const int kAffordanceAngleEndValue = 380; +const int kAffordanceDelayBeforeShrinkMs = 200; +const int kAffordanceShrinkAnimationDurationMs = 100; + +// Visual constants. +const SkColor kAffordanceGlowStartColor = SkColorSetARGB(24, 255, 255, 255); +const SkColor kAffordanceGlowEndColor = SkColorSetARGB(0, 255, 255, 255); +const SkColor kAffordanceArcColor = SkColorSetARGB(80, 0, 0, 0); +const int kAffordanceFrameRateHz = 60; + +views::Widget* CreateAffordanceWidget(aura::RootWindow* root_window) { + views::Widget* widget = new views::Widget; + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; + params.keep_on_top = true; + params.accept_events = false; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.context = root_window; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + widget->Init(params); + widget->SetOpacity(0xFF); + ash::GetRootWindowController(root_window)->GetContainer( + ash::internal::kShellWindowId_OverlayContainer)->AddChild( + widget->GetNativeWindow()); + return widget; +} + +void PaintAffordanceArc(gfx::Canvas* canvas, + gfx::Point& center, + int radius, + int start_angle, + int end_angle) { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(2 * kAffordanceArcWidth); + paint.setColor(kAffordanceArcColor); + paint.setAntiAlias(true); + + SkPath arc_path; + arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, + center.y() - radius, + 2 * radius, + 2 * radius), + start_angle, end_angle); + canvas->DrawPath(arc_path, paint); +} + +void PaintAffordanceGlow(gfx::Canvas* canvas, + gfx::Point& center, + int start_radius, + int end_radius, + SkColor* colors, + SkScalar* pos, + int num_colors) { + SkPoint sk_center; + int radius = (end_radius + start_radius) / 2; + int glow_width = end_radius - start_radius; + sk_center.iset(center.x(), center.y()); + skia::RefPtr<SkShader> shader = skia::AdoptRef( + SkGradientShader::CreateTwoPointRadial( + sk_center, + SkIntToScalar(start_radius), + sk_center, + SkIntToScalar(end_radius), + colors, + pos, + num_colors, + SkShader::kClamp_TileMode)); + DCHECK(shader); + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(glow_width); + paint.setShader(shader.get()); + paint.setAntiAlias(true); + SkPath arc_path; + arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, + center.y() - radius, + 2 * radius, + 2 * radius), + 0, 360); + canvas->DrawPath(arc_path, paint); +} + +} // namespace + +namespace ash { +namespace internal { + +// View of the LongPressAffordanceHandler. Draws the actual contents and +// updates as the animation proceeds. It also maintains the views::Widget that +// the animation is shown in. +class LongPressAffordanceHandler::LongPressAffordanceView + : public views::View { + public: + LongPressAffordanceView(const gfx::Point& event_location, + aura::RootWindow* root_window) + : views::View(), + widget_(CreateAffordanceWidget(root_window)), + current_angle_(kAffordanceAngleStartValue), + current_scale_(kAffordanceScaleStartValue) { + widget_->SetContentsView(this); + widget_->SetAlwaysOnTop(true); + + // We are owned by the LongPressAffordance. + set_owned_by_client(); + gfx::Point point = event_location; + aura::client::GetScreenPositionClient(root_window)->ConvertPointToScreen( + root_window, &point); + widget_->SetBounds(gfx::Rect( + point.x() - (kAffordanceOuterRadius + kAffordanceGlowWidth), + point.y() - (kAffordanceOuterRadius + kAffordanceGlowWidth), + GetPreferredSize().width(), + GetPreferredSize().height())); + widget_->Show(); + widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue); + } + + virtual ~LongPressAffordanceView() { + } + + void UpdateWithGrowAnimation(ui::Animation* animation) { + // Update the portion of the circle filled so far and re-draw. + current_angle_ = animation->CurrentValueBetween(kAffordanceAngleStartValue, + kAffordanceAngleEndValue); + current_scale_ = animation->CurrentValueBetween(kAffordanceScaleStartValue, + kAffordanceScaleEndValue); + widget_->GetNativeView()->layer()->SetOpacity( + animation->CurrentValueBetween(kAffordanceOpacityStartValue, + kAffordanceOpacityEndValue)); + SchedulePaint(); + } + + void UpdateWithShrinkAnimation(ui::Animation* animation) { + current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue, + kAffordanceShrinkScaleEndValue); + widget_->GetNativeView()->layer()->SetOpacity( + animation->CurrentValueBetween(kAffordanceOpacityEndValue, + kAffordanceOpacityStartValue)); + SchedulePaint(); + } + + private: + // Overridden from views::View. + virtual gfx::Size GetPreferredSize() OVERRIDE { + return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth), + 2 * (kAffordanceOuterRadius + kAffordanceGlowWidth)); + } + + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + gfx::Point center(GetPreferredSize().width() / 2, + GetPreferredSize().height() / 2); + canvas->Save(); + + gfx::Transform scale; + scale.Scale(current_scale_, current_scale_); + // We want to scale from the center. + canvas->Translate(center.OffsetFromOrigin()); + canvas->Transform(scale); + canvas->Translate(-center.OffsetFromOrigin()); + + // Paint affordance glow + int start_radius = kAffordanceInnerRadius - kAffordanceGlowWidth; + int end_radius = kAffordanceOuterRadius + kAffordanceGlowWidth; + const int num_colors = 3; + SkScalar pos[num_colors] = {0, 0.5, 1}; + SkColor colors[num_colors] = {kAffordanceGlowEndColor, + kAffordanceGlowStartColor, kAffordanceGlowEndColor}; + PaintAffordanceGlow(canvas, center, start_radius, end_radius, colors, pos, + num_colors); + + // Paint inner circle. + PaintAffordanceArc(canvas, center, kAffordanceInnerRadius, + kAffordanceInnerStartAngle, -current_angle_); + // Paint outer circle. + PaintAffordanceArc(canvas, center, kAffordanceOuterRadius, + kAffordanceOuterStartAngle, current_angle_); + + canvas->Restore(); + } + + scoped_ptr<views::Widget> widget_; + int current_angle_; + double current_scale_; + + DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView); +}; + +//////////////////////////////////////////////////////////////////////////////// +// LongPressAffordanceHandler, public + +LongPressAffordanceHandler::LongPressAffordanceHandler() + : ui::LinearAnimation(kAffordanceFrameRateHz, this), + tap_down_touch_id_(-1), + tap_down_display_id_(0), + current_animation_type_(NONE) {} + +LongPressAffordanceHandler::~LongPressAffordanceHandler() {} + +void LongPressAffordanceHandler::ProcessEvent(aura::Window* target, + ui::LocatedEvent* event, + int touch_id) { + // Once we have a touch id, we are only interested in event of that touch id. + if (tap_down_touch_id_ != -1 && tap_down_touch_id_ != touch_id) + return; + int64 timer_start_time_ms = + ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000; + switch (event->type()) { + case ui::ET_GESTURE_TAP_DOWN: + // Start animation. + tap_down_location_ = event->root_location(); + tap_down_touch_id_ = touch_id; + current_animation_type_ = GROW_ANIMATION; + tap_down_display_id_ = + Shell::GetScreen()->GetDisplayNearestWindow(target).id(); + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(timer_start_time_ms), + this, + &LongPressAffordanceHandler::StartAnimation); + break; + case ui::ET_TOUCH_MOVED: + // If animation is running, We want it to be robust to small finger + // movements. So we stop the animation only when the finger moves a + // certain distance. + if (!ui::gestures::IsInsideManhattanSquare( + event->root_location(), tap_down_location_)) + StopAnimation(); + break; + case ui::ET_TOUCH_CANCELLED: + case ui::ET_GESTURE_END: + // We will stop the animation on TOUCH_RELEASED. + break; + case ui::ET_GESTURE_LONG_PRESS: + if (is_animating()) + End(); + break; + default: + // On all other touch and gesture events, we hide the animation. + StopAnimation(); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// LongPressAffordanceHandler, private + +void LongPressAffordanceHandler::StartAnimation() { + aura::RootWindow* root_window = NULL; + switch (current_animation_type_) { + case GROW_ANIMATION: + root_window = ash::Shell::GetInstance()->display_controller()-> + GetRootWindowForDisplayId(tap_down_display_id_); + if (!root_window) { + StopAnimation(); + return; + } + view_.reset(new LongPressAffordanceView(tap_down_location_, root_window)); + SetDuration( + ui::GestureConfiguration::long_press_time_in_seconds() * 1000 - + ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 - + kAffordanceDelayBeforeShrinkMs); + Start(); + break; + case SHRINK_ANIMATION: + SetDuration(kAffordanceShrinkAnimationDurationMs); + Start(); + break; + default: + NOTREACHED(); + break; + } +} + +void LongPressAffordanceHandler::StopAnimation() { + if (timer_.IsRunning()) + timer_.Stop(); + // Since, Animation::Stop() calls AnimationEnded(), we need to reset the + // |current_animation_type_| before Stop(), otherwise AnimationEnded() may + // start the timer again. + current_animation_type_ = NONE; + if (is_animating()) + Stop(); + view_.reset(); + tap_down_touch_id_ = -1; + tap_down_display_id_ = 0; +} + +void LongPressAffordanceHandler::AnimateToState(double state) { + DCHECK(view_.get()); + switch (current_animation_type_) { + case GROW_ANIMATION: + view_->UpdateWithGrowAnimation(this); + break; + case SHRINK_ANIMATION: + view_->UpdateWithShrinkAnimation(this); + break; + default: + NOTREACHED(); + break; + } +} + +bool LongPressAffordanceHandler::ShouldSendCanceledFromStop() { + return false; +} + +void LongPressAffordanceHandler::AnimationEnded( + const ui::Animation* animation) { + switch (current_animation_type_) { + case GROW_ANIMATION: + current_animation_type_ = SHRINK_ANIMATION; + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs), + this, &LongPressAffordanceHandler::StartAnimation); + break; + case SHRINK_ANIMATION: + current_animation_type_ = NONE; + // fall through to reset the view. + default: + view_.reset(); + tap_down_touch_id_ = -1; + tap_down_display_id_ = 0; + break; + } +} + +} // namespace internal +} // namespace ash |