1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/feature_list.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/themes/theme_properties.h"
16 #include "chrome/browser/themes/theme_service.h"
17 #include "chrome/browser/themes/theme_service_factory.h"
18 #include "chrome/browser/ui/layout_constants.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/browser/ui/ui_features.h"
21 #include "chrome/browser/ui/views/chrome_layout_provider.h"
22 #include "chrome/browser/ui/views/chrome_typography.h"
23 #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
24 #include "ui/accessibility/ax_enums.mojom.h"
25 #include "ui/accessibility/ax_node_data.h"
26 #include "ui/base/models/image_model.h"
27 #include "ui/base/models/menu_model.h"
28 #include "ui/display/display.h"
29 #include "ui/display/screen.h"
30 #include "ui/gfx/color_utils.h"
31 #include "ui/gfx/geometry/insets.h"
32 #include "ui/gfx/paint_vector_icon.h"
33 #include "ui/gfx/text_utils.h"
34 #include "ui/views/animation/ink_drop.h"
35 #include "ui/views/animation/ink_drop_highlight.h"
36 #include "ui/views/animation/installable_ink_drop.h"
37 #include "ui/views/background.h"
38 #include "ui/views/controls/button/label_button_border.h"
39 #include "ui/views/controls/menu/menu_item_view.h"
40 #include "ui/views/controls/menu/menu_model_adapter.h"
41 #include "ui/views/controls/menu/menu_runner.h"
42 #include "ui/views/metadata/metadata_impl_macros.h"
43 #include "ui/views/view_class_properties.h"
44 #include "ui/views/widget/widget.h"
45 
46 namespace {
47 
48 constexpr int kBorderThicknessDpWithLabel = 1;
49 constexpr int kBorderThicknessDpWithoutLabel = 2;
50 
GetDefaultTextColor(const ui::ThemeProvider * theme_provider)51 SkColor GetDefaultTextColor(const ui::ThemeProvider* theme_provider) {
52   DCHECK(theme_provider);
53   // TODO(crbug.com/967317): Update to match mocks, i.e. return
54   // gfx::kGoogleGrey900, if needed.
55   return color_utils::GetColorWithMaxContrast(
56       theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR));
57 }
58 
59 }  // namespace
60 
ToolbarButton(PressedCallback callback)61 ToolbarButton::ToolbarButton(PressedCallback callback)
62     : ToolbarButton(std::move(callback), nullptr, nullptr) {}
63 
ToolbarButton(PressedCallback callback,std::unique_ptr<ui::MenuModel> model,TabStripModel * tab_strip_model,bool trigger_menu_on_long_press)64 ToolbarButton::ToolbarButton(PressedCallback callback,
65                              std::unique_ptr<ui::MenuModel> model,
66                              TabStripModel* tab_strip_model,
67                              bool trigger_menu_on_long_press)
68     : views::LabelButton(std::move(callback),
69                          base::string16(),
70                          CONTEXT_TOOLBAR_BUTTON),
71       model_(std::move(model)),
72       tab_strip_model_(tab_strip_model),
73       trigger_menu_on_long_press_(trigger_menu_on_long_press),
74       highlight_color_animation_(this) {
75   SetHasInkDropActionOnClick(true);
76   set_context_menu_controller(this);
77 
78   if (base::FeatureList::IsEnabled(views::kInstallableInkDropFeature)) {
79     installable_ink_drop_ = std::make_unique<views::InstallableInkDrop>(this);
80     installable_ink_drop_->SetConfig(GetToolbarInstallableInkDropConfig(this));
81   }
82 
83   InstallToolbarButtonHighlightPathGenerator(this);
84 
85   SetInkDropMode(InkDropMode::ON);
86 
87   // Make sure icons are flipped by default so that back, forward, etc. follows
88   // UI direction.
89   SetFlipCanvasOnPaintForRTLUI(true);
90 
91   SetInkDropVisibleOpacity(kToolbarInkDropVisibleOpacity);
92 
93   SetImageLabelSpacing(ChromeLayoutProvider::Get()->GetDistanceMetric(
94       DISTANCE_RELATED_LABEL_HORIZONTAL_LIST));
95   SetHorizontalAlignment(gfx::ALIGN_RIGHT);
96 
97   // Because we're using the internal padding to keep track of the changes we
98   // make to the leading margin to handle Fitts' Law, it's easier to just
99   // allocate the property once and modify the value.
100   SetProperty(views::kInternalPaddingKey, gfx::Insets());
101 
102   UpdateColorsAndInsets();
103 
104   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
105 }
106 
~ToolbarButton()107 ToolbarButton::~ToolbarButton() {}
108 
SetHighlight(const base::string16 & highlight_text,base::Optional<SkColor> highlight_color)109 void ToolbarButton::SetHighlight(const base::string16& highlight_text,
110                                  base::Optional<SkColor> highlight_color) {
111   if (highlight_text.empty() && !highlight_color.has_value()) {
112     ClearHighlight();
113     return;
114   }
115 
116   highlight_color_animation_.Show(highlight_color);
117   SetText(highlight_text);
118 }
119 
SetText(const base::string16 & text)120 void ToolbarButton::SetText(const base::string16& text) {
121   LabelButton::SetText(text);
122   UpdateColorsAndInsets();
123 }
124 
ClearHighlight()125 void ToolbarButton::ClearHighlight() {
126   highlight_color_animation_.Hide();
127   ShrinkDownThenClearText();
128 }
129 
UpdateColorsAndInsets()130 void ToolbarButton::UpdateColorsAndInsets() {
131   const int highlight_radius =
132       ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
133           views::EMPHASIS_MAXIMUM, size());
134 
135   SetEnabledTextColors(highlight_color_animation_.GetTextColor());
136 
137   // ToolbarButtons are always the height the location bar.
138   const gfx::Insets paint_insets =
139       gfx::Insets((height() - GetLayoutConstant(LOCATION_BAR_HEIGHT)) / 2) +
140       *GetProperty(views::kInternalPaddingKey);
141 
142   base::Optional<SkColor> background_color =
143       highlight_color_animation_.GetBackgroundColor();
144   if (background_color) {
145     SetBackground(views::CreateBackgroundFromPainter(
146         views::Painter::CreateSolidRoundRectPainter(
147             *background_color, highlight_radius, paint_insets)));
148   } else {
149     SetBackground(nullptr);
150   }
151 
152   gfx::Insets target_insets =
153       layout_insets_.value_or(::GetLayoutInsets(TOOLBAR_BUTTON)) +
154       layout_inset_delta_ + *GetProperty(views::kInternalPaddingKey);
155   base::Optional<SkColor> border_color =
156       highlight_color_animation_.GetBorderColor();
157   if (!border() || target_insets != border()->GetInsets() ||
158       last_border_color_ != border_color ||
159       last_paint_insets_ != paint_insets) {
160     if (border_color) {
161       int border_thickness_dp = GetText().empty()
162                                     ? kBorderThicknessDpWithoutLabel
163                                     : kBorderThicknessDpWithLabel;
164       // Create a border with insets totalling |target_insets|, split into
165       // painted insets (matching the background) and internal padding to
166       // position child views correctly.
167       std::unique_ptr<views::Border> border = views::CreateRoundedRectBorder(
168           border_thickness_dp, highlight_radius, paint_insets, *border_color);
169       const gfx::Insets extra_insets = target_insets - border->GetInsets();
170       SetBorder(views::CreatePaddedBorder(std::move(border), extra_insets));
171     } else {
172       SetBorder(views::CreateEmptyBorder(target_insets));
173     }
174     last_border_color_ = border_color;
175     last_paint_insets_ = paint_insets;
176   }
177 
178   // Update spacing on the outer-side of the label to match the current
179   // highlight radius.
180   SetLabelSideSpacing(highlight_radius / 2);
181 }
182 
GetForegroundColor(ButtonState state) const183 SkColor ToolbarButton::GetForegroundColor(ButtonState state) const {
184   const ui::ThemeProvider* tp = GetThemeProvider();
185   DCHECK(tp);
186   switch (state) {
187     case ButtonState::STATE_HOVERED:
188       return tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_HOVERED);
189     case ButtonState::STATE_PRESSED:
190       return tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_PRESSED);
191     case ButtonState::STATE_DISABLED:
192       return tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE);
193     case ButtonState::STATE_NORMAL:
194       return tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
195     default:
196       NOTREACHED();
197       return gfx::kPlaceholderColor;
198   }
199 }
200 
UpdateIconsWithColors(const gfx::VectorIcon & icon,SkColor normal_color,SkColor hovered_color,SkColor pressed_color,SkColor disabled_color)201 void ToolbarButton::UpdateIconsWithColors(const gfx::VectorIcon& icon,
202                                           SkColor normal_color,
203                                           SkColor hovered_color,
204                                           SkColor pressed_color,
205                                           SkColor disabled_color) {
206   SetImageModel(ButtonState::STATE_NORMAL,
207                 ui::ImageModel::FromVectorIcon(icon, normal_color));
208   SetImageModel(ButtonState::STATE_HOVERED,
209                 ui::ImageModel::FromVectorIcon(icon, hovered_color));
210   SetImageModel(ButtonState::STATE_PRESSED,
211                 ui::ImageModel::FromVectorIcon(icon, pressed_color));
212   SetImageModel(Button::STATE_DISABLED,
213                 ui::ImageModel::FromVectorIcon(icon, disabled_color));
214 }
215 
UpdateIconsWithStandardColors(const gfx::VectorIcon & icon)216 void ToolbarButton::UpdateIconsWithStandardColors(const gfx::VectorIcon& icon) {
217   UpdateIconsWithColors(icon, GetForegroundColor(ButtonState::STATE_NORMAL),
218                         GetForegroundColor(ButtonState::STATE_HOVERED),
219                         GetForegroundColor(ButtonState::STATE_PRESSED),
220                         GetForegroundColor(ButtonState::STATE_DISABLED));
221 }
222 
SetLabelSideSpacing(int spacing)223 void ToolbarButton::SetLabelSideSpacing(int spacing) {
224   gfx::Insets label_insets;
225   // Add the spacing only if text is non-empty.
226   if (!GetText().empty()) {
227     // Add spacing to the opposing side.
228     label_insets =
229         gfx::MaybeFlipForRTL(GetHorizontalAlignment()) == gfx::ALIGN_RIGHT
230             ? gfx::Insets(0, spacing, 0, 0)
231             : gfx::Insets(0, 0, 0, spacing);
232   }
233   if (!label()->border() || label_insets != label()->border()->GetInsets()) {
234     label()->SetBorder(views::CreateEmptyBorder(label_insets));
235     // Forces LabelButton to dump the cached preferred size and recompute it.
236     PreferredSizeChanged();
237   }
238 }
239 
SetLayoutInsetDelta(const gfx::Insets & inset_delta)240 void ToolbarButton::SetLayoutInsetDelta(const gfx::Insets& inset_delta) {
241   if (layout_inset_delta_ == inset_delta)
242     return;
243   layout_inset_delta_ = inset_delta;
244   UpdateColorsAndInsets();
245 }
246 
SetLeadingMargin(int margin)247 void ToolbarButton::SetLeadingMargin(int margin) {
248   gfx::Insets* const internal_padding = GetProperty(views::kInternalPaddingKey);
249   if (internal_padding->left() == margin)
250     return;
251   internal_padding->set_left(margin);
252   UpdateColorsAndInsets();
253 }
254 
SetTrailingMargin(int margin)255 void ToolbarButton::SetTrailingMargin(int margin) {
256   gfx::Insets* const internal_padding = GetProperty(views::kInternalPaddingKey);
257   if (internal_padding->right() == margin)
258     return;
259   internal_padding->set_right(margin);
260   UpdateColorsAndInsets();
261 }
262 
ClearPendingMenu()263 void ToolbarButton::ClearPendingMenu() {
264   show_menu_factory_.InvalidateWeakPtrs();
265 }
266 
IsMenuShowing() const267 bool ToolbarButton::IsMenuShowing() const {
268   return menu_showing_;
269 }
270 
GetLayoutInsets() const271 base::Optional<gfx::Insets> ToolbarButton::GetLayoutInsets() const {
272   return layout_insets_;
273 }
274 
SetLayoutInsets(const base::Optional<gfx::Insets> & insets)275 void ToolbarButton::SetLayoutInsets(const base::Optional<gfx::Insets>& insets) {
276   if (layout_insets_ == insets)
277     return;
278   layout_insets_ = insets;
279   UpdateColorsAndInsets();
280 }
281 
OnBoundsChanged(const gfx::Rect & previous_bounds)282 void ToolbarButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
283   if (size() != previous_bounds.size())
284     UpdateColorsAndInsets();
285   LabelButton::OnBoundsChanged(previous_bounds);
286 }
287 
OnThemeChanged()288 void ToolbarButton::OnThemeChanged() {
289   if (installable_ink_drop_)
290     installable_ink_drop_->SetConfig(GetToolbarInstallableInkDropConfig(this));
291   UpdateIcon();
292 
293   // Call this after UpdateIcon() to properly reset images.
294   LabelButton::OnThemeChanged();
295 }
296 
GetAnchorBoundsInScreen() const297 gfx::Rect ToolbarButton::GetAnchorBoundsInScreen() const {
298   gfx::Rect bounds = GetBoundsInScreen();
299   gfx::Insets insets = GetToolbarInkDropInsets(this);
300   // If the button is extended, don't inset the leading edge. The anchored menu
301   // should extend to the screen edge as well so the menu is easier to hit
302   // (Fitts's law).
303   // TODO(pbos): Make sure the button is aware of that it is being extended or
304   // not (leading_margin_ cannot be used as it can be 0 in fullscreen on Touch).
305   // When this is implemented, use 0 as a replacement for leading_margin_ in
306   // fullscreen only. Always keep the rest.
307   insets.Set(insets.top(), 0, insets.bottom(), 0);
308   bounds.Inset(insets);
309   return bounds;
310 }
311 
OnMousePressed(const ui::MouseEvent & event)312 bool ToolbarButton::OnMousePressed(const ui::MouseEvent& event) {
313   if (trigger_menu_on_long_press_ && IsTriggerableEvent(event) &&
314       GetEnabled() && ShouldShowMenu() && HitTestPoint(event.location())) {
315     // Store the y pos of the mouse coordinates so we can use them later to
316     // determine if the user dragged the mouse down (which should pop up the
317     // drag down menu immediately, instead of waiting for the timer)
318     y_position_on_lbuttondown_ = event.y();
319 
320     // Schedule a task that will show the menu.
321     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
322         FROM_HERE,
323         base::BindOnce(&ToolbarButton::ShowDropDownMenu,
324                        show_menu_factory_.GetWeakPtr(),
325                        ui::GetMenuSourceTypeForEvent(event)),
326         base::TimeDelta::FromMilliseconds(500));
327   }
328 
329   return LabelButton::OnMousePressed(event);
330 }
331 
OnMouseDragged(const ui::MouseEvent & event)332 bool ToolbarButton::OnMouseDragged(const ui::MouseEvent& event) {
333   bool result = LabelButton::OnMouseDragged(event);
334 
335   if (trigger_menu_on_long_press_ && show_menu_factory_.HasWeakPtrs()) {
336     // If the mouse is dragged to a y position lower than where it was when
337     // clicked then we should not wait for the menu to appear but show
338     // it immediately.
339     if (event.y() > y_position_on_lbuttondown_ + GetHorizontalDragThreshold()) {
340       show_menu_factory_.InvalidateWeakPtrs();
341       ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event));
342     }
343   }
344 
345   return result;
346 }
347 
OnMouseReleased(const ui::MouseEvent & event)348 void ToolbarButton::OnMouseReleased(const ui::MouseEvent& event) {
349   if (IsTriggerableEvent(event) ||
350       (event.IsRightMouseButton() && !HitTestPoint(event.location()))) {
351     LabelButton::OnMouseReleased(event);
352   }
353 
354   if (IsTriggerableEvent(event))
355     show_menu_factory_.InvalidateWeakPtrs();
356 }
357 
OnMouseCaptureLost()358 void ToolbarButton::OnMouseCaptureLost() {}
359 
OnMouseExited(const ui::MouseEvent & event)360 void ToolbarButton::OnMouseExited(const ui::MouseEvent& event) {
361   // Starting a drag results in a MouseExited, we need to ignore it.
362   // A right click release triggers an exit event. We want to
363   // remain in a PUSHED state until the drop down menu closes.
364   if (GetState() != STATE_DISABLED && !InDrag() && GetState() != STATE_PRESSED)
365     SetState(STATE_NORMAL);
366 }
367 
OnGestureEvent(ui::GestureEvent * event)368 void ToolbarButton::OnGestureEvent(ui::GestureEvent* event) {
369   if (menu_showing_) {
370     // While dropdown menu is showing the button should not handle gestures.
371     event->StopPropagation();
372     return;
373   }
374 
375   LabelButton::OnGestureEvent(event);
376 }
377 
GetAccessibleNodeData(ui::AXNodeData * node_data)378 void ToolbarButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
379   Button::GetAccessibleNodeData(node_data);
380   if (model_)
381     node_data->SetHasPopup(ax::mojom::HasPopup::kMenu);
382 }
383 
CreateInkDrop()384 std::unique_ptr<views::InkDrop> ToolbarButton::CreateInkDrop() {
385   // Ensure this doesn't get called when InstallableInkDrops are enabled.
386   DCHECK(!base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
387   return views::LabelButton::CreateInkDrop();
388 }
389 
CreateInkDropHighlight() const390 std::unique_ptr<views::InkDropHighlight> ToolbarButton::CreateInkDropHighlight()
391     const {
392   // Ensure this doesn't get called when InstallableInkDrops are enabled.
393   DCHECK(!base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
394   return CreateToolbarInkDropHighlight(this);
395 }
396 
GetInkDropBaseColor() const397 SkColor ToolbarButton::GetInkDropBaseColor() const {
398   // Ensure this doesn't get called when InstallableInkDrops are enabled.
399   DCHECK(!base::FeatureList::IsEnabled(views::kInstallableInkDropFeature));
400   base::Optional<SkColor> drop_base_color =
401       highlight_color_animation_.GetInkDropBaseColor();
402   if (drop_base_color)
403     return *drop_base_color;
404   return GetToolbarInkDropBaseColor(this);
405 }
406 
GetInkDrop()407 views::InkDrop* ToolbarButton::GetInkDrop() {
408   if (installable_ink_drop_)
409     return installable_ink_drop_.get();
410   return views::LabelButton::GetInkDrop();
411 }
412 
ShowContextMenuForViewImpl(View * source,const gfx::Point & point,ui::MenuSourceType source_type)413 void ToolbarButton::ShowContextMenuForViewImpl(View* source,
414                                                const gfx::Point& point,
415                                                ui::MenuSourceType source_type) {
416   if (!GetEnabled())
417     return;
418 
419   show_menu_factory_.InvalidateWeakPtrs();
420   ShowDropDownMenu(source_type);
421 }
422 
423 // static
AdjustHighlightColorForContrast(const ui::ThemeProvider * theme_provider,SkColor desired_dark_color,SkColor desired_light_color,SkColor dark_extreme,SkColor light_extreme)424 SkColor ToolbarButton::AdjustHighlightColorForContrast(
425     const ui::ThemeProvider* theme_provider,
426     SkColor desired_dark_color,
427     SkColor desired_light_color,
428     SkColor dark_extreme,
429     SkColor light_extreme) {
430   const SkColor background_color = GetDefaultBackgroundColor(theme_provider);
431   const SkColor contrasting_color = color_utils::PickContrastingColor(
432       desired_dark_color, desired_light_color, background_color);
433   const SkColor limit =
434       contrasting_color == desired_dark_color ? dark_extreme : light_extreme;
435   // Setting highlight color will set the text to the highlight color, and the
436   // background to the same color with a low alpha. This means that our target
437   // contrast is between the text (the highlight color) and a blend of the
438   // highlight color and the toolbar color.
439   const SkColor base_color = color_utils::AlphaBlend(
440       contrasting_color, background_color, kToolbarButtonBackgroundAlpha);
441 
442   // Add a fudge factor to the minimum contrast ratio since we'll actually be
443   // blending with the adjusted color.
444   return color_utils::BlendForMinContrast(
445              contrasting_color, base_color, limit,
446              color_utils::kMinimumReadableContrastRatio * 1.05)
447       .color;
448 }
449 
450 // static
GetDefaultBackgroundColor(const ui::ThemeProvider * theme_provider)451 SkColor ToolbarButton::GetDefaultBackgroundColor(
452     const ui::ThemeProvider* theme_provider) {
453   return color_utils::GetColorWithMaxContrast(
454       GetDefaultTextColor(theme_provider));
455 }
456 
457 // static
GetDefaultBorderColor(views::View * host_view)458 SkColor ToolbarButton::GetDefaultBorderColor(views::View* host_view) {
459   return SkColorSetA(GetToolbarInkDropBaseColor(host_view),
460                      kToolbarButtonBackgroundAlpha);
461 }
462 
ShouldShowMenu()463 bool ToolbarButton::ShouldShowMenu() {
464   return model_ != nullptr;
465 }
466 
ShowDropDownMenu(ui::MenuSourceType source_type)467 void ToolbarButton::ShowDropDownMenu(ui::MenuSourceType source_type) {
468   if (!ShouldShowMenu())
469     return;
470 
471   gfx::Rect menu_anchor_bounds = GetAnchorBoundsInScreen();
472 
473 #if defined(OS_CHROMEOS)
474   // A window won't overlap between displays on ChromeOS.
475   // Use the left bound of the display on which
476   // the menu button exists.
477   gfx::NativeView view = GetWidget()->GetNativeView();
478   display::Display display =
479       display::Screen::GetScreen()->GetDisplayNearestView(view);
480   int left_bound = display.bounds().x();
481 #else
482   // The window might be positioned over the edge between two screens. We'll
483   // want to position the dropdown on the screen the mouse cursor is on.
484   display::Screen* screen = display::Screen::GetScreen();
485   display::Display display =
486       screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint());
487   int left_bound = display.bounds().x();
488 #endif
489   if (menu_anchor_bounds.x() < left_bound)
490     menu_anchor_bounds.set_x(left_bound);
491 
492   // Make the button look depressed while the menu is open.
493   SetState(STATE_PRESSED);
494 
495   menu_showing_ = true;
496 
497   AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr /* event */);
498 
499   // Exit if the model is null. Although ToolbarButton::ShouldShowMenu()
500   // performs the same check, its overrides may not.
501   if (!model_)
502     return;
503 
504   if (tab_strip_model_ && !tab_strip_model_->GetActiveWebContents())
505     return;
506 
507   // Create and run menu.
508   menu_model_adapter_ = std::make_unique<views::MenuModelAdapter>(
509       model_.get(), base::BindRepeating(&ToolbarButton::OnMenuClosed,
510                                         base::Unretained(this)));
511   menu_model_adapter_->set_triggerable_event_flags(GetTriggerableEventFlags());
512   menu_runner_ = std::make_unique<views::MenuRunner>(
513       menu_model_adapter_->CreateMenu(), views::MenuRunner::HAS_MNEMONICS);
514   menu_runner_->RunMenuAt(GetWidget(), nullptr, menu_anchor_bounds,
515                           views::MenuAnchorPosition::kTopLeft, source_type);
516 }
517 
OnMenuClosed()518 void ToolbarButton::OnMenuClosed() {
519   AnimateInkDrop(views::InkDropState::DEACTIVATED, nullptr /* event */);
520 
521   menu_showing_ = false;
522 
523   // Set the state back to normal after the drop down menu is closed.
524   if (GetState() != STATE_DISABLED) {
525     GetInkDrop()->SetHovered(IsMouseHovered());
526     SetState(STATE_NORMAL);
527   }
528 
529   menu_runner_.reset();
530   menu_model_adapter_.reset();
531 }
532 
533 namespace {
534 
535 // The default duration does not work well for dark mode where the animation has
536 // to make a big contrast difference.
537 // TODO(crbug.com/967317): This needs to be consistent with the duration of the
538 // border animation in ToolbarIconContainerView.
539 constexpr base::TimeDelta kHighlightAnimationDuration =
540     base::TimeDelta::FromMilliseconds(300);
541 constexpr SkAlpha kBackgroundBaseLayerAlpha = 204;
542 
FadeWithAnimation(SkColor color,const gfx::Animation & animation)543 SkColor FadeWithAnimation(SkColor color, const gfx::Animation& animation) {
544   return SkColorSetA(color, SkColorGetA(color) * animation.GetCurrentValue());
545 }
546 
547 }  // namespace
548 
HighlightColorAnimation(ToolbarButton * parent)549 ToolbarButton::HighlightColorAnimation::HighlightColorAnimation(
550     ToolbarButton* parent)
551     : parent_(parent), highlight_color_animation_(this) {
552   DCHECK(parent_);
553   highlight_color_animation_.SetTweenType(gfx::Tween::EASE_IN_OUT);
554   highlight_color_animation_.SetSlideDuration(kHighlightAnimationDuration);
555 }
556 
~HighlightColorAnimation()557 ToolbarButton::HighlightColorAnimation::~HighlightColorAnimation() {}
558 
Show(base::Optional<SkColor> highlight_color)559 void ToolbarButton::HighlightColorAnimation::Show(
560     base::Optional<SkColor> highlight_color) {
561   // If the animation is showing, we will jump to a different color in the
562   // middle of the animation and continue animating towards the new
563   // |highlight_color_|. If the animation is fully shown, we will jump directly
564   // to the new |highlight_color_|. This is not ideal but making it smoother is
565   // not worth the extra complexity given this should be very rare.
566   if (highlight_color_animation_.GetCurrentValue() == 0.0f ||
567       highlight_color_animation_.IsClosing()) {
568     highlight_color_animation_.Show();
569   }
570   highlight_color_ = highlight_color;
571   parent_->UpdateColorsAndInsets();
572 }
573 
Hide()574 void ToolbarButton::HighlightColorAnimation::Hide() {
575   highlight_color_animation_.Hide();
576 }
577 
GetTextColor() const578 base::Optional<SkColor> ToolbarButton::HighlightColorAnimation::GetTextColor()
579     const {
580   if (!IsShown() || !parent_->GetThemeProvider())
581     return base::nullopt;
582   SkColor text_color;
583   if (highlight_color_) {
584     text_color = *highlight_color_;
585   } else {
586     text_color = GetDefaultTextColor(parent_->GetThemeProvider());
587   }
588   return FadeWithAnimation(text_color, highlight_color_animation_);
589 }
590 
GetBorderColor() const591 base::Optional<SkColor> ToolbarButton::HighlightColorAnimation::GetBorderColor()
592     const {
593   if (!IsShown() || !parent_->GetThemeProvider()) {
594     return base::nullopt;
595   }
596 
597   SkColor border_color;
598   if (highlight_color_) {
599     border_color = *highlight_color_;
600   } else {
601     border_color = ToolbarButton::GetDefaultBorderColor(parent_);
602   }
603   return FadeWithAnimation(border_color, highlight_color_animation_);
604 }
605 
606 base::Optional<SkColor>
GetBackgroundColor() const607 ToolbarButton::HighlightColorAnimation::GetBackgroundColor() const {
608   if (!IsShown() || !parent_->GetThemeProvider())
609     return base::nullopt;
610   SkColor bg_color =
611       SkColorSetA(GetDefaultBackgroundColor(parent_->GetThemeProvider()),
612                   kBackgroundBaseLayerAlpha);
613   if (highlight_color_) {
614     // TODO(crbug.com/967317): Change the highlight opacity to 4% to match the
615     // mocks, if needed.
616     bg_color = color_utils::GetResultingPaintColor(
617         /*fg=*/SkColorSetA(*highlight_color_,
618                            SkColorGetA(*highlight_color_) *
619                                kToolbarInkDropHighlightVisibleOpacity),
620         /*bg=*/bg_color);
621   }
622   return FadeWithAnimation(bg_color, highlight_color_animation_);
623 }
624 
625 base::Optional<SkColor>
GetInkDropBaseColor() const626 ToolbarButton::HighlightColorAnimation::GetInkDropBaseColor() const {
627   if (!highlight_color_)
628     return base::nullopt;
629   return *highlight_color_;
630 }
631 
AnimationEnded(const gfx::Animation * animation)632 void ToolbarButton::HighlightColorAnimation::AnimationEnded(
633     const gfx::Animation* animation) {
634   // Only reset the color after the animation slides _back_ and not when it
635   // finishes sliding fully _open_.
636   if (highlight_color_animation_.GetCurrentValue() == 0.0f)
637     ClearHighlightColor();
638 }
639 
AnimationProgressed(const gfx::Animation * animation)640 void ToolbarButton::HighlightColorAnimation::AnimationProgressed(
641     const gfx::Animation* animation) {
642   parent_->UpdateColorsAndInsets();
643 }
644 
IsShown() const645 bool ToolbarButton::HighlightColorAnimation::IsShown() const {
646   return highlight_color_animation_.is_animating() ||
647          highlight_color_animation_.GetCurrentValue() == 1.0f;
648 }
649 
ClearHighlightColor()650 void ToolbarButton::HighlightColorAnimation::ClearHighlightColor() {
651   highlight_color_animation_.Reset(0.0f);
652   highlight_color_.reset();
653   parent_->UpdateColorsAndInsets();
654 }
655 
656 BEGIN_METADATA(ToolbarButton, views::LabelButton)
657 ADD_PROPERTY_METADATA(base::Optional<gfx::Insets>, LayoutInsets)
658 END_METADATA
659