Commit c657e3be authored by bruthig's avatar bruthig Committed by Commit bot

Added material design mouse hover feedback support.

Overview of changes:
- Added InkDropHover class that manages animating a Layer to show
  visual feedback for mouse hover.
- Wired the InkDropHover class in to the InkDropAnimationControllerImpl.
- Wired some Views up to use the hover animation.

Overview of Hover behavior:
- Hover will fade in when mouse enters an enabled View
- Hover will fade out instantly if an ink drop animation is initiated.
- Hover will fade back in, after a delay, when an ink drop animation
  completes and the mouse is still hovering the View.

TEST=InkDropHoverTest.InitialStateAfterConstruction
TEST=InkDropHoverTest.IsHoveredStateTransitions
TEST=InkDropAnimationControllerFactoryTest.HoveredStateAfterAnimateToState
TEST=HoveredStateAfterHoverTimerFiresWhenHostIsHovered.SetHoveredIsHovered
TEST=HoveredStateAfterHoverTimerFiresWhenHostIsHovered.HoveredStateAfterHoverTimerFiresWhenHostIsHovered
TEST=HoveredStateAfterHoverTimerFiresWhenHostIsHovered.HoveredStateAfterHoverTimerFiresWhenHostIsNotHovered

BUG=537238, 518919

Review URL: https://codereview.chromium.org/1390113006

