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_controller.h"
7 #include "base/command_line.h"
8 #include "ui/base/ui_base_switches.h"
9 #include "ui/compositor/layer.h"
10 #include "ui/compositor/layer_animation_observer.h"
11 #include "ui/compositor/layer_animation_sequence.h"
12 #include "ui/compositor/paint_recorder.h"
13 #include "ui/compositor/scoped_layer_animation_settings.h"
14 #include "ui/events/event.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/views/view.h"
20 // Animation constants
21 const float kMinimumScale
= 0.1f
;
22 const float kMinimumScaleCenteringOffset
= 0.5f
- kMinimumScale
/ 2.0f
;
24 const int kHideAnimationDurationFastMs
= 100;
25 const int kHideAnimationDurationSlowMs
= 1000;
27 const int kShowInkDropAnimationDurationFastMs
= 250;
28 const int kShowInkDropAnimationDurationSlowMs
= 750;
30 const int kShowLongPressAnimationDurationFastMs
= 250;
31 const int kShowLongPressAnimationDurationSlowMs
= 2500;
33 const int kRoundedRectCorners
= 5;
34 const int kCircleRadius
= 30;
36 const SkColor kInkDropColor
= SK_ColorLTGRAY
;
37 const SkColor kLongPressColor
= SkColorSetRGB(182, 182, 182);
39 // Checks CommandLine switches to determine if the visual feedback should be
41 bool UseCircularFeedback() {
42 static bool circular
=
43 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
44 (::switches::kMaterialDesignInkDrop
)) !=
45 ::switches::kMaterialDesignInkDropSquare
;
49 // Checks CommandLine switches to determine if the visual feedback should have
50 // a fast animations speed.
51 bool UseFastAnimations() {
53 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
54 (::switches::kMaterialDesignInkDropAnimationSpeed
)) !=
55 ::switches::kMaterialDesignInkDropAnimationSpeedSlow
;
63 // An animation observer that should be set on animations of the provided
64 // ui::Layer. Can be used to either start a hide animation, or to trigger one
65 // upon completion of the current animation.
67 // Sequential animations with PreemptionStrategy::ENQUEUE_NEW_ANIMATION cannot
68 // be used as the observed animation can complete before user input is received
69 // which determines if the hide animation should run.
70 class AppearAnimationObserver
: public ui::LayerAnimationObserver
{
72 // Will automatically start a hide animation of |layer| if |hide| is true.
73 // Otherwise StartHideAnimation() or HideNowIfDoneOrOnceCompleted() must be
75 AppearAnimationObserver(ui::Layer
* layer
, bool hide
);
76 ~AppearAnimationObserver() override
;
78 // Returns true during both the appearing animation, and the hiding animation.
79 bool IsAnimationActive();
81 // Starts a hide animation, preempting any current animations on |layer_|.
82 void StartHideAnimation();
84 // Starts a hide animation if |layer_| is no longer animating. Otherwise the
85 // hide animation will be started once the current animation is completed.
86 void HideNowIfDoneOrOnceCompleted();
88 // Hides |background_layer| (without animation) after the current animation
90 void SetBackgroundToHide(ui::Layer
* background_layer
);
93 // ui::ImplicitAnimationObserver:
94 void OnLayerAnimationEnded(ui::LayerAnimationSequence
* sequence
) override
;
95 void OnLayerAnimationAborted(ui::LayerAnimationSequence
* sequence
) override
;
96 void OnLayerAnimationScheduled(
97 ui::LayerAnimationSequence
* sequence
) override
{}
99 bool RequiresNotificationWhenAnimatorDestroyed() const override
;
101 // The ui::Layer being observed, which hide animations will be set on.
104 // Optional ui::Layer which will be hidden upon the completion of animating
106 ui::Layer
* background_layer_
;
108 // If true the hide animation will immediately be scheduled upon completion of
109 // the observed animation.
112 DISALLOW_COPY_AND_ASSIGN(AppearAnimationObserver
);
115 AppearAnimationObserver::AppearAnimationObserver(ui::Layer
* layer
, bool hide
)
116 : layer_(layer
), background_layer_(nullptr), hide_(hide
) {
119 AppearAnimationObserver::~AppearAnimationObserver() {
123 bool AppearAnimationObserver::IsAnimationActive() {
124 // Initial animation ongoing
125 if (!attached_sequences().empty())
127 // Maintain the animation until told to hide.
131 // Check the state of the triggered hide animation
132 return layer_
->GetAnimator()->IsAnimatingProperty(
133 ui::LayerAnimationElement::OPACITY
) &&
134 layer_
->GetTargetOpacity() == 0.0f
&&
135 layer_
->GetAnimator()->IsAnimatingProperty(
136 ui::LayerAnimationElement::VISIBILITY
) &&
137 !layer_
->GetTargetVisibility();
140 void AppearAnimationObserver::StartHideAnimation() {
141 if (background_layer_
)
142 background_layer_
->SetVisible(false);
143 if (!layer_
->GetTargetVisibility())
146 ui::ScopedLayerAnimationSettings
animation(layer_
->GetAnimator());
147 animation
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
148 UseFastAnimations() ? kHideAnimationDurationFastMs
149 : kHideAnimationDurationSlowMs
));
150 animation
.SetPreemptionStrategy(
151 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
152 layer_
->SetOpacity(0.0f
);
153 layer_
->SetVisible(false);
156 void AppearAnimationObserver::HideNowIfDoneOrOnceCompleted() {
158 if (attached_sequences().empty())
159 StartHideAnimation();
162 void AppearAnimationObserver::SetBackgroundToHide(ui::Layer
* background_layer
) {
163 background_layer_
= background_layer
;
166 void AppearAnimationObserver::OnLayerAnimationEnded(
167 ui::LayerAnimationSequence
* sequence
) {
169 StartHideAnimation();
172 void AppearAnimationObserver::OnLayerAnimationAborted(
173 ui::LayerAnimationSequence
* sequence
) {
175 StartHideAnimation();
178 bool AppearAnimationObserver::RequiresNotificationWhenAnimatorDestroyed()
180 // Ensures that OnImplicitAnimationsCompleted is called even if the observed
181 // animation is deleted. Allows for setting the proper state on |layer_|.
185 // Renders the visual feedback. Will render a circle if |circle_| is true,
186 // otherwise renders a rounded rectangle.
187 class InkDropDelegate
: public ui::LayerDelegate
{
189 InkDropDelegate(ui::Layer
* layer
, SkColor color
);
190 ~InkDropDelegate() override
;
192 // Sets the visual style of the feedback.
193 void set_should_render_circle(bool should_render_circle
) {
194 should_render_circle_
= should_render_circle
;
197 // ui::LayerDelegate:
198 void OnPaintLayer(const ui::PaintContext
& context
) override
;
199 void OnDelegatedFrameDamage(const gfx::Rect
& damage_rect_in_dip
) override
;
200 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
;
201 base::Closure
PrepareForLayerBoundsChange() override
;
204 // The ui::Layer being rendered to.
207 // The color to paint.
210 // When true renders a circle, otherwise renders a rounded rectangle.
211 bool should_render_circle_
;
213 DISALLOW_COPY_AND_ASSIGN(InkDropDelegate
);
216 InkDropDelegate::InkDropDelegate(ui::Layer
* layer
, SkColor color
)
217 : layer_(layer
), color_(color
), should_render_circle_(true) {
220 InkDropDelegate::~InkDropDelegate() {
223 void InkDropDelegate::OnPaintLayer(const ui::PaintContext
& context
) {
225 paint
.setColor(color_
);
226 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
227 paint
.setStyle(SkPaint::kFill_Style
);
229 gfx::Rect bounds
= layer_
->bounds();
231 ui::PaintRecorder
recorder(context
, layer_
->size());
232 gfx::Canvas
* canvas
= recorder
.canvas();
233 if (should_render_circle_
) {
234 gfx::Point
midpoint(bounds
.width() * 0.5f
, bounds
.height() * 0.5f
);
235 canvas
->DrawCircle(midpoint
, kCircleRadius
, paint
);
237 canvas
->DrawRoundRect(bounds
, kRoundedRectCorners
, paint
);
241 void InkDropDelegate::OnDelegatedFrameDamage(
242 const gfx::Rect
& damage_rect_in_dip
) {
245 void InkDropDelegate::OnDeviceScaleFactorChanged(float device_scale_factor
) {
248 base::Closure
InkDropDelegate::PrepareForLayerBoundsChange() {
249 return base::Closure();
252 InkDropAnimationController::InkDropAnimationController(views::View
* view
)
253 : ink_drop_layer_(new ui::Layer()),
255 new InkDropDelegate(ink_drop_layer_
.get(), kInkDropColor
)),
256 appear_animation_observer_(nullptr),
257 long_press_layer_(new ui::Layer()),
258 long_press_delegate_(
259 new InkDropDelegate(long_press_layer_
.get(), kLongPressColor
)),
260 long_press_animation_observer_(nullptr),
262 view_
->SetPaintToLayer(true);
263 view_
->AddPreTargetHandler(this);
264 ui::Layer
* layer
= view_
->layer();
265 layer
->SetMasksToBounds(!UseCircularFeedback());
266 SetupAnimationLayer(layer
, long_press_layer_
.get(),
267 long_press_delegate_
.get());
268 SetupAnimationLayer(layer
, ink_drop_layer_
.get(), ink_drop_delegate_
.get());
269 ink_drop_delegate_
->set_should_render_circle(UseCircularFeedback());
272 InkDropAnimationController::~InkDropAnimationController() {
273 view_
->RemovePreTargetHandler(this);
276 void InkDropAnimationController::AnimateTapDown() {
277 if ((appear_animation_observer_
&&
278 appear_animation_observer_
->IsAnimationActive()) ||
279 (long_press_animation_observer_
&&
280 long_press_animation_observer_
->IsAnimationActive())) {
281 // Only one animation at a time. Subsequent tap downs are ignored until the
282 // current animation completes.
285 appear_animation_observer_
.reset(
286 new AppearAnimationObserver(ink_drop_layer_
.get(), false));
287 AnimateShow(ink_drop_layer_
.get(), appear_animation_observer_
.get(),
288 UseCircularFeedback(),
289 base::TimeDelta::FromMilliseconds(
290 (UseFastAnimations() ? kShowInkDropAnimationDurationFastMs
291 : kShowInkDropAnimationDurationSlowMs
)));
294 void InkDropAnimationController::AnimateHide() {
295 if (appear_animation_observer_
)
296 appear_animation_observer_
->HideNowIfDoneOrOnceCompleted();
299 void InkDropAnimationController::AnimateLongPress() {
300 // Only one animation at a time. Subsequent long presses are ignored until the
301 // current animation completes.
302 if (long_press_animation_observer_
&&
303 long_press_animation_observer_
->IsAnimationActive()) {
306 appear_animation_observer_
.reset();
307 long_press_animation_observer_
.reset(
308 new AppearAnimationObserver(long_press_layer_
.get(), true));
309 long_press_animation_observer_
->SetBackgroundToHide(ink_drop_layer_
.get());
310 AnimateShow(long_press_layer_
.get(), long_press_animation_observer_
.get(),
312 base::TimeDelta::FromMilliseconds(
313 UseFastAnimations() ? kShowLongPressAnimationDurationFastMs
314 : kShowLongPressAnimationDurationSlowMs
));
317 void InkDropAnimationController::AnimateShow(ui::Layer
* layer
,
318 AppearAnimationObserver
* observer
,
320 base::TimeDelta duration
) {
321 SetLayerBounds(layer
, circle
, view_
->width(), view_
->height());
322 layer
->SetVisible(true);
323 layer
->SetOpacity(1.0f
);
325 float start_x
= layer
->bounds().width() * kMinimumScaleCenteringOffset
;
326 float start_y
= layer
->bounds().height() * kMinimumScaleCenteringOffset
;
328 gfx::Transform initial_transform
;
329 initial_transform
.Translate(start_x
, start_y
);
330 initial_transform
.Scale(kMinimumScale
, kMinimumScale
);
331 layer
->SetTransform(initial_transform
);
333 ui::LayerAnimator
* animator
= layer
->GetAnimator();
334 ui::ScopedLayerAnimationSettings
animation(animator
);
335 animation
.SetPreemptionStrategy(
336 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
338 gfx::Transform identity_transform
;
339 ui::LayerAnimationElement
* element
=
340 ui::LayerAnimationElement::CreateTransformElement(identity_transform
,
342 ui::LayerAnimationSequence
* sequence
=
343 new ui::LayerAnimationSequence(element
);
344 sequence
->AddObserver(observer
);
345 animator
->StartAnimation(sequence
);
348 void InkDropAnimationController::SetLayerBounds(ui::Layer
* layer
,
352 float circle_width
= circle
? 2.0f
* kCircleRadius
: width
;
353 float circle_height
= circle
? 2.0f
* kCircleRadius
: height
;
354 float circle_x
= circle
? (width
- circle_width
) * 0.5f
: 0;
355 float circle_y
= circle
? (height
- circle_height
) * 0.5f
: 0;
356 layer
->SetBounds(gfx::Rect(circle_x
, circle_y
, circle_width
, circle_height
));
359 void InkDropAnimationController::SetupAnimationLayer(
362 InkDropDelegate
* delegate
) {
363 layer
->SetFillsBoundsOpaquely(false);
364 layer
->set_delegate(delegate
);
365 layer
->SetVisible(false);
366 layer
->SetBounds(gfx::Rect());
368 parent
->StackAtBottom(layer
);
371 void InkDropAnimationController::OnGestureEvent(ui::GestureEvent
* event
) {
372 if (event
->target() != view_
)
375 switch (event
->type()) {
376 case ui::ET_GESTURE_TAP_DOWN
:
379 case ui::ET_GESTURE_LONG_PRESS
:
382 case ui::ET_GESTURE_END
:
383 case ui::ET_GESTURE_TAP_CANCEL
:
384 case ui::ET_GESTURE_TAP
: