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

views: support Mac menu closure animations

This change:
1) Adds MenuClosureAnimationMac, which implements the Mac menu closure
   animation;
2) Adds logic to MenuItemView to force drawing the item in a selected
   or unselected state as needed, for use in this animation;
3) Adds support to MenuController for disabling handling input events,
   so that the closure animation can appear to be "synchronous";
4) Makes MenuController::Accept possibly asynchronous;
5) Updates unit tests to account for (4)

Bug: 829347
Change-Id: I8676718e6a5e9704b422ed4cb08e8d10002bf45a
Reviewed-on: https://chromium-review.googlesource.com/999803
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550160}
parent 5208b8e8
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/button/menu_button.h"
#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/test/menu_test_utils.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
MenuTestBase::MenuTestBase() MenuTestBase::MenuTestBase()
...@@ -29,6 +30,7 @@ void MenuTestBase::Click(views::View* view, const base::Closure& next) { ...@@ -29,6 +30,7 @@ void MenuTestBase::Click(views::View* view, const base::Closure& next) {
ui_controls::LEFT, ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP, ui_controls::DOWN | ui_controls::UP,
next); next);
views::test::WaitForMenuClosureAnimation();
} }
void MenuTestBase::KeyPress(ui::KeyboardCode keycode, base::OnceClosure next) { void MenuTestBase::KeyPress(ui::KeyboardCode keycode, base::OnceClosure next) {
...@@ -42,6 +44,8 @@ int MenuTestBase::GetMenuRunnerFlags() { ...@@ -42,6 +44,8 @@ int MenuTestBase::GetMenuRunnerFlags() {
} }
void MenuTestBase::SetUp() { void MenuTestBase::SetUp() {
views::test::DisableMenuClosureAnimations();
button_ = new views::MenuButton(base::ASCIIToUTF16("Menu Test"), this, true); button_ = new views::MenuButton(base::ASCIIToUTF16("Menu Test"), this, true);
menu_ = new views::MenuItemView(this); menu_ = new views::MenuItemView(this);
BuildMenu(menu_); BuildMenu(menu_);
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_controller.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/controls/menu/submenu_view.h"
#include "ui/views/test/menu_test_utils.h"
namespace { namespace {
...@@ -127,6 +128,7 @@ void TestWhileContextMenuOpen(Browser* browser, ...@@ -127,6 +128,7 @@ void TestWhileContextMenuOpen(Browser* browser,
ui_controls::SendMouseMove(action_view_loc.x(), action_view_loc.y()); ui_controls::SendMouseMove(action_view_loc.x(), action_view_loc.y());
EXPECT_TRUE(ui_test_utils::SendMouseEventsSync( EXPECT_TRUE(ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP)); ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP));
views::test::WaitForMenuClosureAnimation();
// Test resumes in the main test body. // Test resumes in the main test body.
} }
...@@ -216,6 +218,7 @@ IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest, ...@@ -216,6 +218,7 @@ IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest, IN_PROC_BROWSER_TEST_F(ToolbarActionViewInteractiveUITest,
MAYBE_TestContextMenuOnOverflowedAction) { MAYBE_TestContextMenuOnOverflowedAction) {
views::MenuController::TurnOffMenuSelectionHoldForTest(); views::MenuController::TurnOffMenuSelectionHoldForTest();
views::test::DisableMenuClosureAnimations();
// Load an extension that has a home page (important for the context menu's // Load an extension that has a home page (important for the context menu's
// first item being enabled). // first item being enabled).
......
...@@ -120,6 +120,7 @@ jumbo_component("views") { ...@@ -120,6 +120,7 @@ jumbo_component("views") {
"controls/label.h", "controls/label.h",
"controls/link.h", "controls/link.h",
"controls/link_listener.h", "controls/link_listener.h",
"controls/menu/menu_closure_animation_mac.h",
"controls/menu/menu_config.h", "controls/menu/menu_config.h",
"controls/menu/menu_controller.h", "controls/menu/menu_controller.h",
"controls/menu/menu_controller_delegate.h", "controls/menu/menu_controller_delegate.h",
...@@ -317,6 +318,7 @@ jumbo_component("views") { ...@@ -317,6 +318,7 @@ jumbo_component("views") {
"controls/label.cc", "controls/label.cc",
"controls/link.cc", "controls/link.cc",
"controls/menu/display_change_listener_mac.cc", "controls/menu/display_change_listener_mac.cc",
"controls/menu/menu_closure_animation_mac.mm",
"controls/menu/menu_config.cc", "controls/menu/menu_config.cc",
"controls/menu/menu_config_chromeos.cc", "controls/menu/menu_config_chromeos.cc",
"controls/menu/menu_config_linux.cc", "controls/menu/menu_config_linux.cc",
......
// Copyright 2018 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_CONTROLS_MENU_MENU_CLOSURE_ANIMATION_MAC_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_CLOSURE_ANIMATION_MAC_H_
#include "base/macros.h"
#include "base/timer/timer.h"
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/views/views_export.h"
namespace views {
class MenuItemView;
// This class implements the Mac menu closure animation:
// 1) For 100ms, the selected item is drawn as unselected
// 2) Then, for another 100ms, the selected item is drawn as selected
// 3) Then, and the window fades over 250ms to transparency
// Note that this class is owned by the involved MenuController, so if the menu
// is destructed early for any reason, this class will be destructed also, which
// 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
// received it is being deleted.
class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate {
public:
// After this closure animation is done, |callback| is run to finally accept
// |item|.
MenuClosureAnimationMac(MenuItemView* item, base::OnceClosure callback);
~MenuClosureAnimationMac() override;
// Start the animation.
void Start();
// Returns the MenuItemView this animation targets.
MenuItemView* item() { return item_; }
// Causes animations to take no time for testing purposes. Note that this
// still causes the completion callback to be run asynchronously, so test
// situations have the same control flow as non-test situations.
static void DisableAnimationsForTesting();
private:
enum class AnimationStep {
kStart,
kUnselected,
kSelected,
kFading,
kFinish,
};
static constexpr AnimationStep NextStepFor(AnimationStep step);
void AdvanceAnimation();
// gfx::AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
void AnimationCanceled(const gfx::Animation* animation) override;
base::OnceClosure callback_;
base::OneShotTimer timer_;
std::unique_ptr<gfx::Animation> fade_animation_;
MenuItemView* item_;
AnimationStep step_;
DISALLOW_COPY_AND_ASSIGN(MenuClosureAnimationMac);
};
} // namespace views
#endif // UI_VIEWS_CONTROLS_MENU_MENU_CLOSURE_ANIMATION_MAC_H_
// Copyright 2018 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/controls/menu/menu_closure_animation_mac.h"
#import <Cocoa/Cocoa.h>
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/widget/widget.h"
namespace {
static bool g_disable_animations_for_testing = false;
}
namespace views {
MenuClosureAnimationMac::MenuClosureAnimationMac(MenuItemView* item,
base::OnceClosure callback)
: callback_(std::move(callback)),
item_(item),
step_(AnimationStep::kStart) {}
MenuClosureAnimationMac::~MenuClosureAnimationMac() {}
void MenuClosureAnimationMac::Start() {
DCHECK_EQ(step_, AnimationStep::kStart);
if (g_disable_animations_for_testing) {
// Even when disabling animations, simulate the fact that the eventual
// accept callback will happen after a runloop cycle by skipping to the end
// of the animation.
step_ = AnimationStep::kFading;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&MenuClosureAnimationMac::AdvanceAnimation,
base::Unretained(this)));
return;
}
AdvanceAnimation();
}
// static
constexpr MenuClosureAnimationMac::AnimationStep
MenuClosureAnimationMac::NextStepFor(
MenuClosureAnimationMac::AnimationStep step) {
switch (step) {
case AnimationStep::kStart:
return AnimationStep::kUnselected;
case AnimationStep::kUnselected:
return AnimationStep::kSelected;
case AnimationStep::kSelected:
return AnimationStep::kFading;
case AnimationStep::kFading:
return AnimationStep::kFinish;
case AnimationStep::kFinish:
return AnimationStep::kFinish;
}
}
void MenuClosureAnimationMac::AdvanceAnimation() {
step_ = NextStepFor(step_);
if (step_ == AnimationStep::kUnselected ||
step_ == AnimationStep::kSelected) {
item_->SetForcedVisualSelection(step_ == AnimationStep::kSelected);
timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(80),
base::BindRepeating(&MenuClosureAnimationMac::AdvanceAnimation,
base::Unretained(this)));
} else if (step_ == AnimationStep::kFading) {
auto fade = std::make_unique<gfx::LinearAnimation>(this);
fade->SetDuration(base::TimeDelta::FromMilliseconds(200));
fade_animation_ = std::move(fade);
fade_animation_->Start();
} else if (step_ == AnimationStep::kFinish) {
std::move(callback_).Run();
}
}
// static
void MenuClosureAnimationMac::DisableAnimationsForTesting() {
g_disable_animations_for_testing = true;
}
void MenuClosureAnimationMac::AnimationProgressed(
const gfx::Animation* animation) {
NSWindow* window = item_->GetWidget()->GetNativeWindow();
[window setAlphaValue:animation->CurrentValueBetween(1.0, 0.0)];
}
void MenuClosureAnimationMac::AnimationEnded(const gfx::Animation* animation) {
AdvanceAnimation();
}
void MenuClosureAnimationMac::AnimationCanceled(
const gfx::Animation* animation) {
NOTREACHED();
}
} // namespace views
...@@ -1148,6 +1148,13 @@ void MenuController::TurnOffMenuSelectionHoldForTest() { ...@@ -1148,6 +1148,13 @@ void MenuController::TurnOffMenuSelectionHoldForTest() {
menu_selection_hold_time_ms = -1; menu_selection_hold_time_ms = -1;
} }
void MenuController::OnMenuItemDestroying(MenuItemView* menu_item) {
#if defined(OS_MACOSX)
if (menu_closure_animation_ && menu_closure_animation_->item() == menu_item)
menu_closure_animation_.reset();
#endif
}
void MenuController::SetSelection(MenuItemView* menu_item, void MenuController::SetSelection(MenuItemView* menu_item,
int selection_types) { int selection_types) {
size_t paths_differ_at = 0; size_t paths_differ_at = 0;
...@@ -1473,6 +1480,18 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds, ...@@ -1473,6 +1480,18 @@ 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)
menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>(
item,
base::BindOnce(&MenuController::ReallyAccept, base::Unretained(this),
base::Unretained(item), event_flags));
menu_closure_animation_->Start();
#else
ReallyAccept(item, event_flags);
#endif
}
void MenuController::ReallyAccept(MenuItemView* item, int event_flags) {
DCHECK(IsBlockingRun()); DCHECK(IsBlockingRun());
result_ = item; result_ = item;
if (item && !menu_stack_.empty() && if (item && !menu_stack_.empty() &&
...@@ -2747,4 +2766,12 @@ void MenuController::SetHotTrackedButton(Button* hot_button) { ...@@ -2747,4 +2766,12 @@ void MenuController::SetHotTrackedButton(Button* hot_button) {
} }
} }
bool MenuController::CanProcessInputEvents() const {
#if defined(OS_MACOSX)
return !menu_closure_animation_;
#else
return true;
#endif
}
} // namespace views } // namespace views
...@@ -25,6 +25,10 @@ ...@@ -25,6 +25,10 @@
#include "ui/views/controls/menu/menu_delegate.h" #include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/widget/widget_observer.h" #include "ui/views/widget/widget_observer.h"
#if defined(OS_MACOSX)
#include "ui/views/controls/menu/menu_closure_animation_mac.h"
#endif
namespace ui { namespace ui {
class OSExchangeData; class OSExchangeData;
} }
...@@ -204,6 +208,13 @@ class VIEWS_EXPORT MenuController ...@@ -204,6 +208,13 @@ class VIEWS_EXPORT MenuController
} }
bool use_touchable_layout() const { return use_touchable_layout_; } bool use_touchable_layout() const { return use_touchable_layout_; }
// Notifies |this| that |menu_item| is being destroyed.
void OnMenuItemDestroying(MenuItemView* menu_item);
// Returns whether this menu can handle input events right now. This method
// can return false while running animations.
bool CanProcessInputEvents() const;
private: private:
friend class internal::MenuRunnerImpl; friend class internal::MenuRunnerImpl;
friend class test::MenuControllerTest; friend class test::MenuControllerTest;
...@@ -344,6 +355,7 @@ class VIEWS_EXPORT MenuController ...@@ -344,6 +355,7 @@ class VIEWS_EXPORT MenuController
// Invoked when the user accepts the selected item. This is only used // Invoked when the user accepts the selected item. This is only used
// when blocking. This schedules the loop to quit. // when blocking. This schedules the loop to quit.
void Accept(MenuItemView* item, int event_flags); void Accept(MenuItemView* item, int event_flags);
void ReallyAccept(MenuItemView* item, int event_flags);
bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location); bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location);
...@@ -699,6 +711,10 @@ class VIEWS_EXPORT MenuController ...@@ -699,6 +711,10 @@ class VIEWS_EXPORT MenuController
// A mask of the EventFlags for the mouse buttons currently pressed. // A mask of the EventFlags for the mouse buttons currently pressed.
int current_mouse_pressed_state_ = 0; int current_mouse_pressed_state_ = 0;
#if defined(OS_MACOSX)
std::unique_ptr<MenuClosureAnimationMac> menu_closure_animation_;
#endif
#if defined(USE_AURA) #if defined(USE_AURA)
std::unique_ptr<MenuPreTargetHandler> menu_pre_target_handler_; std::unique_ptr<MenuPreTargetHandler> menu_pre_target_handler_;
#endif #endif
......
...@@ -464,6 +464,7 @@ class MenuControllerTest : public ViewsTestBase { ...@@ -464,6 +464,7 @@ class MenuControllerTest : public ViewsTestBase {
void Accept(MenuItemView* item, int event_flags) { void Accept(MenuItemView* item, int event_flags) {
menu_controller_->Accept(item, event_flags); menu_controller_->Accept(item, event_flags);
views::test::WaitForMenuClosureAnimation();
} }
// Causes the |menu_controller_| to begin dragging. Use TestDragDropClient to // Causes the |menu_controller_| to begin dragging. Use TestDragDropClient to
...@@ -943,6 +944,8 @@ TEST_F(MenuControllerTest, ChildButtonHotTrackedWhenNested) { ...@@ -943,6 +944,8 @@ TEST_F(MenuControllerTest, ChildButtonHotTrackedWhenNested) {
// Tests that a menu opened asynchronously, will notify its // Tests that a menu opened asynchronously, will notify its
// MenuControllerDelegate when Accept is called. // MenuControllerDelegate when Accept is called.
TEST_F(MenuControllerTest, AsynchronousAccept) { TEST_F(MenuControllerTest, AsynchronousAccept) {
views::test::DisableMenuClosureAnimations();
MenuController* controller = menu_controller(); MenuController* controller = menu_controller();
controller->Run(owner(), nullptr, menu_item(), gfx::Rect(), controller->Run(owner(), nullptr, menu_item(), gfx::Rect(),
MENU_ANCHOR_TOPLEFT, false, false); MENU_ANCHOR_TOPLEFT, false, false);
...@@ -1254,6 +1257,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { ...@@ -1254,6 +1257,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) {
// Tests that having the MenuController deleted during OnGestureEvent does not // Tests that having the MenuController deleted during OnGestureEvent 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, AsynchronousGestureDeletesController) { TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) {
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());
...@@ -1277,6 +1281,7 @@ TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) { ...@@ -1277,6 +1281,7 @@ TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) {
// gesture event. The remainder of this test, and TearDown should not crash. // gesture event. The remainder of this test, and TearDown should not crash.
DestroyMenuControllerOnMenuClosed(nested_delegate.get()); DestroyMenuControllerOnMenuClosed(nested_delegate.get());
controller->OnGestureEvent(sub_menu, &event); controller->OnGestureEvent(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();
......
...@@ -14,40 +14,41 @@ MenuHostRootView::MenuHostRootView(Widget* widget, SubmenuView* submenu) ...@@ -14,40 +14,41 @@ MenuHostRootView::MenuHostRootView(Widget* widget, SubmenuView* submenu)
: internal::RootView(widget), submenu_(submenu) {} : internal::RootView(widget), submenu_(submenu) {}
bool MenuHostRootView::OnMousePressed(const ui::MouseEvent& event) { bool MenuHostRootView::OnMousePressed(const ui::MouseEvent& event) {
return GetMenuController() && return GetMenuControllerForInputEvents() &&
GetMenuController()->OnMousePressed(submenu_, event); GetMenuControllerForInputEvents()->OnMousePressed(submenu_, event);
} }
bool MenuHostRootView::OnMouseDragged(const ui::MouseEvent& event) { bool MenuHostRootView::OnMouseDragged(const ui::MouseEvent& event) {
return GetMenuController() && return GetMenuControllerForInputEvents() &&
GetMenuController()->OnMouseDragged(submenu_, event); GetMenuControllerForInputEvents()->OnMouseDragged(submenu_, event);
} }
void MenuHostRootView::OnMouseReleased(const ui::MouseEvent& event) { void MenuHostRootView::OnMouseReleased(const ui::MouseEvent& event) {
if (GetMenuController()) if (GetMenuControllerForInputEvents())
GetMenuController()->OnMouseReleased(submenu_, event); GetMenuControllerForInputEvents()->OnMouseReleased(submenu_, event);
} }
void MenuHostRootView::OnMouseMoved(const ui::MouseEvent& event) { void MenuHostRootView::OnMouseMoved(const ui::MouseEvent& event) {
if (GetMenuController()) if (GetMenuControllerForInputEvents())
GetMenuController()->OnMouseMoved(submenu_, event); GetMenuControllerForInputEvents()->OnMouseMoved(submenu_, event);
} }
bool MenuHostRootView::OnMouseWheel(const ui::MouseWheelEvent& event) { bool MenuHostRootView::OnMouseWheel(const ui::MouseWheelEvent& event) {
return GetMenuController() && return GetMenuControllerForInputEvents() &&
GetMenuController()->OnMouseWheel(submenu_, event); GetMenuControllerForInputEvents()->OnMouseWheel(submenu_, event);
} }
View* MenuHostRootView::GetTooltipHandlerForPoint(const gfx::Point& point) { View* MenuHostRootView::GetTooltipHandlerForPoint(const gfx::Point& point) {
return GetMenuController() return GetMenuControllerForInputEvents()
? GetMenuController()->GetTooltipHandlerForPoint(submenu_, point) ? GetMenuControllerForInputEvents()->GetTooltipHandlerForPoint(
submenu_, point)
: nullptr; : nullptr;
} }
void MenuHostRootView::ViewHierarchyChanged( void MenuHostRootView::ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) { const ViewHierarchyChangedDetails& details) {
if (GetMenuController()) if (GetMenuControllerForInputEvents())
GetMenuController()->ViewHierarchyChanged(submenu_, details); GetMenuControllerForInputEvents()->ViewHierarchyChanged(submenu_, details);
RootView::ViewHierarchyChanged(details); RootView::ViewHierarchyChanged(details);
} }
...@@ -87,4 +88,10 @@ MenuController* MenuHostRootView::GetMenuController() { ...@@ -87,4 +88,10 @@ MenuController* MenuHostRootView::GetMenuController() {
return submenu_ ? submenu_->GetMenuItem()->GetMenuController() : NULL; return submenu_ ? submenu_->GetMenuItem()->GetMenuController() : NULL;
} }
MenuController* MenuHostRootView::GetMenuControllerForInputEvents() {
return GetMenuController() && GetMenuController()->CanProcessInputEvents()
? GetMenuController()
: nullptr;
}
} // namespace views } // namespace views
...@@ -47,6 +47,7 @@ class MenuHostRootView : public internal::RootView { ...@@ -47,6 +47,7 @@ class MenuHostRootView : public internal::RootView {
// Returns the MenuController for this MenuHostRootView. // Returns the MenuController for this MenuHostRootView.
MenuController* GetMenuController(); MenuController* GetMenuController();
MenuController* GetMenuControllerForInputEvents();
// The SubmenuView we contain. // The SubmenuView we contain.
SubmenuView* submenu_; SubmenuView* submenu_;
......
...@@ -628,6 +628,11 @@ void MenuItemView::SetMargins(int top_margin, int bottom_margin) { ...@@ -628,6 +628,11 @@ void MenuItemView::SetMargins(int top_margin, int bottom_margin) {
invalidate_dimensions(); invalidate_dimensions();
} }
void MenuItemView::SetForcedVisualSelection(bool selected) {
forced_visual_selection_ = selected;
SchedulePaint();
}
MenuItemView::MenuItemView(MenuItemView* parent, MenuItemView::MenuItemView(MenuItemView* parent,
int command, int command,
MenuItemView::Type type) MenuItemView::Type type)
...@@ -654,6 +659,8 @@ MenuItemView::MenuItemView(MenuItemView* parent, ...@@ -654,6 +659,8 @@ MenuItemView::MenuItemView(MenuItemView* parent,
} }
MenuItemView::~MenuItemView() { MenuItemView::~MenuItemView() {
if (GetMenuController())
GetMenuController()->OnMenuItemDestroying(this);
delete submenu_; delete submenu_;
for (auto* item : removed_items_) for (auto* item : removed_items_)
delete item; delete item;
...@@ -825,6 +832,8 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { ...@@ -825,6 +832,8 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
(mode == PB_NORMAL && IsSelected() && (mode == PB_NORMAL && IsSelected() &&
parent_menu_item_->GetSubmenu()->GetShowSelection(this) && parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
(NonIconChildViewsCount() == 0)); (NonIconChildViewsCount() == 0));
if (forced_visual_selection_.has_value())
render_selection = *forced_visual_selection_;
MenuDelegate *delegate = GetDelegate(); MenuDelegate *delegate = GetDelegate();
bool emphasized = bool emphasized =
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/base/models/menu_separator_types.h" #include "ui/base/models/menu_separator_types.h"
...@@ -337,6 +338,11 @@ class VIEWS_EXPORT MenuItemView : public View { ...@@ -337,6 +338,11 @@ class VIEWS_EXPORT MenuItemView : public View {
use_right_margin_ = use_right_margin; use_right_margin_ = use_right_margin;
} }
// Controls whether this menu has a forced visual selection state. This is
// used when animating item acceptance on Mac. Note that once this is set
// there's no way to unset it for this MenuItemView!
void SetForcedVisualSelection(bool selected);
protected: protected:
// Creates a MenuItemView. This is used by the various AddXXX methods. // Creates a MenuItemView. This is used by the various AddXXX methods.
MenuItemView(MenuItemView* parent, int command, Type type); MenuItemView(MenuItemView* parent, int command, Type type);
...@@ -556,6 +562,9 @@ class VIEWS_EXPORT MenuItemView : public View { ...@@ -556,6 +562,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// The submenu indicator arrow icon in case the menu item has a Submenu. // The submenu indicator arrow icon in case the menu item has a Submenu.
ImageView* submenu_arrow_image_view_; ImageView* submenu_arrow_image_view_;
// The forced visual selection state of this item, if any.
base::Optional<bool> forced_visual_selection_;
DISALLOW_COPY_AND_ASSIGN(MenuItemView); DISALLOW_COPY_AND_ASSIGN(MenuItemView);
}; };
......
...@@ -163,6 +163,7 @@ TEST_F(MenuRunnerTest, LatinMnemonic) { ...@@ -163,6 +163,7 @@ TEST_F(MenuRunnerTest, LatinMnemonic) {
if (IsMus()) if (IsMus())
return; return;
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0); InitMenuRunner(0);
MenuRunner* runner = menu_runner(); MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
...@@ -171,6 +172,7 @@ TEST_F(MenuRunnerTest, LatinMnemonic) { ...@@ -171,6 +172,7 @@ TEST_F(MenuRunnerTest, LatinMnemonic) {
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow()); ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
generator.PressKey(ui::VKEY_O, 0); generator.PressKey(ui::VKEY_O, 0);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning()); EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate(); TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(1, delegate->execute_command_id()); EXPECT_EQ(1, delegate->execute_command_id());
...@@ -186,6 +188,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { ...@@ -186,6 +188,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) {
if (IsMus()) if (IsMus())
return; return;
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0); InitMenuRunner(0);
MenuRunner* runner = menu_runner(); MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
...@@ -195,6 +198,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { ...@@ -195,6 +198,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) {
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow()); ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
ui::KeyEvent key_press(0x062f, ui::VKEY_N, 0); ui::KeyEvent key_press(0x062f, ui::VKEY_N, 0);
generator.Dispatch(&key_press); generator.Dispatch(&key_press);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning()); EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate(); TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(2, delegate->execute_command_id()); EXPECT_EQ(2, delegate->execute_command_id());
......
...@@ -4,8 +4,14 @@ ...@@ -4,8 +4,14 @@
#include "ui/views/test/menu_test_utils.h" #include "ui/views/test/menu_test_utils.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_controller.h"
#if defined(OS_MACOSX)
#include "ui/views/controls/menu/menu_closure_animation_mac.h"
#endif
namespace views { namespace views {
namespace test { namespace test {
...@@ -75,5 +81,17 @@ void MenuControllerTestApi::SetShowing(bool showing) { ...@@ -75,5 +81,17 @@ void MenuControllerTestApi::SetShowing(bool showing) {
controller_->showing_ = showing; controller_->showing_ = showing;
} }
void DisableMenuClosureAnimations() {
#if defined(OS_MACOSX)
MenuClosureAnimationMac::DisableAnimationsForTesting();
#endif
}
void WaitForMenuClosureAnimation() {
#if defined(OS_MACOSX)
base::RunLoop().RunUntilIdle();
#endif
}
} // namespace test } // namespace test
} // namespace views } // namespace views
...@@ -98,6 +98,15 @@ class MenuControllerTestApi { ...@@ -98,6 +98,15 @@ class MenuControllerTestApi {
DISALLOW_COPY_AND_ASSIGN(MenuControllerTestApi); DISALLOW_COPY_AND_ASSIGN(MenuControllerTestApi);
}; };
// On platforms which have menu closure animations, these functions are
// necessary to:
// 1) Disable those animations (make them take zero time) to avoid slowing
// down tests;
// 2) Wait for maybe-asynchronous menu closure to finish.
// On platforms without menu closure animations, these do nothing.
void DisableMenuClosureAnimations();
void WaitForMenuClosureAnimation();
} // namespace test } // namespace test
} // namespace views } // namespace views
......
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