blob: 9541a7c9062179cc22c99cfb7cffb8e13fa2b7c2 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/tabs/new_tab_button.h"
#include "build/build_config.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/feature_promos/new_tab_promo_bubble_view.h"
#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "components/feature_engagement/buildflags.h"
#include "ui/base/default_theme_provider.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/widget/widget.h"
#if defined(OS_WIN)
#include "ui/display/win/screen_win.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/views/win/hwnd_util.h"
#endif
#if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
#include "chrome/browser/feature_engagement/new_tab/new_tab_tracker.h"
#include "chrome/browser/feature_engagement/new_tab/new_tab_tracker_factory.h"
#include "chrome/browser/ui/views/feature_promos/new_tab_promo_bubble_view.h"
#endif
namespace {
constexpr gfx::Size kButtonSize(28, 28);
} // namespace
NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
: views::ImageButton(listener), tab_strip_(tab_strip) {
set_animate_on_state_change(true);
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
set_triggerable_event_flags(triggerable_event_flags() |
ui::EF_MIDDLE_MOUSE_BUTTON);
#endif
// Initialize the ink drop mode for a ripple highlight on button press.
ink_drop_container_ = new views::InkDropContainerView();
AddChildView(ink_drop_container_);
ink_drop_container_->SetVisible(false);
SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
set_ink_drop_visible_opacity(0.08f);
SetFocusPainter(nullptr);
SetInstallFocusRingOnFocus(true);
// The button is placed vertically exactly in the center of the tabstrip.
const int extra_vertical_space = GetLayoutConstant(TAB_HEIGHT) -
GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP) -
kButtonSize.height();
constexpr int kHorizontalInset = 8;
SetBorder(views::CreateEmptyBorder(gfx::Insets(
extra_vertical_space / 2, kHorizontalInset, 0, kHorizontalInset)));
}
NewTabButton::~NewTabButton() {
if (destroyed_)
*destroyed_ = true;
}
// static
void NewTabButton::ShowPromoForLastActiveBrowser() {
BrowserView* browser = static_cast<BrowserView*>(
BrowserList::GetInstance()->GetLastActive()->window());
browser->tabstrip()->new_tab_button()->ShowPromo();
}
// static
void NewTabButton::CloseBubbleForLastActiveBrowser() {
BrowserView* browser = static_cast<BrowserView*>(
BrowserList::GetInstance()->GetLastActive()->window());
browser->tabstrip()->new_tab_button()->CloseBubble();
}
void NewTabButton::ShowPromo() {
DCHECK(!new_tab_promo_);
// Owned by its native widget. Will be destroyed as its widget is destroyed.
new_tab_promo_ = NewTabPromoBubbleView::CreateOwned(this);
new_tab_promo_observer_.Add(new_tab_promo_->GetWidget());
SchedulePaint();
}
void NewTabButton::CloseBubble() {
if (new_tab_promo_)
new_tab_promo_->CloseBubble();
}
void NewTabButton::FrameColorsChanged() {
UpdateInkDropBaseColor();
}
void NewTabButton::AnimateInkDropToStateForTesting(views::InkDropState state) {
GetInkDrop()->AnimateToState(state);
}
#if defined(OS_WIN)
void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
if (!event.IsOnlyRightMouseButton()) {
views::ImageButton::OnMouseReleased(event);
return;
}
gfx::Point point = event.location();
views::View::ConvertPointToScreen(this, &point);
point = display::win::ScreenWin::DIPToScreenPoint(point);
bool destroyed = false;
destroyed_ = &destroyed;
gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
if (!destroyed)
SetState(views::Button::STATE_NORMAL);
}
#endif
void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
// Consume all gesture events here so that the parent (Tab) does not
// start consuming gestures.
views::ImageButton::OnGestureEvent(event);
event->SetHandled();
}
void NewTabButton::AddInkDropLayer(ui::Layer* ink_drop_layer) {
DCHECK(ink_drop_layer->bounds().size() == GetContentsBounds().size());
DCHECK(ink_drop_container_->bounds().size() == GetContentsBounds().size());
ink_drop_container_->AddInkDropLayer(ink_drop_layer);
InstallInkDropMask(ink_drop_layer);
}
void NewTabButton::RemoveInkDropLayer(ui::Layer* ink_drop_layer) {
ResetInkDropMask();
ink_drop_container_->RemoveInkDropLayer(ink_drop_layer);
}
std::unique_ptr<views::InkDropRipple> NewTabButton::CreateInkDropRipple()
const {
const gfx::Rect contents_bounds = GetContentsBounds();
return std::make_unique<views::FloodFillInkDropRipple>(
contents_bounds.size(), gfx::Insets(),
GetInkDropCenterBasedOnLastEvent() - contents_bounds.OffsetFromOrigin(),
GetInkDropBaseColor(), ink_drop_visible_opacity());
}
std::unique_ptr<views::InkDropHighlight> NewTabButton::CreateInkDropHighlight()
const {
const gfx::Rect bounds(GetContentsBounds().size());
auto highlight = CreateDefaultInkDropHighlight(
gfx::RectF(bounds).CenterPoint(), bounds.size());
highlight->set_visible_opacity(0.1f);
return highlight;
}
void NewTabButton::NotifyClick(const ui::Event& event) {
ImageButton::NotifyClick(event);
GetInkDrop()->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
}
std::unique_ptr<views::InkDrop> NewTabButton::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop =
std::make_unique<views::InkDropImpl>(this, GetContentsBounds().size());
ink_drop->SetAutoHighlightMode(views::InkDropImpl::AutoHighlightMode::NONE);
ink_drop->SetShowHighlightOnHover(true);
UpdateInkDropBaseColor();
return ink_drop;
}
std::unique_ptr<views::InkDropMask> NewTabButton::CreateInkDropMask() const {
return std::make_unique<views::RoundRectInkDropMask>(
GetContentsBounds().size(), gfx::Insets(), GetCornerRadius());
}
void NewTabButton::PaintButtonContents(gfx::Canvas* canvas) {
gfx::ScopedCanvas scoped_canvas(canvas);
canvas->Translate(GetContentsBounds().OffsetFromOrigin());
PaintFill(canvas);
PaintPlusIcon(canvas);
}
void NewTabButton::Layout() {
ImageButton::Layout();
// TODO(pkasting): Instead of setting this bounds rect, maybe have the
// container match the view bounds, then undo the coordinate transforms in
// the ink drop creation methods above.
const gfx::Rect contents_bounds = GetContentsBounds();
ink_drop_container_->SetBoundsRect(contents_bounds);
focus_ring()->SetPath(
GetBorderPath(GetContentsBounds().origin(), 1.0f, false));
}
gfx::Size NewTabButton::CalculatePreferredSize() const {
gfx::Size size = kButtonSize;
const auto insets = GetInsets();
size.Enlarge(insets.width(), insets.height());
return size;
}
void NewTabButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
const gfx::Size ink_drop_size = GetContentsBounds().size();
GetInkDrop()->HostSizeChanged(ink_drop_size);
UpdateInkDropMaskLayerSize(ink_drop_size);
}
bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
DCHECK(mask);
const float scale = GetWidget()->GetCompositor()->device_scale_factor();
// TODO(pkasting): Fitts' Law horizontally when appropriate.
gfx::Path border = GetBorderPath(GetContentsBounds().origin(), scale,
tab_strip_->SizeTabButtonToTopOfTabStrip());
mask->addPath(border, SkMatrix::MakeScale(1 / scale));
return true;
}
void NewTabButton::OnWidgetDestroying(views::Widget* widget) {
#if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
feature_engagement::NewTabTrackerFactory::GetInstance()
->GetForProfile(tab_strip_->controller()->GetProfile())
->OnPromoClosed();
#endif
new_tab_promo_observer_.Remove(widget);
new_tab_promo_ = nullptr;
// When the promo widget is destroyed, the NewTabButton needs to be recolored.
SchedulePaint();
}
int NewTabButton::GetCornerRadius() const {
return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
views::EMPHASIS_MAXIMUM, GetContentsBounds().size());
}
void NewTabButton::PaintFill(gfx::Canvas* canvas) const {
gfx::ScopedCanvas scoped_canvas(canvas);
canvas->UndoDeviceScaleFactor();
cc::PaintFlags flags;
flags.setAntiAlias(true);
bool has_custom_image;
const int bg_id = tab_strip_->GetBackgroundResourceId(&has_custom_image);
const float scale = canvas->image_scale();
if (has_custom_image && !new_tab_promo_observer_.IsObservingSources()) {
float x_scale = scale;
const gfx::Rect& contents_bounds = GetContentsBounds();
int x = GetMirroredX() + contents_bounds.x() + background_offset_;
if (base::i18n::IsRTL()) {
// The new tab background is mirrored in RTL mode, but the theme
// background should never be mirrored. Mirror it here to compensate.
x_scale = -scale;
// Offset by |width| such that the same region is painted as if there
// was no flip.
x += contents_bounds.width();
}
canvas->InitPaintFlagsForTiling(
*GetThemeProvider()->GetImageSkiaNamed(bg_id), x, contents_bounds.y(),
x_scale, scale, 0, 0, SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode, &flags);
} else {
flags.setColor(GetButtonFillColor());
}
canvas->DrawPath(GetBorderPath(gfx::Point(), scale, false), flags);
}
void NewTabButton::PaintPlusIcon(gfx::Canvas* canvas) const {
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setColor(tab_strip_->GetTabForegroundColor(TAB_INACTIVE));
flags.setStrokeCap(cc::PaintFlags::kRound_Cap);
constexpr int kStrokeWidth = 2;
flags.setStrokeWidth(kStrokeWidth);
const int radius =
ui::MaterialDesignController::IsTouchOptimizedUiEnabled() ? 7 : 6;
const int offset = GetCornerRadius() - radius;
// The cap will be added outside the end of the stroke; inset to compensate.
constexpr int kCapRadius = kStrokeWidth / 2;
const int start = offset + kCapRadius;
const int end = offset + (radius * 2) - kCapRadius;
const int center = offset + radius;
// Horizontal stroke.
canvas->DrawLine(gfx::PointF(start, center), gfx::PointF(end, center), flags);
// Vertical stroke.
canvas->DrawLine(gfx::PointF(center, start), gfx::PointF(center, end), flags);
}
SkColor NewTabButton::GetButtonFillColor() const {
if (new_tab_promo_observer_.IsObservingSources()) {
return GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ProminentButtonColor);
}
return GetThemeProvider()->GetDisplayProperty(
ThemeProperties::SHOULD_FILL_BACKGROUND_TAB_COLOR)
? tab_strip_->GetTabBackgroundColor(TAB_INACTIVE)
: SK_ColorTRANSPARENT;
}
gfx::Path NewTabButton::GetBorderPath(const gfx::Point& origin,
float scale,
bool extend_to_top) const {
gfx::PointF scaled_origin(origin);
scaled_origin.Scale(scale);
const float radius = GetCornerRadius() * scale;
gfx::Path path;
if (extend_to_top) {
path.moveTo(scaled_origin.x(), 0);
const float diameter = radius * 2;
path.rLineTo(diameter, 0);
path.rLineTo(0, scaled_origin.y() + radius);
path.rArcTo(radius, radius, 0, SkPath::kSmall_ArcSize,
SkPath::kCW_Direction, -diameter, 0);
path.close();
} else {
path.addCircle(scaled_origin.x() + radius, scaled_origin.y() + radius,
radius);
}
return path;
}
void NewTabButton::UpdateInkDropBaseColor() {
set_ink_drop_base_color(color_utils::BlendTowardOppositeLuma(
GetButtonFillColor(), SK_AlphaOPAQUE));
}