blob: 67f2bdf2c64eaae34b9b81e084502358fb908630 [file] [log] [blame]
// Copyright 2016 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/autoclick/autoclick_ring_handler.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/transform.h"
#include "ui/views/view.h"
namespace ash {
namespace {
const int kAutoclickRingOuterRadius = 30;
const int kAutoclickRingInnerRadius = 20;
// Angles from x-axis at which the outer and inner circles start.
const int kAutoclickRingInnerStartAngle = -90;
const int kAutoclickRingGlowWidth = 20;
// The following is half width to avoid division by 2.
const int kAutoclickRingArcWidth = 2;
// Start and end values for various animations.
const double kAutoclickRingScaleStartValue = 1.0;
const double kAutoclickRingScaleEndValue = 1.0;
const double kAutoclickRingShrinkScaleEndValue = 0.5;
const double kAutoclickRingOpacityStartValue = 0.1;
const double kAutoclickRingOpacityEndValue = 0.5;
const int kAutoclickRingAngleStartValue = -90;
// The sweep angle is a bit greater than 360 to make sure the circle
// completes at the end of the animation.
const int kAutoclickRingAngleEndValue = 360;
// Visual constants.
const SkColor kAutoclickRingArcColor = SkColorSetARGB(255, 0, 255, 0);
const SkColor kAutoclickRingCircleColor = SkColorSetARGB(255, 0, 0, 255);
void PaintAutoclickRingCircle(gfx::Canvas* canvas,
gfx::Point& center,
int radius) {
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(2 * kAutoclickRingArcWidth);
flags.setColor(kAutoclickRingCircleColor);
flags.setAntiAlias(true);
canvas->DrawCircle(center, radius, flags);
}
void PaintAutoclickRingArc(gfx::Canvas* canvas,
const gfx::Point& center,
int radius,
int start_angle,
int end_angle) {
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(2 * kAutoclickRingArcWidth);
flags.setColor(kAutoclickRingArcColor);
flags.setAntiAlias(true);
SkPath arc_path;
arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, center.y() - radius,
2 * radius, 2 * radius),
start_angle, end_angle - start_angle);
canvas->DrawPath(arc_path, flags);
}
} // namespace
// View of the AutoclickRingHandler. Draws the actual contents and updates as
// the animation proceeds. It also maintains the views::Widget that the
// animation is shown in.
class AutoclickRingHandler::AutoclickRingView : public views::View {
public:
AutoclickRingView(const gfx::Point& event_location,
views::Widget* ring_widget)
: views::View(),
widget_(ring_widget),
current_angle_(kAutoclickRingAngleStartValue),
current_scale_(kAutoclickRingScaleStartValue) {
widget_->SetContentsView(this);
// We are owned by the AutoclickRingHandler.
set_owned_by_client();
SetNewLocation(event_location);
}
~AutoclickRingView() override = default;
void SetNewLocation(const gfx::Point& new_event_location) {
gfx::Point point = new_event_location;
widget_->SetBounds(gfx::Rect(
point.x() - (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth),
point.y() - (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth),
GetPreferredSize().width(), GetPreferredSize().height()));
widget_->Show();
widget_->GetNativeView()->layer()->SetOpacity(
kAutoclickRingOpacityStartValue);
}
void UpdateWithGrowAnimation(gfx::Animation* animation) {
// Update the portion of the circle filled so far and re-draw.
current_angle_ = animation->CurrentValueBetween(
kAutoclickRingInnerStartAngle, kAutoclickRingAngleEndValue);
current_scale_ = animation->CurrentValueBetween(
kAutoclickRingScaleStartValue, kAutoclickRingScaleEndValue);
widget_->GetNativeView()->layer()->SetOpacity(
animation->CurrentValueBetween(kAutoclickRingOpacityStartValue,
kAutoclickRingOpacityEndValue));
SchedulePaint();
}
void UpdateWithShrinkAnimation(gfx::Animation* animation) {
current_angle_ = animation->CurrentValueBetween(
kAutoclickRingInnerStartAngle, kAutoclickRingAngleEndValue);
current_scale_ = animation->CurrentValueBetween(
kAutoclickRingScaleEndValue, kAutoclickRingShrinkScaleEndValue);
widget_->GetNativeView()->layer()->SetOpacity(
animation->CurrentValueBetween(kAutoclickRingOpacityStartValue,
kAutoclickRingOpacityEndValue));
SchedulePaint();
}
private:
// Overridden from views::View.
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(2 * (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth),
2 * (kAutoclickRingOuterRadius + kAutoclickRingGlowWidth));
}
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 inner circle.
PaintAutoclickRingArc(canvas, center, kAutoclickRingInnerRadius,
kAutoclickRingInnerStartAngle, current_angle_);
// Paint outer circle.
PaintAutoclickRingCircle(canvas, center, kAutoclickRingOuterRadius);
canvas->Restore();
}
views::Widget* widget_;
int current_angle_;
double current_scale_;
DISALLOW_COPY_AND_ASSIGN(AutoclickRingView);
};
////////////////////////////////////////////////////////////////////////////////
// AutoclickRingHandler, public
AutoclickRingHandler::AutoclickRingHandler()
: gfx::LinearAnimation(nullptr),
ring_widget_(nullptr),
current_animation_type_(AnimationType::NONE) {}
AutoclickRingHandler::~AutoclickRingHandler() {
StopAutoclickRing();
}
void AutoclickRingHandler::StartGesture(
base::TimeDelta duration,
const gfx::Point& center_point_in_screen,
views::Widget* widget) {
StopAutoclickRing();
tap_down_location_ = center_point_in_screen;
ring_widget_ = widget;
current_animation_type_ = AnimationType::GROW_ANIMATION;
animation_duration_ = duration;
StartAnimation(base::TimeDelta());
}
void AutoclickRingHandler::StopGesture() {
StopAutoclickRing();
}
void AutoclickRingHandler::SetGestureCenter(
const gfx::Point& center_point_in_screen,
views::Widget* widget) {
tap_down_location_ = center_point_in_screen;
ring_widget_ = widget;
}
////////////////////////////////////////////////////////////////////////////////
// AutoclickRingHandler, private
void AutoclickRingHandler::StartAnimation(base::TimeDelta delay) {
switch (current_animation_type_) {
case AnimationType::GROW_ANIMATION: {
view_.reset(new AutoclickRingView(tap_down_location_, ring_widget_));
SetDuration(delay);
Start();
break;
}
case AnimationType::SHRINK_ANIMATION: {
view_.reset(new AutoclickRingView(tap_down_location_, ring_widget_));
SetDuration(delay);
Start();
break;
}
case AnimationType::NONE:
NOTREACHED();
break;
}
}
void AutoclickRingHandler::StopAutoclickRing() {
// 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_ = AnimationType::NONE;
Stop();
view_.reset();
}
void AutoclickRingHandler::AnimateToState(double state) {
DCHECK(view_.get());
switch (current_animation_type_) {
case AnimationType::GROW_ANIMATION:
view_->SetNewLocation(tap_down_location_);
view_->UpdateWithGrowAnimation(this);
break;
case AnimationType::SHRINK_ANIMATION:
view_->SetNewLocation(tap_down_location_);
view_->UpdateWithShrinkAnimation(this);
break;
case AnimationType::NONE:
NOTREACHED();
break;
}
}
void AutoclickRingHandler::AnimationStopped() {
switch (current_animation_type_) {
case AnimationType::GROW_ANIMATION:
current_animation_type_ = AnimationType::SHRINK_ANIMATION;
StartAnimation(animation_duration_);
break;
case AnimationType::SHRINK_ANIMATION:
current_animation_type_ = AnimationType::NONE;
break;
case AnimationType::NONE:
// fall through to reset the view.
view_.reset();
break;
}
}
} // namespace ash