blob: ab9e413153a6885c81959ec01e9a450eaf1a59e2 [file] [log] [blame]
// 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/root_window_controller.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.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/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/layer.h"
#include "ui/events/gesture_detection/gesture_configuration.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 ash {
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::Window* 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.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
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);
GetRootWindowController(root_window)->GetContainer(
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
// 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::Window* 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);
}
~LongPressAffordanceView() override {}
void UpdateWithGrowAnimation(gfx::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(gfx::Animation* animation) {
current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue,
kAffordanceShrinkScaleEndValue);
widget_->GetNativeView()->layer()->SetOpacity(
animation->CurrentValueBetween(kAffordanceOpacityEndValue,
kAffordanceOpacityStartValue));
SchedulePaint();
}
private:
// Overridden from views::View.
gfx::Size GetPreferredSize() const override {
return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth),
2 * (kAffordanceOuterRadius + kAffordanceGlowWidth));
}
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()
: gfx::LinearAnimation(kAffordanceFrameRateHz, NULL),
tap_down_target_(NULL),
current_animation_type_(NONE) {}
LongPressAffordanceHandler::~LongPressAffordanceHandler() {
StopAffordance();
}
void LongPressAffordanceHandler::ProcessEvent(aura::Window* target,
ui::GestureEvent* event) {
// Once we have a target, we are only interested in events with that target.
if (tap_down_target_ && tap_down_target_ != target)
return;
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN: {
// Start timer that will start animation on "semi-long-press".
tap_down_location_ = event->root_location();
SetTapDownTarget(target);
current_animation_type_ = GROW_ANIMATION;
timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(
ui::GestureConfiguration::GetInstance()
->semi_long_press_time_in_ms()),
this,
&LongPressAffordanceHandler::StartAnimation);
break;
}
case ui::ET_GESTURE_TAP:
case ui::ET_GESTURE_TAP_CANCEL:
StopAffordance();
break;
case ui::ET_GESTURE_LONG_PRESS:
End();
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// LongPressAffordanceHandler, private
void LongPressAffordanceHandler::StartAnimation() {
switch (current_animation_type_) {
case GROW_ANIMATION: {
aura::Window* root_window = tap_down_target_->GetRootWindow();
if (!root_window) {
StopAffordance();
return;
}
view_.reset(new LongPressAffordanceView(tap_down_location_, root_window));
SetDuration(
ui::GestureConfiguration::GetInstance()->long_press_time_in_ms() -
ui::GestureConfiguration::GetInstance()
->semi_long_press_time_in_ms() -
kAffordanceDelayBeforeShrinkMs);
Start();
break;
}
case SHRINK_ANIMATION:
SetDuration(kAffordanceShrinkAnimationDurationMs);
Start();
break;
default:
NOTREACHED();
break;
}
}
void LongPressAffordanceHandler::StopAffordance() {
if (timer_.IsRunning())
timer_.Stop();
// Since, Animation::Stop() calls AnimationStopped(), we need to reset the
// |current_animation_type_| before Stop(), otherwise AnimationStopped() may
// start the timer again.
current_animation_type_ = NONE;
Stop();
view_.reset();
SetTapDownTarget(NULL);
}
void LongPressAffordanceHandler::SetTapDownTarget(aura::Window* target) {
if (tap_down_target_ == target)
return;
if (tap_down_target_)
tap_down_target_->RemoveObserver(this);
tap_down_target_ = target;
if (tap_down_target_)
tap_down_target_->AddObserver(this);
}
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;
}
}
void LongPressAffordanceHandler::AnimationStopped() {
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();
SetTapDownTarget(NULL);
break;
}
}
void LongPressAffordanceHandler::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(tap_down_target_, window);
StopAffordance();
}
} // namespace ash