Commit 83c23dde authored by Taylor Bergquist's avatar Taylor Bergquist Committed by Commit Bot

Update webui tab counter animation to more closely match spec.

The deficiency from spec is the bounciness in the border returning to
its start position - it should be more springlike, overshooting and
then returning, maybe more than once.

Bug: 1024009
Change-Id: Ibd374ed742fba7aa18705c744ec7420af58274c7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1992207
Commit-Queue: Taylor Bergquist <tbergquist@chromium.org>
Reviewed-by: default avatarPeter Boström <pbos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#732530}
parent f1955dfe
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/theme_provider.h" #include "ui/base/theme_provider.h"
#include "ui/gfx/animation/throb_animation.h" #include "ui/gfx/animation/multi_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/animation/tween.h" #include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_palette.h" #include "ui/gfx/color_palette.h"
#include "ui/views/animation/ink_drop.h" #include "ui/views/animation/ink_drop.h"
...@@ -36,105 +37,138 @@ namespace { ...@@ -36,105 +37,138 @@ namespace {
constexpr int kDesiredBorderHeight = 22; constexpr int kDesiredBorderHeight = 22;
// TODO(999557): Change this to 32 when the font is changed to Roboto. // TODO(999557): Change this to 32 when the font is changed to Roboto.
constexpr int kDoubleDigitWidth = 30; constexpr int kDoubleDigitWidth = 30;
constexpr int kOffscreenLabelDistance = 16;
constexpr base::TimeDelta kFirstPartDuration =
base::TimeDelta::FromMilliseconds(100);
// Animates the label and border. |border_view_| does a little bounce. At the
// peak of |border_view_|'s bounce, the |disappearing_label_| begins to scroll
// away in the same direction and is replaced with |appearing_label_|, which
// shows the new number of tabs. This animation is played upside-down when a tab
// is added vs. removed.
class TabCounterAnimator : public gfx::AnimationDelegate { class TabCounterAnimator : public gfx::AnimationDelegate {
public: public:
explicit TabCounterAnimator(views::View* animated_view) TabCounterAnimator(views::View* appearing_label,
: animated_view_(animated_view) { views::View* disappearing_label,
animation_ = std::make_unique<gfx::ThrobAnimation>(this); views::View* border_view)
animation_->SetTweenType(gfx::Tween::Type::FAST_OUT_SLOW_IN); : appearing_label_(appearing_label),
animation_->SetThrobDuration(base::TimeDelta::FromMilliseconds(100)); disappearing_label_(disappearing_label),
label_animation_(
std::vector<gfx::MultiAnimation::Part>{
// Stay in place by canceling out our container's animation.
gfx::MultiAnimation::Part(kFirstPartDuration,
gfx::Tween::Type::ZERO),
// Swap out to the new label.
gfx::MultiAnimation::Part(
base::TimeDelta::FromMilliseconds(200),
gfx::Tween::Type::EASE_IN_OUT)},
gfx::MultiAnimation::kDefaultTimerInterval),
border_view_(border_view),
border_animation_(
std::vector<gfx::MultiAnimation::Part>{
gfx::MultiAnimation::Part(kFirstPartDuration,
gfx::Tween::Type::EASE_OUT),
gfx::MultiAnimation::Part(
base::TimeDelta::FromMilliseconds(150),
gfx::Tween::Type::EASE_IN)},
gfx::MultiAnimation::kDefaultTimerInterval) {
label_animation_.set_delegate(this);
label_animation_.set_continuous(false);
border_animation_.set_delegate(this);
border_animation_.set_continuous(false);
} }
~TabCounterAnimator() override = default; ~TabCounterAnimator() override = default;
void Animate() { void Animate(bool increasing) {
starting_bounds_ = animated_view_->bounds(); increasing_ = increasing;
target_bounds_ =
gfx::Rect(starting_bounds_.x(), starting_bounds_.y() - 4, border_animation_.Stop();
starting_bounds_.width(), starting_bounds_.height()); border_animation_.Start();
animation_->StartThrobbing(1); label_animation_.Stop();
label_animation_.Start();
AnimationProgressed(&label_animation_);
} }
// AnimationDelegate: // AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override { void AnimationProgressed(const gfx::Animation* animation) override {
gfx::Rect bounds_rect = gfx::Tween::RectValueBetween( // |border_view_| does a hop (if |increasing_| is false) or a dip (if true).
animation->GetCurrentValue(), starting_bounds_, target_bounds_); const float border_animation_value =
animated_view_->SetBoundsRect(bounds_rect); border_animation_.current_part_index() == 0
? border_animation_.GetCurrentValue()
: 1 - border_animation_.GetCurrentValue();
const int border_y_delta = gfx::Tween::IntValueBetween(
border_animation_value, 0, GetBorderTargetYDelta());
border_view_->SetY(GetBorderStartingY() + border_y_delta);
// |appearing_label_| scrolls into view - from above if |increasing_|
// is false, and from below otherwise.
const int appearing_label_position = gfx::Tween::IntValueBetween(
label_animation_.GetCurrentValue(),
GetAppearingLabelStartPosition() - border_y_delta, 0);
appearing_label_->SetY(appearing_label_position);
// |disappearing_label_| scrolls out of view - out the bottom if
// |increasing_| is false, and from below otherwise.
const int disappearing_label_position = gfx::Tween::IntValueBetween(
label_animation_.GetCurrentValue(), -border_y_delta,
GetDisappearingLabelTargetPosition());
disappearing_label_->SetY(disappearing_label_position);
}
void AnimationEnded(const gfx::Animation* animation) override {
AnimationProgressed(animation);
} }
private: private:
views::View* const animated_view_; int GetBorderTargetYDelta() const { return increasing_ ? 4 : -4; }
gfx::Rect starting_bounds_;
gfx::Rect target_bounds_;
std::unique_ptr<gfx::ThrobAnimation> animation_;
DISALLOW_COPY_AND_ASSIGN(TabCounterAnimator); int GetAppearingLabelStartPosition() const {
}; return increasing_ ? -kOffscreenLabelDistance : kOffscreenLabelDistance;
}
class TabCounterUpdater : public TabStripModelObserver { int GetDisappearingLabelTargetPosition() const {
public: // We want to exit out the opposite side that |appearing_label_| entered
TabCounterUpdater(views::Button* button, // from.
views::Label* tab_counter, return -GetAppearingLabelStartPosition();
views::View* tab_counter_container)
: button_(button),
tab_counter_(tab_counter),
tab_counter_container_(tab_counter_container),
animator_(tab_counter_container) {}
~TabCounterUpdater() override = default;
void UpdateCounter(TabStripModel* model) {
const int num_tabs = model->count();
button_->SetTooltipText(
base::i18n::MessageFormatter::FormatWithNumberedArgs(
l10n_util::GetStringUTF16(IDS_TOOLTIP_WEBUI_TAB_STRIP_TAB_COUNTER),
num_tabs));
tab_counter_->SetText(base::FormatNumber(num_tabs));
const int button_height = button_->GetLocalBounds().height();
const int inset_height = (button_height - kDesiredBorderHeight) / 2;
int inset_width = inset_height;
int border_width = kDesiredBorderHeight;
if (num_tabs < 10) {
inset_width = inset_height;
border_width = kDesiredBorderHeight;
} else if (num_tabs < 100) {
inset_width = (button_height - kDoubleDigitWidth) / 2;
border_width = kDoubleDigitWidth;
} else {
// In the triple-digit case, fall back to ':D' to match Android.
tab_counter_->SetText(base::string16(base::ASCIIToUTF16(":D")));
inset_width = inset_height;
border_width = kDesiredBorderHeight;
}
tab_counter_container_->SetBounds(inset_width, inset_height, border_width,
kDesiredBorderHeight);
tab_counter_->SetBounds(0, 0, border_width, kDesiredBorderHeight);
animator_.Animate();
} }
// TabStripModelObserver: int GetBorderStartingY() const {
void OnTabStripModelChanged( // When at rest, |border_view_| should be vertically centered within its
TabStripModel* tab_strip_model, // container.
const TabStripModelChange& change, views::View* border_container = border_view_->parent();
const TabStripSelectionChange& selection) override { int border_available_space = border_container->GetLocalBounds().height();
UpdateCounter(tab_strip_model); return (border_available_space - border_view_->GetLocalBounds().height()) /
2;
} }
private: // The label that will be animated into view, showing the new value.
views::Button* const button_; views::View* const appearing_label_;
views::Label* const tab_counter_; // The label that will be animated out of view, showing the old value.
views::View* const tab_counter_container_; views::View* const disappearing_label_;
TabCounterAnimator animator_; gfx::MultiAnimation label_animation_;
views::View* const border_view_;
gfx::MultiAnimation border_animation_;
// True if the counter is currently animating an increase in the displayed
// number. The animation is played one way to show a decrease, and upside
// down from that to show an increase.
bool increasing_ = false;
DISALLOW_COPY_AND_ASSIGN(TabCounterAnimator);
}; };
class WebUITabCounterButton : public views::Button { class WebUITabCounterButton : public views::Button,
public TabStripModelObserver {
public: public:
explicit WebUITabCounterButton(views::ButtonListener* listener); explicit WebUITabCounterButton(views::ButtonListener* listener);
~WebUITabCounterButton() override; ~WebUITabCounterButton() override;
void UpdateText(int num_tabs);
void UpdateColors(); void UpdateColors();
void Init(TabStripModel* model);
// views::Button: // views::Button:
void AfterPropertyChange(const void* key, int64_t old_value) override; void AfterPropertyChange(const void* key, int64_t old_value) override;
...@@ -142,10 +176,19 @@ class WebUITabCounterButton : public views::Button { ...@@ -142,10 +176,19 @@ class WebUITabCounterButton : public views::Button {
void RemoveLayerBeneathView(ui::Layer* old_layer) override; void RemoveLayerBeneathView(ui::Layer* old_layer) override;
void OnThemeChanged() override; void OnThemeChanged() override;
std::unique_ptr<TabCounterUpdater> counter_updater_; // TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
views::InkDropContainerView* ink_drop_container_; views::InkDropContainerView* ink_drop_container_;
views::Label* label_; views::Label* appearing_label_;
views::View* border_; views::Label* disappearing_label_;
views::View* border_view_;
std::unique_ptr<TabCounterAnimator> animator_;
base::Optional<int> last_num_tabs_ = base::nullopt;
}; };
WebUITabCounterButton::WebUITabCounterButton(views::ButtonListener* listener) WebUITabCounterButton::WebUITabCounterButton(views::ButtonListener* listener)
...@@ -153,12 +196,49 @@ WebUITabCounterButton::WebUITabCounterButton(views::ButtonListener* listener) ...@@ -153,12 +196,49 @@ WebUITabCounterButton::WebUITabCounterButton(views::ButtonListener* listener)
WebUITabCounterButton::~WebUITabCounterButton() = default; WebUITabCounterButton::~WebUITabCounterButton() = default;
void WebUITabCounterButton::UpdateText(int num_tabs) {
SetTooltipText(base::i18n::MessageFormatter::FormatWithNumberedArgs(
l10n_util::GetStringUTF16(IDS_TOOLTIP_WEBUI_TAB_STRIP_TAB_COUNTER),
num_tabs));
disappearing_label_->SetText(appearing_label_->GetText());
appearing_label_->SetText(base::FormatNumber(num_tabs));
const int button_height = GetLocalBounds().height();
const int inset_height = (button_height - kDesiredBorderHeight) / 2;
int inset_width = inset_height;
int border_width = kDesiredBorderHeight;
if (num_tabs < 10) {
inset_width = inset_height;
border_width = kDesiredBorderHeight;
} else if (num_tabs < 100) {
inset_width = (button_height - kDoubleDigitWidth) / 2;
border_width = kDoubleDigitWidth;
} else {
// In the triple-digit case, fall back to ':D' to match Android.
appearing_label_->SetText(base::string16(base::ASCIIToUTF16(":D")));
inset_width = inset_height;
border_width = kDesiredBorderHeight;
}
border_view_->SetBounds(inset_width, inset_height, border_width,
kDesiredBorderHeight);
appearing_label_->SetBounds(0, 0, border_width, kDesiredBorderHeight);
disappearing_label_->SetBounds(0, -kOffscreenLabelDistance, border_width,
kDesiredBorderHeight);
if (last_num_tabs_) {
const bool increasing = last_num_tabs_.value() < num_tabs;
animator_->Animate(increasing);
}
last_num_tabs_ = num_tabs;
}
void WebUITabCounterButton::UpdateColors() { void WebUITabCounterButton::UpdateColors() {
const ui::ThemeProvider* theme_provider = GetThemeProvider(); const ui::ThemeProvider* theme_provider = GetThemeProvider();
const SkColor toolbar_color = const SkColor toolbar_color =
theme_provider ? theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR) theme_provider ? theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR)
: gfx::kPlaceholderColor; : gfx::kPlaceholderColor;
label_->SetBackgroundColor(toolbar_color); appearing_label_->SetBackgroundColor(toolbar_color);
disappearing_label_->SetBackgroundColor(toolbar_color);
const SkColor normal_text_color = const SkColor normal_text_color =
theme_provider theme_provider
...@@ -169,14 +249,46 @@ void WebUITabCounterButton::UpdateColors() { ...@@ -169,14 +249,46 @@ void WebUITabCounterButton::UpdateColors() {
? GetFeaturePromoHighlightColorForToolbar(theme_provider) ? GetFeaturePromoHighlightColorForToolbar(theme_provider)
: normal_text_color; : normal_text_color;
label_->SetEnabledColor(current_text_color); appearing_label_->SetEnabledColor(current_text_color);
border_->SetBorder(views::CreateRoundedRectBorder( disappearing_label_->SetEnabledColor(current_text_color);
border_view_->SetBorder(views::CreateRoundedRectBorder(
2, 2,
views::LayoutProvider::Get()->GetCornerRadiusMetric( views::LayoutProvider::Get()->GetCornerRadiusMetric(
views::EMPHASIS_MEDIUM), views::EMPHASIS_MEDIUM),
current_text_color)); current_text_color));
} }
void WebUITabCounterButton::Init(TabStripModel* tab_strip_model) {
SetID(VIEW_ID_WEBUI_TAB_STRIP_TAB_COUNTER);
SetProperty(views::kFlexBehaviorKey,
views::FlexSpecification::ForSizeRule(
views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred)
.WithOrder(1));
const int button_height = GetLayoutConstant(TOOLBAR_BUTTON_HEIGHT);
SetPreferredSize(gfx::Size(button_height, button_height));
ink_drop_container_ =
AddChildView(std::make_unique<views::InkDropContainerView>());
ink_drop_container_->SetBoundsRect(GetLocalBounds());
border_view_ = AddChildView(std::make_unique<views::View>());
appearing_label_ = border_view_->AddChildView(std::make_unique<views::Label>(
base::string16(), CONTEXT_WEB_UI_TAB_COUNTER));
disappearing_label_ =
border_view_->AddChildView(std::make_unique<views::Label>(
base::string16(), CONTEXT_WEB_UI_TAB_COUNTER));
animator_ = std::make_unique<TabCounterAnimator>(
appearing_label_, disappearing_label_, border_view_);
tab_strip_model->AddObserver(this);
UpdateText(tab_strip_model->count());
}
void WebUITabCounterButton::AfterPropertyChange(const void* key, void WebUITabCounterButton::AfterPropertyChange(const void* key,
int64_t old_value) { int64_t old_value) {
if (key != kHasInProductHelpPromoKey) if (key != kHasInProductHelpPromoKey)
...@@ -197,6 +309,13 @@ void WebUITabCounterButton::OnThemeChanged() { ...@@ -197,6 +309,13 @@ void WebUITabCounterButton::OnThemeChanged() {
ConfigureInkDropForToolbar(this); ConfigureInkDropForToolbar(this);
} }
void WebUITabCounterButton::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
UpdateText(tab_strip_model->count());
}
} // namespace } // namespace
std::unique_ptr<views::View> CreateWebUITabCounterButton( std::unique_ptr<views::View> CreateWebUITabCounterButton(
...@@ -204,34 +323,7 @@ std::unique_ptr<views::View> CreateWebUITabCounterButton( ...@@ -204,34 +323,7 @@ std::unique_ptr<views::View> CreateWebUITabCounterButton(
TabStripModel* tab_strip_model) { TabStripModel* tab_strip_model) {
auto tab_counter = std::make_unique<WebUITabCounterButton>(listener); auto tab_counter = std::make_unique<WebUITabCounterButton>(listener);
tab_counter->SetID(VIEW_ID_WEBUI_TAB_STRIP_TAB_COUNTER); tab_counter->Init(tab_strip_model);
tab_counter->SetProperty(views::kFlexBehaviorKey,
views::FlexSpecification::ForSizeRule(
views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred)
.WithOrder(1));
const int button_height = GetLayoutConstant(TOOLBAR_BUTTON_HEIGHT);
tab_counter->SetPreferredSize(gfx::Size(button_height, button_height));
views::InkDropContainerView* ink_drop_container = tab_counter->AddChildView(
std::make_unique<views::InkDropContainerView>());
tab_counter->ink_drop_container_ = ink_drop_container;
ink_drop_container->SetBoundsRect(tab_counter->GetLocalBounds());
views::View* border =
tab_counter->AddChildView(std::make_unique<views::View>());
tab_counter->border_ = border;
views::Label* label = border->AddChildView(std::make_unique<views::Label>(
base::string16(), CONTEXT_WEB_UI_TAB_COUNTER));
tab_counter->label_ = label;
tab_counter->counter_updater_ =
std::make_unique<TabCounterUpdater>(tab_counter.get(), label, border);
tab_strip_model->AddObserver(tab_counter->counter_updater_.get());
tab_counter->counter_updater_->UpdateCounter(tab_strip_model);
return tab_counter; return tab_counter;
} }
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