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