Commit 951a1129 authored by Elly Fong-Jones's avatar Elly Fong-Jones Committed by Commit Bot

macviews: run menu closure animation for dismissals by clicking outside

This change:
1) Adds support to MenuClosureAnimationMac for animating menu closure without
   animating selecting an item;
2) Has MenuController use MenuClosureAnimationMac to animate cancels caused by
   outside clicks

Bug: 833856
Change-Id: I9743b396798e4b125c038b35decc9aceb083926d
Reviewed-on: https://chromium-review.googlesource.com/1231837
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594738}
parent acbc35cd
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/submenu_view.h" #include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/test/menu_test_utils.h"
#include "ui/views/widget/root_view.h" #include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
...@@ -211,6 +212,7 @@ class MenuModelAdapterTest : public ViewEventTestBase, ...@@ -211,6 +212,7 @@ class MenuModelAdapterTest : public ViewEventTestBase,
// Open the submenu. // Open the submenu.
void Step1() { void Step1() {
views::test::DisableMenuClosureAnimations();
views::SubmenuView* topmenu = menu_->GetSubmenu(); views::SubmenuView* topmenu = menu_->GetSubmenu();
ASSERT_TRUE(topmenu); ASSERT_TRUE(topmenu);
ASSERT_TRUE(topmenu->IsShowing()); ASSERT_TRUE(topmenu->IsShowing());
...@@ -251,6 +253,7 @@ class MenuModelAdapterTest : public ViewEventTestBase, ...@@ -251,6 +253,7 @@ class MenuModelAdapterTest : public ViewEventTestBase,
// All done. // All done.
void Step4() { void Step4() {
views::SubmenuView* topmenu = menu_->GetSubmenu(); views::SubmenuView* topmenu = menu_->GetSubmenu();
views::test::WaitForMenuClosureAnimation();
ASSERT_TRUE(topmenu); ASSERT_TRUE(topmenu);
ASSERT_FALSE(topmenu->IsShowing()); ASSERT_FALSE(topmenu->IsShowing());
ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); ASSERT_FALSE(top_menu_model_.IsSubmenuShowing());
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
namespace views { namespace views {
class MenuItemView; class MenuItemView;
class SubmenuView;
// This class implements the Mac menu closure animation: // This class implements the Mac menu closure animation:
// 1) For 100ms, the selected item is drawn as unselected // 1) For 100ms, the selected item is drawn as unselected
...@@ -24,11 +25,19 @@ class MenuItemView; ...@@ -24,11 +25,19 @@ class MenuItemView;
// will stop the timer or animation (if they are running), so the callback will // will stop the timer or animation (if they are running), so the callback will
// *not* be run - which is good, since the MenuController that would have // *not* be run - which is good, since the MenuController that would have
// received it is being deleted. // received it is being deleted.
//
// This class also supports animating a menu away without animating the
// selection effect, which is achieved by passing nullptr for the item to
// animate. In this case, the animation skips straight to step 3 above.
class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate {
public: public:
// After this closure animation is done, |callback| is run to finally accept // After this closure animation is done, |callback| is run to finish
// |item|. // dismissing the menu. If |item| is given, this will animate the item being
MenuClosureAnimationMac(MenuItemView* item, base::OnceClosure callback); // accepted before animating the menu closing; if |item| is nullptr, only the
// menu closure will be animated.
MenuClosureAnimationMac(MenuItemView* item,
SubmenuView* menu,
base::OnceClosure callback);
~MenuClosureAnimationMac() override; ~MenuClosureAnimationMac() override;
// Start the animation. // Start the animation.
...@@ -37,6 +46,9 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { ...@@ -37,6 +46,9 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate {
// Returns the MenuItemView this animation targets. // Returns the MenuItemView this animation targets.
MenuItemView* item() { return item_; } MenuItemView* item() { return item_; }
// Returns the SubmenuView this animation targets.
SubmenuView* menu() { return menu_; }
// Causes animations to take no time for testing purposes. Note that this // Causes animations to take no time for testing purposes. Note that this
// still causes the completion callback to be run asynchronously, so test // still causes the completion callback to be run asynchronously, so test
// situations have the same control flow as non-test situations. // situations have the same control flow as non-test situations.
...@@ -51,7 +63,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { ...@@ -51,7 +63,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate {
kFinish, kFinish,
}; };
static constexpr AnimationStep NextStepFor(AnimationStep step); AnimationStep NextStepFor(AnimationStep step) const;
void AdvanceAnimation(); void AdvanceAnimation();
...@@ -64,6 +76,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { ...@@ -64,6 +76,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate {
base::OneShotTimer timer_; base::OneShotTimer timer_;
std::unique_ptr<gfx::Animation> fade_animation_; std::unique_ptr<gfx::Animation> fade_animation_;
MenuItemView* item_; MenuItemView* item_;
SubmenuView* menu_;
AnimationStep step_; AnimationStep step_;
DISALLOW_COPY_AND_ASSIGN(MenuClosureAnimationMac); DISALLOW_COPY_AND_ASSIGN(MenuClosureAnimationMac);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "ui/gfx/animation/linear_animation.h" #include "ui/gfx/animation/linear_animation.h"
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
namespace { namespace {
...@@ -19,9 +20,11 @@ static bool g_disable_animations_for_testing = false; ...@@ -19,9 +20,11 @@ static bool g_disable_animations_for_testing = false;
namespace views { namespace views {
MenuClosureAnimationMac::MenuClosureAnimationMac(MenuItemView* item, MenuClosureAnimationMac::MenuClosureAnimationMac(MenuItemView* item,
SubmenuView* menu,
base::OnceClosure callback) base::OnceClosure callback)
: callback_(std::move(callback)), : callback_(std::move(callback)),
item_(item), item_(item),
menu_(menu),
step_(AnimationStep::kStart) {} step_(AnimationStep::kStart) {}
MenuClosureAnimationMac::~MenuClosureAnimationMac() {} MenuClosureAnimationMac::~MenuClosureAnimationMac() {}
...@@ -42,12 +45,11 @@ void MenuClosureAnimationMac::Start() { ...@@ -42,12 +45,11 @@ void MenuClosureAnimationMac::Start() {
} }
// static // static
constexpr MenuClosureAnimationMac::AnimationStep MenuClosureAnimationMac::AnimationStep MenuClosureAnimationMac::NextStepFor(
MenuClosureAnimationMac::NextStepFor( MenuClosureAnimationMac::AnimationStep step) const {
MenuClosureAnimationMac::AnimationStep step) {
switch (step) { switch (step) {
case AnimationStep::kStart: case AnimationStep::kStart:
return AnimationStep::kUnselected; return item_ ? AnimationStep::kUnselected : AnimationStep::kFading;
case AnimationStep::kUnselected: case AnimationStep::kUnselected:
return AnimationStep::kSelected; return AnimationStep::kSelected;
case AnimationStep::kSelected: case AnimationStep::kSelected:
...@@ -84,7 +86,7 @@ void MenuClosureAnimationMac::DisableAnimationsForTesting() { ...@@ -84,7 +86,7 @@ void MenuClosureAnimationMac::DisableAnimationsForTesting() {
void MenuClosureAnimationMac::AnimationProgressed( void MenuClosureAnimationMac::AnimationProgressed(
const gfx::Animation* animation) { const gfx::Animation* animation) {
NSWindow* window = item_->GetWidget()->GetNativeWindow(); NSWindow* window = menu_->GetWidget()->GetNativeWindow();
[window setAlphaValue:animation->CurrentValueBetween(1.0, 0.0)]; [window setAlphaValue:animation->CurrentValueBetween(1.0, 0.0)];
} }
......
...@@ -1529,7 +1529,7 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds, ...@@ -1529,7 +1529,7 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds,
void MenuController::Accept(MenuItemView* item, int event_flags) { void MenuController::Accept(MenuItemView* item, int event_flags) {
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>( menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>(
item, item, item->GetParentMenuItem()->GetSubmenu(),
base::BindOnce(&MenuController::ReallyAccept, base::Unretained(this), base::BindOnce(&MenuController::ReallyAccept, base::Unretained(this),
base::Unretained(item), event_flags)); base::Unretained(item), event_flags));
menu_closure_animation_->Start(); menu_closure_animation_->Start();
...@@ -2600,7 +2600,15 @@ void MenuController::RepostEventAndCancel(SubmenuView* source, ...@@ -2600,7 +2600,15 @@ void MenuController::RepostEventAndCancel(SubmenuView* source,
if (last_part.type != MenuPart::NONE) if (last_part.type != MenuPart::NONE)
exit_type = EXIT_OUTERMOST; exit_type = EXIT_OUTERMOST;
} }
#if defined(OS_MACOSX)
menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>(
nullptr, source,
base::BindOnce(&MenuController::Cancel, base::Unretained(this),
exit_type));
menu_closure_animation_->Start();
#else
Cancel(exit_type); Cancel(exit_type);
#endif
} }
void MenuController::SetDropMenuItem(MenuItemView* new_target, void MenuController::SetDropMenuItem(MenuItemView* new_target,
......
...@@ -1229,6 +1229,7 @@ TEST_F(MenuControllerTest, PreserveGestureForOwner) { ...@@ -1229,6 +1229,7 @@ TEST_F(MenuControllerTest, PreserveGestureForOwner) {
// Tests that touch outside menu does not closes the menu when forwarding // Tests that touch outside menu does not closes the menu when forwarding
// gesture events to owner. // gesture events to owner.
TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) {
views::test::DisableMenuClosureAnimations();
MenuController* controller = menu_controller(); MenuController* controller = menu_controller();
// Owner wants the gesture events. // Owner wants the gesture events.
...@@ -1256,6 +1257,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { ...@@ -1256,6 +1257,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) {
// Touch outside again and menu should be closed. // Touch outside again and menu should be closed.
controller->OnTouchEvent(sub_menu, &touch_event); controller->OnTouchEvent(sub_menu, &touch_event);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(IsShowing()); EXPECT_FALSE(IsShowing());
EXPECT_EQ(MenuController::EXIT_ALL, controller->exit_type()); EXPECT_EQ(MenuController::EXIT_ALL, controller->exit_type());
} }
...@@ -1264,6 +1266,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { ...@@ -1264,6 +1266,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) {
// occur outside of the bounds of the menu. Instead a proper shutdown should // occur outside of the bounds of the menu. Instead a proper shutdown should
// occur. // occur.
TEST_F(MenuControllerTest, AsynchronousRepostEvent) { TEST_F(MenuControllerTest, AsynchronousRepostEvent) {
views::test::DisableMenuClosureAnimations();
MenuController* controller = menu_controller(); MenuController* controller = menu_controller();
TestMenuControllerDelegate* delegate = menu_controller_delegate(); TestMenuControllerDelegate* delegate = menu_controller_delegate();
std::unique_ptr<TestMenuControllerDelegate> nested_delegate( std::unique_ptr<TestMenuControllerDelegate> nested_delegate(
...@@ -1288,6 +1291,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) { ...@@ -1288,6 +1291,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) {
// When attempting to select outside of all menus this should lead to a // When attempting to select outside of all menus this should lead to a
// shutdown. This should not crash while attempting to repost the event. // shutdown. This should not crash while attempting to repost the event.
SetSelectionOnPointerDown(sub_menu, &event); SetSelectionOnPointerDown(sub_menu, &event);
views::test::WaitForMenuClosureAnimation();
EXPECT_EQ(delegate, GetCurrentDelegate()); EXPECT_EQ(delegate, GetCurrentDelegate());
EXPECT_EQ(1, delegate->on_menu_closed_called()); EXPECT_EQ(1, delegate->on_menu_closed_called());
...@@ -1302,6 +1306,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) { ...@@ -1302,6 +1306,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) {
// Tests that an asynchronous menu reposts touch events that occur outside of // Tests that an asynchronous menu reposts touch events that occur outside of
// the bounds of the menu, and that the menu closes. // the bounds of the menu, and that the menu closes.
TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) {
views::test::DisableMenuClosureAnimations();
MenuController* controller = menu_controller(); MenuController* controller = menu_controller();
TestMenuControllerDelegate* delegate = menu_controller_delegate(); TestMenuControllerDelegate* delegate = menu_controller_delegate();
...@@ -1316,6 +1321,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { ...@@ -1316,6 +1321,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) {
ui::ET_TOUCH_PRESSED, location, ui::EventTimeForNow(), ui::ET_TOUCH_PRESSED, location, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
controller->OnTouchEvent(sub_menu, &event); controller->OnTouchEvent(sub_menu, &event);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(IsShowing()); EXPECT_FALSE(IsShowing());
EXPECT_EQ(1, delegate->on_menu_closed_called()); EXPECT_EQ(1, delegate->on_menu_closed_called());
...@@ -1329,6 +1335,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { ...@@ -1329,6 +1335,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) {
// Tests that having the MenuController deleted during RepostEvent does not // Tests that having the MenuController deleted during RepostEvent does not
// cause a crash. ASAN bots should not detect use-after-free in MenuController. // cause a crash. ASAN bots should not detect use-after-free in MenuController.
TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) {
views::test::DisableMenuClosureAnimations();
MenuController* controller = menu_controller(); MenuController* controller = menu_controller();
std::unique_ptr<TestMenuControllerDelegate> nested_delegate( std::unique_ptr<TestMenuControllerDelegate> nested_delegate(
new TestMenuControllerDelegate()); new TestMenuControllerDelegate());
...@@ -1355,6 +1362,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { ...@@ -1355,6 +1362,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) {
// When attempting to select outside of all menus this should lead to a // When attempting to select outside of all menus this should lead to a
// shutdown. This should not crash while attempting to repost the event. // shutdown. This should not crash while attempting to repost the event.
SetSelectionOnPointerDown(sub_menu, &event); SetSelectionOnPointerDown(sub_menu, &event);
views::test::WaitForMenuClosureAnimation();
// Close to remove observers before test TearDown // Close to remove observers before test TearDown
sub_menu->Close(); sub_menu->Close();
......
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