| // 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/assistant_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 "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/command_line.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/constants/chromeos_switches.h" |
| #include "ui/accessibility/ax_node_data.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 = 12.f; |
| constexpr float kBackgroundPaddingDip = 6.f; |
| constexpr int kBackgroundMorphDurationMs = 150; |
| |
| 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 AssistantIcon : public ui::Layer, public ui::CompositorAnimationObserver { |
| public: |
| AssistantIcon() : Layer(ui::LAYER_NOT_DRAWN) { |
| set_name("AssistantOverlay:ICON_LAYER"); |
| SetBounds(gfx::Rect(0, 0, kIconInitSizeDip, kIconInitSizeDip)); |
| SetFillsBoundsOpaquely(false); |
| SetMasksToBounds(false); |
| |
| InitMoleculeShape(); |
| } |
| |
| ~AssistantIcon() override { StopAnimation(); } |
| |
| void StartAnimation() { |
| ui::Compositor* compositor = GetCompositor(); |
| if (compositor && !compositor->HasAnimationObserver(this)) { |
| animating_compositor_ = compositor; |
| animating_compositor_->AddAnimationObserver(this); |
| } |
| } |
| |
| void StopAnimation() { |
| if (animating_compositor_ && |
| animating_compositor_->HasAnimationObserver(this)) { |
| animating_compositor_->RemoveAnimationObserver(this); |
| } |
| animating_compositor_ = nullptr; |
| } |
| |
| // ui::CompositorAnimationObserver |
| void OnCompositingShuttingDown(ui::Compositor* compositor) override { |
| DCHECK(compositor); |
| if (animating_compositor_ == compositor) { |
| StopAnimation(); |
| } |
| } |
| |
| void OnAnimationStep(base::TimeTicks timestamp) override { |
| uint64_t elapsed = (timestamp - base::TimeTicks()).InMilliseconds(); |
| for (int i = 0; i < DOT_COUNT; ++i) { |
| float normalizedTime = |
| ((elapsed - kMoleculeAnimationOffset * kMoleculeOrder[i]) % |
| kMoleculeAnimationDurationMs) / |
| static_cast<float>(kMoleculeAnimationDurationMs); |
| |
| gfx::Transform transform; |
| transform.Translate(0, |
| kMoleculeAmplitude * sin(normalizedTime * 2 * M_PI)); |
| |
| dot_layers_[i]->SetTransform(transform); |
| } |
| } |
| |
| 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"; |
| } |
| |
| /** |
| * 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] = std::make_unique<views::CircleLayerDelegate>( |
| kMoleculeColors[i], kMoleculeRadiusDip[i]); |
| dot_layers_[i] = std::make_unique<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]; |
| |
| ui::Compositor* animating_compositor_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(AssistantIcon); |
| }; |
| |
| class AssistantIconBackground : public ui::Layer, public ui::LayerDelegate { |
| public: |
| AssistantIconBackground() |
| : 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_(std::make_unique<views::CircleLayerDelegate>( |
| SK_ColorWHITE, |
| kBackgroundSizeDip / 2)), |
| rect_layer_delegate_(std::make_unique<views::RectangleLayerDelegate>( |
| SK_ColorWHITE, |
| gfx::SizeF(small_size_))) { |
| set_name("AssistantOverlay:BACKGROUND_LAYER"); |
| SetBounds(gfx::Rect(0, 0, kBackgroundInitSizeDip, kBackgroundInitSizeDip)); |
| SetFillsBoundsOpaquely(false); |
| SetMasksToBounds(false); |
| |
| shadow_values_ = |
| gfx::ShadowValue::MakeMdShadowValues(kBackgroundShadowElevationDip); |
| const gfx::Insets shadow_margin = |
| gfx::ShadowValue::GetMargin(shadow_values_); |
| |
| border_shadow_delegate_ = |
| std::make_unique<views::BorderShadowLayerDelegate>( |
| shadow_values_, gfx::Rect(large_size_), SK_ColorWHITE, |
| kBackgroundCornerRadiusDip); |
| |
| large_shadow_layer_ = std::make_unique<ui::Layer>(); |
| large_shadow_layer_->set_delegate(border_shadow_delegate_.get()); |
| large_shadow_layer_->SetFillsBoundsOpaquely(false); |
| large_shadow_layer_->SetBounds( |
| gfx::Rect(shadow_margin.left(), shadow_margin.top(), |
| kBackgroundLargeWidthDip - shadow_margin.width(), |
| kBackgroundLargeHeightDip - shadow_margin.height())); |
| Add(large_shadow_layer_.get()); |
| |
| shadow_layer_ = std::make_unique<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()); |
| |
| for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) |
| AddPaintLayer(static_cast<PaintedShape>(i)); |
| } |
| ~AssistantIconBackground() override = default; |
| |
| void MoveLargeShadow(const gfx::PointF& new_center) { |
| gfx::Transform transform; |
| transform.Translate(new_center.x() - kBackgroundLargeWidthDip / 2, |
| new_center.y() - kBackgroundLargeHeightDip / 2); |
| large_shadow_layer_->SetTransform(transform); |
| } |
| |
| 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); |
| // Also hide the large shadow layer, it will be shown when animation ends. |
| large_shadow_layer_->SetVisible(false); |
| // Move the shadow to the right place. |
| MoveLargeShadow(new_center); |
| |
| 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 SetToLarge(const gfx::PointF& new_center) { |
| PaintedShapeTransforms transforms; |
| SetPaintedLayersVisible(true); |
| // Hide the shadow layer |
| shadow_layer_->SetVisible(false); |
| // Show the large shadow behind |
| large_shadow_layer_->SetVisible(true); |
| // Move the shadow to the right place. |
| MoveLargeShadow(new_center); |
| |
| center_point_ = new_center; |
| // Set the painted layers to the large rectangle size |
| CalculateRectTransforms(large_size_, kBackgroundCornerRadiusDip, |
| &transforms); |
| SetTransforms(transforms); |
| } |
| |
| void ResetShape() { |
| // This reverts to the original small round shape. |
| shadow_layer_->SetVisible(true); |
| large_shadow_layer_->SetVisible(false); |
| 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) { |
| ui::LayerDelegate* delegate = nullptr; |
| switch (painted_shape) { |
| case TOP_LEFT_CIRCLE: |
| case TOP_RIGHT_CIRCLE: |
| case BOTTOM_RIGHT_CIRCLE: |
| case BOTTOM_LEFT_CIRCLE: |
| delegate = circle_layer_delegate_.get(); |
| break; |
| case HORIZONTAL_RECT: |
| case VERTICAL_RECT: |
| 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)); |
| |
| 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]); |
| } |
| |
| void SetPaintedLayersVisible(bool visible) { |
| for (int i = 0; i < PAINTED_SHAPE_COUNT; ++i) |
| painted_layers_[i]->SetVisible(visible); |
| } |
| |
| 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); |
| 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); |
| } |
| |
| { |
| ui::ScopedLayerAnimationSettings animation( |
| large_shadow_layer_->GetAnimator()); |
| animation.SetTweenType(tween); |
| animation.SetTransitionDuration(duration); |
| |
| large_shadow_layer_->SetVisible(true); |
| } |
| } |
| |
| 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(SK_ColorWHITE); |
| 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 OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| |
| // ui::Layers for all of the painted shape layers that compose the morphing |
| // shape. |
| std::unique_ptr<ui::Layer> 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_; |
| |
| // ui::LayerDelegate to paint rectangles for all the rectangle layers. |
| std::unique_ptr<views::RectangleLayerDelegate> rect_layer_delegate_; |
| |
| // ui::LayerDelegate to paint rounded rectangle with shadow. |
| std::unique_ptr<views::BorderShadowLayerDelegate> border_shadow_delegate_; |
| |
| gfx::ShadowValues shadow_values_; |
| |
| // This layer shows the small circle with shadow. |
| std::unique_ptr<ui::Layer> shadow_layer_; |
| |
| // This layer shows the large rounded rectangle with shadow. |
| std::unique_ptr<ui::Layer> large_shadow_layer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AssistantIconBackground); |
| }; |
| |
| AssistantOverlay::AssistantOverlay(AppListButton* host_view) |
| : ripple_layer_(std::make_unique<ui::Layer>()), |
| icon_layer_(std::make_unique<AssistantIcon>()), |
| background_layer_(std::make_unique<AssistantIconBackground>()), |
| host_view_(host_view), |
| circle_layer_delegate_(kRippleColor, kRippleCircleInitRadiusDip) { |
| SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| layer()->set_name("AssistantOverlay: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("AssistantOverlay:PAINTED_LAYER"); |
| layer()->Add(ripple_layer_.get()); |
| |
| layer()->Add(background_layer_.get()); |
| |
| layer()->Add(icon_layer_.get()); |
| } |
| |
| AssistantOverlay::~AssistantOverlay() = default; |
| |
| void AssistantOverlay::StartAnimation(bool show_icon) { |
| animation_state_ = AnimationState::STARTING; |
| 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_->GetCenterPoint(); |
| 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); |
| |
| const bool is_tablet_mode = Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| const int icon_x_offset = is_tablet_mode ? 0 : kIconOffsetDip; |
| const int icon_y_offset = |
| is_tablet_mode ? -kRippleCircleRadiusDip : -kIconOffsetDip; |
| // Setup icon animation. |
| scale_factor = kIconSizeDip / kIconInitSizeDip; |
| transform.MakeIdentity(); |
| transform.Translate(center.x() - kIconSizeDip / 2 + icon_x_offset, |
| center.y() - kIconSizeDip / 2 + icon_y_offset); |
| 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 + icon_x_offset, |
| center.y() - kBackgroundSizeDip / 2 + icon_y_offset); |
| 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 AssistantOverlay::BurstAnimation() { |
| animation_state_ = AnimationState::BURSTING; |
| |
| gfx::Point center = host_view_->GetCenterPoint(); |
| 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. |
| // 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. |
| const bool is_tablet_mode = Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| const int icon_x_offset = is_tablet_mode ? 0 : kIconOffsetDip; |
| const int icon_y_offset = |
| is_tablet_mode ? -kRippleCircleRadiusDip : -kIconOffsetDip; |
| float x_offset = center.x() - kBackgroundSizeDip / 2 + icon_x_offset; |
| float y_offset = center.y() - kBackgroundSizeDip / 2 + icon_y_offset; |
| |
| background_layer_->AnimateToLarge( |
| gfx::PointF( |
| kBackgroundLargeWidthDip / 2 + kBackgroundPaddingDip - x_offset, |
| -kBackgroundLargeHeightDip / 2 - kBackgroundPaddingDip - y_offset), |
| nullptr); |
| } |
| |
| void AssistantOverlay::WaitingAnimation() { |
| // If we are already playing burst animation, it will end up at waiting state |
| // anyway. No need to do anything. |
| if (IsBursting()) |
| return; |
| |
| animation_state_ = AnimationState::WAITING; |
| |
| gfx::Point center = host_view_->GetCenterPoint(); |
| gfx::Transform transform; |
| |
| ripple_layer_->SetOpacity(0); |
| icon_layer_->SetOpacity(0); |
| background_layer_->SetOpacity(0); |
| SetVisible(true); |
| |
| // Setup icon layer. |
| { |
| 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); |
| |
| 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); |
| |
| icon_layer_->SetOpacity(1); |
| icon_layer_->StartAnimation(); |
| } |
| |
| // Setup background layer. |
| { |
| float x_offset = center.x() - kBackgroundSizeDip / 2; |
| float y_offset = center.y() - kBackgroundSizeDip / 2; |
| |
| transform.MakeIdentity(); |
| background_layer_->SetTransform(transform); |
| background_layer_->SetToLarge(gfx::PointF( |
| kBackgroundLargeWidthDip / 2 + kBackgroundPaddingDip - x_offset, |
| -kBackgroundLargeHeightDip / 2 - kBackgroundPaddingDip - y_offset)); |
| |
| ui::ScopedLayerAnimationSettings settings(background_layer_->GetAnimator()); |
| settings.SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(kBackgroundMorphDurationMs)); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::PreemptionStrategy::ENQUEUE_NEW_ANIMATION); |
| settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN); |
| |
| background_layer_->SetOpacity(1); |
| } |
| } |
| |
| void AssistantOverlay::EndAnimation() { |
| if (IsBursting() || IsHidden()) { |
| // Too late, user action already fired, we have to finish what's started. |
| // Or the widget has already been hidden, no need to play the end animation. |
| return; |
| } |
| |
| // Play reverse animation |
| // Setup ripple animations. |
| SkMScalar scale_factor = |
| kRippleCircleStartRadiusDip / kRippleCircleInitRadiusDip; |
| gfx::Transform transform; |
| |
| const gfx::Point center = host_view_->GetCenterPoint(); |
| 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 AssistantOverlay::HideAnimation() { |
| animation_state_ = AnimationState::HIDDEN; |
| |
| // 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); |
| } |
| } |
| |
| } // namespace ash |