| // 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/app_list_folder_view.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/app_list/app_list_metrics.h" |
| #include "ash/app_list/app_list_model_provider.h" |
| #include "ash/app_list/app_list_util.h" |
| #include "ash/app_list/model/app_list_folder_item.h" |
| #include "ash/app_list/model/app_list_model.h" |
| #include "ash/app_list/views/app_list_a11y_announcer.h" |
| #include "ash/app_list/views/app_list_folder_controller.h" |
| #include "ash/app_list/views/app_list_item_view.h" |
| #include "ash/app_list/views/folder_background_view.h" |
| #include "ash/app_list/views/folder_header_view.h" |
| #include "ash/app_list/views/page_switcher.h" |
| #include "ash/app_list/views/scrollable_apps_grid_view.h" |
| #include "ash/app_list/views/search_box_view.h" |
| #include "ash/app_list/views/top_icon_animation_view.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/controls/rounded_scroll_bar.h" |
| #include "ash/controls/scroll_view_gradient_helper.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.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/metrics_util.h" |
| #include "ash/public/cpp/pagination/pagination_model.h" |
| #include "ash/public/cpp/style/color_provider.h" |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/events/event.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/gfx/animation/slide_animation.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/animation_delegate_views.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/scroll_view.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/layout/flex_layout.h" |
| #include "ui/views/layout/flex_layout_types.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/widget/widget.h" |
| |
| using views::BoxLayout; |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr int kFolderHeaderPadding = 12; |
| constexpr int kOnscreenKeyboardTopPadding = 16; |
| |
| constexpr int kTileSpacingInFolder = 8; |
| |
| // Insets for the vertical scroll bar. The top is pushed down slightly to align |
| // with the icons, which keeps the scroll bar out of the rounded corner area. |
| constexpr gfx::Insets kVerticalScrollInsets(kTileSpacingInFolder, 0, 1, 1); |
| |
| // Duration for fading in the target page when opening |
| // or closing a folder, and the duration for the top folder icon animation |
| // for flying in or out the folder. |
| constexpr base::TimeDelta kFolderTransitionDuration = base::Milliseconds(250); |
| |
| // Transit from the background of the folder item's icon to the opened |
| // folder's background when opening the folder. Transit the other way when |
| // closing the folder. |
| class BackgroundAnimation : public AppListFolderView::Animation, |
| public ui::ImplicitAnimationObserver { |
| public: |
| BackgroundAnimation(bool show, |
| AppListFolderView* folder_view, |
| views::View* background_view) |
| : show_(show), |
| folder_view_(folder_view), |
| background_view_(background_view) {} |
| |
| BackgroundAnimation(const BackgroundAnimation&) = delete; |
| BackgroundAnimation& operator=(const BackgroundAnimation&) = delete; |
| |
| ~BackgroundAnimation() override = default; |
| |
| private: |
| // AppListFolderView::Animation: |
| void ScheduleAnimation(base::OnceClosure completion_callback) override { |
| DCHECK(!completion_callback_); |
| completion_callback_ = std::move(completion_callback); |
| |
| // Calculate the source and target states. |
| const int icon_radius = |
| folder_view_->GetAppListConfig()->folder_icon_radius(); |
| const int folder_radius = |
| folder_view_->GetAppListConfig()->folder_background_radius(); |
| const int from_radius = show_ ? icon_radius : folder_radius; |
| const int to_radius = show_ ? folder_radius : icon_radius; |
| gfx::Rect from_rect = show_ ? folder_view_->folder_item_icon_bounds() |
| : background_view_->bounds(); |
| from_rect -= background_view_->bounds().OffsetFromOrigin(); |
| gfx::Rect to_rect = show_ ? background_view_->bounds() |
| : folder_view_->folder_item_icon_bounds(); |
| to_rect -= background_view_->bounds().OffsetFromOrigin(); |
| const SkColor background_color = |
| AppListColorProvider::Get()->GetFolderBackgroundColor(); |
| const SkColor bubble_color = |
| AppListColorProvider::Get()->GetFolderBubbleColor(); |
| const SkColor from_color = show_ ? bubble_color : background_color; |
| const SkColor to_color = show_ ? background_color : bubble_color; |
| |
| background_view_->layer()->SetColor(from_color); |
| background_view_->layer()->SetClipRect(from_rect); |
| background_view_->layer()->SetRoundedCornerRadius( |
| gfx::RoundedCornersF(from_radius)); |
| |
| ui::ScopedLayerAnimationSettings settings( |
| background_view_->layer()->GetAnimator()); |
| settings.SetTransitionDuration(kFolderTransitionDuration); |
| settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); |
| settings.AddObserver(this); |
| background_view_->layer()->SetColor(to_color); |
| background_view_->layer()->SetClipRect(to_rect); |
| background_view_->layer()->SetRoundedCornerRadius( |
| gfx::RoundedCornersF(to_radius)); |
| is_animating_ = true; |
| } |
| |
| bool IsAnimationRunning() override { return is_animating_; } |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { |
| is_animating_ = false; |
| folder_view_->RecordAnimationSmoothness(); |
| |
| if (completion_callback_) |
| std::move(completion_callback_).Run(); |
| } |
| |
| // True if opening the folder. |
| const bool show_; |
| |
| bool is_animating_ = false; |
| |
| AppListFolderView* const folder_view_; // Not owned. |
| views::View* const background_view_; // Not owned. |
| |
| base::OnceClosure completion_callback_; |
| }; |
| |
| // Decrease the opacity of the folder item's title when opening the folder. |
| // Increase it when closing the folder. |
| class FolderItemTitleAnimation : public AppListFolderView::Animation, |
| public views::AnimationDelegateViews { |
| public: |
| FolderItemTitleAnimation(bool show, |
| AppListFolderView* folder_view, |
| AppListItemView* folder_item_view) |
| : views::AnimationDelegateViews(folder_view), |
| show_(show), |
| animation_(this), |
| folder_view_(folder_view), |
| folder_item_view_(folder_item_view) { |
| SkColor title_color = AppListColorProvider::Get()->GetAppListItemTextColor( |
| /*is_in_folder=*/false); |
| // Calculate the source and target states. |
| from_color_ = show_ ? title_color : SK_ColorTRANSPARENT; |
| to_color_ = show_ ? SK_ColorTRANSPARENT : title_color; |
| |
| animation_.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); |
| animation_.SetSlideDuration( |
| ui::ScopedAnimationDurationScaleMode::duration_multiplier() * |
| kFolderTransitionDuration); |
| } |
| |
| FolderItemTitleAnimation(const FolderItemTitleAnimation&) = delete; |
| FolderItemTitleAnimation& operator=(const FolderItemTitleAnimation&) = delete; |
| |
| ~FolderItemTitleAnimation() override = default; |
| |
| private: |
| // AppListFolderView::Animation: |
| void ScheduleAnimation(base::OnceClosure completion_callback) override { |
| DCHECK(!completion_callback_); |
| completion_callback_ = std::move(completion_callback); |
| animation_.Show(); |
| } |
| bool IsAnimationRunning() override { return animation_.is_animating(); } |
| |
| // gfx::AnimationDelegate |
| void AnimationProgressed(const gfx::Animation* animation) override { |
| folder_item_view_->title()->SetEnabledColor(gfx::Tween::ColorValueBetween( |
| animation->GetCurrentValue(), from_color_, to_color_)); |
| } |
| |
| void AnimationEnded(const gfx::Animation* animation) override { |
| folder_item_view_->title()->SetEnabledColor(to_color_); |
| folder_view_->RecordAnimationSmoothness(); |
| |
| if (completion_callback_) |
| std::move(completion_callback_).Run(); |
| } |
| |
| void AnimationCanceled(const gfx::Animation* animation) override { |
| AnimationEnded(animation); |
| } |
| |
| // True if opening the folder. |
| const bool show_; |
| |
| // The source and target state of the title's color. |
| SkColor from_color_; |
| SkColor to_color_; |
| |
| gfx::SlideAnimation animation_; |
| |
| AppListFolderView* const folder_view_; // Not owned. |
| |
| // The app list item view with which the folder view is associated. |
| // NOTE: Users of `FolderItemTitleAnimation` should ensure the animation does |
| // not outlive the `folder_item_view_`. |
| AppListItemView* const folder_item_view_; |
| |
| base::OnceClosure completion_callback_; |
| }; |
| |
| // Transit from the items within the folder item icon to the same items in the |
| // opened folder when opening the folder. Transit the other way when closing the |
| // folder. |
| class TopIconAnimation : public AppListFolderView::Animation, |
| public TopIconAnimationObserver { |
| public: |
| TopIconAnimation(bool show, |
| AppListFolderView* folder_view, |
| views::ScrollView* scroll_view, |
| AppListItemView* folder_item_view) |
| : show_(show), |
| folder_view_(folder_view), |
| scroll_view_(scroll_view), |
| folder_item_view_(folder_item_view) {} |
| |
| TopIconAnimation(const TopIconAnimation&) = delete; |
| TopIconAnimation& operator=(const TopIconAnimation&) = delete; |
| |
| ~TopIconAnimation() override { |
| for (auto* view : top_icon_views_) |
| view->RemoveObserver(this); |
| top_icon_views_.clear(); |
| } |
| |
| // AppListFolderView::Animation |
| void ScheduleAnimation(base::OnceClosure completion_callback) override { |
| DCHECK(!completion_callback_); |
| completion_callback_ = std::move(completion_callback); |
| |
| // Hide the original items in the folder until the animation ends. |
| SetFirstPageItemViewsVisible(false); |
| folder_item_view_->SetIconVisible(false); |
| |
| // Calculate the start and end bounds of the top item icons in the |
| // animation. |
| std::vector<gfx::Rect> top_item_views_bounds = |
| GetTopItemViewsBoundsInFolderIcon(); |
| std::vector<gfx::Rect> first_page_item_views_bounds = |
| GetFirstPageItemViewsBounds(); |
| top_icon_views_.clear(); |
| |
| const AppListConfigType app_list_config_type = |
| folder_view_->GetAppListConfig()->type(); |
| |
| // Get top folder items that should be animated - note that item index in |
| // the folder item list may not match the intended item bounds in |
| // `first_page_item_views_bounds` if it's preceded by a drag item in the |
| // item list. |
| std::vector<const AppListItem*> top_items; |
| const AppListItem* drag_item = folder_view_->items_grid_view()->drag_item(); |
| const AppListItemList* folder_items = |
| folder_view_->folder_item()->item_list(); |
| for (size_t i = 0; i < folder_items->item_count(); ++i) { |
| if (top_items.size() == first_page_item_views_bounds.size()) |
| break; |
| const AppListItem* top_item = folder_items->item_at(i); |
| if (top_item->GetIcon(app_list_config_type).isNull() || |
| top_item == drag_item) { |
| // The item being dragged should be excluded. |
| continue; |
| } |
| top_items.push_back(top_item); |
| } |
| |
| for (size_t i = 0; i < top_items.size(); ++i) { |
| const AppListItem* top_item = top_items[i]; |
| bool item_in_folder_icon = i < top_item_views_bounds.size(); |
| gfx::Rect scaled_rect = item_in_folder_icon |
| ? top_item_views_bounds[i] |
| : folder_view_->folder_item_icon_bounds(); |
| |
| auto icon_view = std::make_unique<TopIconAnimationView>( |
| folder_view_->items_grid_view(), |
| top_item->GetIcon(app_list_config_type), |
| base::UTF8ToUTF16(top_item->GetDisplayName()), scaled_rect, show_, |
| item_in_folder_icon); |
| auto* icon_view_ptr = icon_view.get(); |
| |
| icon_view_ptr->AddObserver(this); |
| // Add the transitional views into child views, and set its bounds to the |
| // same location of the item in the folder list view. |
| top_icon_views_.push_back( |
| folder_view_->background_view()->AddChildView(std::move(icon_view))); |
| icon_view_ptr->SetBoundsRect(first_page_item_views_bounds[i]); |
| icon_view_ptr->TransformView(kFolderTransitionDuration); |
| } |
| |
| if (top_icon_views_.empty()) |
| OnAnimationComplete(); |
| } |
| |
| bool IsAnimationRunning() override { return !top_icon_views_.empty(); } |
| |
| // TopIconAnimationObserver |
| void OnTopIconAnimationsComplete(TopIconAnimationView* view) override { |
| // Clean up the transitional view for which the animation completes. |
| view->RemoveObserver(this); |
| auto to_delete = |
| std::find(top_icon_views_.begin(), top_icon_views_.end(), view); |
| DCHECK(to_delete != top_icon_views_.end()); |
| top_icon_views_.erase(to_delete); |
| |
| folder_view_->RecordAnimationSmoothness(); |
| |
| // An empty list indicates that all animations are done. |
| if (top_icon_views_.empty()) |
| OnAnimationComplete(); |
| } |
| |
| // Called when all top icon animations complete. |
| void OnAnimationComplete() { |
| // Set top item views visible when opening the folder. |
| if (show_) |
| SetFirstPageItemViewsVisible(true); |
| |
| // Show the folder icon when closing the folder. |
| if (!show_) |
| folder_item_view_->SetIconVisible(true); |
| |
| if (completion_callback_) |
| std::move(completion_callback_).Run(); |
| } |
| |
| private: |
| std::vector<gfx::Rect> GetTopItemViewsBoundsInFolderIcon() { |
| const AppListConfig* const app_list_config = |
| folder_view_->GetAppListConfig(); |
| size_t effective_folder_size = |
| folder_view_->folder_item()->ChildItemCount(); |
| // If a folder item is being dragged, it should be hidden from the folder |
| // item icon, and top icons bounds should be calculated as if the item is |
| // not in the folder. |
| if (folder_view_->items_grid_view()->drag_item()) |
| effective_folder_size -= 1; |
| |
| std::vector<gfx::Rect> top_icons_bounds = FolderImage::GetTopIconsBounds( |
| *app_list_config, folder_view_->folder_item_icon_bounds(), |
| std::min(effective_folder_size, FolderImage::kNumFolderTopItems)); |
| std::vector<gfx::Rect> top_item_views_bounds; |
| const int icon_dimension = app_list_config->grid_icon_dimension(); |
| const int icon_bottom_padding = app_list_config->grid_icon_bottom_padding(); |
| const int tile_width = app_list_config->grid_tile_width(); |
| const int tile_height = app_list_config->grid_tile_height(); |
| for (gfx::Rect bounds : top_icons_bounds) { |
| // Calculate the item view's bounds based on the icon bounds. |
| gfx::Rect item_bounds( |
| (icon_dimension - tile_width) / 2, |
| (icon_dimension + icon_bottom_padding - tile_height) / 2, tile_width, |
| tile_height); |
| item_bounds = gfx::ScaleToRoundedRect( |
| item_bounds, bounds.width() / static_cast<float>(icon_dimension), |
| bounds.height() / static_cast<float>(icon_dimension)); |
| item_bounds.Offset(bounds.x(), bounds.y()); |
| |
| top_item_views_bounds.emplace_back(item_bounds); |
| } |
| return top_item_views_bounds; |
| } |
| |
| void SetFirstPageItemViewsVisible(bool visible) { |
| // Items grid view has to be visible in case an item is being reparented, so |
| // only set the opacity here. |
| folder_view_->items_grid_view()->layer()->SetOpacity(visible ? 1.0f : 0.0f); |
| } |
| |
| // Get the bounds of the items in the first page of the opened folder relative |
| // to AppListFolderView. |
| std::vector<gfx::Rect> GetFirstPageItemViewsBounds() { |
| std::vector<gfx::Rect> items_bounds; |
| // Go over items in the folder, and collect bounds of items that fit within |
| // the bounds of the first "page" of apps. |
| const size_t count = folder_view_->folder_item()->ChildItemCount(); |
| views::View* container = |
| features::IsProductivityLauncherEnabled() |
| ? static_cast<views::View*>(scroll_view_) |
| : static_cast<views::View*>(folder_view_->items_grid_view()); |
| const gfx::RectF container_bounds(container->GetLocalBounds()); |
| for (size_t i = 0; i < count; ++i) { |
| views::View* item = folder_view_->items_grid_view()->GetItemViewAt(i); |
| if (folder_view_->items_grid_view()->IsViewHiddenForDrag(item)) |
| continue; |
| |
| // Stop if the item bounds are not within the container bounds - assumes |
| // that subsequent item bounds would not be within the container view |
| // either. |
| gfx::RectF bounds_in_container(item->GetLocalBounds()); |
| views::View::ConvertRectToTarget(item, container, &bounds_in_container); |
| if (!container_bounds.Contains(bounds_in_container)) |
| break; |
| |
| // Return the item bounds in AppListFolderView coordinates. |
| gfx::RectF bounds_in_folder(item->GetLocalBounds()); |
| views::View::ConvertRectToTarget(item, folder_view_, &bounds_in_folder); |
| items_bounds.emplace_back( |
| folder_view_->GetMirroredRect(gfx::ToRoundedRect(bounds_in_folder))); |
| } |
| return items_bounds; |
| } |
| |
| // True if opening the folder. |
| const bool show_; |
| |
| AppListFolderView* const folder_view_; // Not owned. |
| |
| // The scroll view that contains the apps grid. Used with |
| // ProductivityLauncher. |
| views::ScrollView* const scroll_view_; |
| |
| // The app list item view with which the folder view is associated. |
| // NOTE: Users of `TopIconAnimation` should ensure the animation does |
| // not outlive the `folder_item_view_`. |
| AppListItemView* const folder_item_view_; |
| |
| std::vector<TopIconAnimationView*> top_icon_views_; |
| |
| base::OnceClosure completion_callback_; |
| }; |
| |
| // Transit from the bounds of the folder item icon to the opened folder's |
| // bounds and transit opacity from 0 to 1 when opening the folder. Transit the |
| // other way when closing the folder. |
| class ContentsContainerAnimation : public AppListFolderView::Animation, |
| public ui::ImplicitAnimationObserver { |
| public: |
| ContentsContainerAnimation(bool show, |
| bool hide_for_reparent, |
| AppListFolderView* folder_view) |
| : show_(show), |
| hide_for_reparent_(hide_for_reparent), |
| folder_view_(folder_view) {} |
| |
| ContentsContainerAnimation(const ContentsContainerAnimation&) = delete; |
| ContentsContainerAnimation& operator=(const ContentsContainerAnimation&) = |
| delete; |
| |
| ~ContentsContainerAnimation() override { StopObservingImplicitAnimations(); } |
| |
| // AppListFolderView::Animation |
| void ScheduleAnimation(base::OnceClosure completion_callback) override { |
| DCHECK(!completion_callback_); |
| completion_callback_ = std::move(completion_callback); |
| |
| // Transform used to scale the folder's contents container from the bounds |
| // of the folder icon to that of the opened folder. |
| gfx::Transform transform; |
| const gfx::Rect scaled_rect(folder_view_->folder_item_icon_bounds()); |
| const gfx::Rect rect(folder_view_->contents_container()->bounds()); |
| ui::Layer* layer = folder_view_->contents_container()->layer(); |
| transform.Translate(scaled_rect.x() - rect.x(), scaled_rect.y() - rect.y()); |
| transform.Scale(static_cast<double>(scaled_rect.width()) / rect.width(), |
| static_cast<double>(scaled_rect.height()) / rect.height()); |
| |
| if (show_) |
| layer->SetTransform(transform); |
| layer->SetOpacity(show_ ? 0.0f : 1.0f); |
| |
| // The folder should be set visible only after it is scaled down and |
| // transparent to prevent the flash of the view right before the animation. |
| folder_view_->SetVisible(true); |
| |
| ui::ScopedLayerAnimationSettings animation(layer->GetAnimator()); |
| animation.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); |
| animation.AddObserver(this); |
| animation.SetTransitionDuration(kFolderTransitionDuration); |
| layer->SetTransform(show_ ? gfx::Transform() : transform); |
| layer->SetOpacity(show_ ? 1.0f : 0.0f); |
| |
| is_animation_running_ = true; |
| } |
| |
| bool IsAnimationRunning() override { return is_animation_running_; } |
| |
| // ui::ImplicitAnimationObserver |
| void OnImplicitAnimationsCompleted() override { |
| is_animation_running_ = false; |
| |
| // If the view is hidden for reparenting a folder item, it has to be |
| // visible, so that drag_view_ can keep receiving mouse events. |
| if (!show_ && !hide_for_reparent_) |
| folder_view_->SetVisible(false); |
| |
| // Set the view bounds offscreen, so that it won't overlap the root level |
| // apps grid view during folder item reparenting transitional period. |
| // Keeping the same width and height avoids re-layout and ensures that |
| // AppListItemView continues to receive events. The view will be set |
| // invisible at the end of the drag. |
| if (hide_for_reparent_) { |
| const gfx::Rect& bounds = folder_view_->bounds(); |
| folder_view_->SetPosition(gfx::Point(-bounds.width(), -bounds.height())); |
| } |
| |
| // Reset the transform after animation so that the following folder's |
| // preferred bounds is calculated correctly. |
| folder_view_->contents_container()->layer()->SetTransform(gfx::Transform()); |
| folder_view_->RecordAnimationSmoothness(); |
| |
| if (completion_callback_) |
| std::move(completion_callback_).Run(); |
| } |
| |
| private: |
| // True if opening the folder. |
| const bool show_; |
| |
| // True if an item in the folder is being reparented to root grid view. |
| const bool hide_for_reparent_; |
| |
| AppListFolderView* const folder_view_; |
| |
| bool is_animation_running_ = false; |
| |
| base::OnceClosure completion_callback_; |
| }; |
| |
| // ScrollViewWithMaxHeight limits its preferred size to a maximum height that |
| // shows 4 apps grid rows. |
| class ScrollViewWithMaxHeight : public views::ScrollView { |
| public: |
| explicit ScrollViewWithMaxHeight(AppListFolderView* folder_view) |
| : views::ScrollView(views::ScrollView::ScrollWithLayers::kEnabled), |
| folder_view_(folder_view) {} |
| ScrollViewWithMaxHeight(const ScrollViewWithMaxHeight&) = delete; |
| ScrollViewWithMaxHeight& operator=(const ScrollViewWithMaxHeight&) = delete; |
| ~ScrollViewWithMaxHeight() override = default; |
| |
| // views::View: |
| gfx::Size CalculatePreferredSize() const override { |
| gfx::Size size = views::ScrollView::CalculatePreferredSize(); |
| const int tile_height = |
| folder_view_->items_grid_view()->GetTotalTileSize(/*page=*/0).height(); |
| // Show a maximum of 4 full rows, plus a little bit of the next row to make |
| // it obvious the view can scroll. |
| const int max_height = (tile_height * 4) + (tile_height / 4); |
| size.set_height(std::min(size.height(), max_height)); |
| return size; |
| } |
| |
| private: |
| AppListFolderView* const folder_view_; |
| }; |
| |
| } // namespace |
| |
| AppListFolderView::AppListFolderView(AppListFolderController* folder_controller, |
| AppsGridView* root_apps_grid_view, |
| ContentsView* contents_view, |
| AppListA11yAnnouncer* a11y_announcer, |
| AppListViewDelegate* view_delegate) |
| : folder_controller_(folder_controller), |
| root_apps_grid_view_(root_apps_grid_view), |
| a11y_announcer_(a11y_announcer), |
| view_delegate_(view_delegate) { |
| DCHECK(folder_controller_); |
| DCHECK(root_apps_grid_view_); |
| DCHECK(a11y_announcer_); |
| DCHECK(view_delegate_); |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| |
| // The background's corner radius cannot be changed in the same layer of the |
| // contents container using layer animation, so use another layer to perform |
| // such changes. |
| background_view_ = AddChildView(std::make_unique<views::View>()); |
| background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| background_view_->layer()->SetFillsBoundsOpaquely(false); |
| background_view_->layer()->SetBackgroundBlur( |
| ColorProvider::kBackgroundBlurSigma); |
| background_view_->layer()->SetBackdropFilterQuality( |
| ColorProvider::kBackgroundBlurQuality); |
| |
| contents_container_ = AddChildView(std::make_unique<views::View>()); |
| contents_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| |
| if (features::IsProductivityLauncherEnabled()) |
| CreateScrollableAppsGrid(); |
| else |
| CreatePagedAppsGrid(contents_view); |
| |
| AppListModelProvider::Get()->AddObserver(this); |
| } |
| |
| void AppListFolderView::CreatePagedAppsGrid(ContentsView* contents_view) { |
| DCHECK(contents_view); |
| // Cache typed apps grid view pointer to perform setup specific to |
| // PagedAppsGridView (e.g. `SetMaxRows()`) without requiring static cast. |
| PagedAppsGridView* items_grid_view = |
| contents_container_->AddChildView(std::make_unique<PagedAppsGridView>( |
| contents_view, a11y_announcer_, this, /*folder_controller=*/nullptr, |
| /*container_delegate=*/this)); |
| contents_container_->layer()->SetMasksToBounds(true); |
| items_grid_view_ = items_grid_view; |
| |
| items_grid_view_->Init(); |
| items_grid_view->SetMaxColumnsAndRows( |
| kMaxFolderColumns, |
| /*max_rows_on_first_page=*/kMaxPagedFolderRows, |
| /*max_rows=*/kMaxPagedFolderRows); |
| items_grid_view->SetFixedTilePadding(kTileSpacingInFolder / 2, |
| kTileSpacingInFolder / 2); |
| |
| folder_header_view_ = contents_container_->AddChildView( |
| std::make_unique<FolderHeaderView>(this)); |
| folder_header_view_->SetProperty(views::kMarginsKey, |
| gfx::Insets(kFolderHeaderPadding, 0)); |
| |
| page_switcher_ = |
| contents_container_->AddChildView(std::make_unique<PageSwitcher>( |
| items_grid_view->pagination_model(), false /* vertical */, |
| view_delegate_->IsInTabletMode(), |
| AppListColorProvider::Get()->GetFolderBackgroundColor())); |
| |
| contents_container_->SetLayoutManager(std::make_unique<views::FlexLayout>()) |
| ->SetOrientation(views::LayoutOrientation::kVertical) |
| .SetInteriorMargin(gfx::Insets(kTileSpacingInFolder)) |
| .SetCollapseMargins(true) |
| .SetChildViewIgnoredByLayout(page_switcher_, true); |
| } |
| |
| void AppListFolderView::CreateScrollableAppsGrid() { |
| // The top part of the folder contents is a scrollable apps grid. |
| scroll_view_ = contents_container_->AddChildView( |
| std::make_unique<ScrollViewWithMaxHeight>(this)); |
| scroll_view_->ClipHeightTo(0, std::numeric_limits<int>::max()); |
| scroll_view_->SetDrawOverflowIndicator(false); |
| // Don't paint a background. The folder already has one. |
| scroll_view_->SetBackgroundColor(absl::nullopt); |
| // Arrow keys are used to select app icons. |
| scroll_view_->SetAllowKeyboardScrolling(false); |
| |
| // Set up fade in/fade out gradients at top/bottom of scroll view. |
| scroll_view_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| gradient_helper_ = std::make_unique<ScrollViewGradientHelper>(scroll_view_); |
| |
| // Set up scroll bars. |
| scroll_view_->SetHorizontalScrollBarMode( |
| views::ScrollView::ScrollBarMode::kDisabled); |
| auto vertical_scroll = |
| std::make_unique<RoundedScrollBar>(/*horizontal=*/false); |
| vertical_scroll->SetInsets(kVerticalScrollInsets); |
| scroll_view_->SetVerticalScrollBar(std::move(vertical_scroll)); |
| |
| // Add margins inside the scroll contents. |
| auto scroll_contents = std::make_unique<views::View>(); |
| scroll_contents->SetLayoutManager(std::make_unique<views::FlexLayout>()) |
| ->SetOrientation(views::LayoutOrientation::kVertical) |
| .SetInteriorMargin(gfx::Insets(kTileSpacingInFolder)) |
| .SetCollapseMargins(true); |
| |
| // Create the apps grid. |
| auto* items_grid_view = |
| scroll_contents->AddChildView(std::make_unique<ScrollableAppsGridView>( |
| a11y_announcer_, view_delegate_, this, scroll_view_, |
| /*folder_controller=*/nullptr, /*focus_delegate=*/nullptr)); |
| items_grid_view_ = items_grid_view; |
| items_grid_view->Init(); |
| items_grid_view->SetMaxColumns(kMaxFolderColumns); |
| items_grid_view->SetFixedTilePadding(kTileSpacingInFolder / 2, |
| kTileSpacingInFolder / 2); |
| scroll_view_->SetContents(std::move(scroll_contents)); |
| |
| // In the common case, the parent view is large and the folder has a small |
| // number of apps, so the scroll view's size will be limited by the apps grid |
| // view's preferred size. However, if the parent view is small, the scroll |
| // view will scale down, so there is enough space for the header view. |
| scroll_view_->SetProperty( |
| views::kFlexBehaviorKey, |
| views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, |
| views::MaximumFlexSizeRule::kPreferred)); |
| |
| folder_header_view_ = contents_container_->AddChildView( |
| std::make_unique<FolderHeaderView>(this)); |
| folder_header_view_->SetProperty(views::kMarginsKey, |
| gfx::Insets(kFolderHeaderPadding, 0)); |
| |
| // No margins on `contents_container_` because the scroll view needs to fully |
| // extend to the parent's edges. |
| contents_container_->SetLayoutManager(std::make_unique<views::FlexLayout>()) |
| ->SetOrientation(views::LayoutOrientation::kVertical); |
| } |
| |
| AppListFolderView::~AppListFolderView() { |
| AppListModelProvider::Get()->RemoveObserver(this); |
| |
| // This prevents the AppsGridView's destructor from calling the now-deleted |
| // AppListFolderView's methods if a drag is in progress at the time. |
| items_grid_view_->set_folder_delegate(nullptr); |
| |
| // Make sure |page_switcher_| is deleted before |items_grid_view_| because |
| // |page_switcher_| uses the PaginationModel owned by |items_grid_view_|. |
| delete page_switcher_; |
| } |
| |
| void AppListFolderView::UpdateAppListConfig(const AppListConfig* config) { |
| items_grid_view_->UpdateAppListConfig(config); |
| ShrinkGridTileMarginsWhenNeeded(); |
| } |
| |
| void AppListFolderView::ConfigureForFolderItemView( |
| AppListItemView* folder_item_view) { |
| DCHECK(folder_item_view->is_folder()); |
| DCHECK(folder_item_view->item()); |
| DCHECK(items_grid_view_->app_list_config()); |
| |
| // Clear any remaining state from the last time the folder was shown. E.g. |
| // cancel any pending hide animations. |
| ResetState(/*restore_folder_item_view_state=*/true); |
| |
| folder_item_view_ = folder_item_view; |
| folder_item_view_observer_.Observe(folder_item_view); |
| |
| folder_item_ = static_cast<AppListFolderItem*>(folder_item_view->item()); |
| |
| AppListModel* const model = AppListModelProvider::Get()->model(); |
| items_grid_view_->SetModel(model); |
| items_grid_view_->SetItemList(folder_item_->item_list()); |
| folder_header_view_->SetFolderItem(folder_item_); |
| |
| model_observation_.Observe(model); |
| |
| UpdatePreferredBounds(); |
| } |
| |
| void AppListFolderView::ScheduleShowHideAnimation(bool show, |
| bool hide_for_reparent) { |
| if (show) |
| a11y_announcer_->AnnounceFolderOpened(); |
| else |
| a11y_announcer_->AnnounceFolderClosed(); |
| |
| show_hide_metrics_tracker_ = |
| GetWidget()->GetCompositor()->RequestNewThroughputTracker(); |
| show_hide_metrics_tracker_->Start( |
| metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.AppListFolder.ShowHide.AnimationSmoothness", smoothness); |
| }))); |
| |
| if (!features::IsProductivityLauncherEnabled()) { |
| static_cast<PagedAppsGridView*>(items_grid_view_) |
| ->pagination_model() |
| ->SelectPage(0, false); |
| } |
| |
| folder_visibility_animations_.clear(); |
| |
| // Animate the background corner radius, opacity and bounds. |
| folder_visibility_animations_.push_back( |
| std::make_unique<BackgroundAnimation>(show, this, background_view_)); |
| |
| // Animate the folder item's title's opacity. |
| folder_visibility_animations_.push_back( |
| std::make_unique<FolderItemTitleAnimation>(show, this, |
| folder_item_view_)); |
| |
| // Animate the bounds and opacity of items in the first page of the opened |
| // folder. |
| folder_visibility_animations_.push_back(std::make_unique<TopIconAnimation>( |
| show, this, scroll_view_, folder_item_view_)); |
| |
| // Animate the bounds and opacity of the contents container. |
| folder_visibility_animations_.push_back( |
| std::make_unique<ContentsContainerAnimation>(show, hide_for_reparent, |
| this)); |
| |
| base::RepeatingClosure animation_completion_callback; |
| if (!show) { |
| animation_completion_callback = base::BarrierClosure( |
| folder_visibility_animations_.size(), |
| base::BindOnce(&AppListFolderView::OnHideAnimationDone, |
| weak_ptr_factory_.GetWeakPtr(), hide_for_reparent)); |
| } else if (animation_done_test_callback_) { |
| animation_completion_callback = base::BarrierClosure( |
| folder_visibility_animations_.size(), |
| base::BindOnce(&AppListFolderView::OnShowAnimationDone, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| for (auto& animation : folder_visibility_animations_) |
| animation->ScheduleAnimation(animation_completion_callback); |
| } |
| |
| void AppListFolderView::Layout() { |
| views::View::Layout(); |
| |
| if (gradient_helper_) |
| gradient_helper_->UpdateGradientZone(); |
| |
| // Position page switcher independently of the layout manager, as its |
| // position does not fit with vertical layout alignment (it's expected to |
| // float over the header view in the bottom right corner). |
| if (page_switcher_) { |
| const gfx::Size page_switcher_size = page_switcher_->GetPreferredSize(); |
| const gfx::Rect folder_header_bounds = folder_header_view_->bounds(); |
| const int page_switcher_x = |
| folder_header_bounds.right() - page_switcher_size.width(); |
| // The page switcher has a different height than the folder header, but it |
| // still needs to be aligned with it. |
| const int page_switcher_y = |
| folder_header_bounds.y() - |
| (page_switcher_size.height() - folder_header_bounds.height()) / 2; |
| page_switcher_->SetBoundsRect(gfx::Rect( |
| gfx::Point(page_switcher_x, page_switcher_y), page_switcher_size)); |
| } |
| // `BackgroundAnimation` animates the clip rect during open/close. |
| if (!IsAnimationRunning()) { |
| // The folder view can change size due to app install/uninstall. Ensure the |
| // rounded corners have the correct position. https://crbug.com/993282 |
| background_view_->layer()->SetClipRect(background_view_->GetLocalBounds()); |
| } |
| } |
| |
| void AppListFolderView::ChildPreferredSizeChanged(views::View* child) { |
| UpdatePreferredBounds(); |
| PreferredSizeChanged(); |
| } |
| |
| void AppListFolderView::OnActiveAppListModelsChanged( |
| AppListModel* model, |
| SearchModel* search_model) { |
| // If the active model changed, close the folder view, as the backing app list |
| // item is about to go away. |
| if (folder_item_) { |
| ResetState(/*restore_folder_item_view_state=*/false); |
| |
| folder_controller_->ShowApps(/*folder_item_view=*/nullptr, |
| /*select_folder=*/false); |
| } |
| } |
| |
| void AppListFolderView::OnViewIsDeleting(views::View* view) { |
| DCHECK_EQ(view, folder_item_view_); |
| |
| // If the original view got removed, clear any references to it, this includes |
| // animations that may try to access the view to update its visibility. |
| folder_visibility_animations_.clear(); |
| folder_item_view_observer_.Reset(); |
| folder_item_view_ = nullptr; |
| } |
| |
| void AppListFolderView::OnAppListItemWillBeDeleted(AppListItem* item) { |
| if (item == folder_item_) { |
| ResetState(/*restore_folder_item_view_state=*/true); |
| |
| // If the folder item associated with this view is removed from the model, |
| // (e.g. the last item in the folder was deleted), reset the view and signal |
| // the container view to show the app list instead. |
| // Pass nullptr to ShowApps() to avoid triggering animation from the deleted |
| // folder. |
| folder_controller_->ShowApps(/*folder_item_view=*/nullptr, |
| /*select_folder=*/false); |
| } |
| } |
| |
| void AppListFolderView::ResetState(bool restore_folder_item_view_state) { |
| DVLOG(1) << __FUNCTION__; |
| if (folder_item_) { |
| items_grid_view_->ClearSelectedView(); |
| items_grid_view_->SetItemList(nullptr); |
| items_grid_view_->SetModel(nullptr); |
| folder_header_view_->SetFolderItem(nullptr); |
| folder_item_ = nullptr; |
| } |
| |
| model_observation_.Reset(); |
| |
| show_hide_metrics_tracker_.reset(); |
| |
| // Clear in-progress animations, as they may depend on the |
| // `folder_item_view_`. |
| folder_visibility_animations_.clear(); |
| |
| // Transition all the states immediately to the end of folder closing |
| // animation. |
| background_view_->layer()->SetColor(SK_ColorTRANSPARENT); |
| if (restore_folder_item_view_state && folder_item_view_) { |
| folder_item_view_->SetIconVisible(true); |
| folder_item_view_->title()->SetEnabledColor( |
| AppListColorProvider::Get()->GetAppListItemTextColor( |
| /*is_in_folder=*/false)); |
| } |
| |
| folder_item_view_observer_.Reset(); |
| folder_item_view_ = nullptr; |
| |
| preferred_bounds_ = gfx::Rect(); |
| folder_item_icon_bounds_ = gfx::Rect(); |
| } |
| |
| void AppListFolderView::OnShowAnimationDone() { |
| if (animation_done_test_callback_) |
| std::move(animation_done_test_callback_).Run(); |
| } |
| |
| void AppListFolderView::OnHideAnimationDone(bool hide_for_reparent) { |
| // If the folder view is hiding for folder closure, reset the |
| // folder state when the animations complete. Not resetting state |
| // immediately so the folder view keeps tracking folder item |
| // view's liveness (so it can reset animations if the folder item |
| // view gets deleted). |
| // If the view is hidden for reparent, the state will be cleared |
| // when the reparent drag ends. |
| if (!hide_for_reparent) { |
| ResetState( |
| /*reset_folder_item_view_state=*/true); |
| } |
| |
| if (animation_done_test_callback_) |
| std::move(animation_done_test_callback_).Run(); |
| } |
| |
| void AppListFolderView::UpdatePreferredBounds() { |
| if (!folder_item_view_) |
| return; |
| |
| // Calculate the folder icon's bounds relative to our parent. |
| gfx::RectF rect(folder_item_view_->GetIconBounds()); |
| ConvertRectToTarget(folder_item_view_, parent(), &rect); |
| gfx::Rect icon_bounds_in_container = |
| parent()->GetMirroredRect(gfx::ToEnclosingRect(rect)); |
| |
| // The opened folder view's center should try to overlap with the folder |
| // item's center while it must fit within the bounds of the parent. |
| preferred_bounds_ = gfx::Rect(GetPreferredSize()); |
| preferred_bounds_ += (icon_bounds_in_container.CenterPoint() - |
| preferred_bounds_.CenterPoint()); |
| |
| if (!bounding_box_.IsEmpty()) |
| preferred_bounds_.AdjustToFit(bounding_box_); |
| |
| // Calculate the folder icon's bounds relative to this view. |
| folder_item_icon_bounds_ = |
| icon_bounds_in_container - preferred_bounds_.OffsetFromOrigin(); |
| |
| // Adjust folder item icon bounds for RTL (cannot use GetMirroredRect(), as |
| // the current view bounds might not match the preferred bounds). |
| if (base::i18n::IsRTL()) { |
| folder_item_icon_bounds_.set_x(preferred_bounds_.width() - |
| folder_item_icon_bounds_.x() - |
| folder_item_icon_bounds_.width()); |
| } |
| } |
| |
| int AppListFolderView::GetYOffsetForFolder() { |
| auto* const keyboard_controller = keyboard::KeyboardUIController::Get(); |
| if (!keyboard_controller->IsEnabled()) |
| return 0; |
| |
| // This view should be on top of on-screen keyboard to prevent the folder |
| // title from being blocked. |
| const gfx::Rect occluded_bounds = |
| keyboard_controller->GetWorkspaceOccludedBoundsInScreen(); |
| if (!occluded_bounds.IsEmpty()) { |
| gfx::Point keyboard_top_right = occluded_bounds.top_right(); |
| ConvertPointFromScreen(parent(), &keyboard_top_right); |
| |
| // Our final Y-Offset is determined by combining the space from the bottom |
| // of the folder to the top of the keyboard, and the padding that should |
| // exist between the keyboard and the folder bottom. |
| // std::min() is used so that positive offsets are ignored. |
| return std::min(keyboard_top_right.y() - kOnscreenKeyboardTopPadding - |
| preferred_bounds_.bottom(), |
| 0); |
| } |
| |
| // If no offset is calculated above, then we need none. |
| return 0; |
| } |
| |
| bool AppListFolderView::IsAnimationRunning() const { |
| for (auto& animation : folder_visibility_animations_) { |
| if (animation->IsAnimationRunning()) |
| return true; |
| } |
| return false; |
| } |
| |
| void AppListFolderView::SetBoundingBox(const gfx::Rect& bounding_box) { |
| bounding_box_ = bounding_box; |
| ShrinkGridTileMarginsWhenNeeded(); |
| } |
| |
| void AppListFolderView::SetAnimationDoneTestCallback( |
| base::OnceClosure animation_done_callback) { |
| DCHECK(!animation_done_callback || !animation_done_test_callback_); |
| animation_done_test_callback_ = std::move(animation_done_callback); |
| } |
| |
| void AppListFolderView::RecordAnimationSmoothness() { |
| // RecordAnimationSmoothness is called when ContentsContainerAnimation |
| // ends as well. Do not record show/hide metrics for that. |
| if (show_hide_metrics_tracker_) { |
| show_hide_metrics_tracker_->Stop(); |
| show_hide_metrics_tracker_.reset(); |
| } |
| } |
| |
| void AppListFolderView::OnTabletModeChanged(bool started) { |
| folder_header_view()->set_tablet_mode(started); |
| if (page_switcher_) |
| page_switcher_->set_is_tablet_mode(started); |
| } |
| |
| void AppListFolderView::OnScrollEvent(ui::ScrollEvent* event) { |
| items_grid_view_->HandleScrollFromParentView( |
| gfx::Vector2d(event->x_offset(), event->y_offset()), event->type()); |
| event->SetHandled(); |
| } |
| |
| void AppListFolderView::OnMouseEvent(ui::MouseEvent* event) { |
| if (event->type() == ui::ET_MOUSEWHEEL) { |
| items_grid_view_->HandleScrollFromParentView( |
| event->AsMouseWheelEvent()->offset(), ui::ET_MOUSEWHEEL); |
| event->SetHandled(); |
| } |
| } |
| |
| bool AppListFolderView::IsDragPointOutsideOfFolder( |
| const gfx::Point& drag_point) { |
| gfx::Point drag_point_in_folder = drag_point; |
| views::View::ConvertPointToTarget(items_grid_view_, this, |
| &drag_point_in_folder); |
| return !GetLocalBounds().Contains(drag_point_in_folder); |
| } |
| |
| // When user drags a folder item out of the folder boundary ink bubble, the |
| // folder view UI will be hidden, and switch back to top level AppsGridView. |
| // The dragged item will seamlessly move on the top level AppsGridView. |
| // In order to achieve the above, we keep the folder view and its child grid |
| // view visible with opacity 0, so that the drag_view_ on the hidden grid view |
| // will keep receiving mouse event. At the same time, we initiated a new |
| // drag_view_ in the top level grid view, and keep it moving with the hidden |
| // grid view's drag_view_, so that the dragged item can be engaged in drag and |
| // drop flow in the top level grid view. During the reparenting process, the |
| // drag_view_ in hidden grid view will dispatch the drag and drop event to |
| // the top level grid view, until the drag ends. |
| void AppListFolderView::ReparentItem( |
| AppListItemView* original_drag_view, |
| const gfx::Point& drag_point_in_folder_grid) { |
| // Convert the drag point relative to the root level AppsGridView. |
| gfx::Point to_root_level_grid = drag_point_in_folder_grid; |
| ConvertPointToTarget(items_grid_view_, root_apps_grid_view_, |
| &to_root_level_grid); |
| // Ensures the icon updates to reflect that the icon has been removed during |
| // the drag |
| folder_item_->NotifyOfDraggedItem(original_drag_view->item()); |
| root_apps_grid_view_->InitiateDragFromReparentItemInRootLevelGridView( |
| original_drag_view, to_root_level_grid, |
| base::BindOnce(&AppListFolderView::CancelReparentDragFromRootGrid, |
| weak_ptr_factory_.GetWeakPtr())); |
| folder_controller_->ReparentFolderItemTransit(folder_item_); |
| } |
| |
| void AppListFolderView::DispatchDragEventForReparent( |
| AppsGridView::Pointer pointer, |
| const gfx::Point& drag_point_in_folder_grid) { |
| gfx::Point drag_point_in_root_grid = drag_point_in_folder_grid; |
| // Temporarily reset the transform of the contents container so that the point |
| // can be correctly converted to the root grid's coordinates. |
| gfx::Transform original_transform = contents_container_->GetTransform(); |
| contents_container_->SetTransform(gfx::Transform()); |
| ConvertPointToTarget(items_grid_view_, root_apps_grid_view_, |
| &drag_point_in_root_grid); |
| contents_container_->SetTransform(original_transform); |
| |
| root_apps_grid_view_->UpdateDragFromReparentItem(pointer, |
| drag_point_in_root_grid); |
| } |
| |
| void AppListFolderView::DispatchEndDragEventForReparent( |
| bool events_forwarded_to_drag_drop_host, |
| bool cancel_drag, |
| std::unique_ptr<AppDragIconProxy> drag_icon_proxy) { |
| folder_item_->NotifyOfDraggedItem(nullptr); |
| root_apps_grid_view_->EndDragFromReparentItemInRootLevel( |
| folder_item_view_, events_forwarded_to_drag_drop_host, cancel_drag, |
| std::move(drag_icon_proxy)); |
| folder_controller_->ReparentDragEnded(); |
| |
| // The view was not hidden in order to keeping receiving mouse events. Hide it |
| // now as the reparenting ended. |
| HideViewImmediately(); |
| } |
| |
| void AppListFolderView::HideViewImmediately() { |
| SetVisible(false); |
| ResetState(/*restore_folder_item_view_state=*/true); |
| } |
| |
| void AppListFolderView::ResetItemsGridForClose() { |
| if (items_grid_view()->has_dragged_item()) |
| items_grid_view()->EndDrag(true); |
| items_grid_view()->ClearSelectedView(); |
| } |
| |
| void AppListFolderView::CloseFolderPage() { |
| DVLOG(1) << __FUNCTION__; |
| // When a folder closes only show the selection highlight if there was already |
| // one showing. |
| const bool select_folder = items_grid_view()->has_selected_view(); |
| ResetItemsGridForClose(); |
| folder_controller_->ShowApps(folder_item_view_, select_folder); |
| } |
| |
| void AppListFolderView::FocusFirstItem(bool silent) { |
| DVLOG(1) << __FUNCTION__; |
| AppListItemView* first_item_view = |
| items_grid_view()->view_model()->view_at(0); |
| if (silent) { |
| first_item_view->SilentlyRequestFocus(); |
| } else { |
| first_item_view->RequestFocus(); |
| } |
| } |
| |
| bool AppListFolderView::IsOEMFolder() const { |
| return folder_item_->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM; |
| } |
| |
| void AppListFolderView::HandleKeyboardReparent(AppListItemView* reparented_view, |
| ui::KeyboardCode key_code) { |
| folder_controller_->ReparentFolderItemTransit(folder_item_); |
| root_apps_grid_view_->HandleKeyboardReparent(reparented_view, |
| folder_item_view_, key_code); |
| } |
| |
| bool AppListFolderView::IsPointWithinPageFlipBuffer( |
| const gfx::Point& point) const { |
| // The page flip buffer is anywhere within the bounds of the |
| // |contents_container_|. |
| gfx::Point point_in_parent = point; |
| ConvertPointToTarget(items_grid_view_, contents_container_, &point_in_parent); |
| return GetContentsBounds().Contains(point_in_parent); |
| } |
| |
| bool AppListFolderView::IsPointWithinBottomDragBuffer( |
| const gfx::Point& point, |
| int page_flip_zone_size) const { |
| // Folders page horizontally and do not have a bottom drag buffer. |
| return false; |
| } |
| |
| void AppListFolderView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| node_data->role = ax::mojom::Role::kGenericContainer; |
| } |
| |
| void AppListFolderView::OnGestureEvent(ui::GestureEvent* event) { |
| // Capture scroll events so they don't bubble up to the apps container, where |
| // they may cause the root apps grid view to scroll, or get translated into |
| // apps grid view drag. |
| if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) |
| event->SetHandled(); |
| } |
| |
| void AppListFolderView::SetItemName(AppListFolderItem* item, |
| const std::string& name) { |
| AppListModelProvider::Get()->model()->SetItemName(item, name); |
| } |
| |
| const AppListConfig* AppListFolderView::GetAppListConfig() const { |
| return items_grid_view_->app_list_config(); |
| } |
| |
| ui::Compositor* AppListFolderView::GetCompositor() { |
| return GetWidget()->GetCompositor(); |
| } |
| |
| void AppListFolderView::CancelReparentDragFromRootGrid() { |
| items_grid_view_->EndDrag(/*cancel=*/true); |
| } |
| |
| void AppListFolderView::ShrinkGridTileMarginsWhenNeeded() { |
| // Productivity launcher uses scrollable grid for folders, which handles the |
| // case where the items grid does not fit into bounds provided by the folder |
| // bounding box. |
| if (features::IsProductivityLauncherEnabled()) |
| return; |
| |
| if (bounding_box_.IsEmpty() || !GetAppListConfig()) |
| return; |
| |
| // Calculate the expected folder height when it has the max possible number of |
| // rows. The margins should be shrunk if this height does not fit within |
| // the bounding box. |
| const int max_folder_height = |
| folder_header_view_->GetPreferredSize().height() + kFolderHeaderPadding + |
| GetAppListConfig()->grid_tile_height() * kMaxPagedFolderRows + |
| (kMaxPagedFolderRows - 1) * kTileSpacingInFolder; |
| const bool shrink_margins = max_folder_height > bounding_box_.height(); |
| items_grid_view_->SetFixedTilePadding( |
| (shrink_margins ? 0 : kTileSpacingInFolder) / 2, |
| (shrink_margins ? 0 : kTileSpacingInFolder) / 2); |
| } |
| |
| BEGIN_METADATA(AppListFolderView, views::View) |
| END_METADATA |
| |
| } // namespace ash |