Cr-Commit-Position: refs/heads/master@{#371511}
parent 08842a7e
......@@ -43,9 +43,24 @@ void ButtonInkDropDelegate::OnAction(InkDropState state) {
ink_drop_animation_controller_->AnimateToState(state);
}
void ButtonInkDropDelegate::SetHovered(bool is_hovered) {
ink_drop_animation_controller_->SetHovered(is_hovered);
}
////////////////////////////////////////////////////////////////////////////////
// ui::EventHandler:
void ButtonInkDropDelegate::OnMouseEvent(ui::MouseEvent* event) {
switch (event->type()) {
case ui::ET_MOUSE_ENTERED:
case ui::ET_MOUSE_EXITED:
SetHovered(ink_drop_host_->ShouldShowInkDropHover());
break;
default:
return;
}
}
void ButtonInkDropDelegate::OnGestureEvent(ui::GestureEvent* event) {
InkDropState current_ink_drop_state =
ink_drop_animation_controller_->GetInkDropState();
......
......@@ -35,8 +35,10 @@ class VIEWS_EXPORT ButtonInkDropDelegate : public InkDropDelegate,
int small_corner_radius) override;
void OnLayout() override;
void OnAction(InkDropState state) override;
void SetHovered(bool is_hovered) override;
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
private:
......
......@@ -11,7 +11,6 @@
#include "base/time/time.h"
#include "ui/compositor/layer_animator.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/transform.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/views_export.h"
......
......@@ -21,8 +21,8 @@ class Layer;
namespace views {
// Pure virtual base class that manages an ink drop animation's lifetime and
// state.
// Pure virtual base class that manages the lifetime and state of an ink drop
// animation as well as visual hover state feedback.
class VIEWS_EXPORT InkDropAnimationController {
public:
virtual ~InkDropAnimationController() {}
......@@ -33,6 +33,12 @@ class VIEWS_EXPORT InkDropAnimationController {
// Animates from the current InkDropState to |ink_drop_state|.
virtual void AnimateToState(InkDropState ink_drop_state) = 0;
// Enables or disables the hover state.
virtual void SetHovered(bool is_hovered) = 0;
// Returns true if the hover state is enabled.
virtual bool IsHovered() const = 0;
virtual gfx::Size GetInkDropLargeSize() const = 0;
// Sets the different sizes of the ink drop.
......
......@@ -27,6 +27,8 @@ class InkDropAnimationControllerStub
// InkDropAnimationController:
InkDropState GetInkDropState() const override;
void AnimateToState(InkDropState state) override;
void SetHovered(bool is_hovered) override;
bool IsHovered() const override;
gfx::Size GetInkDropLargeSize() const override;
void SetInkDropSize(const gfx::Size& large_size,
int large_corner_radius,
......@@ -35,10 +37,15 @@ class InkDropAnimationControllerStub
void SetInkDropCenter(const gfx::Point& center_point) override;
private:
// Tracks whether the ink drop is hovered or not. This is used to ensure that
// this behaves like all other InkDropAnimationController implementations.
bool is_hovered_;
DISALLOW_COPY_AND_ASSIGN(InkDropAnimationControllerStub);
};
InkDropAnimationControllerStub::InkDropAnimationControllerStub() {}
InkDropAnimationControllerStub::InkDropAnimationControllerStub()
: is_hovered_(false) {}
InkDropAnimationControllerStub::~InkDropAnimationControllerStub() {}
......@@ -46,7 +53,17 @@ InkDropState InkDropAnimationControllerStub::GetInkDropState() const {
return InkDropState::HIDDEN;
}
void InkDropAnimationControllerStub::AnimateToState(InkDropState state) {}
void InkDropAnimationControllerStub::AnimateToState(InkDropState state) {
SetHovered(false);
}
void InkDropAnimationControllerStub::SetHovered(bool is_hovered) {
is_hovered_ = is_hovered;
}
bool InkDropAnimationControllerStub::IsHovered() const {
return is_hovered_;
}
gfx::Size InkDropAnimationControllerStub::GetInkDropLargeSize() const {
return gfx::Size();
......
......@@ -4,12 +4,17 @@
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/timer/timer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/base/test/material_design_controller_test_api.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/views/animation/ink_drop_animation_controller.h"
#include "ui/views/animation/ink_drop_animation_controller_factory.h"
#include "ui/views/animation/ink_drop_animation_controller_impl.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/animation/test/test_ink_drop_host.h"
......@@ -17,7 +22,8 @@
namespace views {
class InkDropAnimationControllerFactoryTest
: public testing::TestWithParam<ui::MaterialDesignController::Mode> {
: public testing::TestWithParam<
testing::tuple<ui::MaterialDesignController::Mode>> {
public:
InkDropAnimationControllerFactoryTest();
~InkDropAnimationControllerFactoryTest();
......@@ -31,8 +37,14 @@ class InkDropAnimationControllerFactoryTest
scoped_ptr<InkDropAnimationController> ink_drop_animation_controller_;
private:
// Extracts and returns the material design mode from the test parameters.
ui::MaterialDesignController::Mode GetMaterialMode() const;
scoped_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
// Required by base::Timer's.
scoped_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
DISALLOW_COPY_AND_ASSIGN(InkDropAnimationControllerFactoryTest);
};
......@@ -42,7 +54,7 @@ InkDropAnimationControllerFactoryTest::InkDropAnimationControllerFactoryTest()
// initialize and cache the mode. This ensures that these tests will run from
// a non-initialized state.
ui::test::MaterialDesignControllerTestAPI::UninitializeMode();
ui::test::MaterialDesignControllerTestAPI::SetMode(GetParam());
ui::test::MaterialDesignControllerTestAPI::SetMode(GetMaterialMode());
ink_drop_animation_controller_.reset(
InkDropAnimationControllerFactory::CreateInkDropAnimationController(
&test_ink_drop_host_)
......@@ -52,6 +64,20 @@ InkDropAnimationControllerFactoryTest::InkDropAnimationControllerFactoryTest()
zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
switch (GetMaterialMode()) {
case ui::MaterialDesignController::NON_MATERIAL:
break;
case ui::MaterialDesignController::MATERIAL_NORMAL:
case ui::MaterialDesignController::MATERIAL_HYBRID:
// The Timer's used by the InkDropAnimationControllerImpl class require a
// base::ThreadTaskRunnerHandle instance.
scoped_refptr<base::TestMockTimeTaskRunner> task_runner(
new base::TestMockTimeTaskRunner);
thread_task_runner_handle_.reset(
new base::ThreadTaskRunnerHandle(task_runner));
break;
}
}
InkDropAnimationControllerFactoryTest::
......@@ -59,13 +85,19 @@ InkDropAnimationControllerFactoryTest::
ui::test::MaterialDesignControllerTestAPI::UninitializeMode();
}
ui::MaterialDesignController::Mode
InkDropAnimationControllerFactoryTest::GetMaterialMode() const {
return testing::get<0>(GetParam());
}
// Note: First argument is optional and intentionally left blank.
// (it's a prefix for the generated test cases)
INSTANTIATE_TEST_CASE_P(
,
InkDropAnimationControllerFactoryTest,
testing::Values(ui::MaterialDesignController::NON_MATERIAL,
ui::MaterialDesignController::MATERIAL_NORMAL));
ui::MaterialDesignController::MATERIAL_NORMAL,
ui::MaterialDesignController::MATERIAL_HYBRID));
TEST_P(InkDropAnimationControllerFactoryTest,
VerifyAllInkDropLayersRemovedAfterDestruction) {
......@@ -79,6 +111,13 @@ TEST_P(InkDropAnimationControllerFactoryTest, StateIsHiddenInitially) {
ink_drop_animation_controller_->GetInkDropState());
}
TEST_P(InkDropAnimationControllerFactoryTest, HoveredStateAfterAnimateToState) {
ink_drop_animation_controller_->SetHovered(true);
ink_drop_animation_controller_->AnimateToState(InkDropState::ACTION_PENDING);
EXPECT_FALSE(ink_drop_animation_controller_->IsHovered());
}
TEST_P(InkDropAnimationControllerFactoryTest, TypicalQuickAction) {
ink_drop_animation_controller_->AnimateToState(InkDropState::ACTION_PENDING);
ink_drop_animation_controller_->AnimateToState(InkDropState::QUICK_ACTION);
......
......@@ -4,19 +4,54 @@
#include "ui/views/animation/ink_drop_animation_controller_impl.h"
#include "base/timer/timer.h"
#include "ui/compositor/layer.h"
#include "ui/views/animation/ink_drop_animation.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/ink_drop_hover.h"
namespace views {
namespace {
// The duration, in milliseconds, of the hover state fade in animation when it
// is triggered by user input.
const int kHoverFadeInFromUserInputDurationInMs = 250;
// The duration, in milliseconds, of the hover state fade out animation when it
// is triggered by user input.
const int kHoverFadeOutFromUserInputDurationInMs = 250;
// The duration, in milliseconds, of the hover state fade in animation when it
// is triggered by an ink drop ripple animation ending.
const int kHoverFadeInAfterAnimationDurationInMs = 250;
// The duration, in milliseconds, of the hover state fade out animation when it
// is triggered by an ink drop ripple animation starting.
const int kHoverFadeOutBeforeAnimationDurationInMs = 0;
// The amount of time in milliseconds that |hover_| should delay after a ripple
// animation before fading in.
const int kHoverFadeInAfterAnimationDelayInMs = 1000;
} // namespace
InkDropAnimationControllerImpl::InkDropAnimationControllerImpl(
InkDropHost* ink_drop_host)
: ink_drop_host_(ink_drop_host) {}
: ink_drop_host_(ink_drop_host),
ink_drop_large_corner_radius_(0),
ink_drop_small_corner_radius_(0),
root_layer_(new ui::Layer(ui::LAYER_NOT_DRAWN)),
hover_after_animation_timer_(nullptr) {
root_layer_->set_name("InkDropAnimationControllerImpl:RootLayer");
ink_drop_host_->AddInkDropLayer(root_layer_.get());
}
InkDropAnimationControllerImpl::~InkDropAnimationControllerImpl() {
// Explicitly destroy the InkDropAnimation so that this still exists if
// views::InkDropAnimationObserver methods are called on this.
DestroyInkDropAnimation();
ink_drop_host_->RemoveInkDropLayer(root_layer_.get());
}
InkDropState InkDropAnimationControllerImpl::GetInkDropState() const {
......@@ -32,6 +67,18 @@ void InkDropAnimationControllerImpl::AnimateToState(
ink_drop_animation_->AnimateToState(ink_drop_state);
}
void InkDropAnimationControllerImpl::SetHovered(bool is_hovered) {
SetHoveredInternal(is_hovered,
is_hovered ? base::TimeDelta::FromMilliseconds(
kHoverFadeInFromUserInputDurationInMs)
: base::TimeDelta::FromMilliseconds(
kHoverFadeOutFromUserInputDurationInMs));
}
bool InkDropAnimationControllerImpl::IsHovered() const {
return hover_ && hover_->IsVisible();
}
gfx::Size InkDropAnimationControllerImpl::GetInkDropLargeSize() const {
return ink_drop_large_size_;
}
......@@ -49,7 +96,9 @@ void InkDropAnimationControllerImpl::SetInkDropSize(const gfx::Size& large_size,
ink_drop_large_corner_radius_ = large_corner_radius;
ink_drop_small_size_ = small_size;
ink_drop_small_corner_radius_ = small_corner_radius;
ink_drop_animation_.reset();
DestroyInkDropAnimation();
DestroyInkDropHover();
}
void InkDropAnimationControllerImpl::SetInkDropCenter(
......@@ -57,6 +106,8 @@ void InkDropAnimationControllerImpl::SetInkDropCenter(
ink_drop_center_ = center_point;
if (ink_drop_animation_)
ink_drop_animation_->SetCenterPoint(ink_drop_center_);
if (hover_)
hover_->SetCenterPoint(ink_drop_center_);
}
void InkDropAnimationControllerImpl::CreateInkDropAnimation() {
......@@ -68,19 +119,40 @@ void InkDropAnimationControllerImpl::CreateInkDropAnimation() {
ink_drop_animation_->AddObserver(this);
ink_drop_animation_->SetCenterPoint(ink_drop_center_);
ink_drop_host_->AddInkDropLayer(ink_drop_animation_->root_layer());
root_layer_->Add(ink_drop_animation_->root_layer());
}
void InkDropAnimationControllerImpl::DestroyInkDropAnimation() {
if (!ink_drop_animation_)
return;
ink_drop_host_->RemoveInkDropLayer(ink_drop_animation_->root_layer());
root_layer_->Remove(ink_drop_animation_->root_layer());
ink_drop_animation_->RemoveObserver(this);
ink_drop_animation_.reset();
}
void InkDropAnimationControllerImpl::CreateInkDropHover() {
DestroyInkDropHover();
hover_.reset(
new InkDropHover(ink_drop_small_size_, ink_drop_small_corner_radius_));
hover_->SetCenterPoint(ink_drop_center_);
root_layer_->Add(hover_->layer());
}
void InkDropAnimationControllerImpl::DestroyInkDropHover() {
if (!hover_)
return;
root_layer_->Remove(hover_->layer());
hover_.reset();
}
void InkDropAnimationControllerImpl::InkDropAnimationStarted(
InkDropState ink_drop_state) {}
InkDropState ink_drop_state) {
if (IsHovered() && ink_drop_state != views::InkDropState::HIDDEN) {
SetHoveredInternal(false, base::TimeDelta::FromMilliseconds(
kHoverFadeOutBeforeAnimationDurationInMs));
}
}
void InkDropAnimationControllerImpl::InkDropAnimationEnded(
InkDropState ink_drop_state,
......@@ -94,6 +166,9 @@ void InkDropAnimationControllerImpl::InkDropAnimationEnded(
ink_drop_animation_->AnimateToState(views::InkDropState::HIDDEN);
break;
case views::InkDropState::HIDDEN:
// TODO(bruthig): Consider only starting the timer if the InkDropHost is
// hovered now, as oppposed to when the timer fires.
StartHoverAfterAnimationTimer();
// TODO(bruthig): Investigate whether creating and destroying
// InkDropAnimations is expensive and consider creating an
// InkDropAnimationPool. See www.crbug.com/522175.
......@@ -104,4 +179,48 @@ void InkDropAnimationControllerImpl::InkDropAnimationEnded(
}
}
void InkDropAnimationControllerImpl::SetHoveredInternal(
bool is_hovered,
base::TimeDelta animation_duration) {
StopHoverAfterAnimationTimer();
if (IsHovered() == is_hovered)
return;
if (is_hovered) {
if (!hover_)
CreateInkDropHover();
if (GetInkDropState() == views::InkDropState::HIDDEN) {
hover_->FadeIn(animation_duration);
}
} else {
hover_->FadeOut(animation_duration);
}
}
void InkDropAnimationControllerImpl::StartHoverAfterAnimationTimer() {
StopHoverAfterAnimationTimer();
if (!hover_after_animation_timer_)
hover_after_animation_timer_.reset(new base::OneShotTimer);
hover_after_animation_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kHoverFadeInAfterAnimationDelayInMs),
base::Bind(&InkDropAnimationControllerImpl::HoverAfterAnimationTimerFired,
base::Unretained(this)));
}
void InkDropAnimationControllerImpl::StopHoverAfterAnimationTimer() {
if (hover_after_animation_timer_)
hover_after_animation_timer_.reset();
}
void InkDropAnimationControllerImpl::HoverAfterAnimationTimerFired() {
SetHoveredInternal(ink_drop_host_->ShouldShowInkDropHover(),
base::TimeDelta::FromMilliseconds(
kHoverFadeInAfterAnimationDurationInMs));
hover_after_animation_timer_.reset();
}
} // namespace views
......@@ -13,9 +13,15 @@
#include "ui/views/animation/ink_drop_animation_observer.h"
#include "ui/views/views_export.h"
namespace base {
class Timer;
} // namespace base
namespace views {
class InkDropAnimation;
class InkDropHost;
class InkDropHover;
class InkDropAnimationControllerFactoryTest;
// A functional implementation of an InkDropAnimationController.
class VIEWS_EXPORT InkDropAnimationControllerImpl
......@@ -30,6 +36,8 @@ class VIEWS_EXPORT InkDropAnimationControllerImpl
// InkDropAnimationController:
InkDropState GetInkDropState() const override;
void AnimateToState(InkDropState ink_drop_state) override;
void SetHovered(bool is_hovered) override;
bool IsHovered() const override;
gfx::Size GetInkDropLargeSize() const override;
void SetInkDropSize(const gfx::Size& large_size,
int large_corner_radius,
......@@ -38,6 +46,9 @@ class VIEWS_EXPORT InkDropAnimationControllerImpl
void SetInkDropCenter(const gfx::Point& center_point) override;
private:
friend class InkDropAnimationControllerFactoryTest;
friend class InkDropAnimationControllerImplTest;
// Creates a new InkDropAnimation and sets it to |ink_drop_animation_|. If
// |ink_drop_animation_| wasn't null then it will be destroyed using
// DestroyInkDropAnimation().
......@@ -46,12 +57,35 @@ class VIEWS_EXPORT InkDropAnimationControllerImpl
// Destroys the current |ink_drop_animation_|.
void DestroyInkDropAnimation();
// Creates a new InkDropHover and sets it to |hover_|. If |hover_| wasn't null
// then it will be destroyed using DestroyInkDropHover().
void CreateInkDropHover();
// Destroys the current |hover_|.
void DestroyInkDropHover();
// views::InkDropAnimationObserver:
void InkDropAnimationStarted(InkDropState ink_drop_state) override;
void InkDropAnimationEnded(InkDropState ink_drop_state,
InkDropAnimationEndedReason reason) override;
// The host of the ink drop.
// Enables or disables the hover state based on |is_hovered| and if an
// animation is triggered it will be scheduled to have the given
// |animation_duration|.
void SetHoveredInternal(bool is_hovered, base::TimeDelta animation_duration);
// Starts the |hover_after_animation_timer_| timer. This will stop the current
// |hover_after_animation_timer_| instance if it exists.
void StartHoverAfterAnimationTimer();
// Stops and destroys the current |hover_after_animation_timer_| instance.
void StopHoverAfterAnimationTimer();
// Callback for when the |hover_after_animation_timer_| fires.
void HoverAfterAnimationTimerFired();
// The host of the ink drop. Used to poll for information such as whether the
// hover should be shown or not.
InkDropHost* ink_drop_host_;
// Cached size for the ink drop's large size animations.
......@@ -69,10 +103,21 @@ class VIEWS_EXPORT InkDropAnimationControllerImpl
// Cached center point for the ink drop.
gfx::Point ink_drop_center_;
// The root Layer that parents the InkDropAnimation layers and the
// InkDropHover layers. The |root_layer_| is the one that is added and removed
// from the InkDropHost.
scoped_ptr<ui::Layer> root_layer_;
// The current InkDropHover. Lazily created using CreateInkDropHover();
scoped_ptr<InkDropHover> hover_;
// The current InkDropAnimation. Created on demand using
// CreateInkDropAnimation().
scoped_ptr<InkDropAnimation> ink_drop_animation_;
// The timer used to delay the hover fade in after an ink drop animation.
scoped_ptr<base::Timer> hover_after_animation_timer_;
DISALLOW_COPY_AND_ASSIGN(InkDropAnimationControllerImpl);
};
......
// 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 "base/macros.h"
#include "base/test/test_simple_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/views/animation/ink_drop_animation_controller_impl.h"
#include "ui/views/animation/test/test_ink_drop_host.h"
namespace views {
// NOTE: The InkDropAnimationControllerImpl class is also tested by the
// InkDropAnimationControllerFactoryTest tests.
class InkDropAnimationControllerImplTest : public testing::Test {
public:
InkDropAnimationControllerImplTest();
~InkDropAnimationControllerImplTest() override;
protected:
TestInkDropHost ink_drop_host_;
// The test target.
InkDropAnimationControllerImpl ink_drop_animation_controller_;
// Used to control the tasks scheduled by the InkDropAnimationControllerImpl's
// Timer.
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
// Required by base::Timer's.
scoped_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
private:
// Ensures all animations complete immediately.
scoped_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
DISALLOW_COPY_AND_ASSIGN(InkDropAnimationControllerImplTest);
};
InkDropAnimationControllerImplTest::InkDropAnimationControllerImplTest()
: ink_drop_animation_controller_(&ink_drop_host_),
task_runner_(new base::TestSimpleTaskRunner),
thread_task_runner_handle_(
new base::ThreadTaskRunnerHandle(task_runner_)) {
zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
}
InkDropAnimationControllerImplTest::~InkDropAnimationControllerImplTest() {}
TEST_F(InkDropAnimationControllerImplTest, SetHoveredIsHovered) {
ink_drop_host_.set_should_show_hover(true);
ink_drop_animation_controller_.SetHovered(true);
EXPECT_TRUE(ink_drop_animation_controller_.IsHovered());
ink_drop_animation_controller_.SetHovered(false);
EXPECT_FALSE(ink_drop_animation_controller_.IsHovered());
}
TEST_F(InkDropAnimationControllerImplTest,
HoveredStateAfterHoverTimerFiresWhenHostIsHovered) {
ink_drop_host_.set_should_show_hover(true);
ink_drop_animation_controller_.AnimateToState(InkDropState::QUICK_ACTION);
EXPECT_TRUE(task_runner_->HasPendingTask());
task_runner_->RunPendingTasks();
EXPECT_TRUE(ink_drop_animation_controller_.IsHovered());
}
TEST_F(InkDropAnimationControllerImplTest,
HoveredStateAfterHoverTimerFiresWhenHostIsNotHovered) {
ink_drop_host_.set_should_show_hover(false);
ink_drop_animation_controller_.AnimateToState(InkDropState::QUICK_ACTION);
EXPECT_TRUE(task_runner_->HasPendingTask());
task_runner_->RunPendingTasks();
EXPECT_FALSE(ink_drop_animation_controller_.IsHovered());
}
} // namespace views
......@@ -42,6 +42,9 @@ class VIEWS_EXPORT InkDropDelegate {
// as well as a NONE value.
virtual void OnAction(InkDropState state) = 0;
// Enables or disables the hover state.
virtual void SetHovered(bool is_hovered) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(InkDropDelegate);
};
......
......@@ -34,9 +34,11 @@ class VIEWS_EXPORT InkDropHost {
virtual void RemoveInkDropLayer(ui::Layer* ink_drop_layer) = 0;
// Returns the Point where the ink drop should be centered.
// TODO(varkha): This should be moved to InkDropConsumer.
virtual gfx::Point CalculateInkDropCenter() const = 0;
// Returns true if the InkDropHover should be shown.
virtual bool ShouldShowInkDropHover() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(InkDropHost);
};
......
// 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_hover.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/animation/ink_drop_painted_layer_delegates.h"
namespace views {
namespace {
// The opacity of the hover when it is visible.
const float kHoverVisibleOpacity = 0.08f;
// The opacity of the hover when it is not visible.
const float kHiddenOpacity = 0.0f;
// The hover color.
const SkColor kHoverColor = SK_ColorBLACK;
} // namespace
InkDropHover::InkDropHover(const gfx::Size& size, int corner_radius)
: layer_delegate_(
new RoundedRectangleLayerDelegate(kHoverColor, size, corner_radius)),
layer_(new ui::Layer()) {
layer_->SetBounds(gfx::Rect(size));
layer_->SetFillsBoundsOpaquely(false);
layer_->set_delegate(layer_delegate_.get());
layer_->SetVisible(false);
layer_->SetOpacity(kHoverVisibleOpacity);
layer_->SetMasksToBounds(false);
layer_->set_name("InkDropHover:layer");
SetCenterPoint(gfx::Rect(size).CenterPoint());
}
InkDropHover::~InkDropHover() {}
bool InkDropHover::IsVisible() const {
return layer_->visible();
}
void InkDropHover::FadeIn(const base::TimeDelta& duration) {
layer_->SetOpacity(kHiddenOpacity);
layer_->SetVisible(true);
AnimateFade(FADE_IN, duration);
}
void InkDropHover::FadeOut(const base::TimeDelta& duration) {
AnimateFade(FADE_OUT, duration);
}
void InkDropHover::AnimateFade(HoverAnimationType animation_type,
const base::TimeDelta& duration) {
// The |animation_observer| will be destroyed when the
// AnimationStartedCallback() returns true.
ui::CallbackLayerAnimationObserver* animation_observer =
new ui::CallbackLayerAnimationObserver(
base::Bind(&InkDropHover::AnimationEndedCallback,
base::Unretained(this), animation_type));
ui::LayerAnimator* animator = layer_->GetAnimator();
ui::ScopedLayerAnimationSettings animation(animator);
animation.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
ui::LayerAnimationElement* animation_element =
ui::LayerAnimationElement::CreateOpacityElement(
animation_type == FADE_IN ? kHoverVisibleOpacity : kHiddenOpacity,
duration);
ui::LayerAnimationSequence* animation_sequence =
new ui::LayerAnimationSequence(animation_element);
animation_sequence->AddObserver(animation_observer);
animator->StartAnimation(animation_sequence);
animation_observer->SetActive();
}
void InkDropHover::SetCenterPoint(const gfx::Point& center_point) {
gfx::Transform transform;
transform.Translate(center_point.x() - layer_->bounds().CenterPoint().x(),
center_point.y() - layer_->bounds().CenterPoint().y());
layer_->SetTransform(transform);
}
bool InkDropHover::AnimationEndedCallback(
HoverAnimationType animation_type,
const ui::CallbackLayerAnimationObserver& observer) {
// AnimationEndedCallback() may be invoked when this is being destroyed and
// |layer_| may be null.
if (animation_type == FADE_OUT && layer_)
layer_->SetVisible(false);
return true;
}
} // namespace views
// 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.
#ifndef UI_VIEWS_ANIMATION_INK_DROP_HOVER_H_
#define UI_VIEWS_ANIMATION_INK_DROP_HOVER_H_
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/views_export.h"
namespace ui {
class Layer;
class CallbackLayerAnimationObserver;
} // namespace ui
namespace views {
class RoundedRectangleLayerDelegate;
// Manages fade in/out animations for a painted Layer that is used to provide
// visual feedback on ui::Views for mouse hover states.
class VIEWS_EXPORT InkDropHover {
public:
InkDropHover(const gfx::Size& size, int corner_radius);
~InkDropHover();
// Returns true if the hover layer is visible.
bool IsVisible() const;
// Fades in the hover visual over the given |duration|.
void FadeIn(const base::TimeDelta& duration);
// Fades out the hover visual over the given |duration|.
void FadeOut(const base::TimeDelta& duration);
// The root Layer that can be added in to a Layer tree.
ui::Layer* layer() { return layer_.get(); }
// Sets the |center_point| of the hover layer relative to its parent Layer.
void SetCenterPoint(const gfx::Point& center_point);
private:
enum HoverAnimationType { FADE_IN, FADE_OUT };
// Animates a fade in/out as specified by |animation_type| over the given
// |duration|.
void AnimateFade(HoverAnimationType animation_type,
const base::TimeDelta& duration);
// The callback that will be invoked when a fade in/out animation is complete.
bool AnimationEndedCallback(
HoverAnimationType animation_type,
const ui::CallbackLayerAnimationObserver& observer);
// The LayerDelegate that paints the hover |layer_|.
scoped_ptr<RoundedRectangleLayerDelegate> layer_delegate_;
// The visual hover layer that is painted by |layer_delegate_|.
scoped_ptr<ui::Layer> layer_;
DISALLOW_COPY_AND_ASSIGN(InkDropHover);
};
} // namespace views
#endif // UI_VIEWS_ANIMATION_INK_DROP_HOVER_H_
// 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_hover.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/geometry/size.h"
namespace views {
namespace test {
class InkDropHoverTest : public testing::Test {
public:
InkDropHoverTest();
~InkDropHoverTest() override;
protected:
scoped_ptr<InkDropHover> CreateInkDropHover() const;
private:
// Enables zero duration animations during the tests.
scoped_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
DISALLOW_COPY_AND_ASSIGN(InkDropHoverTest);
};
InkDropHoverTest::InkDropHoverTest() {
zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
}
InkDropHoverTest::~InkDropHoverTest() {}
scoped_ptr<InkDropHover> InkDropHoverTest::CreateInkDropHover() const {
return make_scoped_ptr(new InkDropHover(gfx::Size(10, 10), 3));
}
TEST_F(InkDropHoverTest, InitialStateAfterConstruction) {
scoped_ptr<InkDropHover> ink_drop_hover = CreateInkDropHover();
EXPECT_FALSE(ink_drop_hover->IsVisible());
}
TEST_F(InkDropHoverTest, IsHoveredStateTransitions) {
scoped_ptr<InkDropHover> ink_drop_hover = CreateInkDropHover();
ink_drop_hover->FadeIn(base::TimeDelta::FromMilliseconds(0));
EXPECT_TRUE(ink_drop_hover->IsVisible());
ink_drop_hover->FadeOut(base::TimeDelta::FromMilliseconds(0));
EXPECT_FALSE(ink_drop_hover->IsVisible());
}
} // namespace test
} // namespace views
......@@ -8,6 +8,7 @@
#include "ui/compositor/paint_recorder.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
namespace views {
......@@ -82,4 +83,34 @@ void RectangleLayerDelegate::OnPaintLayer(const ui::PaintContext& context) {
canvas->DrawRect(gfx::Rect(size_), paint);
}
////////////////////////////////////////////////////////////////////////////////
//
// RoundedRectangleLayerDelegate
//
RoundedRectangleLayerDelegate::RoundedRectangleLayerDelegate(SkColor color,
gfx::Size size,
int corner_radius)
: BasePaintedLayerDelegate(color),
size_(size),
corner_radius_(corner_radius) {}
RoundedRectangleLayerDelegate::~RoundedRectangleLayerDelegate() {}
gfx::PointF RoundedRectangleLayerDelegate::GetCenterPoint() const {
return gfx::RectF(gfx::SizeF(size_)).CenterPoint();
}
void RoundedRectangleLayerDelegate::OnPaintLayer(
const ui::PaintContext& context) {
SkPaint paint;
paint.setColor(color());
paint.setFlags(SkPaint::kAntiAlias_Flag);
paint.setStyle(SkPaint::kFill_Style);
ui::PaintRecorder recorder(context, size_);
gfx::Canvas* canvas = recorder.canvas();
canvas->DrawRoundRect(gfx::Rect(size_), corner_radius_, paint);
}
} // namespace views
......@@ -81,6 +81,31 @@ class RectangleLayerDelegate : public BasePaintedLayerDelegate {
DISALLOW_COPY_AND_ASSIGN(RectangleLayerDelegate);
};
// A BasePaintedLayerDelegate that paints a rounded rectangle of a specified
// color, size and corner radius.
class RoundedRectangleLayerDelegate : public BasePaintedLayerDelegate {
public:
RoundedRectangleLayerDelegate(SkColor color,
gfx::Size size,
int corner_radius);
~RoundedRectangleLayerDelegate() override;
const gfx::Size& size() const { return size_; }
// ui::LayerDelegate:
gfx::PointF GetCenterPoint() const override;
void OnPaintLayer(const ui::PaintContext& context) override;
private:
// The size of the rectangle.
gfx::Size size_;
// The radius of the corners.
int corner_radius_;
DISALLOW_COPY_AND_ASSIGN(RoundedRectangleLayerDelegate);
};
} // namespace views
#endif // UI_VIEWS_ANIMATION_INK_DROP_PAINTED_LAYER_DELEGATES_H_
......@@ -6,7 +6,8 @@
namespace views {
TestInkDropHost::TestInkDropHost() : num_ink_drop_layers_(0) {}
TestInkDropHost::TestInkDropHost()
: num_ink_drop_layers_(0), should_show_hover_(false) {}
TestInkDropHost::~TestInkDropHost() {}
......@@ -22,4 +23,8 @@ gfx::Point TestInkDropHost::CalculateInkDropCenter() const {
return gfx::Point();
}
bool TestInkDropHost::ShouldShowInkDropHover() const {
return should_show_hover_;
}
} // namespace views
......@@ -19,14 +19,21 @@ class TestInkDropHost : public InkDropHost {
int num_ink_drop_layers() const { return num_ink_drop_layers_; }
void set_should_show_hover(bool should_show_hover) {
should_show_hover_ = should_show_hover;
}
// TestInkDropHost:
void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
gfx::Point CalculateInkDropCenter() const override;
bool ShouldShowInkDropHover() const override;
private:
int num_ink_drop_layers_;
bool should_show_hover_;
DISALLOW_COPY_AND_ASSIGN(TestInkDropHost);
};
......
......@@ -123,6 +123,8 @@ void CustomButton::Layout() {
}
void CustomButton::OnEnabledChanged() {
// TODO(bruthig): Is there any reason we are not calling
// Button::OnEnabledChanged() here?
if (enabled() ? (state_ != STATE_DISABLED) : (state_ == STATE_DISABLED))
return;
......@@ -130,6 +132,7 @@ void CustomButton::OnEnabledChanged() {
SetState(ShouldEnterHoveredState() ? STATE_HOVERED : STATE_NORMAL);
else
SetState(STATE_DISABLED);
UpdateInkDropHoverState();
}
const char* CustomButton::GetClassName() const {
......@@ -352,6 +355,10 @@ gfx::Point CustomButton::CalculateInkDropCenter() const {
return GetLocalBounds().CenterPoint();
}
bool CustomButton::ShouldShowInkDropHover() const {
return enabled() && IsMouseHovered() && !InDrag();
}
////////////////////////////////////////////////////////////////////////////////
// CustomButton, protected:
......@@ -408,6 +415,11 @@ bool CustomButton::ShouldEnterHoveredState() {
return check_mouse_position && IsMouseHovered();
}
void CustomButton::UpdateInkDropHoverState() {
if (ink_drop_delegate_)
ink_drop_delegate_->SetHovered(ShouldShowInkDropHover());
}
////////////////////////////////////////////////////////////////////////////////
// CustomButton, View overrides (protected):
......
......@@ -106,6 +106,7 @@ class VIEWS_EXPORT CustomButton : public Button,
void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
gfx::Point CalculateInkDropCenter() const override;
bool ShouldShowInkDropHover() const override;
protected:
// Construct the Button with a Listener. See comment for Button's ctor.
......@@ -136,6 +137,9 @@ class VIEWS_EXPORT CustomButton : public Button,
// state). This does not take into account enabled state.
bool ShouldEnterHoveredState();
// Updates the |ink_drop_delegate_|'s hover state.
void UpdateInkDropHoverState();
InkDropDelegate* ink_drop_delegate() const { return ink_drop_delegate_; }
void set_ink_drop_delegate(InkDropDelegate* ink_drop_delegate) {
ink_drop_delegate_ = ink_drop_delegate;
......
......@@ -123,6 +123,8 @@ class TestInkDropDelegate : public InkDropDelegate {
}
}
void SetHovered(bool is_hovered) override {}
private:
InkDropHost* ink_drop_host_;
bool* ink_shown_;
......
......@@ -27,6 +27,8 @@
'animation/ink_drop_animation_observer.h',
'animation/ink_drop_delegate.h',
'animation/ink_drop_host.h',
'animation/ink_drop_hover.cc',
'animation/ink_drop_hover.h',
'animation/ink_drop_painted_layer_delegates.cc',
'animation/ink_drop_painted_layer_delegates.h',
'animation/ink_drop_state.cc',
......@@ -544,7 +546,9 @@
'accessible_pane_view_unittest.cc',
'animation/bounds_animator_unittest.cc',
'animation/ink_drop_animation_controller_factory_unittest.cc',
'animation/ink_drop_animation_controller_impl_unittest.cc',
'animation/ink_drop_animation_unittest.cc',
'animation/ink_drop_hover_unittest.cc',
'bubble/bubble_border_unittest.cc',
'bubble/bubble_delegate_unittest.cc',
'bubble/bubble_frame_view_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment