Revert "Delete tab pulsing feature."
This reverts commit 1dd90008554cac4a0a1637f7ec1d45aeb16eb254.
Reason for revert: breaks linux-jumbo-rel (failure log: https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket.appspot.com/8924308573690053872/+/steps/compile/0/stdout)
Original change's description:
> Delete tab pulsing feature.
>
> Previously, when hovering tab context menu items "Close tabs to the
> right" and "Close other tabs", the affected tabs would pulse. This patch
> deletes that feature. This caused SimpleMenuModel::CommandIdHighlighted
> and transitively MenuModel::HighlightChangedTo to become unused. This
> also caused TabStrip::animation_container_ to become effectively unused,
> since pulsing was the only animation that needed to be coordinated
> between tabs.
>
> Bug: 921243
> Change-Id: Iae4ef39d979fa5f5a96ffc465275f25385f5949f
> Reviewed-on: https://chromium-review.googlesource.com/c/1407958
> Commit-Queue: Bret Sepulveda <bsep@chromium.org>
> Reviewed-by: Drew Wilson <atwilson@chromium.org>
> Reviewed-by: Scott Violet <sky@chromium.org>
> Reviewed-by: Avi Drissman <avi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#622657}
TBR=avi@chromium.org,sky@chromium.org,atwilson@chromium.org,bsep@chromium.org
Change-Id: I96285c3f0b2601cc59c98b5d8abde97fd04afdf4
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 921243
Reviewed-on: https://chromium-review.googlesource.com/c/1409886
Reviewed-by: Kevin Marshall <kmarshall@chromium.org>
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#622668}
diff --git a/chrome/browser/status_icons/status_icon_menu_model.cc b/chrome/browser/status_icons/status_icon_menu_model.cc
index 6deae7c..2d46f71 100644
--- a/chrome/browser/status_icons/status_icon_menu_model.cc
+++ b/chrome/browser/status_icons/status_icon_menu_model.cc
@@ -25,6 +25,12 @@
};
////////////////////////////////////////////////////////////////////////////////
+// StatusIconMenuModel::Delegate, public:
+
+void StatusIconMenuModel::Delegate::CommandIdHighlighted(int command_id) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
// StatusIconMenuModel, public:
StatusIconMenuModel::StatusIconMenuModel(Delegate* delegate)
@@ -163,6 +169,11 @@
////////////////////////////////////////////////////////////////////////////////
// StatusIconMenuModel, private:
+void StatusIconMenuModel::CommandIdHighlighted(int command_id) {
+ if (delegate_)
+ delegate_->CommandIdHighlighted(command_id);
+}
+
void StatusIconMenuModel::ExecuteCommand(int command_id, int event_flags) {
if (delegate_)
delegate_->ExecuteCommand(command_id, event_flags);
diff --git a/chrome/browser/status_icons/status_icon_menu_model.h b/chrome/browser/status_icons/status_icon_menu_model.h
index 556f531f..c1514ee 100644
--- a/chrome/browser/status_icons/status_icon_menu_model.h
+++ b/chrome/browser/status_icons/status_icon_menu_model.h
@@ -28,6 +28,10 @@
public:
class Delegate {
public:
+ // Notifies the delegate that the item with the specified command id was
+ // visually highlighted within the menu.
+ virtual void CommandIdHighlighted(int command_id);
+
// Performs the action associates with the specified command id.
// The passed |event_flags| are the flags from the event which issued this
// command and they can be examined to find modifier keys.
@@ -93,6 +97,7 @@
private:
// Overridden from ui::SimpleMenuModel::Delegate:
+ void CommandIdHighlighted(int command_id) override;
void ExecuteCommand(int command_id, int event_flags) override;
struct ItemState;
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 7dc8104..67bc9fb 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -1200,6 +1200,27 @@
}
}
+std::vector<int> TabStripModel::GetIndicesClosedByCommand(
+ int index,
+ ContextMenuCommand id) const {
+ DCHECK(ContainsIndex(index));
+ DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);
+ bool is_selected = IsTabSelected(index);
+ int last_unclosed_tab = -1;
+ if (id == CommandCloseTabsToRight) {
+ last_unclosed_tab =
+ is_selected ? selection_model_.selected_indices().back() : index;
+ }
+
+ // NOTE: callers expect the vector to be sorted in descending order.
+ std::vector<int> indices;
+ for (int i = count() - 1; i > last_unclosed_tab; --i) {
+ if (i != index && !IsTabPinned(i) && (!is_selected || !IsTabSelected(i)))
+ indices.push_back(i);
+ }
+ return indices;
+}
+
bool TabStripModel::WillContextMenuMute(int index) {
std::vector<int> indices = GetIndicesForCommand(index);
return !chrome::AreAllTabsMuted(*this, indices);
@@ -1316,27 +1337,6 @@
return selection_model_.selected_indices();
}
-std::vector<int> TabStripModel::GetIndicesClosedByCommand(
- int index,
- ContextMenuCommand id) const {
- DCHECK(ContainsIndex(index));
- DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);
- bool is_selected = IsTabSelected(index);
- int last_unclosed_tab = -1;
- if (id == CommandCloseTabsToRight) {
- last_unclosed_tab =
- is_selected ? selection_model_.selected_indices().back() : index;
- }
-
- // NOTE: callers expect the vector to be sorted in descending order.
- std::vector<int> indices;
- for (int i = count() - 1; i > last_unclosed_tab; --i) {
- if (i != index && !IsTabPinned(i) && (!is_selected || !IsTabSelected(i)))
- indices.push_back(i);
- }
- return indices;
-}
-
bool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const {
const GURL& url = contents->GetLastCommittedURL();
return url.SchemeIs(content::kChromeUIScheme) &&
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index aeca460e..7dfc763 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -12,7 +12,6 @@
#include <vector>
#include "base/containers/span.h"
-#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
@@ -379,6 +378,12 @@
void ExecuteContextMenuCommand(int context_index,
ContextMenuCommand command_id);
+ // Returns a vector of indices of the tabs that will close when executing the
+ // command |id| for the tab at |index|. The returned indices are sorted in
+ // descending order.
+ std::vector<int> GetIndicesClosedByCommand(int index,
+ ContextMenuCommand id) const;
+
// Returns true if 'CommandToggleTabAudioMuted' will mute. |index| is the
// index supplied to |ExecuteContextMenuCommand|.
bool WillContextMenuMute(int index);
@@ -419,8 +424,6 @@
bool ShouldResetOpenerOnActiveTabChange(content::WebContents* contents) const;
private:
- FRIEND_TEST_ALL_PREFIXES(TabStripModelTest, GetIndicesClosedByCommand);
-
class WebContentsData;
struct DetachedWebContents;
struct DetachNotifications;
@@ -457,12 +460,6 @@
// increasing order.
std::vector<int> GetIndicesForCommand(int index) const;
- // Returns a vector of indices of the tabs that will close when executing the
- // command |id| for the tab at |index|. The returned indices are sorted in
- // descending order.
- std::vector<int> GetIndicesClosedByCommand(int index,
- ContextMenuCommand id) const;
-
// Returns true if the specified WebContents is a New Tab at the end of
// the tabstrip. We check for this because opener relationships are _not_
// forgotten for the New Tab page opened as a result of a New Tab gesture
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index d5a3afb55..fc006c5 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -427,6 +427,20 @@
return actual;
}
+ std::string GetIndicesClosedByCommandAsString(
+ const TabStripModel& model,
+ int index,
+ TabStripModel::ContextMenuCommand id) const {
+ std::vector<int> indices = model.GetIndicesClosedByCommand(index, id);
+ std::string result;
+ for (size_t i = 0; i < indices.size(); ++i) {
+ if (i != 0)
+ result += " ";
+ result += base::IntToString(indices[i]);
+ }
+ return result;
+ }
+
void PrepareTabstripForSelectionTest(TabStripModel* model,
int tab_count,
int pinned_count,
@@ -1328,46 +1342,34 @@
TabStripModel tabstrip(&delegate, profile());
EXPECT_TRUE(tabstrip.empty());
- const auto indicesClosedAsString =
- [&tabstrip](int index, TabStripModel::ContextMenuCommand id) {
- std::vector<int> indices =
- tabstrip.GetIndicesClosedByCommand(index, id);
- std::string result;
- for (size_t i = 0; i < indices.size(); ++i) {
- if (i != 0)
- result += " ";
- result += base::IntToString(indices[i]);
- }
- return result;
- };
-
for (int i = 0; i < 5; ++i)
tabstrip.AppendWebContents(CreateWebContents(), true);
EXPECT_EQ("4 3 2 1",
- indicesClosedAsString(0, TabStripModel::CommandCloseTabsToRight));
- EXPECT_EQ("4 3 2",
- indicesClosedAsString(1, TabStripModel::CommandCloseTabsToRight));
+ GetIndicesClosedByCommandAsString(
+ tabstrip, 0, TabStripModel::CommandCloseTabsToRight));
+ EXPECT_EQ("4 3 2", GetIndicesClosedByCommandAsString(
+ tabstrip, 1, TabStripModel::CommandCloseTabsToRight));
- EXPECT_EQ("4 3 2 1",
- indicesClosedAsString(0, TabStripModel::CommandCloseOtherTabs));
- EXPECT_EQ("4 3 2 0",
- indicesClosedAsString(1, TabStripModel::CommandCloseOtherTabs));
+ EXPECT_EQ("4 3 2 1", GetIndicesClosedByCommandAsString(
+ tabstrip, 0, TabStripModel::CommandCloseOtherTabs));
+ EXPECT_EQ("4 3 2 0", GetIndicesClosedByCommandAsString(
+ tabstrip, 1, TabStripModel::CommandCloseOtherTabs));
// Pin the first two tabs. Pinned tabs shouldn't be closed by the close other
// commands.
tabstrip.SetTabPinned(0, true);
tabstrip.SetTabPinned(1, true);
- EXPECT_EQ("4 3 2",
- indicesClosedAsString(0, TabStripModel::CommandCloseTabsToRight));
- EXPECT_EQ("4 3",
- indicesClosedAsString(2, TabStripModel::CommandCloseTabsToRight));
+ EXPECT_EQ("4 3 2", GetIndicesClosedByCommandAsString(
+ tabstrip, 0, TabStripModel::CommandCloseTabsToRight));
+ EXPECT_EQ("4 3", GetIndicesClosedByCommandAsString(
+ tabstrip, 2, TabStripModel::CommandCloseTabsToRight));
- EXPECT_EQ("4 3 2",
- indicesClosedAsString(0, TabStripModel::CommandCloseOtherTabs));
- EXPECT_EQ("4 3",
- indicesClosedAsString(2, TabStripModel::CommandCloseOtherTabs));
+ EXPECT_EQ("4 3 2", GetIndicesClosedByCommandAsString(
+ tabstrip, 0, TabStripModel::CommandCloseOtherTabs));
+ EXPECT_EQ("4 3", GetIndicesClosedByCommandAsString(
+ tabstrip, 2, TabStripModel::CommandCloseOtherTabs));
tabstrip.CloseAllTabs();
EXPECT_TRUE(tabstrip.empty());
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.cc b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
index 66543de..39be1df 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model.cc
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
@@ -156,6 +156,9 @@
return nullptr;
}
+void BackForwardMenuModel::HighlightChangedTo(int index) {
+}
+
void BackForwardMenuModel::ActivatedAt(int index) {
ActivatedAt(index, 0);
}
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.h b/chrome/browser/ui/toolbar/back_forward_menu_model.h
index 5139414..48fd16ac 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model.h
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model.h
@@ -66,6 +66,7 @@
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
bool IsEnabledAt(int index) const override;
MenuModel* GetSubmenuModelAt(int index) const override;
+ void HighlightChangedTo(int index) override;
void ActivatedAt(int index) override;
void ActivatedAt(int index, int event_flags) override;
void MenuWillShow() override;
diff --git a/chrome/browser/ui/views/menu_model_adapter_test.cc b/chrome/browser/ui/views/menu_model_adapter_test.cc
index 9021259..7e52499 100644
--- a/chrome/browser/ui/views/menu_model_adapter_test.cc
+++ b/chrome/browser/ui/views/menu_model_adapter_test.cc
@@ -73,6 +73,8 @@
ui::MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
+ void HighlightChangedTo(int index) override {}
+
void ActivatedAt(int index) override {}
void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {}
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 29f9919..e268ea4 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -92,8 +92,11 @@
class BrowserTabStripController::TabContextMenuContents
: public ui::SimpleMenuModel::Delegate {
public:
- TabContextMenuContents(Tab* tab, BrowserTabStripController* controller)
- : tab_(tab), controller_(controller) {
+ TabContextMenuContents(Tab* tab,
+ BrowserTabStripController* controller)
+ : tab_(tab),
+ controller_(controller),
+ last_command_(TabStripModel::CommandFirst) {
model_.reset(new TabMenuModel(
this, controller->model_,
controller->tabstrip_->GetModelIndexOfTab(tab)));
@@ -102,6 +105,11 @@
views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
}
+ ~TabContextMenuContents() override {
+ if (controller_)
+ controller_->tabstrip_->StopAllHighlighting();
+ }
+
void Cancel() {
controller_ = NULL;
}
@@ -128,14 +136,25 @@
accelerator) :
false;
}
+ void CommandIdHighlighted(int command_id) override {
+ controller_->StopHighlightTabsForCommand(last_command_, tab_);
+ last_command_ = static_cast<TabStripModel::ContextMenuCommand>(command_id);
+ controller_->StartHighlightTabsForCommand(last_command_, tab_);
+ }
void ExecuteCommand(int command_id, int event_flags) override {
// Executing the command destroys |this|, and can also end up destroying
// |controller_|. So stop the highlights before executing the command.
+ controller_->tabstrip_->StopAllHighlighting();
controller_->ExecuteCommandForTab(
static_cast<TabStripModel::ContextMenuCommand>(command_id),
tab_);
}
+ void MenuClosed(ui::SimpleMenuModel* /*source*/) override {
+ if (controller_)
+ controller_->tabstrip_->StopAllHighlighting();
+ }
+
private:
std::unique_ptr<TabMenuModel> model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
@@ -146,6 +165,10 @@
// A pointer back to our hosting controller, for command state information.
BrowserTabStripController* controller_;
+ // The last command that was selected, so that we can start/stop highlighting
+ // appropriately as the user moves through the menu.
+ TabStripModel::ContextMenuCommand last_command_;
+
DISALLOW_COPY_AND_ASSIGN(TabContextMenuContents);
};
@@ -584,6 +607,33 @@
TabRendererDataFromModel(web_contents, model_index, EXISTING_TAB));
}
+void BrowserTabStripController::StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id,
+ Tab* tab) {
+ if (command_id == TabStripModel::CommandCloseOtherTabs ||
+ command_id == TabStripModel::CommandCloseTabsToRight) {
+ int model_index = tabstrip_->GetModelIndexOfTab(tab);
+ if (IsValidIndex(model_index)) {
+ std::vector<int> indices =
+ model_->GetIndicesClosedByCommand(model_index, command_id);
+ for (std::vector<int>::const_iterator i(indices.begin());
+ i != indices.end(); ++i) {
+ tabstrip_->StartHighlight(*i);
+ }
+ }
+ }
+}
+
+void BrowserTabStripController::StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id,
+ Tab* tab) {
+ if (command_id == TabStripModel::CommandCloseTabsToRight ||
+ command_id == TabStripModel::CommandCloseOtherTabs) {
+ // Just tell all Tabs to stop pulsing - it's safe.
+ tabstrip_->StopAllHighlighting();
+ }
+}
+
void BrowserTabStripController::AddTab(WebContents* contents,
int index,
bool is_active) {
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index 9dfcf326..d72fb97 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -126,6 +126,13 @@
// Invokes tabstrip_->SetTabData.
void SetTabDataAt(content::WebContents* web_contents, int model_index);
+ void StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id,
+ Tab* tab);
+ void StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id,
+ Tab* tab);
+
// Adds a tab.
void AddTab(content::WebContents* contents, int index, bool is_active);
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 7efd7a00..46554c2 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -55,6 +55,7 @@
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/theme_provider.h"
#include "ui/compositor/clip_recorder.h"
+#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
@@ -118,8 +119,10 @@
// static
const char Tab::kViewClassName[] = "Tab";
-Tab::Tab(TabController* controller)
+Tab::Tab(TabController* controller, gfx::AnimationContainer* container)
: controller_(controller),
+ pulse_animation_(this),
+ animation_container_(container),
title_(new views::Label()),
title_animation_(this),
hover_controller_(this) {
@@ -161,7 +164,14 @@
set_context_menu_controller(this);
+ constexpr int kPulseDurationMs = 200;
+ pulse_animation_.SetSlideDuration(kPulseDurationMs);
+ pulse_animation_.SetContainer(animation_container_.get());
+
title_animation_.SetDuration(base::TimeDelta::FromMilliseconds(100));
+ title_animation_.SetContainer(animation_container_.get());
+
+ hover_controller_.SetAnimationContainer(animation_container_.get());
// Enable keyboard focus.
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
@@ -186,6 +196,11 @@
return;
}
+ // Ignore if the pulse animation is being performed on active tab because
+ // it repaints the same image. See PaintTab().
+ if (animation == &pulse_animation_ && IsActive())
+ return;
+
SchedulePaint();
}
@@ -738,6 +753,14 @@
return icon_->ShowingLoadingAnimation();
}
+void Tab::StartPulse() {
+ pulse_animation_.StartThrobbing(std::numeric_limits<int>::max());
+}
+
+void Tab::StopPulse() {
+ pulse_animation_.Stop();
+}
+
void Tab::SetTabNeedsAttention(bool attention) {
icon_->SetAttention(TabIcon::AttentionType::kTabWantsAttentionStatus,
attention);
@@ -825,7 +848,9 @@
return is_selected ? (kSelectedTabThrobScale * opacity) : opacity;
};
- if (hover_controller_.ShouldDraw())
+ if (pulse_animation_.is_animating())
+ val += pulse_animation_.GetCurrentValue() * offset();
+ else if (hover_controller_.ShouldDraw())
val += hover_controller_.GetAnimationValue() * offset();
return val;
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 1ceaec8..a9e3d7c 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -33,7 +33,9 @@
namespace gfx {
class Animation;
+class AnimationContainer;
class LinearAnimation;
+class ThrobAnimation;
}
namespace views {
class Label;
@@ -59,7 +61,7 @@
static constexpr int kMinimumContentsWidthForCloseButtons = 68;
static constexpr int kTouchMinimumContentsWidthForCloseButtons = 100;
- explicit Tab(TabController* controller);
+ Tab(TabController* controller, gfx::AnimationContainer* container);
~Tab() override;
// gfx::AnimationDelegate:
@@ -151,6 +153,10 @@
bool ShowingLoadingAnimation() const;
+ // Starts/Stops a pulse animation.
+ void StartPulse();
+ void StopPulse();
+
// Sets the visibility of the indicator shown when the tab needs to indicate
// to the user that it needs their attention.
void SetTabNeedsAttention(bool attention);
@@ -239,6 +245,11 @@
// True if the tab has been detached.
bool detached_ = false;
+ // Whole-tab throbbing "pulse" animation.
+ gfx::ThrobAnimation pulse_animation_;
+
+ scoped_refptr<gfx::AnimationContainer> animation_container_;
+
TabIcon* icon_ = nullptr;
AlertIndicator* alert_indicator_ = nullptr;
TabCloseButton* close_button_ = nullptr;
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 1c6cc0e..07b33cc 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -56,6 +56,7 @@
#include "ui/base/resource/resource_bundle.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
+#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
@@ -418,6 +419,15 @@
tab_at(i)->Layout();
}
+void TabStrip::StartHighlight(int model_index) {
+ tab_at(model_index)->StartPulse();
+}
+
+void TabStrip::StopAllHighlighting() {
+ for (int i = 0; i < tab_count(); ++i)
+ tab_at(i)->StopPulse();
+}
+
void TabStrip::AddTabAt(int model_index, TabRendererData data, bool is_active) {
const bool was_single_tab_mode = SingleTabMode();
@@ -427,7 +437,7 @@
view_index = GetIndexOf(tab_at(model_index - 1)) + 1;
}
- Tab* tab = new Tab(this);
+ Tab* tab = new Tab(this, animation_container_.get());
AddChildViewAt(tab, view_index);
const bool pinned = data.pinned;
tab->SetData(std::move(data));
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index ae80855..d7ec646c 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -23,6 +23,7 @@
#include "chrome/browser/ui/views/tabs/tab_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "ui/base/material_design/material_design_controller_observer.h"
+#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
@@ -139,6 +140,12 @@
// Returns the bounds of the new tab button.
gfx::Rect new_tab_button_bounds() const { return new_tab_button_bounds_; }
+ // Starts highlighting the tab at the specified index.
+ void StartHighlight(int model_index);
+
+ // Stops all tab higlighting.
+ void StopAllHighlighting();
+
// Adds a tab at the specified index.
void AddTabAt(int model_index, TabRendererData data, bool is_active);
@@ -676,6 +683,11 @@
// Valid for the lifetime of a drag over us.
std::unique_ptr<DropArrow> drop_arrow_;
+ // To ensure all tabs pulse at the same time they share the same animation
+ // container. This is that animation container.
+ scoped_refptr<gfx::AnimationContainer> animation_container_{
+ new gfx::AnimationContainer()};
+
// MouseWatcher is used for two things:
// . When a tab is closed to reset the layout.
// . When a mouse is used and the layout dynamically adjusts and is currently
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index 449445d2..211f507d 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -412,7 +412,7 @@
InitWidget(&widget);
FakeTabController tab_controller;
- Tab tab(&tab_controller);
+ Tab tab(&tab_controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SizeToPreferredSize();
@@ -446,7 +446,7 @@
InitWidget(&widget);
FakeTabController controller;
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
SkBitmap bitmap;
@@ -498,7 +498,7 @@
InitWidget(&widget);
FakeTabController controller;
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SizeToPreferredSize();
@@ -548,7 +548,7 @@
// shouldn't change the insets of the close button.
TEST_F(TabTest, CloseButtonLayout) {
FakeTabController tab_controller;
- Tab tab(&tab_controller);
+ Tab tab(&tab_controller, nullptr);
tab.SetBounds(0, 0, 100, 50);
LayoutTab(&tab);
gfx::Insets close_button_insets = GetCloseButton(tab)->GetInsets();
@@ -569,7 +569,7 @@
Widget widget;
InitWidget(&widget);
FakeTabController tab_controller;
- Tab tab(&tab_controller);
+ Tab tab(&tab_controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
views::ImageButton* tab_close_button = GetCloseButton(tab);
@@ -590,7 +590,7 @@
InitWidget(&widget);
FakeTabController tab_controller;
- Tab tab(&tab_controller);
+ Tab tab(&tab_controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SizeToPreferredSize();
@@ -701,7 +701,7 @@
TEST_F(TabTest, TitleHiddenWhenSmall) {
FakeTabController tab_controller;
- Tab tab(&tab_controller);
+ Tab tab(&tab_controller, nullptr);
tab.SetBounds(0, 0, 100, 50);
EXPECT_GT(GetTitleWidth(tab), 0);
tab.SetBounds(0, 0, 0, 50);
@@ -715,7 +715,7 @@
for (bool is_active_tab : {false, true}) {
FakeTabController controller;
controller.set_active_tab(is_active_tab);
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SizeToPreferredSize();
@@ -734,7 +734,7 @@
FakeTabController controller;
controller.set_active_tab(false);
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
const int width = tab.tab_style()->GetContentsInsets().width() +
Tab::kMinimumContentsWidthForCloseButtons;
@@ -757,7 +757,7 @@
FakeTabController controller;
controller.set_active_tab(true);
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SetBounds(0, 0, 200, 50);
const views::View* close = GetCloseButton(tab);
@@ -777,7 +777,7 @@
InitWidget(&widget);
FakeTabController controller;
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
tab.SizeToPreferredSize();
@@ -800,7 +800,7 @@
FakeTabController controller;
controller.set_active_tab(true);
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
TabRendererData data;
data.alert_state = TabAlertState::AUDIO_PLAYING;
@@ -845,7 +845,7 @@
Widget widget;
InitWidget(&widget);
FakeTabController controller;
- Tab tab(&controller);
+ Tab tab(&controller, nullptr);
widget.GetContentsView()->AddChildView(&tab);
for (const auto& colors : color_schemes) {
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button_views_unittest.cc b/chrome/browser/ui/views/toolbar/toolbar_button_views_unittest.cc
index 11607fb..2b5f47e 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button_views_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button_views_unittest.cc
@@ -51,6 +51,7 @@
}
bool IsEnabledAt(int index) const override { return false; }
ui::MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
+ void HighlightChangedTo(int index) override {}
void ActivatedAt(int index) override {}
void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {}
ui::MenuModelDelegate* GetMenuModelDelegate() const override {
diff --git a/ui/base/models/menu_model.h b/ui/base/models/menu_model.h
index bb53c04..fb958b18 100644
--- a/ui/base/models/menu_model.h
+++ b/ui/base/models/menu_model.h
@@ -112,6 +112,10 @@
// Returns the model for the submenu at the specified index.
virtual MenuModel* GetSubmenuModelAt(int index) const = 0;
+ // Called when the highlighted menu item changes to the item at the specified
+ // index.
+ virtual void HighlightChangedTo(int index) = 0;
+
// Called when the item at the specified index has been activated.
virtual void ActivatedAt(int index) = 0;
diff --git a/ui/base/models/simple_menu_model.cc b/ui/base/models/simple_menu_model.cc
index b9fbad7..4aa19f6 100644
--- a/ui/base/models/simple_menu_model.cc
+++ b/ui/base/models/simple_menu_model.cc
@@ -50,6 +50,9 @@
return false;
}
+void SimpleMenuModel::Delegate::CommandIdHighlighted(int command_id) {
+}
+
void SimpleMenuModel::Delegate::OnMenuWillShow(SimpleMenuModel* /*source*/) {}
void SimpleMenuModel::Delegate::MenuClosed(SimpleMenuModel* /*source*/) {
@@ -419,6 +422,11 @@
items_[ValidateItemIndex(index)].visible;
}
+void SimpleMenuModel::HighlightChangedTo(int index) {
+ if (delegate_)
+ delegate_->CommandIdHighlighted(GetCommandIdAt(index));
+}
+
void SimpleMenuModel::ActivatedAt(int index) {
ActivatedAt(index, 0);
}
diff --git a/ui/base/models/simple_menu_model.h b/ui/base/models/simple_menu_model.h
index b9f7b9b..ac1e1a7 100644
--- a/ui/base/models/simple_menu_model.h
+++ b/ui/base/models/simple_menu_model.h
@@ -46,6 +46,10 @@
virtual bool GetIconForCommandId(int command_id,
gfx::Image* icon) const;
+ // Notifies the delegate that the item with the specified command id was
+ // visually highlighted within the menu.
+ virtual void CommandIdHighlighted(int command_id);
+
// Performs the action associates with the specified command id.
// The passed |event_flags| are the flags from the event which issued this
// command and they can be examined to find modifier keys.
@@ -181,6 +185,7 @@
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
bool IsEnabledAt(int index) const override;
bool IsVisibleAt(int index) const override;
+ void HighlightChangedTo(int index) override;
void ActivatedAt(int index) override;
void ActivatedAt(int index, int event_flags) override;
MenuModel* GetSubmenuModelAt(int index) const override;
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 7863d712..62a28d66 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -185,6 +185,8 @@
return model_->IsItemEnabledAt(index);
}
+ void HighlightChangedTo(int index) override {}
+
void ActivatedAt(int index) override {
owner_->selected_index_ = index;
owner_->OnPerformAction();
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index d813c85..8751786 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -1249,6 +1249,9 @@
(selection_types & SELECTION_OPEN_SUBMENU) != 0);
}
+ if (menu_item && menu_item->GetDelegate())
+ menu_item->GetDelegate()->SelectionChanged(menu_item);
+
DCHECK(menu_item || (selection_types & SELECTION_EXIT) != 0);
pending_state_.item = menu_item;
diff --git a/ui/views/controls/menu/menu_delegate.h b/ui/views/controls/menu/menu_delegate.h
index 234ef7e..69a8ed5 100644
--- a/ui/views/controls/menu/menu_delegate.h
+++ b/ui/views/controls/menu/menu_delegate.h
@@ -194,6 +194,10 @@
// Views that are not MenuItemViews.
virtual bool ShouldCloseOnDragComplete();
+ // Notification that the user has highlighted the specified item.
+ virtual void SelectionChanged(MenuItemView* menu) {
+ }
+
// Notification the menu has closed. This will not be called if MenuRunner is
// deleted during calls to ExecuteCommand().
virtual void OnMenuClosed(MenuItemView* menu) {}
diff --git a/ui/views/controls/menu/menu_model_adapter.cc b/ui/views/controls/menu/menu_model_adapter.cc
index c91415cb..08132c52 100644
--- a/ui/views/controls/menu/menu_model_adapter.cc
+++ b/ui/views/controls/menu/menu_model_adapter.cc
@@ -220,6 +220,22 @@
return false;
}
+void MenuModelAdapter::SelectionChanged(MenuItemView* menu) {
+ // Ignore selection of the root menu.
+ if (menu == menu->GetRootMenuItem())
+ return;
+
+ const int id = menu->GetCommand();
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
+ model->HighlightChangedTo(index);
+ return;
+ }
+
+ NOTREACHED();
+}
+
void MenuModelAdapter::WillShowMenu(MenuItemView* menu) {
// Look up the menu model for this menu.
const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
diff --git a/ui/views/controls/menu/menu_model_adapter.h b/ui/views/controls/menu/menu_model_adapter.h
index a3598719..e52edfe 100644
--- a/ui/views/controls/menu/menu_model_adapter.h
+++ b/ui/views/controls/menu/menu_model_adapter.h
@@ -75,6 +75,7 @@
bool IsCommandEnabled(int id) const override;
bool IsCommandVisible(int id) const override;
bool IsItemChecked(int id) const override;
+ void SelectionChanged(MenuItemView* menu) override;
void WillShowMenu(MenuItemView* menu) override;
void WillHideMenu(MenuItemView* menu) override;
void OnMenuClosed(MenuItemView* menu) override;
diff --git a/ui/views/controls/menu/menu_model_adapter_unittest.cc b/ui/views/controls/menu/menu_model_adapter_unittest.cc
index 2d95b3cd..392f6c3e 100644
--- a/ui/views/controls/menu/menu_model_adapter_unittest.cc
+++ b/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -82,6 +82,8 @@
return items_[index].submenu;
}
+ void HighlightChangedTo(int index) override {}
+
void ActivatedAt(int index) override { set_last_activation(index); }
void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); }
@@ -339,6 +341,11 @@
const int actionable_submenu_index = 5;
CheckSubmenu(model, menu, &delegate, kRootIdBase + actionable_submenu_index,
2, actionable_submenu_index, kActionableSubmenuIdBase);
+
+ // Check that selecting the root item is safe. The MenuModel does
+ // not care about the root so MenuModelAdapter should do nothing
+ // (not hit the NOTREACHED check) when the root is selected.
+ static_cast<views::MenuDelegate*>(&delegate)->SelectionChanged(menu);
}
} // namespace views