blob: dcf7c8156d9fc207fea1a9311f5542cee72daefd [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 <memory>
#include <string>
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.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/tabs/tab_types.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/grit/generated_resources.h"
#include "components/variations/variations_associated_data.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/base/theme_provider.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.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/widget/widget.h"
#if defined(OS_WIN)
#include "ui/display/win/screen_win.h"
#include "ui/views/win/hwnd_util.h"
#endif
// static
const gfx::Size NewTabButton::kButtonSize{28, 28};
class NewTabButton::HighlightPathGenerator
: public views::HighlightPathGenerator {
public:
HighlightPathGenerator() = default;
HighlightPathGenerator(const HighlightPathGenerator&) = delete;
HighlightPathGenerator& operator=(const HighlightPathGenerator&) = delete;
// views::HighlightPathGenerator:
SkPath GetHighlightPath(const views::View* view) override {
return static_cast<const NewTabButton*>(view)->GetBorderPath(
view->GetContentsBounds().origin(), 1.0f, false);
}
};
NewTabButton::NewTabButton(TabStrip* tab_strip, PressedCallback callback)
: views::ImageButton(std::move(callback)), tab_strip_(tab_strip) {
SetAnimateOnStateChange(true);
// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
// of lacros-chrome is complete.
#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
SetTriggerableEventFlags(GetTriggerableEventFlags() |
ui::EF_MIDDLE_MOUSE_BUTTON);
#endif
ink_drop_container_ =
AddChildView(std::make_unique<views::InkDropContainerView>());
views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
views::InkDrop::Get(this)->SetHighlightOpacity(0.16f);
views::InkDrop::Get(this)->SetVisibleOpacity(0.14f);
SetInstallFocusRingOnFocus(true);
views::HighlightPathGenerator::Install(
this, std::make_unique<NewTabButton::HighlightPathGenerator>());
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
}
NewTabButton::~NewTabButton() {
// TODO(pbos): Revisit explicit removal of InkDrop for classes that override
// Add/RemoveLayerBeneathView(). This is done so that the InkDrop doesn't
// access the non-override versions in ~View.
views::InkDrop::Remove(this);
}
void NewTabButton::FrameColorsChanged() {
UpdateInkDropBaseColor();
SchedulePaint();
}
void NewTabButton::AnimateToStateForTesting(views::InkDropState state) {
views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(state);
}
void NewTabButton::AddLayerBeneathView(ui::Layer* new_layer) {
ink_drop_container_->AddLayerBeneathView(new_layer);
}
void NewTabButton::RemoveLayerBeneathView(ui::Layer* old_layer) {
ink_drop_container_->RemoveLayerBeneathView(old_layer);
}
SkColor NewTabButton::GetForegroundColor() const {
const SkColor background_color = tab_strip_->GetTabBackgroundColor(
TabActive::kInactive, BrowserFrameActiveState::kUseCurrent);
return tab_strip_->GetTabForegroundColor(TabActive::kInactive,
background_color);
}
void NewTabButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
ImageButton::OnBoundsChanged(previous_bounds);
ink_drop_container_->SetBoundsRect(GetLocalBounds());
}
#if defined(OS_WIN)
void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
if (!event.IsOnlyRightMouseButton()) {
views::ImageButton::OnMouseReleased(event);
return;
}
// TODO(pkasting): If we handled right-clicks on the frame, and we made sure
// this event was not handled, it seems like things would Just Work.
gfx::Point point = event.location();
views::View::ConvertPointToScreen(this, &point);
point = display::win::ScreenWin::DIPToScreenPoint(point);
auto weak_this = weak_factory_.GetWeakPtr();
views::ShowSystemMenuAtScreenPixelLocation(views::HWNDForView(this), point);
if (!weak_this)
return;
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::NotifyClick(const ui::Event& event) {
ImageButton::NotifyClick(event);
views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
views::InkDropState::ACTION_TRIGGERED);
}
void NewTabButton::PaintButtonContents(gfx::Canvas* canvas) {
gfx::ScopedCanvas scoped_canvas(canvas);
canvas->Translate(GetContentsBounds().OffsetFromOrigin());
PaintFill(canvas);
PaintIcon(canvas);
}
gfx::Size NewTabButton::CalculatePreferredSize() const {
gfx::Size size = kButtonSize;
const auto insets = GetInsets();
size.Enlarge(insets.width(), insets.height());
return size;
}
bool NewTabButton::GetHitTestMask(SkPath* mask) const {
DCHECK(mask);
gfx::Point origin = GetContentsBounds().origin();
if (base::i18n::IsRTL())
origin.set_x(GetInsets().right());
const float scale = GetWidget()->GetCompositor()->device_scale_factor();
SkPath border = GetBorderPath(origin, scale,
tab_strip_->controller()->IsFrameCondensed());
mask->addPath(border, SkMatrix::Scale(1 / scale, 1 / scale));
return true;
}
int NewTabButton::GetCornerRadius() const {
return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
views::Emphasis::kMaximum, GetContentsBounds().size());
}
void NewTabButton::PaintFill(gfx::Canvas* canvas) const {
gfx::ScopedCanvas scoped_canvas(canvas);
canvas->UndoDeviceScaleFactor();
cc::PaintFlags flags;
flags.setAntiAlias(true);
const float scale = canvas->image_scale();
const absl::optional<int> bg_id =
tab_strip_->GetCustomBackgroundId(BrowserFrameActiveState::kUseCurrent);
if (bg_id.has_value()) {
float x_scale = scale;
const gfx::Rect& contents_bounds = GetContentsBounds();
gfx::RectF bounds_in_tab_strip(GetLocalBounds());
View::ConvertRectToTarget(this, tab_strip_, &bounds_in_tab_strip);
int x = bounds_in_tab_strip.x() + contents_bounds.x() +
tab_strip_->GetBackgroundOffset();
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.value()), x,
contents_bounds.y(), x_scale, scale, 0, 0, SkTileMode::kRepeat,
SkTileMode::kRepeat, &flags);
} else {
flags.setColor(GetButtonFillColor());
}
canvas->DrawPath(GetBorderPath(gfx::Point(), scale, false), flags);
}
void NewTabButton::PaintIcon(gfx::Canvas* canvas) {
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setColor(GetForegroundColor());
flags.setStrokeCap(cc::PaintFlags::kRound_Cap);
constexpr int kStrokeWidth = 2;
flags.setStrokeWidth(kStrokeWidth);
const int radius = ui::TouchUiController::Get()->touch_ui() ? 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 {
return GetThemeProvider()->GetDisplayProperty(
ThemeProperties::SHOULD_FILL_BACKGROUND_TAB_COLOR)
? tab_strip_->GetTabBackgroundColor(
TabActive::kInactive, BrowserFrameActiveState::kUseCurrent)
: SK_ColorTRANSPARENT;
}
SkPath 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;
SkPath 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, SkPathDirection::kCW,
-diameter, 0);
path.close();
} else {
path.addCircle(scaled_origin.x() + radius, scaled_origin.y() + radius,
radius);
}
return path;
}
void NewTabButton::UpdateInkDropBaseColor() {
views::InkDrop::Get(this)->SetBaseColor(
color_utils::GetColorWithMaxContrast(GetButtonFillColor()));
}
BEGIN_METADATA(NewTabButton, views::ImageButton)
END_METADATA