123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- // Copyright 2021 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/recent_apps_view.h"
- #include <algorithm>
- #include <memory>
- #include <string>
- #include <vector>
- #include "ash/app_list/app_list_util.h"
- #include "ash/app_list/app_list_view_delegate.h"
- #include "ash/app_list/model/app_list_item.h"
- #include "ash/app_list/model/app_list_model.h"
- #include "ash/app_list/model/search/search_model.h"
- #include "ash/app_list/model/search/search_result.h"
- #include "ash/app_list/views/app_list_item_view.h"
- #include "ash/app_list/views/app_list_keyboard_controller.h"
- #include "ash/public/cpp/app_list/app_list_config.h"
- #include "ash/public/cpp/app_list/app_list_config_provider.h"
- #include "ash/public/cpp/app_list/app_list_notifier.h"
- #include "ash/public/cpp/app_list/app_list_types.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "base/bind.h"
- #include "base/check.h"
- #include "base/strings/string_util.h"
- #include "extensions/common/constants.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/views/accessibility/view_accessibility.h"
- #include "ui/views/focus/focus_manager.h"
- #include "ui/views/layout/box_layout.h"
- #include "ui/views/view_utils.h"
- #include "url/gurl.h"
- namespace ash {
- namespace {
- constexpr size_t kMinRecommendedApps = 4;
- constexpr size_t kMaxRecommendedApps = 5;
- // Sorts increasing by display index, then decreasing by position priority.
- struct CompareByDisplayIndexAndPositionPriority {
- bool operator()(const SearchResult* result1,
- const SearchResult* result2) const {
- SearchResultDisplayIndex index1 = result1->display_index();
- SearchResultDisplayIndex index2 = result2->display_index();
- if (index1 != index2)
- return index1 < index2;
- return result1->position_priority() > result2->position_priority();
- }
- };
- // Converts a search result app ID to an app list item ID.
- std::string ItemIdFromAppId(const std::string& app_id) {
- // Convert chrome-extension://<id> to just <id>.
- if (base::StartsWith(app_id, extensions::kExtensionScheme)) {
- GURL url(app_id);
- return url.host();
- }
- return app_id;
- }
- // Returns a list of recent apps by filtering zero-state suggestion data.
- std::vector<SearchResult*> GetRecentApps(
- SearchModel* search_model,
- const std::vector<std::string>& ids_to_ignore) {
- SearchModel::SearchResults* results = search_model->results();
- auto filter_function = base::BindRepeating(
- [](const std::vector<std::string>& ids_to_ignore,
- const SearchResult& r) -> bool {
- if (r.display_type() != SearchResultDisplayType::kRecentApps)
- return false;
- for (std::string id : ids_to_ignore) {
- if (base::EndsWith(r.id(), id))
- return false;
- }
- return true;
- },
- ids_to_ignore);
- std::vector<SearchResult*> app_suggestion_results =
- SearchModel::FilterSearchResultsByFunction(
- results, filter_function,
- /*max_results=*/kMaxRecommendedApps);
- std::sort(app_suggestion_results.begin(), app_suggestion_results.end(),
- CompareByDisplayIndexAndPositionPriority());
- return app_suggestion_results;
- }
- } // namespace
- // The grid delegate for each AppListItemView. Recent app icons cannot be
- // dragged, so this implementation is mostly a stub.
- class RecentAppsView::GridDelegateImpl : public AppListItemView::GridDelegate {
- public:
- explicit GridDelegateImpl(AppListViewDelegate* view_delegate)
- : view_delegate_(view_delegate) {}
- GridDelegateImpl(const GridDelegateImpl&) = delete;
- GridDelegateImpl& operator=(const GridDelegateImpl&) = delete;
- ~GridDelegateImpl() override = default;
- // AppListItemView::GridDelegate:
- bool IsInFolder() const override { return false; }
- void SetSelectedView(AppListItemView* view) override {
- DCHECK(view);
- if (view == selected_view_)
- return;
- // Ensure the translucent background of the previous selection goes away.
- if (selected_view_)
- selected_view_->SchedulePaint();
- selected_view_ = view;
- // Ensure the translucent background of this selection is painted.
- selected_view_->SchedulePaint();
- }
- void ClearSelectedView() override { selected_view_ = nullptr; }
- bool IsSelectedView(const AppListItemView* view) const override {
- return view == selected_view_;
- }
- bool InitiateDrag(AppListItemView* view,
- const gfx::Point& location,
- const gfx::Point& root_location,
- base::OnceClosure drag_start_callback,
- base::OnceClosure drag_end_callback) override {
- return false;
- }
- void StartDragAndDropHostDragAfterLongPress() override {}
- bool UpdateDragFromItem(bool is_touch,
- const ui::LocatedEvent& event) override {
- return false;
- }
- void EndDrag(bool cancel) override {}
- void OnAppListItemViewActivated(AppListItemView* pressed_item_view,
- const ui::Event& event) override {
- // NOTE: Avoid using |item->id()| as the parameter. In some rare situations,
- // activating the item may destruct it. Using the reference to an object
- // which may be destroyed during the procedure as the function parameter
- // may bring the crash like https://crbug.com/990282.
- const std::string id = pressed_item_view->item()->id();
- view_delegate_->ActivateItem(id, event.flags(),
- AppListLaunchedFrom::kLaunchedFromRecentApps);
- // `this` may be deleted.
- }
- private:
- AppListViewDelegate* const view_delegate_;
- AppListItemView* selected_view_ = nullptr;
- };
- RecentAppsView::RecentAppsView(AppListKeyboardController* keyboard_controller,
- AppListViewDelegate* view_delegate)
- : keyboard_controller_(keyboard_controller),
- view_delegate_(view_delegate),
- grid_delegate_(std::make_unique<GridDelegateImpl>(view_delegate_)) {
- DCHECK(keyboard_controller_);
- DCHECK(view_delegate_);
- layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
- views::BoxLayout::Orientation::kHorizontal));
- layout_->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
- layout_->set_cross_axis_alignment(
- views::BoxLayout::CrossAxisAlignment::kStart);
- GetViewAccessibility().OverrideRole(ax::mojom::Role::kGroup);
- // TODO(https://crbug.com/1298211): This needs a designated string resource.
- GetViewAccessibility().OverrideName(
- l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_RECENT_APPS_A11Y_NAME));
- SetVisible(false);
- }
- RecentAppsView::~RecentAppsView() {
- if (model_)
- model_->RemoveObserver(this);
- }
- void RecentAppsView::OnAppListItemWillBeDeleted(AppListItem* item) {
- std::vector<std::string> ids_to_remove;
- for (AppListItemView* view : item_views_) {
- if (view->item() && view->item() == item)
- ids_to_remove.push_back(view->item()->id());
- }
- if (!ids_to_remove.empty()) {
- UpdateResults(ids_to_remove);
- UpdateVisibility();
- }
- }
- void RecentAppsView::UpdateAppListConfig(const AppListConfig* app_list_config) {
- app_list_config_ = app_list_config;
- for (auto* item_view : item_views_)
- item_view->UpdateAppListConfig(app_list_config);
- }
- void RecentAppsView::UpdateResults(
- const std::vector<std::string>& ids_to_ignore) {
- if (!search_model_ || !model_)
- return;
- DCHECK(app_list_config_);
- item_views_.clear();
- RemoveAllChildViews();
- std::vector<SearchResult*> apps = GetRecentApps(search_model_, ids_to_ignore);
- std::vector<AppListItem*> items;
- for (SearchResult* app : apps) {
- std::string item_id = ItemIdFromAppId(app->id());
- AppListItem* item = model_->FindItem(item_id);
- if (item)
- items.push_back(item);
- }
- if (items.size() < kMinRecommendedApps) {
- if (auto* notifier = view_delegate_->GetNotifier()) {
- notifier->NotifyResultsUpdated(SearchResultDisplayType::kRecentApps, {});
- }
- return;
- }
- if (auto* notifier = view_delegate_->GetNotifier()) {
- std::vector<AppListNotifier::Result> notifier_results;
- for (const SearchResult* app : apps)
- notifier_results.emplace_back(app->id(), app->metrics_type());
- notifier->NotifyResultsUpdated(SearchResultDisplayType::kRecentApps,
- notifier_results);
- }
- for (AppListItem* item : items) {
- auto* item_view = AddChildView(std::make_unique<AppListItemView>(
- app_list_config_, grid_delegate_.get(), item, view_delegate_,
- AppListItemView::Context::kRecentAppsView));
- item_view->UpdateAppListConfig(app_list_config_);
- item_views_.push_back(item_view);
- item_view->InitializeIconLoader();
- }
- NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- /*send_native_event=*/true);
- }
- void RecentAppsView::SetModels(SearchModel* search_model, AppListModel* model) {
- if (model_ != model) {
- if (model_)
- model_->RemoveObserver(this);
- model_ = model;
- if (model_)
- model_->AddObserver(this);
- }
- search_model_ = search_model;
- UpdateResults(/*ids_to_ignore=*/{});
- UpdateVisibility();
- }
- void RecentAppsView::UpdateVisibility() {
- const bool has_enough_apps = item_views_.size() >= kMinRecommendedApps;
- const bool hidden_by_user = view_delegate_->ShouldHideContinueSection();
- const bool visible = has_enough_apps && !hidden_by_user;
- SetVisible(visible);
- if (auto* notifier = view_delegate_->GetNotifier()) {
- notifier->NotifyContinueSectionVisibilityChanged(
- SearchResultDisplayType::kRecentApps, visible);
- }
- }
- int RecentAppsView::GetItemViewCount() const {
- return item_views_.size();
- }
- AppListItemView* RecentAppsView::GetItemViewAt(int index) const {
- if (static_cast<int>(item_views_.size()) <= index)
- return nullptr;
- return item_views_[index];
- }
- void RecentAppsView::DisableFocusForShowingActiveFolder(bool disabled) {
- for (views::View* child : children())
- child->SetEnabled(!disabled);
- // Prevent items from being accessed by ChromeVox.
- SetViewIgnoredForAccessibility(this, disabled);
- }
- bool RecentAppsView::OnKeyPressed(const ui::KeyEvent& event) {
- if (event.key_code() == ui::VKEY_UP) {
- MoveFocusUp();
- return true;
- }
- if (event.key_code() == ui::VKEY_DOWN) {
- MoveFocusDown();
- return true;
- }
- return false;
- }
- void RecentAppsView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
- // The AppsGridView's space between items is the sum of the padding on left
- // and on right of the individual tiles. Because of rounding errors, there can
- // be an actual difference of 1px over the actual distribution of space
- // needed, and because this is not compensated on the other columns, the grid
- // carries over the error making it progressively more significant for each
- // column. For the RecentAppsView tiles to match the grid we need to calculate
- // padding as the AppsGridView does to account for the rounding errors and
- // then double it, so it is exactly the same spacing as the AppsGridView.
- layout_->set_between_child_spacing(2 * CalculateTilePadding());
- }
- void RecentAppsView::MoveFocusUp() {
- DVLOG(1) << __FUNCTION__;
- // This function should only run when a child has focus.
- DCHECK(Contains(GetFocusManager()->GetFocusedView()));
- DCHECK(!children().empty());
- keyboard_controller_->MoveFocusUpFromRecents();
- }
- void RecentAppsView::MoveFocusDown() {
- DVLOG(1) << __FUNCTION__;
- // This function should only run when a child has focus.
- DCHECK(Contains(GetFocusManager()->GetFocusedView()));
- int column = GetColumnOfFocusedChild();
- DCHECK_GE(column, 0);
- keyboard_controller_->MoveFocusDownFromRecents(column);
- }
- int RecentAppsView::GetColumnOfFocusedChild() const {
- int column = 0;
- for (views::View* child : children()) {
- if (!views::IsViewClass<AppListItemView>(child))
- continue;
- if (child->HasFocus())
- return column;
- ++column;
- }
- return -1;
- }
- int RecentAppsView::CalculateTilePadding() const {
- int content_width = GetContentsBounds().width();
- int tile_width = app_list_config_->grid_tile_width();
- int width_to_distribute = content_width - kMaxRecommendedApps * tile_width;
- return width_to_distribute / ((kMaxRecommendedApps - 1) * 2);
- }
- BEGIN_METADATA(RecentAppsView, views::View)
- END_METADATA
- } // namespace ash
|