Commit d4425b7d authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

app_list: Make search result actions views keyboard accessible

Summary of changes:
*   Added methods for keeping track of currently selected action, and
    for moving the selection to SearchResultActionsView
*   Added SearchResultActionsViewDelegate::
    OnSearchResultActionsUnSelected so the result view can handle the
    case where actions are un selected - primarily, it sends out a
    a11y notification that the default action (that activates the
    search result) is selected again
*   Renamed SearchResultBaseView::SetBackgroundHighlighted to
    SetSelected, and added logic for setting initial selected result
    action, or clearing action selection. (the latter is protected by
    SearchBoxSelectionEnabled feature).
*   Added SearchResultBaseView::SelectNextResultAction - used by result
    selection controller to move the action selection if needed.
    *    Note: The SearchResultBaseView implementations are expected
         to call newly added set_actions_view to register their result
         actions - otherwise this method will be no-op
*   ResultSelectionController::ResetSelection avoids clearing and
    setting selection again if the view that should be selected is
    already selected

BUG=986382

Change-Id: I9932e9c44abe546b71fbcbf1488c9ac076ae5c84
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1713223
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#680507}
parent 90714335
......@@ -211,10 +211,9 @@ void AppListMainView::SearchBoxFocusChanged(
SearchResultBaseView* first_result_view =
contents_view_->search_results_page_view()->first_result_view();
if (!first_result_view || !first_result_view->background_highlighted())
if (!first_result_view || !first_result_view->selected())
return;
first_result_view->SetBackgroundHighlighted(false);
first_result_view->SetSelected(false, base::nullopt);
}
void AppListMainView::AssistantButtonPressed() {
......
......@@ -1368,7 +1368,7 @@ TEST_F(AppListViewFocusTest, FirstResultSelectedAfterSearchResultsUpdated) {
EXPECT_EQ(search_box_view()->search_box(), focused_view());
EXPECT_EQ(list_view->GetResultViewAt(0),
contents_view()->search_results_page_view()->first_result_view());
EXPECT_TRUE(list_view->GetResultViewAt(0)->background_highlighted());
EXPECT_TRUE(list_view->GetResultViewAt(0)->selected());
// Populate both fake list results and tile results.
const int kTileResults = 3;
......@@ -1380,7 +1380,7 @@ TEST_F(AppListViewFocusTest, FirstResultSelectedAfterSearchResultsUpdated) {
EXPECT_EQ(search_box_view()->search_box(), focused_view());
EXPECT_EQ(tile_views[0],
contents_view()->search_results_page_view()->first_result_view());
EXPECT_TRUE(tile_views[0]->background_highlighted());
EXPECT_TRUE(tile_views[0]->selected());
// This section should remain after flag is removed.
if (app_list_features::IsSearchBoxSelectionEnabled()) {
......@@ -1394,7 +1394,7 @@ TEST_F(AppListViewFocusTest, FirstResultSelectedAfterSearchResultsUpdated) {
// Ensure current highlighted result loses highlight on transition
SimulateKeyPress(ui::VKEY_TAB, false);
EXPECT_FALSE(tile_views[0]->background_highlighted());
EXPECT_FALSE(tile_views[0]->selected());
}
// Populate only answer card.
......@@ -1406,7 +1406,7 @@ TEST_F(AppListViewFocusTest, FirstResultSelectedAfterSearchResultsUpdated) {
->GetAnswerCardResultViewForTest());
EXPECT_EQ(answer_container,
contents_view()->search_results_page_view()->first_result_view());
EXPECT_TRUE(answer_container->background_highlighted());
EXPECT_TRUE(answer_container->selected());
// SearchBoxSelection keeps selection within existing results. Tabbing from
// within a single result has no effect.
......@@ -1418,7 +1418,7 @@ TEST_F(AppListViewFocusTest, FirstResultSelectedAfterSearchResultsUpdated) {
EXPECT_EQ(search_box_view()->close_button(), focused_view());
EXPECT_EQ(answer_container,
contents_view()->search_results_page_view()->first_result_view());
EXPECT_FALSE(answer_container->background_highlighted());
EXPECT_FALSE(answer_container->selected());
SimulateKeyPress(ui::VKEY_TAB, true);
}
......@@ -1453,7 +1453,7 @@ TEST_F(AppListViewFocusTest, FirstResultNotSelectedAfterQuicklyHittingTab) {
contents_view()->search_results_page_view()->first_result_view();
EXPECT_EQ(search_box_view()->search_box(), focused_view());
EXPECT_EQ(list_view->GetResultViewAt(0), first_result_view);
EXPECT_TRUE(first_result_view->background_highlighted());
EXPECT_TRUE(first_result_view->selected());
// Type something else.
search_box_view()->search_box()->InsertText(base::ASCIIToUTF16("test2"));
......@@ -1465,7 +1465,7 @@ TEST_F(AppListViewFocusTest, FirstResultNotSelectedAfterQuicklyHittingTab) {
EXPECT_EQ(search_box_view()->close_button(), focused_view());
SimulateKeyPress(ui::VKEY_TAB, false);
EXPECT_EQ(list_view->GetResultViewAt(0), focused_view());
EXPECT_TRUE(first_result_view->background_highlighted());
EXPECT_TRUE(first_result_view->selected());
// Update search results, both list and tile results are populated.
const int kTileResults = 3;
......@@ -1479,7 +1479,7 @@ TEST_F(AppListViewFocusTest, FirstResultNotSelectedAfterQuicklyHittingTab) {
EXPECT_EQ(list_view->GetResultViewAt(0), focused_view());
EXPECT_EQ(tile_views[0], first_result_view);
EXPECT_FALSE(first_result_view->HasFocus());
EXPECT_TRUE(list_view->GetResultViewAt(0)->background_highlighted());
EXPECT_TRUE(list_view->GetResultViewAt(0)->selected());
}
// Tests hitting Enter key when focus is on search box.
......
......@@ -43,7 +43,9 @@ ResultSelectionController::~ResultSelectionController() = default;
bool ResultSelectionController::MoveSelection(const ui::KeyEvent& event) {
ResultLocationDetails next_location = GetNextResultLocation(event);
bool selection_changed = !(next_location == *selected_location_details_);
SetSelection(next_location);
if (selection_changed) {
SetSelection(next_location, event.IsShiftDown());
}
return selection_changed;
}
......@@ -51,7 +53,7 @@ void ResultSelectionController::ResetSelection() {
// Prevents crash on start up
if (result_selection_model_->size() == 0)
return;
ClearSelection();
selected_location_details_ = std::make_unique<ResultLocationDetails>(
0 /* container_index */,
result_selection_model_->size() /* container_count */,
......@@ -60,15 +62,23 @@ void ResultSelectionController::ResetSelection() {
result_selection_model_->at(0)
->horizontally_traversable() /* container_is_horizontal */);
selected_result_ = result_selection_model_->at(0)->GetFirstResultView();
auto* new_selection = result_selection_model_->at(0)->GetFirstResultView();
if (new_selection && new_selection->selected())
return;
if (selected_result_)
selected_result_->SetSelected(false, base::nullopt);
selected_result_ = new_selection;
if (selected_result_)
selected_result_->SetBackgroundHighlighted(true);
selected_result_->SetSelected(true, base::nullopt);
}
void ResultSelectionController::ClearSelection() {
selected_location_details_ = nullptr;
if (selected_result_)
selected_result_->SetBackgroundHighlighted(false);
selected_result_->SetSelected(false, base::nullopt);
selected_result_ = nullptr;
}
......@@ -88,20 +98,27 @@ ResultSelectionController::GetNextResultLocationForLocation(
if (!(IsUnhandledArrowKeyEvent(event) || event.key_code() == ui::VKEY_TAB))
return new_location;
if (selected_result_ && event.key_code() == ui::VKEY_TAB &&
selected_result_->SelectNextResultAction(event.IsShiftDown())) {
return new_location;
}
switch (event.key_code()) {
case ui::VKEY_TAB:
if (event.IsShiftDown()) {
// Reverse tab traversal always goes to the 'previous' result.
if (location.is_first_result())
if (location.is_first_result()) {
ChangeContainer(&new_location, location.container_index - 1);
else
} else {
--new_location.result_index;
}
} else {
// Forward tab traversal always goes to the 'next' result.
if (location.is_last_result())
if (location.is_last_result()) {
ChangeContainer(&new_location, location.container_index + 1);
else
} else {
++new_location.result_index;
}
}
break;
......@@ -162,13 +179,14 @@ ResultSelectionController::GetNextResultLocationForLocation(
}
void ResultSelectionController::SetSelection(
const ResultLocationDetails& location) {
const ResultLocationDetails& location,
bool reverse_tab_order) {
ClearSelection();
selected_result_ = GetResultAtLocation(location);
selected_location_details_ =
std::make_unique<ResultLocationDetails>(location);
selected_result_->SetBackgroundHighlighted(true);
selected_result_->SetSelected(true, reverse_tab_order);
}
SearchResultBaseView* ResultSelectionController::GetResultAtLocation(
......
......@@ -5,6 +5,7 @@
#ifndef ASH_APP_LIST_VIEWS_RESULT_SELECTION_CONTROLLER_H_
#define ASH_APP_LIST_VIEWS_RESULT_SELECTION_CONTROLLER_H_
#include <memory>
#include <vector>
#include "ash/app_list/app_list_export.h"
......@@ -93,7 +94,8 @@ class APP_LIST_EXPORT ResultSelectionController {
const ResultLocationDetails& location);
// Sets the current selection to the provided |location|.
void SetSelection(const ResultLocationDetails& location);
void SetSelection(const ResultLocationDetails& location,
bool reverse_tab_order);
SearchResultBaseView* GetResultAtLocation(
const ResultLocationDetails& location);
......
......@@ -662,10 +662,6 @@ bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
if (!selection_controller->MoveSelection(key_event))
return true;
// Tells ChromeVox to read this result
selection_controller->selected_result()->NotifyAccessibilityEvent(
ax::mojom::Event::kSelection, true);
// Fill text on result change.
SearchResultBaseView* selected_result_view =
selection_controller->selected_result();
......
......@@ -79,6 +79,7 @@ class SearchResultImageButton : public views::ImageButton {
SearchResultActionsView* parent_;
const bool visible_on_hover_;
bool to_be_activate_by_long_press_ = false;
bool selected_ = false;
DISALLOW_COPY_AND_ASSIGN(SearchResultImageButton);
};
......@@ -180,17 +181,23 @@ SearchResultImageButton::CreateInkDropHighlight() const {
}
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()));
if (visible_on_hover_) {
SetVisible(parent_->IsSearchResultHoveredOrSelected() ||
parent()->Contains(GetFocusManager()->GetFocusedView()));
}
const bool selected = parent_->GetSelectedAction() == tag();
if (selected_ != selected) {
selected_ = selected;
if (selected)
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
}
void SearchResultImageButton::OnPaintBackground(gfx::Canvas* canvas) {
if (HasFocus()) {
if (HasFocus() || parent_->GetSelectedAction() == tag()) {
cc::PaintFlags circle_flags;
circle_flags.setAntiAlias(true);
circle_flags.setColor(kButtonHoverColor);
......@@ -227,15 +234,20 @@ SearchResultActionsView::SearchResultActionsView(
SearchResultActionsView::~SearchResultActionsView() {}
void SearchResultActionsView::SetActions(const SearchResult::Actions& actions) {
if (selected_action_.has_value()) {
selected_action_.reset();
delegate_->OnSearchResultActionsUnSelected();
}
buttons_.clear();
RemoveAllChildViews(true);
for (const auto& action : actions)
CreateImageButton(action);
for (size_t i = 0; i < actions.size(); ++i)
CreateImageButton(actions[i], i);
PreferredSizeChanged();
}
bool SearchResultActionsView::IsValidActionIndex(size_t action_index) const {
return action_index < children().size();
return action_index < GetActionCount();
}
bool SearchResultActionsView::IsSearchResultHoveredOrSelected() const {
......@@ -255,13 +267,71 @@ const char* SearchResultActionsView::GetClassName() const {
return "SearchResultActionsView";
}
bool SearchResultActionsView::SelectInitialAction(bool reverse_tab_order) {
if (GetActionCount() == 0)
return false;
if (reverse_tab_order) {
selected_action_ = GetActionCount() - 1;
} else {
selected_action_.reset();
}
UpdateButtonsOnStateChanged();
return selected_action_.has_value();
}
bool SearchResultActionsView::SelectNextAction(bool reverse_tab_order) {
if (GetActionCount() == 0)
return false;
// For reverse tab order, consider moving to non-selected state.
if (reverse_tab_order) {
if (!selected_action_.has_value())
return false;
if (selected_action_.value() == 0) {
ClearSelectedAction();
return true;
}
}
const int next_index =
selected_action_.value_or(-1) + (reverse_tab_order ? -1 : 1);
if (!IsValidActionIndex(next_index))
return false;
selected_action_ = next_index;
UpdateButtonsOnStateChanged();
return true;
}
void SearchResultActionsView::ClearSelectedAction() {
selected_action_.reset();
delegate_->OnSearchResultActionsUnSelected();
UpdateButtonsOnStateChanged();
}
int SearchResultActionsView::GetSelectedAction() const {
return selected_action_.value_or(-1);
}
bool SearchResultActionsView::HasSelectedAction() const {
return selected_action_.has_value();
}
void SearchResultActionsView::CreateImageButton(
const SearchResult::Action& action) {
const SearchResult::Action& action,
int action_index) {
SearchResultImageButton* button = new SearchResultImageButton(this, action);
button->set_tag(action_index);
AddChildView(button);
buttons_.emplace_back(button);
}
size_t SearchResultActionsView::GetActionCount() const {
return buttons_.size();
}
void SearchResultActionsView::ChildVisibilityChanged(views::View* child) {
PreferredSizeChanged();
}
......@@ -271,9 +341,9 @@ void SearchResultActionsView::ButtonPressed(views::Button* sender,
if (!delegate_)
return;
const int index = GetIndexOf(sender);
DCHECK_NE(-1, index);
delegate_->OnSearchResultActionActivated(index, event.flags());
DCHECK_GE(sender->tag(), 0);
DCHECK_LT(sender->tag(), static_cast<int>(GetActionCount()));
delegate_->OnSearchResultActionActivated(sender->tag(), event.flags());
}
} // namespace app_list
......@@ -7,6 +7,7 @@
#include <vector>
#include "ash/app_list/app_list_export.h"
#include "ash/app_list/model/search/search_result.h"
#include "base/macros.h"
#include "ui/views/controls/button/button.h"
......@@ -20,8 +21,8 @@ class SearchResultImageButton;
// SearchResultActionsView displays a SearchResult::Actions in a button
// strip. Each action is presented as a button and horizontally laid out.
class SearchResultActionsView : public views::View,
public views::ButtonListener {
class APP_LIST_EXPORT SearchResultActionsView : public views::View,
public views::ButtonListener {
public:
explicit SearchResultActionsView(SearchResultActionsViewDelegate* delegate);
~SearchResultActionsView() override;
......@@ -41,8 +42,38 @@ class SearchResultActionsView : public views::View,
// views::View:
const char* GetClassName() const override;
// Selects the result action expected to be initially selected when the parent
// result view gets selected.
// |reverse_tab_order| - Whether the parent result view was selected in
// reverse tab order.
// Returns whether an action was selected (returns false if selected_action_
// is not set).
bool SelectInitialAction(bool reverse_tab_order);
// Select the next result action that should be selected during tab traversal.
// It will not change selection if the next selection would be invalid.
// Note that "no selected action" is treated as a valid (zero) state.
//
// |reverse_tab_order| - Whether the selection should be changed assuming
// reverse tab order.
// Returns whether the selection was changed (which includes selected action
// getting cleared).
bool SelectNextAction(bool reverse_tab_order);
// Clears selected action state.
void ClearSelectedAction();
// Returns the selected action index, or -1 if an action is not selected.
int GetSelectedAction() const;
// Whether an action is currently selected.
bool HasSelectedAction() const;
private:
void CreateImageButton(const SearchResult::Action& action);
void CreateImageButton(const SearchResult::Action& action, int action_index);
// Returns the number of available actions.
size_t GetActionCount() const;
// views::View overrides:
void ChildVisibilityChanged(views::View* child) override;
......@@ -50,6 +81,9 @@ class SearchResultActionsView : public views::View,
// views::ButtonListener overrides:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// If an action is currently selected, the selected action index.
base::Optional<int> selected_action_;
SearchResultActionsViewDelegate* delegate_; // Not owned.
std::vector<SearchResultImageButton*> buttons_;
......
......@@ -15,6 +15,11 @@ class SearchResultActionsViewDelegate {
// in SearchResultActionsView.
virtual void OnSearchResultActionActivated(size_t index, int event_flags) = 0;
// Invoked when result action selection is cleared (it changes from the state
// where a result action is selected to the state where no actions are
// selected).
virtual void OnSearchResultActionsUnSelected() = 0;
// Returns true if the associated search result is hovered by mouse, or
// or selected by keyboard.
virtual bool IsSearchResultHoveredOrSelected() = 0;
......
......@@ -186,11 +186,11 @@ class SearchResultAnswerCardView::AnswerCardResultView
// views::Button overrides:
const char* GetClassName() const override { return "AnswerCardResultView"; }
void OnBlur() override { SetBackgroundHighlighted(false); }
void OnBlur() override { SetSelected(false, base::nullopt); }
void OnFocus() override {
ScrollRectToVisible(GetLocalBounds());
SetBackgroundHighlighted(true);
SetSelected(true, base::nullopt);
}
bool OnKeyPressed(const ui::KeyEvent& event) override {
......@@ -210,7 +210,7 @@ class SearchResultAnswerCardView::AnswerCardResultView
}
void PaintButtonContents(gfx::Canvas* canvas) override {
if (background_highlighted())
if (selected())
canvas->FillRect(GetContentsBounds(), kAnswerCardSelectedColor);
}
......
......@@ -5,6 +5,8 @@
#include "ash/app_list/views/search_result_base_view.h"
#include "ash/app_list/model/search/search_result.h"
#include "ash/app_list/views/search_result_actions_view.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/strings/utf_string_conversions.h"
namespace app_list {
......@@ -26,9 +28,35 @@ const char* SearchResultBaseView::GetClassName() const {
return "SearchResultBaseView";
}
void SearchResultBaseView::SetBackgroundHighlighted(bool enabled) {
background_highlighted_ = enabled;
void SearchResultBaseView::SetSelected(bool selected,
base::Optional<bool> reverse_tab_order) {
if (selected_ == selected)
return;
selected_ = selected;
if (app_list_features::IsSearchBoxSelectionEnabled()) {
if (selected) {
SelectInitialResultAction(reverse_tab_order.value_or(false));
} else {
ClearSelectedResultAction();
}
}
SchedulePaint();
}
bool SearchResultBaseView::SelectNextResultAction(bool reverse_tab_order) {
DCHECK(app_list_features::IsSearchBoxSelectionEnabled());
if (!selected() || !actions_view_)
return false;
if (!actions_view_->SelectNextAction(reverse_tab_order))
return false;
SchedulePaint();
return true;
}
void SearchResultBaseView::SetResult(SearchResult* result) {
......@@ -68,4 +96,20 @@ void SearchResultBaseView::ClearResult() {
result_ = nullptr;
}
void SearchResultBaseView::SelectInitialResultAction(bool reverse_tab_order) {
DCHECK(app_list_features::IsSearchBoxSelectionEnabled());
if (actions_view_ && actions_view_->SelectInitialAction(reverse_tab_order))
return;
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
void SearchResultBaseView::ClearSelectedResultAction() {
DCHECK(app_list_features::IsSearchBoxSelectionEnabled());
if (actions_view_)
actions_view_->ClearSelectedAction();
}
} // namespace app_list
......@@ -13,6 +13,7 @@
namespace app_list {
class SearchResult;
class SearchResultActionsView;
// Base class for views that observe and display a search result
class APP_LIST_EXPORT SearchResultBaseView : public views::Button,
......@@ -21,8 +22,22 @@ class APP_LIST_EXPORT SearchResultBaseView : public views::Button,
public:
SearchResultBaseView();
// Set or remove the background highlight.
void SetBackgroundHighlighted(bool enabled);
// Set whether the result is selected. It updates the background highlight,
// and selects the result action associated with the result if
// SearchBoxSelection feature is enabled.
//
// |reverse_tab_order| - Indicates whether the selection was set as part of
// reverse tab traversal. Should be set when selection was changed while
// handling TAB keyboard key. Ignored if |selected| is false.
void SetSelected(bool selected, base::Optional<bool> reverse_tab_order);
// Selects the next result action for the view, if the result supports
// non-default actions (see actions_view_).
// |reverse_tab_order| - whether the action was selected while handling TAB
// key in reverse tab order.
//
// Returns whether the selected result action was changed.
bool SelectNextResultAction(bool reverse_tab_order);
SearchResult* result() const { return result_; }
void SetResult(SearchResult* result);
......@@ -42,7 +57,7 @@ class APP_LIST_EXPORT SearchResultBaseView : public views::Button,
// Clears the result without calling |OnResultChanged| or |OnResultChanging|
void ClearResult();
bool background_highlighted() const { return background_highlighted_; }
bool selected() const { return selected_; }
int index_in_container() const { return index_in_container_.value(); }
......@@ -59,8 +74,31 @@ class APP_LIST_EXPORT SearchResultBaseView : public views::Button,
void UpdateAccessibleName();
void set_actions_view(SearchResultActionsView* actions_view) {
actions_view_ = actions_view;
}
SearchResultActionsView* actions_view() { return actions_view_; }
private:
bool background_highlighted_ = false;
// Selects the initial action that should be associated with the result view,
// notifying a11y hierarchy of the selection. If the result view does not
// support result actions (i.e. does not have actions_view_), this will just
// announce the current result view selection.
// |reverse_tab_order| - whether the action was selected in reverse tab order.
void SelectInitialResultAction(bool reverse_tab_order);
// If non-default result action was selected, clears the actions_view_'s
// selection state.
void ClearSelectedResultAction();
// Whether the result is currently selected.
bool selected_ = false;
// Expected to be set by result view implementations that supports
// extra result actions. It points to the view containing result actions
// buttons. Owned by the views hierarchy.
SearchResultActionsView* actions_view_ = nullptr;
// The index of this view within a |SearchResultContainerView| that holds it.
base::Optional<int> index_in_container_;
......
......@@ -228,7 +228,7 @@ bool SearchResultPageView::IsFirstResultTile() const {
bool SearchResultPageView::IsFirstResultHighlighted() const {
DCHECK(first_result_view_);
return first_result_view_->background_highlighted();
return first_result_view_->selected();
}
bool SearchResultPageView::OnKeyPressed(const ui::KeyEvent& event) {
......@@ -331,7 +331,7 @@ void SearchResultPageView::OnSearchResultContainerResultsChanged() {
// Clear the first search result view's background highlight.
if (first_result_view_ && first_result_view_ != focused_view)
first_result_view_->SetBackgroundHighlighted(false);
first_result_view_->SetSelected(false, base::nullopt);
}
first_result_view_ = result_container_views_[0]->GetFirstResultView();
......@@ -358,7 +358,7 @@ void SearchResultPageView::OnSearchResultContainerResultsChanged() {
// the focus is not set on the first result to prevent frequent focus switch
// between the search box and the first result when the user is typing
// query.
first_result_view_->SetBackgroundHighlighted(true);
first_result_view_->SetSelected(true, base::nullopt);
}
}
......
......@@ -290,12 +290,12 @@ void SearchResultTileItemView::OnFocus() {
} else {
ScrollRectToVisible(GetLocalBounds());
}
SetBackgroundHighlighted(true);
SetSelected(true, base::nullopt);
UpdateBackgroundColor();
}
void SearchResultTileItemView::OnBlur() {
SetBackgroundHighlighted(false);
SetSelected(false, base::nullopt);
UpdateBackgroundColor();
}
......@@ -304,7 +304,7 @@ void SearchResultTileItemView::StateChanged(ButtonState old_state) {
}
void SearchResultTileItemView::PaintButtonContents(gfx::Canvas* canvas) {
if (!result() || !background_highlighted())
if (!result() || !selected())
return;
gfx::Rect rect(GetContentsBounds());
......
......@@ -65,21 +65,21 @@ SearchResultView::SearchResultView(SearchResultListView* list_view,
AppListViewDelegate* view_delegate)
: list_view_(list_view),
view_delegate_(view_delegate),
icon_(new views::ImageView),
display_icon_(new views::ImageView),
badge_icon_(new views::ImageView),
actions_view_(new SearchResultActionsView(this)),
weak_ptr_factory_(this) {
SetFocusBehavior(FocusBehavior::ALWAYS);
icon_ = AddChildView(std::make_unique<views::ImageView>());
display_icon_ = AddChildView(std::make_unique<views::ImageView>());
badge_icon_ = AddChildView(std::make_unique<views::ImageView>());
auto* actions_view =
AddChildView(std::make_unique<SearchResultActionsView>(this));
set_actions_view(actions_view);
icon_->set_can_process_events_within_subtree(false);
display_icon_->set_can_process_events_within_subtree(false);
SetDisplayIcon(gfx::ImageSkia());
badge_icon_->set_can_process_events_within_subtree(false);
AddChildView(icon_);
AddChildView(display_icon_);
AddChildView(badge_icon_);
AddChildView(actions_view_);
set_context_menu_controller(this);
set_notify_enter_exit_on_child(true);
}
......@@ -165,7 +165,7 @@ void SearchResultView::OnQueryRemovalAccepted(bool accepted, int event_flags) {
if (confirm_remove_by_long_press_) {
confirm_remove_by_long_press_ = false;
SetBackgroundHighlighted(false);
SetSelected(false, base::nullopt);
}
RecordZeroStateSearchResultRemovalHistogram(
......@@ -213,12 +213,12 @@ void SearchResultView::Layout() {
const int max_actions_width =
(rect.right() - kActionButtonRightMargin - icon_bounds.right()) / 2;
int actions_width =
std::min(max_actions_width, actions_view_->GetPreferredSize().width());
std::min(max_actions_width, actions_view()->GetPreferredSize().width());
gfx::Rect actions_bounds(rect);
actions_bounds.set_x(rect.right() - kActionButtonRightMargin - actions_width);
actions_bounds.set_width(actions_width);
actions_view_->SetBoundsRect(actions_bounds);
actions_view()->SetBoundsRect(actions_bounds);
}
bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
......@@ -228,11 +228,17 @@ bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
switch (event.key_code()) {
case ui::VKEY_RETURN:
list_view_->SearchResultActivated(this, event.flags());
if (actions_view()->HasSelectedAction()) {
OnSearchResultActionActivated(static_cast<ash::OmniBoxZeroStateAction>(
actions_view()->GetSelectedAction()),
event.flags());
} else {
list_view_->SearchResultActivated(this, event.flags());
}
return true;
case ui::VKEY_UP:
case ui::VKEY_DOWN:
return !actions_view_->children().empty() &&
return !actions_view()->children().empty() &&
list_view_->HandleVerticalFocusMovement(
this, event.key_code() == ui::VKEY_UP);
case ui::VKEY_DELETE:
......@@ -254,11 +260,11 @@ void SearchResultView::PaintButtonContents(gfx::Canvas* canvas) {
gfx::Rect content_rect(rect);
gfx::Rect text_bounds(rect);
text_bounds.set_x(kPreferredIconViewWidth);
if (actions_view_->GetVisible()) {
if (actions_view()->GetVisible()) {
text_bounds.set_width(
rect.width() - kPreferredIconViewWidth - kTextTrailPadding -
actions_view_->bounds().width() -
(actions_view_->children().empty() ? 0 : kActionButtonRightMargin));
actions_view()->bounds().width() -
(actions_view()->children().empty() ? 0 : kActionButtonRightMargin));
} else {
text_bounds.set_width(rect.width() - kPreferredIconViewWidth -
kTextTrailPadding - kActionButtonRightMargin);
......@@ -273,8 +279,9 @@ void SearchResultView::PaintButtonContents(gfx::Canvas* canvas) {
// Possibly call FillRect a second time (these colours are partially
// transparent, so the previous FillRect is not redundant).
if (background_highlighted())
if (selected() && !actions_view()->HasSelectedAction()) {
canvas->FillRect(content_rect, kRowHighlightedColor);
}
gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
canvas->FillRect(border_bottom, kResultBorderColor);
......@@ -305,23 +312,21 @@ void SearchResultView::PaintButtonContents(gfx::Canvas* canvas) {
void SearchResultView::OnFocus() {
ScrollRectToVisible(GetLocalBounds());
SetBackgroundHighlighted(true);
selected_ = true;
actions_view_->UpdateButtonsOnStateChanged();
SetSelected(true, base::nullopt);
actions_view()->UpdateButtonsOnStateChanged();
}
void SearchResultView::OnBlur() {
SetBackgroundHighlighted(false);
selected_ = false;
actions_view_->UpdateButtonsOnStateChanged();
SetSelected(false, base::nullopt);
actions_view()->UpdateButtonsOnStateChanged();
}
void SearchResultView::OnMouseEntered(const ui::MouseEvent& event) {
actions_view_->UpdateButtonsOnStateChanged();
actions_view()->UpdateButtonsOnStateChanged();
}
void SearchResultView::OnMouseExited(const ui::MouseEvent& event) {
actions_view_->UpdateButtonsOnStateChanged();
actions_view()->UpdateButtonsOnStateChanged();
}
void SearchResultView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
......@@ -346,11 +351,11 @@ void SearchResultView::VisibilityChanged(View* starting_from, bool is_visible) {
void SearchResultView::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_LONG_PRESS:
if (actions_view_->IsValidActionIndex(
if (actions_view()->IsValidActionIndex(
ash::OmniBoxZeroStateAction::kRemoveSuggestion)) {
ScrollRectToVisible(GetLocalBounds());
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
SetBackgroundHighlighted(true);
SetSelected(true, base::nullopt);
confirm_remove_by_long_press_ = true;
OnSearchResultActionActivated(
ash::OmniBoxZeroStateAction::kRemoveSuggestion, event->flags());
......@@ -393,9 +398,9 @@ void SearchResultView::OnMetadataChanged() {
badge_icon_->SetVisible(true);
}
// Updates |actions_view_|.
actions_view_->SetActions(result() ? result()->actions()
: SearchResult::Actions());
// Updates |actions_view()|.
actions_view()->SetActions(result() ? result()->actions()
: SearchResult::Actions());
}
void SearchResultView::SetIconImage(const gfx::ImageSkia& source,
......@@ -439,6 +444,13 @@ void SearchResultView::OnSearchResultActionActivated(size_t index,
}
}
void SearchResultView::OnSearchResultActionsUnSelected() {
// If the selection has changed to default result action, announce the
// selection change to a11y stack.
if (selected())
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
bool SearchResultView::IsSearchResultHoveredOrSelected() {
return IsMouseHovered() || selected();
}
......
......@@ -37,7 +37,6 @@ class SearchResultListViewTest;
class AppListViewDelegate;
class SearchResult;
class SearchResultListView;
class SearchResultActionsView;
// SearchResultView displays a SearchResult.
class APP_LIST_EXPORT SearchResultView
......@@ -55,8 +54,6 @@ class APP_LIST_EXPORT SearchResultView
// Sets/gets SearchResult displayed by this view.
void OnResultChanged() override;
bool selected() const { return selected_; }
void SetDisplayIcon(const gfx::ImageSkia& source);
private:
......@@ -111,6 +108,7 @@ class APP_LIST_EXPORT SearchResultView
// SearchResultActionsViewDelegate overrides:
void OnSearchResultActionActivated(size_t index, int event_flags) override;
void OnSearchResultActionsUnSelected() override;
bool IsSearchResultHoveredOrSelected() override;
// Invoked when the context menu closes.
......@@ -127,12 +125,9 @@ class APP_LIST_EXPORT SearchResultView
views::ImageView* badge_icon_; // Owned by views hierarchy.
std::unique_ptr<gfx::RenderText> title_text_;
std::unique_ptr<gfx::RenderText> details_text_;
SearchResultActionsView* actions_view_; // Owned by the views hierarchy.
std::unique_ptr<AppListMenuModelAdapter> context_menu_;
// Whether this view is selected.
bool selected_ = false;
// Whether the removal confirmation dialog is invoked by long press touch.
bool confirm_remove_by_long_press_ = false;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment