blob: 813b8292bd6f1ff4dbff54b1affd3ce4a7178a84 [file] [log] [blame]
// Copyright 2018 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 "ash/app_list/views/search_result_suggestion_chip_view.h"
#include <utility>
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/app_list_view_delegate.h"
#include "ash/app_list/model/search/search_result.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/internal_app_id_constants.h"
#include "ash/public/interfaces/app_list.mojom.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/strings/grit/ui_strings.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/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
namespace app_list {
namespace {
constexpr SkColor kBackgroundColor = SkColorSetA(gfx::kGoogleGrey100, 0x14);
constexpr SkColor kTextColor = gfx::kGoogleGrey100;
constexpr SkColor kRippleColor = SkColorSetA(gfx::kGoogleGrey100, 0x0F);
constexpr SkColor kFocusRingColor = gfx::kGoogleBlue300;
constexpr int kFocusRingWidth = 2;
constexpr int kFocusRingCornerRadius = 16;
constexpr int kMaxTextWidth = 192;
constexpr int kBlurRadius = 5;
constexpr int kIconMarginDip = 8;
constexpr int kPaddingDip = 16;
constexpr int kPreferredHeightDip = 32;
// Records an app being launched.
void LogAppLaunch(int index_in_suggestion_chip_container) {
DCHECK_GE(index_in_suggestion_chip_container, 0);
base::UmaHistogramSparse("Apps.AppListSuggestedChipLaunched",
index_in_suggestion_chip_container);
base::RecordAction(base::UserMetricsAction("AppList_OpenSuggestedApp"));
}
} // namespace
SearchResultSuggestionChipView::SearchResultSuggestionChipView(
AppListViewDelegate* view_delegate)
: view_delegate_(view_delegate),
icon_view_(new views::ImageView()),
text_view_(new views::Label()),
weak_ptr_factory_(this) {
SetFocusBehavior(FocusBehavior::ALWAYS);
SetInkDropMode(InkDropMode::ON);
InitLayout();
}
SearchResultSuggestionChipView::~SearchResultSuggestionChipView() {
ClearResult();
}
void SearchResultSuggestionChipView::SetBackgroundBlurEnabled(bool enabled) {
// Background blur is enabled if and only if layer exists.
if (!!layer() == enabled)
return;
if (!enabled) {
DestroyLayer();
return;
}
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
layer()->SetBackgroundBlur(kBlurRadius);
SetRoundedRectMaskLayer(kPreferredHeightDip / 2);
}
void SearchResultSuggestionChipView::OnResultChanged() {
SetVisible(!!result());
UpdateSuggestionChipView();
}
void SearchResultSuggestionChipView::SetIndexInSuggestionChipContainer(
size_t index) {
index_in_suggestion_chip_container_ = index;
}
void SearchResultSuggestionChipView::OnMetadataChanged() {
UpdateSuggestionChipView();
}
void SearchResultSuggestionChipView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK(result());
LogAppLaunch(index_in_suggestion_chip_container_);
RecordSearchResultOpenSource(result(), view_delegate_->GetModel(),
view_delegate_->GetSearchModel());
view_delegate_->OpenSearchResult(
result()->id(), event.flags(),
ash::mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
ash::mojom::AppListLaunchType::kAppSearchResult,
index_in_suggestion_chip_container_);
}
const char* SearchResultSuggestionChipView::GetClassName() const {
return "SearchResultSuggestionChipView";
}
gfx::Size SearchResultSuggestionChipView::CalculatePreferredSize() const {
const int preferred_width = views::View::CalculatePreferredSize().width();
return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
}
int SearchResultSuggestionChipView::GetHeightForWidth(int width) const {
return kPreferredHeightDip;
}
void SearchResultSuggestionChipView::ChildVisibilityChanged(
views::View* child) {
// When icon visibility is modified we need to update layout padding.
if (child == icon_view_) {
const int padding_left_dip =
icon_view_->visible() ? kIconMarginDip : kPaddingDip;
layout_manager_->set_inside_border_insets(
gfx::Insets(0, padding_left_dip, 0, kPaddingDip));
}
PreferredSizeChanged();
}
void SearchResultSuggestionChipView::OnPaintBackground(gfx::Canvas* canvas) {
cc::PaintFlags flags;
flags.setAntiAlias(true);
gfx::Rect bounds = GetContentsBounds();
// Background.
flags.setColor(kBackgroundColor);
canvas->DrawRoundRect(bounds, height() / 2, flags);
if (HasFocus()) {
flags.setColor(kFocusRingColor);
flags.setStyle(cc::PaintFlags::Style::kStroke_Style);
flags.setStrokeWidth(kFocusRingWidth);
// Pushes the focus ring outside of the chip to create a border.
bounds.Inset(-1, -1);
canvas->DrawRoundRect(bounds, kFocusRingCornerRadius, flags);
}
}
void SearchResultSuggestionChipView::OnFocus() {
SchedulePaint();
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
void SearchResultSuggestionChipView::OnBlur() {
SchedulePaint();
}
void SearchResultSuggestionChipView::OnBoundsChanged(
const gfx::Rect& previous_bounds) {
if (chip_mask_)
chip_mask_->layer()->SetBounds(GetLocalBounds());
}
bool SearchResultSuggestionChipView::OnKeyPressed(const ui::KeyEvent& event) {
if (event.key_code() == ui::VKEY_SPACE)
return false;
return Button::OnKeyPressed(event);
}
std::unique_ptr<views::InkDrop>
SearchResultSuggestionChipView::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop =
Button::CreateDefaultInkDropImpl();
ink_drop->SetShowHighlightOnHover(false);
ink_drop->SetShowHighlightOnFocus(false);
ink_drop->SetAutoHighlightMode(views::InkDropImpl::AutoHighlightMode::NONE);
return std::move(ink_drop);
}
std::unique_ptr<views::InkDropMask>
SearchResultSuggestionChipView::CreateInkDropMask() const {
return std::make_unique<views::RoundRectInkDropMask>(size(), gfx::InsetsF(),
height() / 2);
}
std::unique_ptr<views::InkDropRipple>
SearchResultSuggestionChipView::CreateInkDropRipple() const {
const gfx::Point center = GetLocalBounds().CenterPoint();
const int ripple_radius = width() / 2;
gfx::Rect bounds(center.x() - ripple_radius, center.y() - ripple_radius,
2 * ripple_radius, 2 * ripple_radius);
return std::make_unique<views::FloodFillInkDropRipple>(
size(), GetLocalBounds().InsetsFrom(bounds),
GetInkDropCenterBasedOnLastEvent(), kRippleColor, 1.0f);
}
std::unique_ptr<ui::Layer> SearchResultSuggestionChipView::RecreateLayer() {
std::unique_ptr<ui::Layer> old_layer = views::View::RecreateLayer();
if (layer())
SetRoundedRectMaskLayer(kPreferredHeightDip / 2);
return old_layer;
}
void SearchResultSuggestionChipView::SetIcon(const gfx::ImageSkia& icon) {
icon_view_->SetImage(icon);
icon_view_->SetVisible(true);
}
void SearchResultSuggestionChipView::SetText(const base::string16& text) {
text_view_->SetText(text);
gfx::Size size = text_view_->CalculatePreferredSize();
size.set_width(std::min(kMaxTextWidth, size.width()));
text_view_->SetPreferredSize(size);
}
const base::string16& SearchResultSuggestionChipView::GetText() const {
return text_view_->text();
}
void SearchResultSuggestionChipView::UpdateSuggestionChipView() {
if (!result()) {
SetIcon(gfx::ImageSkia());
SetText(base::string16());
SetAccessibleName(base::string16());
return;
}
SetIcon(result()->chip_icon());
SetText(result()->title());
base::string16 accessible_name = result()->title();
if (result()->id() == app_list::kInternalAppIdContinueReading) {
accessible_name = l10n_util::GetStringFUTF16(
IDS_APP_LIST_CONTINUE_READING_ACCESSIBILE_NAME, accessible_name);
}
SetAccessibleName(accessible_name);
}
void SearchResultSuggestionChipView::InitLayout() {
layout_manager_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(0, kPaddingDip, 0, kPaddingDip), kIconMarginDip));
layout_manager_->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER);
// Create an empty border wherin the focus ring can appear.
SetBorder(views::CreateEmptyBorder(gfx::Insets(kFocusRingWidth)));
// Icon.
const int icon_size =
AppListConfig::instance().suggestion_chip_icon_dimension();
icon_view_ = new views::ImageView;
icon_view_->SetImageSize(gfx::Size(icon_size, icon_size));
icon_view_->SetPreferredSize(gfx::Size(icon_size, icon_size));
icon_view_->SetVisible(false);
AddChildView(icon_view_);
// Text.
text_view_->SetAutoColorReadabilityEnabled(false);
text_view_->SetEnabledColor(kTextColor);
text_view_->SetSubpixelRenderingEnabled(false);
text_view_->SetFontList(AppListConfig::instance().app_title_font());
SetText(base::string16());
AddChildView(text_view_);
}
void SearchResultSuggestionChipView::SetRoundedRectMaskLayer(
int corner_radius) {
chip_mask_ = views::Painter::CreatePaintedLayer(
views::Painter::CreateSolidRoundRectPainter(SK_ColorBLACK,
corner_radius));
chip_mask_->layer()->SetFillsBoundsOpaquely(false);
chip_mask_->layer()->SetBounds(GetLocalBounds());
layer()->SetMaskLayer(chip_mask_->layer());
}
} // namespace app_list