1 // Copyright 2017 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/tabs/new_tab_button.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/strings/string_number_conversions.h"
11 #include "build/build_config.h"
12 #include "chrome/browser/themes/theme_properties.h"
13 #include "chrome/browser/ui/browser_list.h"
14 #include "chrome/browser/ui/layout_constants.h"
15 #include "chrome/browser/ui/tabs/tab_types.h"
16 #include "chrome/browser/ui/views/chrome_layout_provider.h"
17 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
18 #include "chrome/browser/ui/views/tabs/tab_strip.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/variations/variations_associated_data.h"
21 #include "ui/base/pointer/touch_ui_controller.h"
22 #include "ui/base/theme_provider.h"
23 #include "ui/gfx/color_utils.h"
24 #include "ui/gfx/scoped_canvas.h"
25 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
26 #include "ui/views/animation/ink_drop.h"
27 #include "ui/views/animation/ink_drop_impl.h"
28 #include "ui/views/animation/ink_drop_mask.h"
29 #include "ui/views/controls/highlight_path_generator.h"
30 #include "ui/views/widget/widget.h"
31 
32 #if defined(OS_WIN)
33 #include "ui/display/win/screen_win.h"
34 #include "ui/views/win/hwnd_util.h"
35 #endif
36 
37 // static
38 constexpr char NewTabButton::kClassName[];
39 
40 // static
41 const gfx::Size NewTabButton::kButtonSize{28, 28};
42 
43 class NewTabButton::HighlightPathGenerator
44     : public views::HighlightPathGenerator {
45  public:
46   HighlightPathGenerator() = default;
47   HighlightPathGenerator(const HighlightPathGenerator&) = delete;
48   HighlightPathGenerator& operator=(const HighlightPathGenerator&) = delete;
49 
50   // views::HighlightPathGenerator:
GetHighlightPath(const views::View * view)51   SkPath GetHighlightPath(const views::View* view) override {
52     return static_cast<const NewTabButton*>(view)->GetBorderPath(
53         view->GetContentsBounds().origin(), 1.0f, false);
54   }
55 };
56 
NewTabButton(TabStrip * tab_strip,PressedCallback callback)57 NewTabButton::NewTabButton(TabStrip* tab_strip, PressedCallback callback)
58     : views::ImageButton(std::move(callback)), tab_strip_(tab_strip) {
59   SetAnimateOnStateChange(true);
60 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
61   SetTriggerableEventFlags(GetTriggerableEventFlags() |
62                            ui::EF_MIDDLE_MOUSE_BUTTON);
63 #endif
64 
65   ink_drop_container_ =
66       AddChildView(std::make_unique<views::InkDropContainerView>());
67 
68   SetInkDropMode(InkDropMode::ON);
69   SetInkDropHighlightOpacity(0.16f);
70   SetInkDropVisibleOpacity(0.14f);
71 
72   SetInstallFocusRingOnFocus(true);
73   views::HighlightPathGenerator::Install(
74       this, std::make_unique<NewTabButton::HighlightPathGenerator>());
75 
76   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
77 }
78 
~NewTabButton()79 NewTabButton::~NewTabButton() {
80   if (destroyed_)
81     *destroyed_ = true;
82 }
83 
FrameColorsChanged()84 void NewTabButton::FrameColorsChanged() {
85   UpdateInkDropBaseColor();
86   SchedulePaint();
87 }
88 
AnimateInkDropToStateForTesting(views::InkDropState state)89 void NewTabButton::AnimateInkDropToStateForTesting(views::InkDropState state) {
90   GetInkDrop()->AnimateToState(state);
91 }
92 
GetClassName() const93 const char* NewTabButton::GetClassName() const {
94   return kClassName;
95 }
96 
AddLayerBeneathView(ui::Layer * new_layer)97 void NewTabButton::AddLayerBeneathView(ui::Layer* new_layer) {
98   ink_drop_container_->AddLayerBeneathView(new_layer);
99 }
100 
RemoveLayerBeneathView(ui::Layer * old_layer)101 void NewTabButton::RemoveLayerBeneathView(ui::Layer* old_layer) {
102   ink_drop_container_->RemoveLayerBeneathView(old_layer);
103 }
104 
GetForegroundColor() const105 SkColor NewTabButton::GetForegroundColor() const {
106   const SkColor background_color = tab_strip_->GetTabBackgroundColor(
107       TabActive::kInactive, BrowserFrameActiveState::kUseCurrent);
108   return tab_strip_->GetTabForegroundColor(TabActive::kInactive,
109                                            background_color);
110 }
111 
OnBoundsChanged(const gfx::Rect & previous_bounds)112 void NewTabButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
113   ImageButton::OnBoundsChanged(previous_bounds);
114   ink_drop_container_->SetBoundsRect(GetLocalBounds());
115 }
116 
117 #if defined(OS_WIN)
OnMouseReleased(const ui::MouseEvent & event)118 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
119   if (!event.IsOnlyRightMouseButton()) {
120     views::ImageButton::OnMouseReleased(event);
121     return;
122   }
123 
124   // TODO(pkasting): If we handled right-clicks on the frame, and we made sure
125   // this event was not handled, it seems like things would Just Work.
126   gfx::Point point = event.location();
127   views::View::ConvertPointToScreen(this, &point);
128   point = display::win::ScreenWin::DIPToScreenPoint(point);
129   bool destroyed = false;
130   destroyed_ = &destroyed;
131   views::ShowSystemMenuAtScreenPixelLocation(views::HWNDForView(this), point);
132   if (!destroyed)
133     SetState(views::Button::STATE_NORMAL);
134 }
135 #endif
136 
OnGestureEvent(ui::GestureEvent * event)137 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
138   // Consume all gesture events here so that the parent (Tab) does not
139   // start consuming gestures.
140   views::ImageButton::OnGestureEvent(event);
141   event->SetHandled();
142 }
143 
NotifyClick(const ui::Event & event)144 void NewTabButton::NotifyClick(const ui::Event& event) {
145   ImageButton::NotifyClick(event);
146   GetInkDrop()->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
147 }
148 
PaintButtonContents(gfx::Canvas * canvas)149 void NewTabButton::PaintButtonContents(gfx::Canvas* canvas) {
150   gfx::ScopedCanvas scoped_canvas(canvas);
151   canvas->Translate(GetContentsBounds().OffsetFromOrigin());
152   PaintFill(canvas);
153   PaintIcon(canvas);
154 }
155 
CalculatePreferredSize() const156 gfx::Size NewTabButton::CalculatePreferredSize() const {
157   gfx::Size size = kButtonSize;
158   const auto insets = GetInsets();
159   size.Enlarge(insets.width(), insets.height());
160   return size;
161 }
162 
GetHitTestMask(SkPath * mask) const163 bool NewTabButton::GetHitTestMask(SkPath* mask) const {
164   DCHECK(mask);
165 
166   const float scale = GetWidget()->GetCompositor()->device_scale_factor();
167   // TODO(pkasting): Fitts' Law horizontally when appropriate.
168   SkPath border = GetBorderPath(GetContentsBounds().origin(), scale,
169                                 tab_strip_->controller()->IsFrameCondensed());
170   mask->addPath(border, SkMatrix::Scale(1 / scale, 1 / scale));
171   return true;
172 }
173 
GetCornerRadius() const174 int NewTabButton::GetCornerRadius() const {
175   return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
176       views::EMPHASIS_MAXIMUM, GetContentsBounds().size());
177 }
178 
PaintFill(gfx::Canvas * canvas) const179 void NewTabButton::PaintFill(gfx::Canvas* canvas) const {
180   gfx::ScopedCanvas scoped_canvas(canvas);
181   canvas->UndoDeviceScaleFactor();
182   cc::PaintFlags flags;
183   flags.setAntiAlias(true);
184 
185   const float scale = canvas->image_scale();
186   const base::Optional<int> bg_id =
187       tab_strip_->GetCustomBackgroundId(BrowserFrameActiveState::kUseCurrent);
188   if (bg_id.has_value()) {
189     float x_scale = scale;
190     const gfx::Rect& contents_bounds = GetContentsBounds();
191     gfx::RectF bounds_in_tab_strip(GetLocalBounds());
192     View::ConvertRectToTarget(this, tab_strip_, &bounds_in_tab_strip);
193     int x = bounds_in_tab_strip.x() + contents_bounds.x() +
194             tab_strip_->GetBackgroundOffset();
195     if (base::i18n::IsRTL()) {
196       // The new tab background is mirrored in RTL mode, but the theme
197       // background should never be mirrored. Mirror it here to compensate.
198       x_scale = -scale;
199       // Offset by |width| such that the same region is painted as if there
200       // was no flip.
201       x += contents_bounds.width();
202     }
203 
204     canvas->InitPaintFlagsForTiling(
205         *GetThemeProvider()->GetImageSkiaNamed(bg_id.value()), x,
206         contents_bounds.y(), x_scale, scale, 0, 0, SkTileMode::kRepeat,
207         SkTileMode::kRepeat, &flags);
208   } else {
209     flags.setColor(GetButtonFillColor());
210   }
211 
212   canvas->DrawPath(GetBorderPath(gfx::Point(), scale, false), flags);
213 }
214 
PaintIcon(gfx::Canvas * canvas)215 void NewTabButton::PaintIcon(gfx::Canvas* canvas) {
216   cc::PaintFlags flags;
217   flags.setAntiAlias(true);
218   flags.setColor(GetForegroundColor());
219   flags.setStrokeCap(cc::PaintFlags::kRound_Cap);
220   constexpr int kStrokeWidth = 2;
221   flags.setStrokeWidth(kStrokeWidth);
222 
223   const int radius = ui::TouchUiController::Get()->touch_ui() ? 7 : 6;
224   const int offset = GetCornerRadius() - radius;
225   // The cap will be added outside the end of the stroke; inset to compensate.
226   constexpr int kCapRadius = kStrokeWidth / 2;
227   const int start = offset + kCapRadius;
228   const int end = offset + (radius * 2) - kCapRadius;
229   const int center = offset + radius;
230 
231   // Horizontal stroke.
232   canvas->DrawLine(gfx::PointF(start, center), gfx::PointF(end, center), flags);
233 
234   // Vertical stroke.
235   canvas->DrawLine(gfx::PointF(center, start), gfx::PointF(center, end), flags);
236 }
237 
GetButtonFillColor() const238 SkColor NewTabButton::GetButtonFillColor() const {
239   return GetThemeProvider()->GetDisplayProperty(
240              ThemeProperties::SHOULD_FILL_BACKGROUND_TAB_COLOR)
241              ? tab_strip_->GetTabBackgroundColor(
242                    TabActive::kInactive, BrowserFrameActiveState::kUseCurrent)
243              : SK_ColorTRANSPARENT;
244 }
245 
GetBorderPath(const gfx::Point & origin,float scale,bool extend_to_top) const246 SkPath NewTabButton::GetBorderPath(const gfx::Point& origin,
247                                    float scale,
248                                    bool extend_to_top) const {
249   gfx::PointF scaled_origin(origin);
250   scaled_origin.Scale(scale);
251   const float radius = GetCornerRadius() * scale;
252 
253   SkPath path;
254   if (extend_to_top) {
255     path.moveTo(scaled_origin.x(), 0);
256     const float diameter = radius * 2;
257     path.rLineTo(diameter, 0);
258     path.rLineTo(0, scaled_origin.y() + radius);
259     path.rArcTo(radius, radius, 0, SkPath::kSmall_ArcSize, SkPathDirection::kCW,
260                 -diameter, 0);
261     path.close();
262   } else {
263     path.addCircle(scaled_origin.x() + radius, scaled_origin.y() + radius,
264                    radius);
265   }
266   return path;
267 }
268 
UpdateInkDropBaseColor()269 void NewTabButton::UpdateInkDropBaseColor() {
270   SetInkDropBaseColor(
271       color_utils::GetColorWithMaxContrast(GetButtonFillColor()));
272 }
273