1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/animation/ink_drop_animation.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "third_party/skia/include/core/SkColor.h"
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "ui/base/ui_base_switches.h"
14 #include "ui/compositor/layer.h"
15 #include "ui/compositor/layer_animation_observer.h"
16 #include "ui/compositor/layer_animation_sequence.h"
17 #include "ui/compositor/paint_recorder.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/transform_util.h"
21 #include "ui/views/view.h"
25 // The minimum scale factor to use when scaling rectangle layers. Smaller values
26 // were causing visual anomalies.
27 const float kMinimumRectScale
= 0.0001f
;
29 // The minimum scale factor to use when scaling circle layers. Smaller values
30 // were causing visual anomalies.
31 const float kMinimumCircleScale
= 0.001f
;
33 // The ink drop color.
34 const SkColor kInkDropColor
= SK_ColorBLACK
;
36 // The opacity of the ink drop when it is visible.
37 const float kVisibleOpacity
= 0.12f
;
39 // The opacity of the ink drop when it is not visible.
40 const float kHiddenOpacity
= 0.0f
;
42 // Durations for the different InkDropState animations in milliseconds.
43 const int kHiddenStateAnimationDurationMs
= 1;
44 const int kActionPendingStateAnimationDurationMs
= 500;
45 const int kQuickActionStateAnimationDurationMs
= 250;
46 const int kSlowActionPendingStateAnimationDurationMs
= 500;
47 const int kSlowActionStateAnimationDurationMs
= 250;
48 const int kActivatedStateAnimationDurationMs
= 250;
49 const int kDeactivatedStateAnimationDurationMs
= 250;
51 // A multiplicative factor used to slow down InkDropState animations.
52 const int kSlowAnimationDurationFactor
= 3;
54 // Checks CommandLine switches to determine if the visual feedback should have
55 // a fast animations speed.
56 bool UseFastAnimations() {
58 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
59 (::switches::kMaterialDesignInkDropAnimationSpeed
)) !=
60 ::switches::kMaterialDesignInkDropAnimationSpeedSlow
;
64 // Returns the InkDropState animation duration for the given |state|.
65 base::TimeDelta
GetAnimationDuration(views::InkDropState state
) {
68 case views::InkDropState::HIDDEN
:
69 duration
= kHiddenStateAnimationDurationMs
;
71 case views::InkDropState::ACTION_PENDING
:
72 duration
= kActionPendingStateAnimationDurationMs
;
74 case views::InkDropState::QUICK_ACTION
:
75 duration
= kQuickActionStateAnimationDurationMs
;
77 case views::InkDropState::SLOW_ACTION_PENDING
:
78 duration
= kSlowActionPendingStateAnimationDurationMs
;
80 case views::InkDropState::SLOW_ACTION
:
81 duration
= kSlowActionStateAnimationDurationMs
;
83 case views::InkDropState::ACTIVATED
:
84 duration
= kActivatedStateAnimationDurationMs
;
86 case views::InkDropState::DEACTIVATED
:
87 duration
= kDeactivatedStateAnimationDurationMs
;
91 return base::TimeDelta::FromMilliseconds(
92 (UseFastAnimations() ? 1 : kSlowAnimationDurationFactor
) * duration
);
95 // Calculates a Transform for a circle layer. The transform will be set up to
96 // translate the |drawn_center_point| to the origin, scale, and then translate
97 // to the target point defined by |target_center_x| and |target_center_y|.
98 gfx::Transform
CalculateCircleTransform(const gfx::Point
& drawn_center_point
,
100 float target_center_x
,
101 float target_center_y
) {
102 gfx::Transform transform
;
103 transform
.Translate(target_center_x
, target_center_y
);
104 transform
.Scale(scale
, scale
);
105 transform
.Translate(-drawn_center_point
.x(), -drawn_center_point
.y());
109 // Calculates a Transform for a rectangle layer. The transform will be set up to
110 // translate the |drawn_center_point| to the origin and then scale by the
111 // |x_scale| and |y_scale| factors.
112 gfx::Transform
CalculateRectTransform(const gfx::Point
& drawn_center_point
,
115 gfx::Transform transform
;
116 transform
.Scale(x_scale
, y_scale
);
117 transform
.Translate(-drawn_center_point
.x(), -drawn_center_point
.y());
125 // Base ui::LayerDelegate stub that can be extended to paint shapes of a
127 class BasePaintedLayerDelegate
: public ui::LayerDelegate
{
129 ~BasePaintedLayerDelegate() override
;
131 SkColor
color() const { return color_
; }
133 // ui::LayerDelegate:
134 void OnDelegatedFrameDamage(const gfx::Rect
& damage_rect_in_dip
) override
;
135 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
;
136 base::Closure
PrepareForLayerBoundsChange() override
;
139 explicit BasePaintedLayerDelegate(SkColor color
);
142 // The color to paint.
145 DISALLOW_COPY_AND_ASSIGN(BasePaintedLayerDelegate
);
148 BasePaintedLayerDelegate::BasePaintedLayerDelegate(SkColor color
)
151 BasePaintedLayerDelegate::~BasePaintedLayerDelegate() {}
153 void BasePaintedLayerDelegate::OnDelegatedFrameDamage(
154 const gfx::Rect
& damage_rect_in_dip
) {}
156 void BasePaintedLayerDelegate::OnDeviceScaleFactorChanged(
157 float device_scale_factor
) {}
159 base::Closure
BasePaintedLayerDelegate::PrepareForLayerBoundsChange() {
160 return base::Closure();
163 // A BasePaintedLayerDelegate that paints a circle of a specified color and
165 class CircleLayerDelegate
: public BasePaintedLayerDelegate
{
167 CircleLayerDelegate(SkColor color
, int radius
);
168 ~CircleLayerDelegate() override
;
170 int radius() const { return radius_
; }
172 // ui::LayerDelegate:
173 void OnPaintLayer(const ui::PaintContext
& context
) override
;
176 // The radius of the circle.
179 DISALLOW_COPY_AND_ASSIGN(CircleLayerDelegate
);
182 CircleLayerDelegate::CircleLayerDelegate(SkColor color
, int radius
)
183 : BasePaintedLayerDelegate(color
), radius_(radius
) {}
185 CircleLayerDelegate::~CircleLayerDelegate() {}
187 void CircleLayerDelegate::OnPaintLayer(const ui::PaintContext
& context
) {
189 paint
.setColor(color());
190 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
191 paint
.setStyle(SkPaint::kFill_Style
);
193 ui::PaintRecorder
recorder(context
, gfx::Size(radius_
, radius_
));
194 gfx::Canvas
* canvas
= recorder
.canvas();
196 gfx::Point center_point
= gfx::Point(radius_
, radius_
);
197 canvas
->DrawCircle(center_point
, radius_
, paint
);
200 // A BasePaintedLayerDelegate that paints a rectangle of a specified color and
202 class RectangleLayerDelegate
: public BasePaintedLayerDelegate
{
204 RectangleLayerDelegate(SkColor color
, gfx::Size size
);
205 ~RectangleLayerDelegate() override
;
207 const gfx::Size
& size() const { return size_
; }
209 // ui::LayerDelegate:
210 void OnPaintLayer(const ui::PaintContext
& context
) override
;
213 // The size of the rectangle.
216 DISALLOW_COPY_AND_ASSIGN(RectangleLayerDelegate
);
219 RectangleLayerDelegate::RectangleLayerDelegate(SkColor color
, gfx::Size size
)
220 : BasePaintedLayerDelegate(color
), size_(size
) {}
222 RectangleLayerDelegate::~RectangleLayerDelegate() {}
224 void RectangleLayerDelegate::OnPaintLayer(const ui::PaintContext
& context
) {
226 paint
.setColor(color());
227 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
228 paint
.setStyle(SkPaint::kFill_Style
);
230 ui::PaintRecorder
recorder(context
, size_
);
231 gfx::Canvas
* canvas
= recorder
.canvas();
232 canvas
->DrawRect(gfx::Rect(size_
), paint
);
235 InkDropAnimation::InkDropAnimation(const gfx::Size
& large_size
,
236 int large_corner_radius
,
237 const gfx::Size
& small_size
,
238 int small_corner_radius
)
239 : large_size_(large_size
),
240 large_corner_radius_(large_corner_radius
),
241 small_size_(small_size
),
242 small_corner_radius_(small_corner_radius
),
243 circle_layer_delegate_(new CircleLayerDelegate(
245 std::min(large_size_
.width(), large_size_
.height()) / 2)),
246 rect_layer_delegate_(
247 new RectangleLayerDelegate(kInkDropColor
, large_size_
)),
248 root_layer_(new ui::Layer(ui::LAYER_NOT_DRAWN
)),
249 ink_drop_state_(InkDropState::HIDDEN
) {
250 for (int i
= 0; i
< PAINTED_SHAPE_COUNT
; ++i
)
251 AddPaintLayer(static_cast<PaintedShape
>(i
));
253 root_layer_
->SetMasksToBounds(false);
254 root_layer_
->SetBounds(gfx::Rect(large_size_
));
256 ResetTransformsToMinSize();
258 SetOpacity(kHiddenOpacity
);
261 InkDropAnimation::~InkDropAnimation() {}
263 void InkDropAnimation::AnimateToState(InkDropState ink_drop_state
) {
264 if (ink_drop_state_
== ink_drop_state
)
267 if (ink_drop_state_
== InkDropState::HIDDEN
) {
268 ResetTransformsToMinSize();
269 SetOpacity(kVisibleOpacity
);
272 InkDropTransforms transforms
;
274 // Must set the |ink_drop_state_| before handling the state change because
275 // some state changes make recursive calls to AnimateToState() and the last
276 // call should 'win'.
277 ink_drop_state_
= ink_drop_state
;
279 switch (ink_drop_state_
) {
280 case InkDropState::HIDDEN
:
281 GetCurrentTansforms(&transforms
);
282 AnimateToTransforms(transforms
, kHiddenOpacity
,
283 GetAnimationDuration(InkDropState::HIDDEN
),
284 ui::LayerAnimator::ENQUEUE_NEW_ANIMATION
);
286 case InkDropState::ACTION_PENDING
:
287 CalculateCircleTransforms(large_size_
, &transforms
);
288 AnimateToTransforms(transforms
, kVisibleOpacity
,
289 GetAnimationDuration(InkDropState::ACTION_PENDING
),
290 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
292 case InkDropState::QUICK_ACTION
:
293 CalculateCircleTransforms(large_size_
, &transforms
);
294 AnimateToTransforms(transforms
, kHiddenOpacity
,
295 GetAnimationDuration(InkDropState::QUICK_ACTION
),
296 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
297 AnimateToState(InkDropState::HIDDEN
);
299 case InkDropState::SLOW_ACTION_PENDING
:
300 CalculateRectTransforms(small_size_
, small_corner_radius_
, &transforms
);
302 transforms
, kVisibleOpacity
,
303 GetAnimationDuration(InkDropState::SLOW_ACTION_PENDING
),
304 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
306 case InkDropState::SLOW_ACTION
:
307 CalculateRectTransforms(large_size_
, large_corner_radius_
, &transforms
);
308 AnimateToTransforms(transforms
, kHiddenOpacity
,
309 GetAnimationDuration(InkDropState::SLOW_ACTION
),
310 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
311 AnimateToState(InkDropState::HIDDEN
);
313 case InkDropState::ACTIVATED
:
314 CalculateRectTransforms(small_size_
, small_corner_radius_
, &transforms
);
315 AnimateToTransforms(transforms
, kVisibleOpacity
,
316 GetAnimationDuration(InkDropState::ACTIVATED
),
317 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
319 case InkDropState::DEACTIVATED
:
320 CalculateRectTransforms(large_size_
, large_corner_radius_
, &transforms
);
321 AnimateToTransforms(transforms
, kHiddenOpacity
,
322 GetAnimationDuration(InkDropState::DEACTIVATED
),
323 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
324 AnimateToState(InkDropState::HIDDEN
);
329 void InkDropAnimation::AnimateToTransforms(
330 const InkDropTransforms transforms
,
332 base::TimeDelta duration
,
333 ui::LayerAnimator::PreemptionStrategy preemption_strategy
) {
334 ui::LayerAnimator
* root_animator
= root_layer_
->GetAnimator();
335 ui::ScopedLayerAnimationSettings
root_animation(root_animator
);
336 root_animation
.SetPreemptionStrategy(preemption_strategy
);
337 ui::LayerAnimationElement
* root_element
=
338 ui::LayerAnimationElement::CreateOpacityElement(opacity
, duration
);
339 ui::LayerAnimationSequence
* root_sequence
=
340 new ui::LayerAnimationSequence(root_element
);
341 root_animator
->StartAnimation(root_sequence
);
343 for (int i
= 0; i
< PAINTED_SHAPE_COUNT
; ++i
) {
344 ui::LayerAnimator
* animator
= painted_layers_
[i
]->GetAnimator();
345 ui::ScopedLayerAnimationSettings
animation(animator
);
346 animation
.SetPreemptionStrategy(preemption_strategy
);
347 ui::LayerAnimationElement
* element
=
348 ui::LayerAnimationElement::CreateTransformElement(transforms
[i
],
350 ui::LayerAnimationSequence
* sequence
=
351 new ui::LayerAnimationSequence(element
);
352 animator
->StartAnimation(sequence
);
356 void InkDropAnimation::ResetTransformsToMinSize() {
357 InkDropTransforms transforms
;
358 // Using a size of 0x0 creates visual anomalies.
359 CalculateCircleTransforms(gfx::Size(1, 1), &transforms
);
360 SetTransforms(transforms
);
363 void InkDropAnimation::SetTransforms(const InkDropTransforms transforms
) {
364 for (int i
= 0; i
< PAINTED_SHAPE_COUNT
; ++i
)
365 painted_layers_
[i
]->SetTransform(transforms
[i
]);
368 void InkDropAnimation::SetOpacity(float opacity
) {
369 root_layer_
->SetOpacity(opacity
);
372 void InkDropAnimation::CalculateCircleTransforms(
373 const gfx::SizeF
& size
,
374 InkDropTransforms
* transforms_out
) const {
375 CalculateRectTransforms(size
, std::min(size
.width(), size
.height()) / 2.0f
,
379 void InkDropAnimation::CalculateRectTransforms(
380 const gfx::SizeF
& size
,
382 InkDropTransforms
* transforms_out
) const {
383 DCHECK_GE(size
.width() / 2.0f
, corner_radius
)
384 << "The circle's diameter should not be greater than the total width.";
385 DCHECK_GE(size
.height() / 2.0f
, corner_radius
)
386 << "The circle's diameter should not be greater than the total height.";
388 // The shapes are drawn such that their center points are not at the origin.
389 // Thus we use the CalculateCircleTransform() and CalculateRectTransform()
390 // methods to calculate the complex Transforms.
392 const float circle_scale
= std::max(
394 corner_radius
/ static_cast<float>(circle_layer_delegate_
->radius()));
396 const float circle_target_x_offset
= size
.width() / 2.0f
- corner_radius
;
397 const float circle_target_y_offset
= size
.height() / 2.0f
- corner_radius
;
399 (*transforms_out
)[TOP_LEFT_CIRCLE
] = CalculateCircleTransform(
400 painted_layers_
[TOP_LEFT_CIRCLE
]->bounds().CenterPoint(), circle_scale
,
401 -circle_target_x_offset
, -circle_target_y_offset
);
403 (*transforms_out
)[TOP_RIGHT_CIRCLE
] = CalculateCircleTransform(
404 painted_layers_
[TOP_RIGHT_CIRCLE
]->bounds().CenterPoint(), circle_scale
,
405 circle_target_x_offset
, -circle_target_y_offset
);
407 (*transforms_out
)[BOTTOM_RIGHT_CIRCLE
] = CalculateCircleTransform(
408 painted_layers_
[BOTTOM_RIGHT_CIRCLE
]->bounds().CenterPoint(),
409 circle_scale
, circle_target_x_offset
, circle_target_y_offset
);
411 (*transforms_out
)[BOTTOM_LEFT_CIRCLE
] = CalculateCircleTransform(
412 painted_layers_
[BOTTOM_LEFT_CIRCLE
]->bounds().CenterPoint(), circle_scale
,
413 -circle_target_x_offset
, circle_target_y_offset
);
415 const float rect_delegate_width
=
416 static_cast<float>(rect_layer_delegate_
->size().width());
417 const float rect_delegate_height
=
418 static_cast<float>(rect_layer_delegate_
->size().height());
420 (*transforms_out
)[HORIZONTAL_RECT
] = CalculateRectTransform(
421 painted_layers_
[HORIZONTAL_RECT
]->bounds().CenterPoint(),
422 std::max(kMinimumRectScale
, size
.width() / rect_delegate_width
),
423 std::max(kMinimumRectScale
,
424 (size
.height() - 2.0f
* corner_radius
) / rect_delegate_height
));
426 (*transforms_out
)[VERTICAL_RECT
] = CalculateRectTransform(
427 painted_layers_
[VERTICAL_RECT
]->bounds().CenterPoint(),
428 std::max(kMinimumRectScale
,
429 (size
.width() - 2.0f
* corner_radius
) / rect_delegate_width
),
430 std::max(kMinimumRectScale
, size
.height() / rect_delegate_height
));
433 void InkDropAnimation::GetCurrentTansforms(
434 InkDropTransforms
* transforms_out
) const {
435 for (int i
= 0; i
< PAINTED_SHAPE_COUNT
; ++i
)
436 (*transforms_out
)[i
] = painted_layers_
[i
]->GetTargetTransform();
439 void InkDropAnimation::SetCenterPoint(const gfx::Point
& center_point
) {
440 gfx::Transform transform
;
441 transform
.Translate(center_point
.x(), center_point
.y());
442 root_layer_
->SetTransform(transform
);
445 void InkDropAnimation::AddPaintLayer(PaintedShape painted_shape
) {
446 ui::LayerDelegate
* delegate
= nullptr;
447 switch (painted_shape
) {
448 case TOP_LEFT_CIRCLE
:
449 case TOP_RIGHT_CIRCLE
:
450 case BOTTOM_RIGHT_CIRCLE
:
451 case BOTTOM_LEFT_CIRCLE
:
452 delegate
= circle_layer_delegate_
.get();
454 case HORIZONTAL_RECT
:
456 delegate
= rect_layer_delegate_
.get();
458 case PAINTED_SHAPE_COUNT
:
459 NOTREACHED() << "PAINTED_SHAPE_COUNT is not an actual shape type.";
463 ui::Layer
* layer
= new ui::Layer();
464 root_layer_
->Add(layer
);
466 layer
->SetBounds(gfx::Rect(large_size_
));
467 layer
->SetFillsBoundsOpaquely(false);
468 layer
->set_delegate(delegate
);
469 layer
->SetVisible(true);
470 layer
->SetOpacity(1.0);
471 layer
->SetMasksToBounds(false);
473 painted_layers_
[painted_shape
].reset(layer
);