blob: cde9818ce5df9ae2bf4e111720d3579e832f413d [file] [log] [blame]
// Copyright 2015 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 "ui/views/animation/ink_drop_impl.h"
#include "base/auto_reset.h"
#include "base/timer/timer.h"
#include "ui/compositor/layer.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/square_ink_drop_ripple.h"
namespace views {
namespace {
// The duration, in milliseconds, of the highlight state fade in animation when
// it is triggered by user input.
const int kHighlightFadeInFromUserInputDurationInMs = 250;
// The duration, in milliseconds, of the highlight state fade out animation when
// it is triggered by user input.
const int kHighlightFadeOutFromUserInputDurationInMs = 250;
// The duration, in milliseconds, of the highlight state fade in animation when
// it is triggered by an ink drop ripple animation ending.
const int kHighlightFadeInAfterRippleDurationInMs = 250;
// The duration, in milliseconds, of the highlight state fade out animation when
// it is triggered by an ink drop ripple animation starting.
const int kHighlightFadeOutBeforeRippleDurationInMs = 120;
// The amount of time in milliseconds that |highlight_| should delay after a
// ripple animation before fading in.
const int kHighlightFadeInAfterRippleDelayInMs = 1000;
// Returns true if an ink drop with the given |ink_drop_state| should
// automatically transition to the InkDropState::HIDDEN state.
bool ShouldAnimateToHidden(InkDropState ink_drop_state) {
switch (ink_drop_state) {
case views::InkDropState::ACTION_TRIGGERED:
case views::InkDropState::ALTERNATE_ACTION_TRIGGERED:
case views::InkDropState::DEACTIVATED:
return true;
default:
return false;
}
}
} // namespace
InkDropImpl::InkDropImpl(InkDropHost* ink_drop_host)
: ink_drop_host_(ink_drop_host),
root_layer_(new ui::Layer(ui::LAYER_NOT_DRAWN)),
root_layer_added_to_host_(false),
is_hovered_(false),
is_focused_(false),
highlight_after_ripple_timer_(nullptr) {
root_layer_->set_name("InkDropImpl:RootLayer");
}
InkDropImpl::~InkDropImpl() {
// Explicitly destroy the InkDropRipple so that this still exists if
// views::InkDropRippleObserver methods are called on this.
DestroyInkDropRipple();
DestroyInkDropHighlight();
}
InkDropState InkDropImpl::GetTargetInkDropState() const {
if (!ink_drop_ripple_)
return InkDropState::HIDDEN;
return ink_drop_ripple_->target_ink_drop_state();
}
void InkDropImpl::AnimateToState(InkDropState ink_drop_state) {
DestroyHiddenTargetedAnimations();
if (!ink_drop_ripple_)
CreateInkDropRipple();
if (ink_drop_state != views::InkDropState::HIDDEN) {
SetHighlight(false, base::TimeDelta::FromMilliseconds(
kHighlightFadeOutBeforeRippleDurationInMs),
true);
}
ink_drop_ripple_->AnimateToState(ink_drop_state);
}
void InkDropImpl::SnapToActivated() {
DestroyHiddenTargetedAnimations();
if (!ink_drop_ripple_)
CreateInkDropRipple();
SetHighlight(false, base::TimeDelta(), false);
ink_drop_ripple_->SnapToActivated();
}
void InkDropImpl::SetHovered(bool is_hovered) {
is_hovered_ = is_hovered;
SetHighlight(ShouldHighlight(),
ShouldHighlight()
? base::TimeDelta::FromMilliseconds(
kHighlightFadeInFromUserInputDurationInMs)
: base::TimeDelta::FromMilliseconds(
kHighlightFadeOutFromUserInputDurationInMs),
false);
}
void InkDropImpl::SetFocused(bool is_focused) {
is_focused_ = is_focused;
SetHighlight(ShouldHighlight(), base::TimeDelta(), false);
}
void InkDropImpl::DestroyHiddenTargetedAnimations() {
if (ink_drop_ripple_ &&
(ink_drop_ripple_->target_ink_drop_state() == InkDropState::HIDDEN ||
ShouldAnimateToHidden(ink_drop_ripple_->target_ink_drop_state()))) {
DestroyInkDropRipple();
}
}
void InkDropImpl::CreateInkDropRipple() {
DestroyInkDropRipple();
ink_drop_ripple_ = ink_drop_host_->CreateInkDropRipple();
ink_drop_ripple_->set_observer(this);
root_layer_->Add(ink_drop_ripple_->GetRootLayer());
AddRootLayerToHostIfNeeded();
}
void InkDropImpl::DestroyInkDropRipple() {
if (!ink_drop_ripple_)
return;
root_layer_->Remove(ink_drop_ripple_->GetRootLayer());
ink_drop_ripple_.reset();
RemoveRootLayerFromHostIfNeeded();
}
void InkDropImpl::CreateInkDropHighlight() {
DestroyInkDropHighlight();
highlight_ = ink_drop_host_->CreateInkDropHighlight();
if (!highlight_)
return;
highlight_->set_observer(this);
root_layer_->Add(highlight_->layer());
AddRootLayerToHostIfNeeded();
}
void InkDropImpl::DestroyInkDropHighlight() {
if (!highlight_)
return;
root_layer_->Remove(highlight_->layer());
highlight_->set_observer(nullptr);
highlight_.reset();
RemoveRootLayerFromHostIfNeeded();
}
void InkDropImpl::AddRootLayerToHostIfNeeded() {
DCHECK(highlight_ || ink_drop_ripple_);
if (!root_layer_added_to_host_) {
root_layer_added_to_host_ = true;
ink_drop_host_->AddInkDropLayer(root_layer_.get());
}
}
void InkDropImpl::RemoveRootLayerFromHostIfNeeded() {
if (root_layer_added_to_host_ && !highlight_ && !ink_drop_ripple_) {
root_layer_added_to_host_ = false;
ink_drop_host_->RemoveInkDropLayer(root_layer_.get());
}
}
bool InkDropImpl::IsHighlightFadingInOrVisible() const {
return highlight_ && highlight_->IsFadingInOrVisible();
}
// -----------------------------------------------------------------------------
// views::InkDropRippleObserver:
void InkDropImpl::AnimationStarted(InkDropState ink_drop_state) {}
void InkDropImpl::AnimationEnded(InkDropState ink_drop_state,
InkDropAnimationEndedReason reason) {
if (reason != InkDropAnimationEndedReason::SUCCESS)
return;
if (ShouldAnimateToHidden(ink_drop_state)) {
ink_drop_ripple_->AnimateToState(views::InkDropState::HIDDEN);
} else if (ink_drop_state == views::InkDropState::HIDDEN) {
if (is_hovered_)
StartHighlightAfterRippleTimer();
// TODO(bruthig): Investigate whether creating and destroying
// InkDropRipples is expensive and consider creating an
// InkDropRipplePool. See www.crbug.com/522175.
DestroyInkDropRipple();
}
}
// -----------------------------------------------------------------------------
// views::InkDropHighlightObserver:
void InkDropImpl::AnimationStarted(
InkDropHighlight::AnimationType animation_type) {}
void InkDropImpl::AnimationEnded(InkDropHighlight::AnimationType animation_type,
InkDropAnimationEndedReason reason) {
if (animation_type == InkDropHighlight::FADE_OUT &&
reason == InkDropAnimationEndedReason::SUCCESS) {
DestroyInkDropHighlight();
}
}
void InkDropImpl::SetHighlight(bool should_highlight,
base::TimeDelta animation_duration,
bool explode) {
StopHighlightAfterRippleTimer();
if (IsHighlightFadingInOrVisible() == should_highlight)
return;
if (should_highlight) {
CreateInkDropHighlight();
if (highlight_ && !(ink_drop_ripple_ && ink_drop_ripple_->IsVisible()))
highlight_->FadeIn(animation_duration);
} else {
highlight_->FadeOut(animation_duration, explode);
}
}
bool InkDropImpl::ShouldHighlight() const {
return is_focused_ || is_hovered_;
}
void InkDropImpl::StartHighlightAfterRippleTimer() {
StopHighlightAfterRippleTimer();
if (!highlight_after_ripple_timer_)
highlight_after_ripple_timer_.reset(new base::OneShotTimer);
highlight_after_ripple_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kHighlightFadeInAfterRippleDelayInMs),
base::Bind(&InkDropImpl::HighlightAfterRippleTimerFired,
base::Unretained(this)));
}
void InkDropImpl::StopHighlightAfterRippleTimer() {
if (highlight_after_ripple_timer_)
highlight_after_ripple_timer_.reset();
}
void InkDropImpl::HighlightAfterRippleTimerFired() {
SetHighlight(true, base::TimeDelta::FromMilliseconds(
kHighlightFadeInAfterRippleDurationInMs),
true);
highlight_after_ripple_timer_.reset();
}
} // namespace views