123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959 |
- // Copyright 2014 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_page_view.h"
- #include <stddef.h>
- #include <algorithm>
- #include "ash/app_list/app_list_model_provider.h"
- #include "ash/app_list/app_list_util.h"
- #include "ash/app_list/views/app_list_main_view.h"
- #include "ash/app_list/views/contents_view.h"
- #include "ash/app_list/views/privacy_container_view.h"
- #include "ash/app_list/views/productivity_launcher_search_view.h"
- #include "ash/app_list/views/search_box_view.h"
- #include "ash/app_list/views/search_result_base_view.h"
- #include "ash/app_list/views/search_result_list_view.h"
- #include "ash/app_list/views/search_result_page_anchored_dialog.h"
- #include "ash/app_list/views/search_result_tile_item_list_view.h"
- #include "ash/constants/ash_features.h"
- #include "ash/public/cpp/app_list/app_list_color_provider.h"
- #include "ash/public/cpp/app_list/app_list_config.h"
- #include "ash/public/cpp/app_list/app_list_features.h"
- #include "ash/public/cpp/style/color_provider.h"
- #include "ash/search_box/search_box_constants.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/system_shadow.h"
- #include "base/bind.h"
- #include "base/memory/ptr_util.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/time/time.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- #include "ui/accessibility/platform/ax_unique_id.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/metadata/metadata_header_macros.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/color/color_id.h"
- #include "ui/color/color_provider.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/color_palette.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/views/accessibility/view_accessibility.h"
- #include "ui/views/animation/animation_builder.h"
- #include "ui/views/background.h"
- #include "ui/views/border.h"
- #include "ui/views/controls/scroll_view.h"
- #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
- #include "ui/views/controls/textfield/textfield.h"
- #include "ui/views/layout/box_layout.h"
- #include "ui/views/layout/fill_layout.h"
- #include "ui/views/window/dialog_delegate.h"
- namespace ash {
- namespace {
- constexpr int kMinHeight = 440;
- constexpr int kWidth = 640;
- // The horizontal padding of the separator.
- constexpr int kSeparatorPadding = 12;
- constexpr int kSeparatorThickness = 1;
- // The height of the active search box in this page.
- constexpr int kActiveSearchBoxHeight = 56;
- // The spacing between search box bottom and separator line.
- // Add 1 pixel spacing so that the search bbox bottom will not paint over
- // the separator line drawn by SearchResultPageBackground in some scale factors
- // due to the round up.
- constexpr int kSearchBoxBottomSpacing = 1;
- // Minimum spacing between shelf and bottom of search box.
- constexpr int kSearchResultPageMinimumBottomMargin = 24;
- // The shadow type for the shadow of the expanded search box.
- constexpr SystemShadow::Type kSearchBoxSearchResultShadowType =
- SystemShadow::Type::kElevation12;
- // The amount of time by which notifications to accessibility framework about
- // result page changes are delayed.
- constexpr base::TimeDelta kNotifyA11yDelay = base::Milliseconds(1500);
- // The duration of the search result page view expanding animation.
- constexpr base::TimeDelta kExpandingSearchResultDuration =
- base::Milliseconds(200);
- // The duration of the search result page view closing animation.
- constexpr base::TimeDelta kClosingSearchResultDuration =
- base::Milliseconds(100);
- // The duration of the search result page view decreasing height animation
- // within the kExpanded state.
- constexpr base::TimeDelta kDecreasingHeightSearchResultsDuration =
- base::Milliseconds(200);
- // A container view that ensures the card background and the shadow are painted
- // in the correct order.
- class SearchCardView : public views::View {
- public:
- METADATA_HEADER(SearchCardView);
- explicit SearchCardView(std::unique_ptr<views::View> content_view) {
- SetLayoutManager(std::make_unique<views::FillLayout>());
- AddChildView(std::move(content_view));
- }
- SearchCardView(const SearchCardView&) = delete;
- SearchCardView& operator=(const SearchCardView&) = delete;
- ~SearchCardView() override = default;
- };
- BEGIN_METADATA(SearchCardView, views::View)
- END_METADATA
- class ZeroWidthVerticalScrollBar : public views::OverlayScrollBar {
- public:
- ZeroWidthVerticalScrollBar() : OverlayScrollBar(false) {}
- ZeroWidthVerticalScrollBar(const ZeroWidthVerticalScrollBar&) = delete;
- ZeroWidthVerticalScrollBar& operator=(const ZeroWidthVerticalScrollBar&) =
- delete;
- ~ZeroWidthVerticalScrollBar() override = default;
- // OverlayScrollBar overrides:
- int GetThickness() const override { return 0; }
- bool OnKeyPressed(const ui::KeyEvent& event) override {
- // Arrow keys should be handled by FocusManager to move focus. When a search
- // result is focused, it will be set visible in scroll view.
- return false;
- }
- };
- class SearchResultPageBackground : public views::Background {
- public:
- explicit SearchResultPageBackground(SkColor color) {
- SetNativeControlColor(color);
- }
- SearchResultPageBackground(const SearchResultPageBackground&) = delete;
- SearchResultPageBackground& operator=(const SearchResultPageBackground&) =
- delete;
- ~SearchResultPageBackground() override = default;
- private:
- // views::Background overrides:
- void Paint(gfx::Canvas* canvas, views::View* view) const override {
- canvas->DrawColor(get_color());
- gfx::Rect bounds = view->GetContentsBounds();
- if (bounds.height() <= kActiveSearchBoxHeight)
- return;
- // Draw a separator between SearchBoxView and SearchResultPageView.
- bounds.set_y(kActiveSearchBoxHeight + kSearchBoxBottomSpacing);
- bounds.set_height(kSeparatorThickness);
- canvas->FillRect(bounds,
- view->GetColorProvider()->GetColor(
- AppListColorProvider::Get()->GetSeparatorColorId()));
- }
- };
- } // namespace
- class SearchResultPageView::HorizontalSeparator : public views::View {
- public:
- explicit HorizontalSeparator(int preferred_width)
- : preferred_width_(preferred_width) {
- SetBorder(views::CreateEmptyBorder(
- gfx::Insets::TLBR(0, kSeparatorPadding, 0, kSeparatorPadding)));
- }
- HorizontalSeparator(const HorizontalSeparator&) = delete;
- HorizontalSeparator& operator=(const HorizontalSeparator&) = delete;
- ~HorizontalSeparator() override = default;
- // views::View overrides:
- const char* GetClassName() const override { return "HorizontalSeparator"; }
- gfx::Size CalculatePreferredSize() const override {
- return gfx::Size(preferred_width_, kSeparatorThickness);
- }
- void OnPaint(gfx::Canvas* canvas) override {
- gfx::Rect rect = GetContentsBounds();
- canvas->FillRect(rect,
- GetColorProvider()->GetColor(
- AppListColorProvider::Get()->GetSeparatorColorId()));
- View::OnPaint(canvas);
- }
- private:
- const int preferred_width_;
- };
- SearchResultPageView::SearchResultPageView() : contents_view_(new views::View) {
- SetPaintToLayer();
- layer()->SetFillsBoundsOpaquely(false);
- contents_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
- views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
- shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
- this, kSearchBoxSearchResultShadowType);
- shadow_->SetRoundedCornerRadius(kSearchBoxBorderCornerRadiusSearchResult);
- // Hides this view behind the search box by using the same color and
- // background border corner radius. All child views' background should be
- // set transparent so that the rounded corner is not overwritten.
- SetBackground(std::make_unique<SearchResultPageBackground>(
- features::IsProductivityLauncherEnabled()
- ? ColorProvider::Get()->GetBaseLayerColor(
- ColorProvider::BaseLayerType::kTransparent80)
- : AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor()));
- if (features::IsProductivityLauncherEnabled()) {
- layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
- layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
- layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(
- kExpandedSearchBoxCornerRadiusForProductivityLauncher));
- }
- SetLayoutManager(std::make_unique<views::FillLayout>());
- // App list bubble search page has its own scroller and result selection
- // controller so we do not need to construct new ones here.
- if (features::IsProductivityLauncherEnabled()) {
- contents_view_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
- kActiveSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness,
- 0, 0, 0)));
- AddChildView(contents_view_);
- } else {
- auto scroller = std::make_unique<views::ScrollView>();
- // Leaves a placeholder area for the search box and the separator below it.
- scroller->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
- kActiveSearchBoxHeight + kSearchBoxBottomSpacing + kSeparatorThickness,
- 0, 0, 0)));
- scroller->SetDrawOverflowIndicator(false);
- scroller->SetContents(base::WrapUnique(contents_view_));
- // Setting clip height is necessary to make ScrollView take into account its
- // contents' size. Using zeroes doesn't prevent it from scrolling and sizing
- // correctly.
- scroller->ClipHeightTo(0, 0);
- scroller->SetVerticalScrollBar(
- std::make_unique<ZeroWidthVerticalScrollBar>());
- scroller->SetBackgroundColor(absl::nullopt);
- AddChildView(std::move(scroller));
- result_selection_controller_ = std::make_unique<ResultSelectionController>(
- &result_container_views_,
- base::BindRepeating(&SearchResultPageView::SelectedResultChanged,
- base::Unretained(this)));
- }
- AppListModelProvider* const model_provider = AppListModelProvider::Get();
- model_provider->AddObserver(this);
- }
- SearchResultPageView::~SearchResultPageView() {
- AppListModelProvider::Get()->RemoveObserver(this);
- }
- void SearchResultPageView::InitializeContainers(
- AppListViewDelegate* view_delegate,
- AppListMainView* app_list_main_view,
- SearchBoxView* search_box_view) {
- DCHECK(view_delegate);
- view_delegate_ = view_delegate;
- if (features::IsProductivityLauncherEnabled()) {
- // For productivity launcher, the dialog will be anchored to the search box
- // to keep the position of dialogs consistent.
- dialog_controller_ =
- std::make_unique<SearchResultPageDialogController>(search_box_view);
- std::unique_ptr<ProductivityLauncherSearchView> search_view_ptr =
- std::make_unique<ProductivityLauncherSearchView>(
- view_delegate, dialog_controller_.get(), search_box_view);
- productivity_launcher_search_view_ = search_view_ptr.get();
- contents_view_->AddChildView(
- std::make_unique<SearchCardView>(std::move(search_view_ptr)));
- } else {
- dialog_controller_ =
- std::make_unique<SearchResultPageDialogController>(this);
- privacy_container_view_ = AddSearchResultContainerView(
- std::make_unique<PrivacyContainerView>(view_delegate));
- search_result_tile_item_list_view_ = AddSearchResultContainerView(
- std::make_unique<SearchResultTileItemListView>(
- search_box_view->search_box(), view_delegate));
- result_lists_separator_ = contents_view_->AddChildView(
- std::make_unique<HorizontalSeparator>(bounds().width()));
- // productivity_launcher_index is not set as the feature is not enabled.
- search_result_list_view_ =
- AddSearchResultContainerView(std::make_unique<SearchResultListView>(
- app_list_main_view, view_delegate, dialog_controller_.get(),
- SearchResultView::SearchResultViewType::kClassic,
- /*animates_result_updates=*/false, absl::nullopt));
- search_box_view->SetResultSelectionController(
- result_selection_controller());
- }
- }
- void SearchResultPageView::AddSearchResultContainerViewInternal(
- std::unique_ptr<SearchResultContainerView> result_container) {
- auto* result_container_ptr = result_container.get();
- contents_view_->AddChildView(
- std::make_unique<SearchCardView>(std::move(result_container)));
- result_container_views_.push_back(result_container_ptr);
- result_container_ptr->SetResults(
- AppListModelProvider::Get()->search_model()->results());
- result_container_ptr->set_delegate(this);
- }
- bool SearchResultPageView::IsFirstResultTile() const {
- // In the event that the result does not exist, it is not a tile.
- if (!first_result_view_ || !first_result_view_->result())
- return false;
- return first_result_view_->result()->display_type() ==
- SearchResultDisplayType::kTile;
- }
- bool SearchResultPageView::IsFirstResultHighlighted() const {
- DCHECK(first_result_view_);
- return first_result_view_->selected();
- }
- const char* SearchResultPageView::GetClassName() const {
- return "SearchResultPageView";
- }
- gfx::Size SearchResultPageView::CalculatePreferredSize() const {
- // TODO(https://crbug.com/1216097) Update height based on available space.
- if (!features::IsProductivityLauncherEnabled())
- return gfx::Size(kWidth, kMinHeight);
- int adjusted_height = std::min(
- std::max(kMinHeight,
- productivity_launcher_search_view_->TabletModePreferredHeight() +
- kActiveSearchBoxHeight + kSearchBoxBottomSpacing +
- kSeparatorThickness +
- kExpandedSearchBoxCornerRadiusForProductivityLauncher),
- AppListPage::contents_view()->height());
- return gfx::Size(kWidth, adjusted_height);
- }
- void SearchResultPageView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
- if (previous_bounds.size() != bounds().size()) {
- // If the clip rect is currently animating, then animate from the current
- // clip rect bounds to the newly set bounds.
- if (features::IsProductivityLauncherEnabled() &&
- layer()->GetAnimator()->is_animating()) {
- AnimateBetweenBounds(layer()->clip_rect(), gfx::Rect(bounds().size()));
- return;
- }
- // The clip rect set for page state animations needs to be reset when the
- // bounds change because page size change invalidates the previous bounds.
- // This allows content to properly follow target bounds when screen
- // rotates.
- layer()->SetClipRect(gfx::Rect());
- }
- }
- void SearchResultPageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
- if (!GetVisible())
- return;
- // With productivity launcher enabled, individual child result list views will
- // have the list box role.
- if (!features::IsProductivityLauncherEnabled())
- node_data->role = ax::mojom::Role::kListBox;
- std::u16string value;
- SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
- const std::u16string& query = search_box->current_query();
- if (!query.empty()) {
- if (last_search_result_count_ == 1) {
- value = l10n_util::GetStringFUTF16(
- IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_SINGLE_RESULT,
- query);
- } else {
- value = l10n_util::GetStringFUTF16(
- IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT,
- base::NumberToString16(last_search_result_count_), query);
- }
- } else {
- // TODO(https://crbug.com/1216097) Zero state is removed for bubble launcher
- // so we need a new A11y announcement.
- value = l10n_util::GetStringUTF16(
- IDS_APP_LIST_SEARCHBOX_RESULTS_ACCESSIBILITY_ANNOUNCEMENT_ZERO_STATE);
- }
- node_data->SetValue(value);
- }
- void SearchResultPageView::OnThemeChanged() {
- GetBackground()->SetNativeControlColor(
- features::IsProductivityLauncherEnabled()
- ? ColorProvider::Get()->GetBaseLayerColor(
- ColorProvider::BaseLayerType::kTransparent80)
- : AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor());
- // SchedulePaint() marks the entire SearchResultPageView's bounds as dirty.
- SchedulePaint();
- AppListPage::OnThemeChanged();
- }
- void SearchResultPageView::UpdateForNewSearch() {
- notify_a11y_results_changed_timer_.Stop();
- if (productivity_launcher_search_view_) {
- productivity_launcher_search_view_->UpdateForNewSearch(
- ShouldShowSearchResultView());
- }
- }
- void SearchResultPageView::UpdateResultContainersVisibility() {
- bool should_show_page_view = false;
- if (features::IsProductivityLauncherEnabled()) {
- should_show_page_view = ShouldShowSearchResultView();
- AnimateToSearchResultsState(should_show_page_view
- ? SearchResultsState::kExpanded
- : SearchResultsState::kActive);
- } else {
- bool should_show_search_result_view = ShouldShowSearchResultView();
- for (auto* container : result_container_views_) {
- // Containers are wrapped by a `SearchCardView`, so update the parent
- // visibility.
- bool should_show_container_view =
- container->num_results() && should_show_search_result_view;
- container->parent()->SetVisible(should_show_container_view);
- container->SetVisible(should_show_container_view);
- should_show_page_view =
- should_show_page_view || should_show_container_view;
- }
- result_lists_separator_->SetVisible(
- search_result_tile_item_list_view_->num_results() &&
- search_result_list_view_->num_results() &&
- ShouldShowSearchResultView());
- AppListPage::contents_view()
- ->GetSearchBoxView()
- ->OnResultContainerVisibilityChanged(should_show_page_view);
- }
- Layout();
- }
- void SearchResultPageView::SelectedResultChanged() {
- // Result selection should be handled by |productivity_launcher_search_page_|.
- DCHECK(!features::IsProductivityLauncherEnabled());
- if (!result_selection_controller_->selected_location_details() ||
- !result_selection_controller_->selected_result()) {
- return;
- }
- const ResultLocationDetails* selection_details =
- result_selection_controller_->selected_location_details();
- views::View* selected_row = nullptr;
- // For horizontal containers ensure that the whole container fits in the
- // scroll view, to account for vertical padding within the container.
- if (selection_details->container_is_horizontal) {
- selected_row = result_container_views_[selection_details->container_index];
- } else {
- selected_row = result_selection_controller_->selected_result();
- }
- selected_row->ScrollViewToVisible();
- NotifySelectedResultChanged();
- }
- void SearchResultPageView::SetIgnoreResultChangesForA11y(bool ignore) {
- if (ignore_result_changes_for_a11y_ == ignore)
- return;
- ignore_result_changes_for_a11y_ = ignore;
- GetViewAccessibility().OverrideIsLeaf(ignore);
- GetViewAccessibility().OverrideIsIgnored(ignore);
- NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, true);
- }
- void SearchResultPageView::ScheduleResultsChangedA11yNotification() {
- if (!ignore_result_changes_for_a11y_) {
- NotifyA11yResultsChanged();
- return;
- }
- notify_a11y_results_changed_timer_.Start(
- FROM_HERE, kNotifyA11yDelay,
- base::BindOnce(&SearchResultPageView::NotifyA11yResultsChanged,
- base::Unretained(this)));
- }
- void SearchResultPageView::NotifyA11yResultsChanged() {
- SetIgnoreResultChangesForA11y(false);
- NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
- NotifySelectedResultChanged();
- }
- void SearchResultPageView::NotifySelectedResultChanged() {
- // Result selection should be handled by |productivity_launcher_search_page_|.
- DCHECK(!features::IsProductivityLauncherEnabled());
- if (ignore_result_changes_for_a11y_)
- return;
- SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
- if (!result_selection_controller_->selected_location_details() ||
- !result_selection_controller_->selected_result()) {
- search_box->SetA11yActiveDescendant(absl::nullopt);
- return;
- }
- views::View* selected_view =
- result_selection_controller_->selected_result()->GetSelectedView();
- if (!selected_view) {
- search_box->SetA11yActiveDescendant(absl::nullopt);
- return;
- }
- search_box->SetA11yActiveDescendant(
- selected_view->GetViewAccessibility().GetUniqueId().Get());
- }
- void SearchResultPageView::UpdatePageBoundsForState(
- AppListState state,
- const gfx::Rect& contents_bounds,
- const gfx::Rect& search_box_bounds) {
- if (!features::IsProductivityLauncherEnabled()) {
- AppListPage::UpdatePageBoundsForState(state, contents_bounds,
- search_box_bounds);
- return;
- }
- if (state != AppListState::kStateSearchResults)
- return;
- const gfx::Rect to_bounds =
- GetPageBoundsForResultState(current_search_results_state_);
- if (layer()->GetAnimator()->is_animating()) {
- DCHECK(!layer()->clip_rect().IsEmpty());
- // When already animating, for an increasing target height, set the bounds
- // before animating to keep the animation visible.
- if (to_bounds.height() > layer()->clip_rect().height())
- SetBoundsRect(to_bounds);
- AnimateBetweenBounds(layer()->clip_rect(), gfx::Rect(to_bounds.size()));
- } else {
- // When no animation is in progress, we only animate when the target
- // height is decreasing, otherwise set bounds immediately.
- if (to_bounds.height() < bounds().height()) {
- AnimateBetweenBounds(gfx::Rect(bounds().size()),
- gfx::Rect(to_bounds.size()));
- } else {
- SetBoundsRect(to_bounds);
- }
- }
- }
- void SearchResultPageView::AnimateToSearchResultsState(
- SearchResultsState to_state) {
- // The search results page is only visible in expanded state. Exit early when
- // transitioning between states where results UI is invisible.
- if (current_search_results_state_ != SearchResultsState::kExpanded &&
- to_state != SearchResultsState::kExpanded) {
- SetVisible(false);
- current_search_results_state_ = to_state;
- return;
- }
- gfx::Rect from_rect =
- GetPageBoundsForResultState(current_search_results_state_);
- const gfx::Rect to_rect = GetPageBoundsForResultState(to_state);
- if (current_search_results_state_ == SearchResultsState::kExpanded &&
- to_state == SearchResultsState::kExpanded) {
- // Use current bounds when animating within the expanded state.
- from_rect = bounds();
- // Only set bounds when the height is increasing so that the entire
- // animation between |to_rect| and |from_rect| is visible.
- if (to_rect.height() > from_rect.height())
- SetBoundsRect(to_rect);
- } else if (to_state == SearchResultsState::kExpanded) {
- // Set bounds here because this is a result opening transition. We avoid
- // setting bounds for closing transitions because then the animation would
- // be hidden, instead set the bounds for closing transitions once the
- // animation has completed.
- SetBoundsRect(to_rect);
- AppListPage::contents_view()
- ->GetSearchBoxView()
- ->OnResultContainerVisibilityChanged(true);
- }
- current_search_results_state_ = to_state;
- AnimateBetweenBounds(from_rect, to_rect);
- }
- void SearchResultPageView::AnimateBetweenBounds(const gfx::Rect& from_rect,
- const gfx::Rect& to_rect) {
- if (from_rect == to_rect)
- return;
- // Return if already animating to the correct target size.
- if (layer()->GetAnimator()->is_animating() &&
- to_rect.size() == layer()->GetTargetClipRect().size()) {
- return;
- }
- gfx::Rect clip_rect = from_rect;
- clip_rect -= to_rect.OffsetFromOrigin();
- layer()->SetClipRect(clip_rect);
- shadow_.reset();
- base::TimeDelta duration;
- if (from_rect.height() < to_rect.height()) {
- duration = kExpandingSearchResultDuration;
- } else {
- duration = (current_search_results_state_ == SearchResultsState::kExpanded)
- ? kDecreasingHeightSearchResultsDuration
- : kClosingSearchResultDuration;
- }
- views::AnimationBuilder()
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .OnEnded(
- base::BindOnce(&SearchResultPageView::OnAnimationBetweenBoundsEnded,
- base::Unretained(this)))
- .Once()
- .SetDuration(duration)
- .SetClipRect(layer(), gfx::Rect(to_rect.size()),
- gfx::Tween::FAST_OUT_SLOW_IN)
- .SetRoundedCorners(
- layer(),
- gfx::RoundedCornersF(GetCornerRadiusForSearchResultsState(
- current_search_results_state_)),
- gfx::Tween::FAST_OUT_SLOW_IN);
- }
- void SearchResultPageView::OnAnimationBetweenBoundsEnded() {
- shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
- this, kSearchBoxSearchResultShadowType);
- shadow_->SetRoundedCornerRadius(
- GetCornerRadiusForSearchResultsState(current_search_results_state_));
- // To keep the animation visible for closing transitions from expanded search
- // results, bounds are set here once the animation completes.
- SetBoundsRect(GetPageBoundsForResultState(current_search_results_state_));
- // Avoid visible overlap with the search box when the search results are not
- // expanded.
- if (current_search_results_state_ != SearchResultsState::kExpanded) {
- SetVisible(false);
- AppListPage::contents_view()
- ->GetSearchBoxView()
- ->OnResultContainerVisibilityChanged(false);
- }
- }
- gfx::Rect SearchResultPageView::GetPageBoundsForResultState(
- SearchResultsState state) const {
- AppListState app_list_state = (state == SearchResultsState::kClosed)
- ? AppListState::kStateApps
- : AppListState::kStateSearchResults;
- const ContentsView* const contents_view = AppListPage::contents_view();
- const gfx::Rect contents_bounds = contents_view->GetContentsBounds();
- gfx::Rect final_bounds =
- GetPageBoundsForState(app_list_state, contents_bounds,
- contents_view->GetSearchBoxBounds(app_list_state));
- // Ensure the height is set according to |state|, because
- // GetPageBoundForState() returns a height according to |app_list_state| which
- // does not account for kActive search result state.
- if (state == SearchResultsState::kActive)
- final_bounds.set_height(kActiveSearchBoxHeight);
- return final_bounds;
- }
- int SearchResultPageView::GetCornerRadiusForSearchResultsState(
- SearchResultsState state) {
- switch (state) {
- case SearchResultsState::kClosed:
- return kSearchBoxBorderCornerRadius;
- case SearchResultsState::kActive:
- return kExpandedSearchBoxCornerRadiusForProductivityLauncher;
- case SearchResultsState::kExpanded:
- return kExpandedSearchBoxCornerRadiusForProductivityLauncher;
- }
- }
- void SearchResultPageView::OnActiveAppListModelsChanged(
- AppListModel* model,
- SearchModel* search_model) {
- for (auto* container : result_container_views_)
- container->SetResults(search_model->results());
- }
- void SearchResultPageView::OnSearchResultContainerResultsChanging() {
- // Result selection should be handled by |productivity_launcher_search_page_|.
- DCHECK(!features::IsProductivityLauncherEnabled());
- // Block any result selection changes while result updates are in flight.
- // The selection will be reset once the results are all updated.
- result_selection_controller_->set_block_selection_changes(true);
- notify_a11y_results_changed_timer_.Stop();
- SetIgnoreResultChangesForA11y(true);
- }
- void SearchResultPageView::OnSearchResultContainerResultsChanged() {
- // Skip updates during shutdown.
- if (!view_delegate_->HasValidProfile())
- return;
- // Result selection should be handled by |productivity_launcher_search_page_|.
- DCHECK(!features::IsProductivityLauncherEnabled());
- DCHECK(!result_container_views_.empty());
- int result_count = 0;
- // Only sort and layout the containers when they have all updated.
- for (SearchResultContainerView* view : result_container_views_) {
- if (view->UpdateScheduled())
- return;
- result_count += view->num_results();
- }
- last_search_result_count_ = result_count;
- UpdateResultContainersVisibility();
- ScheduleResultsChangedA11yNotification();
- // Find the first result view.
- first_result_view_ = nullptr;
- for (auto* container : result_container_views_) {
- first_result_view_ = container->GetFirstResultView();
- if (first_result_view_)
- break;
- }
- // Reset selection to first when things change. The first result is set as
- // as the default result.
- result_selection_controller_->set_block_selection_changes(false);
- result_selection_controller_->ResetSelection(nullptr /*key_event*/,
- true /* default_selection */);
- // Update SearchBoxView search box autocomplete as necessary based on new
- // first result view.
- AppListPage::contents_view()->GetSearchBoxView()->ProcessAutocomplete(
- first_result_view_);
- }
- bool SearchResultPageView::CanSelectSearchResults() const {
- if (!GetVisible())
- return false;
- if (features::IsProductivityLauncherEnabled())
- return productivity_launcher_search_view_->CanSelectSearchResults();
- return first_result_view_;
- }
- SkColor SearchResultPageView::GetBackgroundColorForState(
- AppListState state) const {
- if (state == AppListState::kStateSearchResults)
- return AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor();
- return AppListColorProvider::Get()->GetSearchBoxBackgroundColor();
- }
- PrivacyContainerView* SearchResultPageView::GetPrivacyContainerViewForTest() {
- return privacy_container_view_;
- }
- SearchResultTileItemListView*
- SearchResultPageView::GetSearchResultTileItemListViewForTest() {
- return search_result_tile_item_list_view_;
- }
- SearchResultListView* SearchResultPageView::GetSearchResultListViewForTest() {
- return search_result_list_view_;
- }
- bool SearchResultPageView::ShouldShowSearchResultView() const {
- SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
- return (!features::IsProductivityLauncherEnabled() ||
- search_box->HasValidQuery());
- }
- void SearchResultPageView::OnHidden() {
- // Hide the search results page when it is behind search box to avoid focus
- // being moved onto suggested apps when zero state is enabled.
- AppListPage::OnHidden();
- notify_a11y_results_changed_timer_.Stop();
- dialog_controller_->Reset(false);
- SetVisible(false);
- for (auto* container_view : result_container_views_) {
- container_view->SetShown(false);
- }
- AppListPage::contents_view()
- ->GetSearchBoxView()
- ->OnResultContainerVisibilityChanged(false);
- }
- void SearchResultPageView::OnShown() {
- AppListPage::OnShown();
- dialog_controller_->Reset(true);
- for (auto* container_view : result_container_views_) {
- container_view->SetShown(ShouldShowSearchResultView());
- }
- AppListPage::contents_view()
- ->GetSearchBoxView()
- ->OnResultContainerVisibilityChanged(ShouldShowSearchResultView());
- if (!features::IsProductivityLauncherEnabled())
- ScheduleResultsChangedA11yNotification();
- }
- void SearchResultPageView::AnimateYPosition(AppListViewState target_view_state,
- const TransformAnimator& animator,
- float default_offset) {
- // Search result page view may host a native view to show answer card results.
- // The native view hosts use view to widget coordinate conversion to calculate
- // the native view bounds, and thus depend on the view transform values.
- // Make sure the view is laid out before starting the transform animation so
- // native views are not placed according to interim, animated page transform
- // value.
- layer()->GetAnimator()->StopAnimatingProperty(
- ui::LayerAnimationElement::TRANSFORM);
- if (needs_layout())
- Layout();
- animator.Run(default_offset, layer());
- if (shadow_)
- animator.Run(default_offset, shadow_->GetNinePatchLayer());
- SearchResultPageAnchoredDialog* search_page_dialog =
- dialog_controller_->dialog();
- if (search_page_dialog) {
- const float offset =
- search_page_dialog->AdjustVerticalTransformOffset(default_offset);
- animator.Run(offset, search_page_dialog->widget()->GetLayer());
- }
- }
- void SearchResultPageView::UpdatePageOpacityForState(AppListState state,
- float search_box_opacity,
- bool restore_opacity) {
- layer()->SetOpacity(search_box_opacity);
- }
- gfx::Rect SearchResultPageView::GetPageBoundsForState(
- AppListState state,
- const gfx::Rect& contents_bounds,
- const gfx::Rect& search_box_bounds) const {
- if (state != AppListState::kStateSearchResults) {
- // Hides this view behind the search box by using the same bounds.
- return search_box_bounds;
- }
- gfx::Rect bounding_rect = contents_bounds;
- bounding_rect.Inset(
- gfx::Insets::TLBR(0, 0, kSearchResultPageMinimumBottomMargin, 0));
- gfx::Rect preferred_bounds = gfx::Rect(
- search_box_bounds.origin(),
- gfx::Size(search_box_bounds.width(), CalculatePreferredSize().height()));
- preferred_bounds.Intersect(bounding_rect);
- return preferred_bounds;
- }
- void SearchResultPageView::OnAnimationStarted(AppListState from_state,
- AppListState to_state) {
- if (from_state != AppListState::kStateSearchResults &&
- to_state != AppListState::kStateSearchResults) {
- return;
- }
- if (features::IsProductivityLauncherEnabled()) {
- SearchResultsState to_result_state;
- if (to_state == AppListState::kStateApps) {
- to_result_state = SearchResultsState::kClosed;
- } else {
- to_result_state = ShouldShowSearchResultView()
- ? SearchResultsState::kExpanded
- : SearchResultsState::kActive;
- }
- AnimateToSearchResultsState(to_result_state);
- } else {
- const ContentsView* const contents_view = AppListPage::contents_view();
- const gfx::Rect contents_bounds = contents_view->GetContentsBounds();
- const gfx::Rect from_rect =
- GetPageBoundsForState(from_state, contents_bounds,
- contents_view->GetSearchBoxBounds(from_state));
- const gfx::Rect to_rect = GetPageBoundsForState(
- to_state, contents_bounds, contents_view->GetSearchBoxBounds(to_state));
- if (from_rect == to_rect)
- return;
- const int to_radius =
- contents_view->GetSearchBoxView()
- ->GetSearchBoxBorderCornerRadiusForState(to_state);
- // Here does the following animations;
- // - clip-rect, so it looks like expanding from |from_rect| to |to_rect|.
- // - rounded-rect
- // - transform of the shadow
- SetBoundsRect(to_rect);
- gfx::Rect clip_rect = from_rect;
- clip_rect -= to_rect.OffsetFromOrigin();
- layer()->SetClipRect(clip_rect);
- {
- auto settings = contents_view->CreateTransitionAnimationSettings(layer());
- layer()->SetClipRect(gfx::Rect(to_rect.size()));
- // This changes the shadow's corner immediately while this corner bounds
- // gradually. This would be fine because this would be unnoticeable to
- // users.
- shadow_->SetRoundedCornerRadius(to_radius);
- }
- // Animate the shadow's bounds through transform.
- {
- gfx::Transform transform;
- transform.Translate(from_rect.origin() - to_rect.origin());
- transform.Scale(
- static_cast<float>(from_rect.width()) / to_rect.width(),
- static_cast<float>(from_rect.height()) / to_rect.height());
- shadow_->GetLayer()->SetTransform(transform);
- auto settings =
- contents_view->CreateTransitionAnimationSettings(shadow_->GetLayer());
- shadow_->GetLayer()->SetTransform(gfx::Transform());
- }
- }
- }
- void SearchResultPageView::OnAnimationUpdated(double progress,
- AppListState from_state,
- AppListState to_state) {
- if (from_state != AppListState::kStateSearchResults &&
- to_state != AppListState::kStateSearchResults) {
- return;
- }
- const SkColor color = gfx::Tween::ColorValueBetween(
- progress, GetBackgroundColorForState(from_state),
- GetBackgroundColorForState(to_state));
- if (color != background()->get_color()) {
- background()->SetNativeControlColor(color);
- SchedulePaint();
- }
- }
- gfx::Size SearchResultPageView::GetPreferredSearchBoxSize() const {
- static gfx::Size size = gfx::Size(kWidth, kActiveSearchBoxHeight);
- return size;
- }
- } // namespace ash
|