blob: 59c20f9c14ca9303925af74917150f7b504a21a2 [file] [log] [blame]
// Copyright 2013 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_actions_view.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include "ash/app_list/views/search_result_actions_view_delegate.h"
#include "ash/app_list/views/search_result_view.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "base/numerics/ranges.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia_operations.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/border.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout.h"
namespace app_list {
namespace {
// Image buttons.
constexpr int kImageButtonSizeDip = 40;
constexpr int kActionButtonBetweenSpacing = 8;
// Button hover color, Google Grey 8%.
constexpr SkColor kButtonHoverColor = SkColorSetA(gfx::kGoogleGrey900, 0x14);
} // namespace
// SearchResultImageButton renders the button defined by SearchResult::Action.
class SearchResultImageButton : public views::ImageButton {
public:
SearchResultImageButton(SearchResultActionsView* parent,
const SearchResult::Action& action);
~SearchResultImageButton() override {}
// views::View overrides:
void OnFocus() override;
void OnBlur() override;
// ui::EventHandler overrides:
void OnGestureEvent(ui::GestureEvent* event) override;
// views::Button overrides:
void StateChanged(ButtonState old_state) override;
// views::InkDropHost overrides:
std::unique_ptr<views::InkDrop> CreateInkDrop() override;
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
const override;
// Updates the button visibility upon state change of the button or the
// search result view associated with it.
void UpdateOnStateChanged();
private:
// views::View overrides:
void OnPaintBackground(gfx::Canvas* canvas) override;
void SetButtonImage(const gfx::ImageSkia& source, int icon_dimension);
int GetInkDropRadius() const;
const char* GetClassName() const override;
SearchResultActionsView* parent_;
const bool visible_on_hover_;
bool to_be_activate_by_long_press_ = false;
DISALLOW_COPY_AND_ASSIGN(SearchResultImageButton);
};
SearchResultImageButton::SearchResultImageButton(
SearchResultActionsView* parent,
const SearchResult::Action& action)
: ImageButton(parent),
parent_(parent),
visible_on_hover_(action.visible_on_hover) {
SetFocusBehavior(FocusBehavior::ALWAYS);
// Avoid drawing default dashed focus and draw customized focus in
// OnPaintBackground();
SetFocusPainter(nullptr);
SetInkDropMode(InkDropMode::ON);
SetPreferredSize({kImageButtonSizeDip, kImageButtonSizeDip});
SetImageAlignment(HorizontalAlignment::ALIGN_CENTER,
VerticalAlignment::ALIGN_MIDDLE);
SetButtonImage(action.image,
AppListConfig::instance().search_list_icon_dimension());
SetAccessibleName(action.tooltip_text);
SetTooltipText(action.tooltip_text);
SetVisible(!visible_on_hover_);
}
void SearchResultImageButton::OnFocus() {
parent_->ActionButtonStateChanged();
SchedulePaint();
if (visible())
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
void SearchResultImageButton::OnBlur() {
parent_->ActionButtonStateChanged();
SchedulePaint();
}
void SearchResultImageButton::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_LONG_PRESS:
to_be_activate_by_long_press_ = true;
event->SetHandled();
break;
case ui::ET_GESTURE_END:
if (to_be_activate_by_long_press_) {
NotifyClick(*event);
SetState(STATE_NORMAL);
to_be_activate_by_long_press_ = false;
event->SetHandled();
}
break;
default:
break;
}
if (!event->handled())
Button::OnGestureEvent(event);
}
void SearchResultImageButton::StateChanged(
views::Button::ButtonState old_state) {
parent_->ActionButtonStateChanged();
}
std::unique_ptr<views::InkDrop> SearchResultImageButton::CreateInkDrop() {
return CreateDefaultFloodFillInkDropImpl();
}
std::unique_ptr<views::InkDropRipple>
SearchResultImageButton::CreateInkDropRipple() const {
const gfx::Point center = GetLocalBounds().CenterPoint();
const int ripple_radius = GetInkDropRadius();
gfx::Rect bounds(center.x() - ripple_radius, center.y() - ripple_radius,
2 * ripple_radius, 2 * ripple_radius);
constexpr SkColor ripple_color = SkColorSetA(gfx::kGoogleGrey900, 0x17);
return std::make_unique<views::FloodFillInkDropRipple>(
size(), GetLocalBounds().InsetsFrom(bounds),
GetInkDropCenterBasedOnLastEvent(), ripple_color, 1.0f);
}
std::unique_ptr<views::InkDropMask> SearchResultImageButton::CreateInkDropMask()
const {
return std::make_unique<views::CircleInkDropMask>(
size(), GetLocalBounds().CenterPoint(), GetInkDropRadius());
}
std::unique_ptr<views::InkDropHighlight>
SearchResultImageButton::CreateInkDropHighlight() const {
constexpr SkColor ripple_color = SkColorSetA(gfx::kGoogleGrey900, 0x12);
return std::make_unique<views::InkDropHighlight>(
gfx::PointF(GetLocalBounds().CenterPoint()),
std::make_unique<views::CircleLayerDelegate>(ripple_color,
GetInkDropRadius()));
}
void SearchResultImageButton::UpdateOnStateChanged() {
if (!visible_on_hover_)
return;
// Show button if the associated result row is hovered or selected, or one
// of the action buttons is selected.
SetVisible(parent_->IsSearchResultHoveredOrSelected() ||
parent()->Contains(GetFocusManager()->GetFocusedView()));
}
void SearchResultImageButton::OnPaintBackground(gfx::Canvas* canvas) {
if (HasFocus()) {
cc::PaintFlags circle_flags;
circle_flags.setAntiAlias(true);
circle_flags.setColor(kButtonHoverColor);
circle_flags.setStyle(cc::PaintFlags::kFill_Style);
canvas->DrawCircle(GetLocalBounds().CenterPoint(), GetInkDropRadius(),
circle_flags);
}
}
void SearchResultImageButton::SetButtonImage(const gfx::ImageSkia& source,
int icon_dimension) {
SetImage(views::ImageButton::STATE_NORMAL,
gfx::ImageSkiaOperations::CreateResizedImage(
source, skia::ImageOperations::RESIZE_BEST,
gfx::Size(icon_dimension, icon_dimension)));
}
int SearchResultImageButton::GetInkDropRadius() const {
return width() / 2;
}
const char* SearchResultImageButton::GetClassName() const {
return "SearchResultImageButton";
}
SearchResultActionsView::SearchResultActionsView(
SearchResultActionsViewDelegate* delegate)
: delegate_(delegate) {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
kActionButtonBetweenSpacing));
}
SearchResultActionsView::~SearchResultActionsView() {}
void SearchResultActionsView::SetActions(const SearchResult::Actions& actions) {
buttons_.clear();
RemoveAllChildViews(true);
for (const auto& action : actions)
CreateImageButton(action);
PreferredSizeChanged();
}
bool SearchResultActionsView::IsValidActionIndex(size_t action_index) const {
return action_index < children().size();
}
bool SearchResultActionsView::IsSearchResultHoveredOrSelected() const {
return delegate_->IsSearchResultHoveredOrSelected();
}
void SearchResultActionsView::UpdateButtonsOnStateChanged() {
for (SearchResultImageButton* button : buttons_)
button->UpdateOnStateChanged();
}
void SearchResultActionsView::ActionButtonStateChanged() {
UpdateButtonsOnStateChanged();
}
void SearchResultActionsView::CreateImageButton(
const SearchResult::Action& action) {
SearchResultImageButton* button = new SearchResultImageButton(this, action);
AddChildView(button);
buttons_.emplace_back(button);
}
void SearchResultActionsView::ChildVisibilityChanged(views::View* child) {
PreferredSizeChanged();
}
void SearchResultActionsView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (!delegate_)
return;
const int index = GetIndexOf(sender);
DCHECK_NE(-1, index);
delegate_->OnSearchResultActionActivated(index, event.flags());
}
} // namespace app_list