blob: ef03f4214b2b85f2f925c440ffd8e3425ea6529e [file] [log] [blame]
// Copyright 2017 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/shelf/voice_interaction_overlay.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "ash/public/cpp/shelf_types.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/app_list_button.h"
#include "ash/shelf/ink_drop_button_listener.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/chromeos_switches.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/app_list/presenter/app_list.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/paint_context.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/animation/ink_drop_painted_layer_delegates.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr int kFullExpandDurationMs = 450;
constexpr int kFullRetractDurationMs = 300;
constexpr int kFullBurstDurationMs = 200;
constexpr float kRippleCircleInitRadiusDip = 40.f;
constexpr float kRippleCircleStartRadiusDip = 1.f;
constexpr float kRippleCircleRadiusDip = 77.f;
constexpr float kRippleCircleBurstRadiusDip = 96.f;
constexpr SkColor kRippleColor = SK_ColorWHITE;
constexpr int kRippleExpandDurationMs = 400;
constexpr int kRippleOpacityDurationMs = 100;
constexpr int kRippleOpacityRetractDurationMs = 200;
constexpr float kRippleOpacity = 0.2f;
constexpr float kIconInitSizeDip = 48.f;
constexpr float kIconStartSizeDip = 4.f;
constexpr float kIconSizeDip = 24.f;
constexpr float kIconEndSizeDip = 48.f;
constexpr float kIconOffsetDip = 56.f;
constexpr float kIconOpacity = 1.f;
constexpr float kBackgroundInitSizeDip = 48.f;
constexpr float kBackgroundStartSizeDip = 10.f;
constexpr float kBackgroundSizeDip = 48.f;
constexpr int kBackgroundOpacityDurationMs = 200;
constexpr float kBackgroundShadowElevationDip = 24.f;
// TODO(xiaohuic): this is 2x device size, 1x actually have a different size.
// Need to figure out a way to dynamically change sizes.
constexpr float kBackgroundLargeWidthDip = 352.5f;
constexpr float kBackgroundLargeHeightDip = 540.0f;
constexpr float kBackgroundCornerRadiusDip = 2.f;
constexpr float kBackgroundPaddingDip = 6.f;
constexpr int kBackgroundMorphDurationMs = 300;
constexpr SkColor kBackgroundColor = SK_ColorWHITE;
constexpr SkColor kBackgroundFinalColor = static_cast<SkColor>(0xFFF5F5F5);
constexpr int kHideDurationMs = 200;
// The minimum scale factor to use when scaling rectangle layers. Smaller values
// were causing visual anomalies.
constexpr float kMinimumRectScale = 0.0001f;
// The minimum scale factor to use when scaling circle layers. Smaller values
// were causing visual anomalies.
constexpr float kMinimumCircleScale = 0.001f;
// These are voice interaction logo specs.
constexpr float kMoleculeOffsetXDip[] = {-10.f, 10.f, 10.f, 19.f};
constexpr float kMoleculeOffsetYDip[] = {-8.f, -2.f, 13.f, -9.f};
constexpr float kMoleculeRadiusDip[] = {12.f, 6.f, 7.f, 3.f};
constexpr float kMoleculeAmplitude = 2.f;
constexpr SkColor kMoleculeColors[] = {
static_cast<SkColor>(0xFF4184F3), // Blue
static_cast<SkColor>(0xFFEA4335), // Red
static_cast<SkColor>(0xFFFBBC05), // Yellow
static_cast<SkColor>(0xFF34A853) // Green
};
constexpr int kMoleculeAnimationDurationMs = 1200;
constexpr int kMoleculeAnimationOffset = 50;
constexpr int kMoleculeOrder[] = {0, 2, 3, 1};
} // namespace
class VoiceInteractionIcon : public ui::Layer {
public:
VoiceInteractionIcon() : Layer(ui::LAYER_NOT_DRAWN) {
set_name("VoiceInteractionOverlay:ICON_LAYER");
SetBounds(gfx::Rect(0, 0, kIconInitSizeDip, kIconInitSizeDip));
SetFillsBoundsOpaquely(false);
SetMasksToBounds(false);
InitMoleculeShape();
}
void StartAnimation() {
animation_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(
base::TimeTicks::kMillisecondsPerSecond /
gfx::LinearAnimation::kDefaultFrameRate),
this, &VoiceInteractionIcon::AnimationProgressed);
}
void StopAnimation() { animation_timer_.Stop(); }
private:
enum Dot {
BLUE_DOT = 0,
RED_DOT,
YELLOW_DOT,
GREEN_DOT,
// The total number of shapes, not an actual shape.
DOT_COUNT
};
std::string ToLayerName(Dot dot) {
switch (dot) {
case BLUE_DOT:
return "BLUE_DOT";
case RED_DOT:
return "RED_DOT";
case YELLOW_DOT:
return "YELLOW_DOT";
case GREEN_DOT:
return "GREEN_DOT";
case DOT_COUNT:
NOTREACHED() << "The DOT_COUNT value should never be used.";
return "DOT_COUNT";
}
return "UNKNOWN";
}
void AnimationProgressed() {
gfx::Transform transform;
uint64_t now =
(base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds();
for (int i = 0; i < DOT_COUNT; ++i) {
float normalizedTime =
((now - kMoleculeAnimationOffset * kMoleculeOrder[i]) %
kMoleculeAnimationDurationMs) /
static_cast<float>(kMoleculeAnimationDurationMs);
transform.MakeIdentity();
transform.Translate(0,
kMoleculeAmplitude * sin(normalizedTime * 2 * M_PI));
dot_layers_[i]->SetTransform(transform);
}
}
/**
* Convenience method to place dots to Molecule shape used by Molecule
* animations.
*/
void InitMoleculeShape() {
for (int i = 0; i < DOT_COUNT; ++i) {
dot_layer_delegates_[i] = base::MakeUnique<views::CircleLayerDelegate>(
kMoleculeColors[i], kMoleculeRadiusDip[i]);
dot_layers_[i] = base::MakeUnique<ui::Layer>();
dot_layers_[i]->SetBounds(gfx::Rect(
kIconInitSizeDip / 2 + kMoleculeOffsetXDip[i] - kMoleculeRadiusDip[i],
kIconInitSizeDip / 2 + kMoleculeOffsetYDip[i] - kMoleculeRadiusDip[i],
kMoleculeRadiusDip[i] * 2, kMoleculeRadiusDip[i] * 2));
dot_layers_[i]->SetFillsBoundsOpaquely(false);
dot_layers_[i]->set_delegate(dot_layer_delegates_[i].get());
dot_layers_[i]->SetVisible(true);
dot_layers_[i]->SetOpacity(1.0);
dot_layers_[i]->SetMasksToBounds(false);
dot_layers_[i]->set_name("DOT:" + ToLayerName(static_cast<Dot>(i)));
Add(dot_layers_[i].get());
}
}
std::unique_ptr<ui::Layer> dot_layers_[DOT_COUNT];
std::unique_ptr<views::CircleLayerDelegate> dot_layer_delegates_[DOT_COUNT];
base::RepeatingTimer animation_timer_;
DISALLOW_COPY_AND_ASSIGN(VoiceInteractionIcon);
};
class VoiceInteractionIconBackground : public ui::Layer,
public ui::LayerDelegate {
public:
VoiceInteractionIconBackground()
: Layer(ui::LAYER_NOT_DRAWN),
large_size_(
gfx::Size(kBackgroundLargeWidthDip, kBackgroundLargeHeightDip)),
small_size_(gfx::Size(kBackgroundSizeDip, kBackgroundSizeDip)),
center_point_(
gfx::PointF(kBackgroundSizeDip / 2, kBackgroundSizeDip / 2)),
circle_layer_delegate_(base::MakeUnique<views::CircleLayerDelegate>(
kBackgroundColor,
kBackgroundSizeDip / 2)),
bg_circle_layer_delegate_(base::MakeUnique<views::CircleLayerDelegate>(
kBackgroundFinalColor,
kBackgroundSizeDip / 2)),
rect_layer_delegate_(base::MakeUnique<views::RectangleLayerDelegate>(
kBackgroundColor,
gfx::SizeF(small_size_))),
bg_rect_layer_delegate_(base::MakeUnique<views::RectangleLayerDelegate>(
kBackgroundFinalColor,
gfx::SizeF(small_size_))) {
set_name("VoiceInteractionOverlay:BACKGROUND_LAYER");
SetBounds(gfx::Rect(0, 0, kBackgroundInitSizeDip, kBackgroundInitSizeDip));
SetFillsBoundsOpaquely(false);
SetMasksToBounds(false);
for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) {
AddPaintLayer(static_cast<PaintedShape>(i), true);
AddPaintLayer(static_cast<PaintedShape>(i), false);
}
shadow_values_ =
gfx::ShadowValue::MakeMdShadowValues(kBackgroundShadowElevationDip);
const gfx::Insets shadow_margin =
gfx::ShadowValue::GetMargin(shadow_values_);
shadow_layer_.reset(new ui::Layer());
shadow_layer_->set_delegate(this);
shadow_layer_->SetFillsBoundsOpaquely(false);
shadow_layer_->SetBounds(
gfx::Rect(shadow_margin.left(), shadow_margin.top(),
kBackgroundInitSizeDip - shadow_margin.width(),
kBackgroundInitSizeDip - shadow_margin.height()));
Add(shadow_layer_.get());
}
~VoiceInteractionIconBackground() override{};
void AnimateToLarge(const gfx::PointF& new_center,
ui::LayerAnimationObserver* animation_observer) {
PaintedShapeTransforms transforms;
// Setup the painted layers to be the small round size and show it
CalculateCircleTransforms(small_size_, &transforms);
SetTransforms(transforms);
SetPaintedLayersVisible(true);
// Hide the shadow layer
shadow_layer_->SetVisible(false);
center_point_ = new_center;
// Animate the painted layers to the large rectangle size
CalculateRectTransforms(large_size_, kBackgroundCornerRadiusDip,
&transforms);
AnimateToTransforms(
transforms,
base::TimeDelta::FromMilliseconds(kBackgroundMorphDurationMs),
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET,
gfx::Tween::LINEAR_OUT_SLOW_IN, animation_observer);
}
void ResetShape() {
// This reverts to the original small round shape.
shadow_layer_->SetVisible(true);
SetPaintedLayersVisible(false);
center_point_.SetPoint(small_size_.width() / 2.f,
small_size_.height() / 2.f);
}
private:
// Enumeration of the different shapes that compose the background.
enum PaintedShape {
TOP_LEFT_CIRCLE = 0,
TOP_RIGHT_CIRCLE,
BOTTOM_RIGHT_CIRCLE,
BOTTOM_LEFT_CIRCLE,
HORIZONTAL_RECT,
VERTICAL_RECT,
// The total number of shapes, not an actual shape.
PAINTED_SHAPE_COUNT
};
typedef gfx::Transform PaintedShapeTransforms[PAINTED_SHAPE_COUNT];
void AddPaintLayer(PaintedShape painted_shape, bool is_background) {
ui::LayerDelegate* delegate = nullptr;
switch (painted_shape) {
case TOP_LEFT_CIRCLE:
case TOP_RIGHT_CIRCLE:
case BOTTOM_RIGHT_CIRCLE:
case BOTTOM_LEFT_CIRCLE:
if (is_background) {
delegate = bg_circle_layer_delegate_.get();
} else {
delegate = circle_layer_delegate_.get();
}
break;
case HORIZONTAL_RECT:
case VERTICAL_RECT:
if (is_background) {
delegate = bg_rect_layer_delegate_.get();
} else {
delegate = rect_layer_delegate_.get();
}
break;
case PAINTED_SHAPE_COUNT:
NOTREACHED() << "PAINTED_SHAPE_COUNT is not an actual shape type.";
break;
}
ui::Layer* layer = new ui::Layer();
Add(layer);
layer->SetBounds(gfx::Rect(small_size_));
layer->SetFillsBoundsOpaquely(false);
layer->set_delegate(delegate);
layer->SetVisible(true);
layer->SetOpacity(1.0);
layer->SetMasksToBounds(false);
layer->set_name("PAINTED_SHAPE_COUNT:" + ToLayerName(painted_shape));
if (is_background) {
bg_painted_layers_[static_cast<int>(painted_shape)].reset(layer);
} else {
painted_layers_[static_cast<int>(painted_shape)].reset(layer);
}
}
void SetTransforms(const PaintedShapeTransforms transforms) {
for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) {
painted_layers_[i]->SetTransform(transforms[i]);
bg_painted_layers_[i]->SetTransform(transforms[i]);
}
}
void SetPaintedLayersVisible(bool visible) {
for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) {
painted_layers_[i]->SetVisible(visible);
painted_layers_[i]->SetOpacity(1);
bg_painted_layers_[i]->SetVisible(visible);
bg_painted_layers_[i]->SetOpacity(1);
}
}
void CalculateCircleTransforms(const gfx::Size& size,
PaintedShapeTransforms* transforms_out) const {
CalculateRectTransforms(size, std::min(size.width(), size.height()) / 2.0f,
transforms_out);
}
void CalculateRectTransforms(const gfx::Size& desired_size,
float corner_radius,
PaintedShapeTransforms* transforms_out) const {
DCHECK_GE(desired_size.width() / 2.0f, corner_radius)
<< "The circle's diameter should not be greater than the total width.";
DCHECK_GE(desired_size.height() / 2.0f, corner_radius)
<< "The circle's diameter should not be greater than the total height.";
gfx::SizeF size(desired_size);
// This function can be called before the layer's been added to a view,
// either at construction time or in tests.
if (GetCompositor()) {
// Modify |desired_size| so that the ripple aligns to pixel bounds.
const float dsf = GetCompositor()->device_scale_factor();
gfx::RectF ripple_bounds((gfx::PointF(center_point_)), gfx::SizeF());
ripple_bounds.Inset(-gfx::InsetsF(desired_size.height() / 2.0f,
desired_size.width() / 2.0f));
ripple_bounds.Scale(dsf);
ripple_bounds = gfx::RectF(gfx::ToEnclosingRect(ripple_bounds));
ripple_bounds.Scale(1.0f / dsf);
size = ripple_bounds.size();
}
// The shapes are drawn such that their center points are not at the origin.
// Thus we use the CalculateCircleTransform() and CalculateRectTransform()
// methods to calculate the complex Transforms.
const float circle_scale = std::max(
kMinimumCircleScale,
corner_radius / static_cast<float>(circle_layer_delegate_->radius()));
const float circle_target_x_offset = size.width() / 2.0f - corner_radius;
const float circle_target_y_offset = size.height() / 2.0f - corner_radius;
(*transforms_out)[TOP_LEFT_CIRCLE] = CalculateCircleTransform(
circle_scale, -circle_target_x_offset, -circle_target_y_offset);
(*transforms_out)[TOP_RIGHT_CIRCLE] = CalculateCircleTransform(
circle_scale, circle_target_x_offset, -circle_target_y_offset);
(*transforms_out)[BOTTOM_RIGHT_CIRCLE] = CalculateCircleTransform(
circle_scale, circle_target_x_offset, circle_target_y_offset);
(*transforms_out)[BOTTOM_LEFT_CIRCLE] = CalculateCircleTransform(
circle_scale, -circle_target_x_offset, circle_target_y_offset);
const float rect_delegate_width = rect_layer_delegate_->size().width();
const float rect_delegate_height = rect_layer_delegate_->size().height();
(*transforms_out)[HORIZONTAL_RECT] = CalculateRectTransform(
std::max(kMinimumRectScale, size.width() / rect_delegate_width),
std::max(kMinimumRectScale, (size.height() - 2.0f * corner_radius) /
rect_delegate_height));
(*transforms_out)[VERTICAL_RECT] = CalculateRectTransform(
std::max(kMinimumRectScale,
(size.width() - 2.0f * corner_radius) / rect_delegate_width),
std::max(kMinimumRectScale, size.height() / rect_delegate_height));
}
gfx::Transform CalculateCircleTransform(float scale,
float target_center_x,
float target_center_y) const {
gfx::Transform transform;
// Offset for the center point of the ripple.
transform.Translate(center_point_.x(), center_point_.y());
// Move circle to target.
transform.Translate(target_center_x, target_center_y);
transform.Scale(scale, scale);
// Align center point of the painted circle.
const gfx::Vector2dF circle_center_offset =
circle_layer_delegate_->GetCenteringOffset();
transform.Translate(-circle_center_offset.x(), -circle_center_offset.y());
return transform;
}
gfx::Transform CalculateRectTransform(float x_scale, float y_scale) const {
gfx::Transform transform;
transform.Translate(center_point_.x(), center_point_.y());
transform.Scale(x_scale, y_scale);
const gfx::Vector2dF rect_center_offset =
rect_layer_delegate_->GetCenteringOffset();
transform.Translate(-rect_center_offset.x(), -rect_center_offset.y());
return transform;
}
void AnimateToTransforms(
const PaintedShapeTransforms transforms,
base::TimeDelta duration,
ui::LayerAnimator::PreemptionStrategy preemption_strategy,
gfx::Tween::Type tween,
ui::LayerAnimationObserver* animation_observer) {
for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) {
ui::LayerAnimator* animator = painted_layers_[i]->GetAnimator();
ui::ScopedLayerAnimationSettings animation(animator);
animation.SetPreemptionStrategy(preemption_strategy);
animation.SetTweenType(tween);
animation.SetTransitionDuration(duration);
painted_layers_[i]->SetOpacity(0);
std::unique_ptr<ui::LayerAnimationElement> element =
ui::LayerAnimationElement::CreateTransformElement(transforms[i],
duration);
ui::LayerAnimationSequence* sequence =
new ui::LayerAnimationSequence(std::move(element));
if (animation_observer)
sequence->AddObserver(animation_observer);
animator->StartAnimation(sequence);
}
for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) {
ui::LayerAnimator* animator = bg_painted_layers_[i]->GetAnimator();
ui::ScopedLayerAnimationSettings animation(animator);
animation.SetPreemptionStrategy(preemption_strategy);
animation.SetTweenType(tween);
std::unique_ptr<ui::LayerAnimationElement> element =
ui::LayerAnimationElement::CreateTransformElement(transforms[i],
duration);
ui::LayerAnimationSequence* sequence =
new ui::LayerAnimationSequence(std::move(element));
if (animation_observer)
sequence->AddObserver(animation_observer);
animator->StartAnimation(sequence);
}
}
std::string ToLayerName(PaintedShape painted_shape) {
switch (painted_shape) {
case TOP_LEFT_CIRCLE:
return "TOP_LEFT_CIRCLE";
case TOP_RIGHT_CIRCLE:
return "TOP_RIGHT_CIRCLE";
case BOTTOM_RIGHT_CIRCLE:
return "BOTTOM_RIGHT_CIRCLE";
case BOTTOM_LEFT_CIRCLE:
return "BOTTOM_LEFT_CIRCLE";
case HORIZONTAL_RECT:
return "HORIZONTAL_RECT";
case VERTICAL_RECT:
return "VERTICAL_RECT";
case PAINTED_SHAPE_COUNT:
NOTREACHED() << "The PAINTED_SHAPE_COUNT value should never be used.";
return "PAINTED_SHAPE_COUNT";
}
return "UNKNOWN";
}
void OnPaintLayer(const ui::PaintContext& context) override {
// Radius is based on the parent layer size, the shadow layer is expanded
// to make room for the shadow.
float radius = size().width() / 2.f;
ui::PaintRecorder recorder(context, shadow_layer_->size());
gfx::Canvas* canvas = recorder.canvas();
cc::PaintFlags flags;
flags.setColor(kBackgroundColor);
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setLooper(gfx::CreateShadowDrawLooper(shadow_values_));
gfx::Rect shadow_bounds = shadow_layer_->bounds();
canvas->DrawCircle(
gfx::PointF(radius - shadow_bounds.x(), radius - shadow_bounds.y()),
radius, flags);
}
void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
// ui::Layers for all of the painted shape layers that compose the morphing
// shape. We have two sets, one is rendered in the foreground, the other set
// behind. We use them to create an animated transition between two colors by
// fading out one set during transformation.
std::unique_ptr<ui::Layer> painted_layers_[PAINTED_SHAPE_COUNT];
std::unique_ptr<ui::Layer> bg_painted_layers_[PAINTED_SHAPE_COUNT];
const gfx::Size large_size_;
const gfx::Size small_size_;
// The center point of the painted shape.
gfx::PointF center_point_;
// ui::LayerDelegate to paint circles for all the circle layers.
std::unique_ptr<views::CircleLayerDelegate> circle_layer_delegate_;
std::unique_ptr<views::CircleLayerDelegate> bg_circle_layer_delegate_;
// ui::LayerDelegate to paint rectangles for all the rectangle layers.
std::unique_ptr<views::RectangleLayerDelegate> rect_layer_delegate_;
std::unique_ptr<views::RectangleLayerDelegate> bg_rect_layer_delegate_;
gfx::ShadowValues shadow_values_;
std::unique_ptr<ui::Layer> shadow_layer_;
DISALLOW_COPY_AND_ASSIGN(VoiceInteractionIconBackground);
};
VoiceInteractionOverlay::VoiceInteractionOverlay(AppListButton* host_view)
: ripple_layer_(base::MakeUnique<ui::Layer>()),
icon_layer_(base::MakeUnique<VoiceInteractionIcon>()),
background_layer_(base::MakeUnique<VoiceInteractionIconBackground>()),
host_view_(host_view),
is_bursting_(false),
show_icon_(false),
should_hide_animation_(false),
circle_layer_delegate_(kRippleColor, kRippleCircleInitRadiusDip) {
SetPaintToLayer(ui::LAYER_NOT_DRAWN);
layer()->set_name("VoiceInteractionOverlay:ROOT_LAYER");
layer()->SetMasksToBounds(false);
ripple_layer_->SetBounds(gfx::Rect(0, 0, kRippleCircleInitRadiusDip * 2,
kRippleCircleInitRadiusDip * 2));
ripple_layer_->set_delegate(&circle_layer_delegate_);
ripple_layer_->SetFillsBoundsOpaquely(false);
ripple_layer_->SetMasksToBounds(true);
ripple_layer_->set_name("VoiceInteractionOverlay:PAINTED_LAYER");
layer()->Add(ripple_layer_.get());
layer()->Add(background_layer_.get());
layer()->Add(icon_layer_.get());
}
VoiceInteractionOverlay::~VoiceInteractionOverlay() {}
void VoiceInteractionOverlay::StartAnimation(bool show_icon) {
is_bursting_ = false;
show_icon_ = show_icon;
SetVisible(true);
// Setup ripple initial state.
ripple_layer_->SetOpacity(0);
SkMScalar scale_factor =
kRippleCircleStartRadiusDip / kRippleCircleInitRadiusDip;
gfx::Transform transform;
const gfx::Point center = host_view_->GetAppListButtonCenterPoint();
transform.Translate(center.x() - kRippleCircleStartRadiusDip,
center.y() - kRippleCircleStartRadiusDip);
transform.Scale(scale_factor, scale_factor);
ripple_layer_->SetTransform(transform);
// Setup ripple animations.
{
scale_factor = kRippleCircleRadiusDip / kRippleCircleInitRadiusDip;
transform.MakeIdentity();
transform.Translate(center.x() - kRippleCircleRadiusDip,
center.y() - kRippleCircleRadiusDip);
transform.Scale(scale_factor, scale_factor);
ui::ScopedLayerAnimationSettings settings(ripple_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kRippleExpandDurationMs));
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
ripple_layer_->SetTransform(transform);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kRippleOpacityDurationMs));
ripple_layer_->SetOpacity(kRippleOpacity);
}
icon_layer_->SetOpacity(0);
background_layer_->SetOpacity(0);
if (!show_icon_)
return;
// Setup icon initial state.
transform.MakeIdentity();
transform.Translate(center.x() - kIconStartSizeDip / 2.f,
center.y() - kIconStartSizeDip / 2.f);
scale_factor = kIconStartSizeDip / kIconInitSizeDip;
transform.Scale(scale_factor, scale_factor);
icon_layer_->SetTransform(transform);
// Setup icon animation.
scale_factor = kIconSizeDip / kIconInitSizeDip;
transform.MakeIdentity();
transform.Translate(center.x() - kIconSizeDip / 2 + kIconOffsetDip,
center.y() - kIconSizeDip / 2 - kIconOffsetDip);
transform.Scale(scale_factor, scale_factor);
{
ui::ScopedLayerAnimationSettings settings(icon_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullExpandDurationMs));
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
icon_layer_->SetTransform(transform);
icon_layer_->SetOpacity(kIconOpacity);
}
// Setup background initial state.
background_layer_->ResetShape();
transform.MakeIdentity();
transform.Translate(center.x() - kBackgroundStartSizeDip / 2.f,
center.y() - kBackgroundStartSizeDip / 2.f);
scale_factor = kBackgroundStartSizeDip / kBackgroundInitSizeDip;
transform.Scale(scale_factor, scale_factor);
background_layer_->SetTransform(transform);
// Setup background animation.
scale_factor = kBackgroundSizeDip / kBackgroundInitSizeDip;
transform.MakeIdentity();
transform.Translate(center.x() - kBackgroundSizeDip / 2 + kIconOffsetDip,
center.y() - kBackgroundSizeDip / 2 - kIconOffsetDip);
transform.Scale(scale_factor, scale_factor);
{
ui::ScopedLayerAnimationSettings settings(background_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullExpandDurationMs));
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
background_layer_->SetTransform(transform);
}
{
ui::ScopedLayerAnimationSettings settings(background_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kBackgroundOpacityDurationMs));
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN_2);
background_layer_->SetOpacity(1);
}
}
void VoiceInteractionOverlay::BurstAnimation() {
is_bursting_ = true;
should_hide_animation_ = false;
gfx::Point center = host_view_->GetAppListButtonCenterPoint();
gfx::Transform transform;
// Setup ripple animations.
{
SkMScalar scale_factor =
kRippleCircleBurstRadiusDip / kRippleCircleInitRadiusDip;
transform.Translate(center.x() - kRippleCircleBurstRadiusDip,
center.y() - kRippleCircleBurstRadiusDip);
transform.Scale(scale_factor, scale_factor);
ui::ScopedLayerAnimationSettings settings(ripple_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullBurstDurationMs));
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION);
ripple_layer_->SetTransform(transform);
ripple_layer_->SetOpacity(0);
}
if (!show_icon_)
return;
// Setup icon animation.
// TODO(xiaohuic): Currently the animation does not support RTL.
{
ui::ScopedLayerAnimationSettings settings(icon_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kBackgroundMorphDurationMs));
settings.SetPreemptionStrategy(
ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION);
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
transform.MakeIdentity();
transform.Translate(kBackgroundLargeWidthDip / 2 + kBackgroundPaddingDip -
kIconEndSizeDip / 2,
-kBackgroundLargeHeightDip / 2 - kBackgroundPaddingDip -
kIconEndSizeDip / 2);
SkMScalar scale_factor = kIconEndSizeDip / kIconInitSizeDip;
transform.Scale(scale_factor, scale_factor);
icon_layer_->SetTransform(transform);
icon_layer_->StartAnimation();
}
// Setup background animation.
ui::CallbackLayerAnimationObserver* observer =
new ui::CallbackLayerAnimationObserver(
base::Bind(&VoiceInteractionOverlay::AnimationEndedCallback,
base::Unretained(this)));
// Transform to new shape.
// We want to animate from the background's current position into a larger
// size. The animation moves the background's center point while morphing from
// circle to a rectangle.
float x_offset = center.x() - kBackgroundSizeDip / 2 + kIconOffsetDip;
float y_offset = center.y() - kBackgroundSizeDip / 2 - kIconOffsetDip;
background_layer_->AnimateToLarge(
gfx::PointF(
kBackgroundLargeWidthDip / 2 + kBackgroundPaddingDip - x_offset,
-kBackgroundLargeHeightDip / 2 - kBackgroundPaddingDip - y_offset),
observer);
observer->SetActive();
}
void VoiceInteractionOverlay::EndAnimation() {
if (is_bursting_) {
// Too late, user action already fired, we have to finish what's started.
return;
}
// Play reverse animation
// Setup ripple animations.
SkMScalar scale_factor =
kRippleCircleStartRadiusDip / kRippleCircleInitRadiusDip;
gfx::Transform transform;
const gfx::Point center = host_view_->GetAppListButtonCenterPoint();
transform.Translate(center.x() - kRippleCircleStartRadiusDip,
center.y() - kRippleCircleStartRadiusDip);
transform.Scale(scale_factor, scale_factor);
{
ui::ScopedLayerAnimationSettings settings(ripple_layer_->GetAnimator());
settings.SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy::
IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullRetractDurationMs));
settings.SetTweenType(gfx::Tween::SLOW_OUT_LINEAR_IN);
ripple_layer_->SetTransform(transform);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kRippleOpacityRetractDurationMs));
ripple_layer_->SetOpacity(0);
}
if (!show_icon_)
return;
// Setup icon animation.
transform.MakeIdentity();
transform.Translate(center.x() - kIconStartSizeDip / 2.f,
center.y() - kIconStartSizeDip / 2.f);
scale_factor = kIconStartSizeDip / kIconInitSizeDip;
transform.Scale(scale_factor, scale_factor);
{
ui::ScopedLayerAnimationSettings settings(icon_layer_->GetAnimator());
settings.SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy::
IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullRetractDurationMs));
settings.SetTweenType(gfx::Tween::SLOW_OUT_LINEAR_IN);
icon_layer_->SetTransform(transform);
icon_layer_->SetOpacity(0);
}
// Setup background animation.
transform.MakeIdentity();
transform.Translate(center.x() - kBackgroundStartSizeDip / 2.f,
center.y() - kBackgroundStartSizeDip / 2.f);
scale_factor = kBackgroundStartSizeDip / kBackgroundInitSizeDip;
transform.Scale(scale_factor, scale_factor);
{
ui::ScopedLayerAnimationSettings settings(background_layer_->GetAnimator());
settings.SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy::
IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kFullRetractDurationMs));
settings.SetTweenType(gfx::Tween::SLOW_OUT_LINEAR_IN);
background_layer_->SetTransform(transform);
background_layer_->SetOpacity(0);
}
}
void VoiceInteractionOverlay::HideAnimation() {
is_bursting_ = false;
if (background_layer_->GetAnimator()->is_animating()) {
// Wait for current animation to finish
should_hide_animation_ = true;
return;
}
should_hide_animation_ = false;
// Setup ripple animations.
{
ui::ScopedLayerAnimationSettings settings(ripple_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kHideDurationMs));
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION);
ripple_layer_->SetOpacity(0);
}
// Setup icon animation.
{
ui::ScopedLayerAnimationSettings settings(icon_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kHideDurationMs));
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION);
icon_layer_->SetOpacity(0);
icon_layer_->StopAnimation();
}
// Setup background animation.
{
ui::ScopedLayerAnimationSettings settings(background_layer_->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kHideDurationMs));
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION);
background_layer_->SetOpacity(0);
}
}
bool VoiceInteractionOverlay::AnimationEndedCallback(
const ui::CallbackLayerAnimationObserver& observer) {
if (should_hide_animation_)
HideAnimation();
return true;
}
} // namespace ash