| // Copyright (c) 2012 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_view.h" |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/app_list/app_list_util.h" |
| #include "ash/app_list/model/app_list_model.h" |
| #include "ash/app_list/views/app_list_folder_view.h" |
| #include "ash/app_list/views/app_list_main_view.h" |
| #include "ash/app_list/views/apps_container_view.h" |
| #include "ash/app_list/views/contents_view.h" |
| #include "ash/app_list/views/search_box_view.h" |
| #include "ash/assistant/ui/assistant_ui_constants.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/ash_features.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/wallpaper_types.h" |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/string_util.h" |
| #include "ui/accessibility/ax_node.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/accessibility/platform/aura_window_properties.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_element.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/keyboard/keyboard_controller.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/ime_util_chromeos.h" |
| #include "ui/wm/core/shadow_types.h" |
| |
| using ash::ColorProfileType; |
| |
| namespace app_list { |
| |
| namespace { |
| |
| // The height of the half app list from the bottom of the screen. |
| constexpr int kHalfAppListHeight = 561; |
| |
| // The fraction of app list height that the app list must be released at in |
| // order to transition to the next state. |
| constexpr int kAppListThresholdDenominator = 3; |
| |
| // The scroll offset in order to transition from PEEKING to FULLSCREEN |
| constexpr int kAppListMinScrollToSwitchStates = 20; |
| |
| // The DIP distance from the bezel in which a gesture drag end results in a |
| // closed app list. |
| constexpr int kAppListBezelMargin = 50; |
| |
| // The size of app info dialog in fullscreen app list. |
| constexpr int kAppInfoDialogWidth = 512; |
| constexpr int kAppInfoDialogHeight = 384; |
| |
| // The animation duration for app list movement. |
| constexpr float kAppListAnimationDurationTestMs = 0; |
| constexpr float kAppListAnimationDurationMs = 200; |
| constexpr float kAppListAnimationDurationFromFullscreenMs = 250; |
| |
| // Events within this threshold from the top of the view will be reserved for |
| // home launcher gestures, if they can be processed. |
| constexpr int kAppListHomeLaucherGesturesThreshold = 32; |
| |
| // Quality of the shield background blur. |
| constexpr float kAppListBlurQuality = 0.33f; |
| |
| // Set animation durations to 0 for testing. |
| // TODO(oshima): Use ui::ScopedAnimationDurationScaleMode instead. |
| bool short_animations_for_testing; |
| |
| // Histogram for the app list dragging in clamshell mode. |
| constexpr char kAppListDragInClamshellHistogram[] = |
| "Apps.StateTransition.Drag.PresentationTime.ClamshellMode"; |
| constexpr char kAppListDragInClamshellMaxLatencyHistogram[] = |
| "Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode"; |
| |
| // Histogram for the app list dragging in tablet mode. |
| constexpr char kAppListDragInTabletHistogram[] = |
| "Apps.StateTransition.Drag.PresentationTime.TabletMode"; |
| constexpr char kAppListDragInTabletMaxLatencyHistogram[] = |
| "Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode"; |
| |
| // This view forwards the focus to the search box widget by providing it as a |
| // FocusTraversable when a focus search is provided. |
| class SearchBoxFocusHost : public views::View { |
| public: |
| explicit SearchBoxFocusHost(views::Widget* search_box_widget) |
| : search_box_widget_(search_box_widget) {} |
| |
| ~SearchBoxFocusHost() override {} |
| |
| views::FocusTraversable* GetFocusTraversable() override { |
| return search_box_widget_; |
| } |
| |
| private: |
| views::Widget* search_box_widget_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SearchBoxFocusHost); |
| }; |
| |
| SkColor GetBackgroundShieldColor(const std::vector<SkColor>& prominent_colors, |
| float color_opacity) { |
| const U8CPU sk_opacity_value = static_cast<U8CPU>(255 * color_opacity); |
| |
| const SkColor default_color = SkColorSetA( |
| app_list::AppListView::kDefaultBackgroundColor, sk_opacity_value); |
| |
| if (prominent_colors.empty()) |
| return default_color; |
| |
| DCHECK_EQ(static_cast<size_t>(ColorProfileType::NUM_OF_COLOR_PROFILES), |
| prominent_colors.size()); |
| |
| const SkColor dark_muted = |
| prominent_colors[static_cast<int>(ColorProfileType::DARK_MUTED)]; |
| if (SK_ColorTRANSPARENT == dark_muted) |
| return default_color; |
| |
| return SkColorSetA( |
| color_utils::GetResultingPaintColor( |
| SkColorSetA(SK_ColorBLACK, AppListView::kAppListColorDarkenAlpha), |
| dark_muted), |
| sk_opacity_value); |
| } |
| |
| DEFINE_UI_CLASS_PROPERTY_KEY(bool, kExcludeWindowFromEventHandling, false) |
| |
| // This targeter prevents routing events to sub-windows, such as |
| // RenderHostWindow in order to handle events in context of app list. |
| class AppListEventTargeter : public aura::WindowTargeter { |
| public: |
| AppListEventTargeter() = default; |
| ~AppListEventTargeter() override = default; |
| |
| // aura::WindowTargeter: |
| bool SubtreeShouldBeExploredForEvent(aura::Window* window, |
| const ui::LocatedEvent& event) override { |
| if (window->GetProperty(kExcludeWindowFromEventHandling)) { |
| // Allow routing to sub-windows for ET_MOUSE_MOVED event which is used by |
| // accessibility to enter the mode of exploration of WebView contents. |
| if (event.type() != ui::ET_MOUSE_MOVED) |
| return false; |
| } |
| |
| if (window->GetProperty(ash::assistant::ui::kOnlyAllowMouseClickEvents)) { |
| if (event.type() != ui::ET_MOUSE_PRESSED && |
| event.type() != ui::ET_MOUSE_RELEASED) { |
| return false; |
| } |
| } |
| |
| return aura::WindowTargeter::SubtreeShouldBeExploredForEvent(window, event); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AppListEventTargeter); |
| }; |
| |
| } // namespace |
| |
| class AppListView::StateAnimationMetricsReporter |
| : public ui::AnimationMetricsReporter { |
| public: |
| explicit StateAnimationMetricsReporter(AppListView* view) : view_(view) {} |
| |
| ~StateAnimationMetricsReporter() override = default; |
| |
| void SetTargetState(ash::mojom::AppListViewState target_state) { |
| target_state_ = target_state; |
| } |
| |
| void Start() { |
| #if defined(DCHECK) |
| DCHECK(!started_); |
| started_ = ui::ScopedAnimationDurationScaleMode::duration_scale_mode() != |
| ui::ScopedAnimationDurationScaleMode::ZERO_DURATION; |
| #endif |
| } |
| |
| void Report(int value) override { |
| UMA_HISTOGRAM_PERCENTAGE("Apps.StateTransition.AnimationSmoothness", value); |
| switch (*target_state_) { |
| case ash::mojom::AppListViewState::kClosed: |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.StateTransition.AnimationSmoothness.Close.ClamshellMode", |
| value); |
| break; |
| case ash::mojom::AppListViewState::kPeeking: |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.StateTransition.AnimationSmoothness.Peeking.ClamshellMode", |
| value); |
| break; |
| case ash::mojom::AppListViewState::kHalf: |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.StateTransition.AnimationSmoothness.Half.ClamshellMode", |
| value); |
| break; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.StateTransition.AnimationSmoothness.FullscreenAllApps." |
| "ClamshellMode", |
| value); |
| break; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Apps.StateTransition.AnimationSmoothness.FullscreenSearch." |
| "ClamshellMode", |
| value); |
| break; |
| } |
| target_state_.reset(); |
| view_->OnStateTransitionAnimationCompleted(); |
| #if defined(DCHECK) |
| started_ = false; |
| #endif |
| } |
| |
| private: |
| #if defined(DCHECK) |
| bool started_ = false; |
| #endif |
| base::Optional<ash::mojom::AppListViewState> target_state_; |
| AppListView* view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StateAnimationMetricsReporter); |
| }; |
| |
| // An animation observer to hide the view at the end of the animation. |
| class HideViewAnimationObserver : public ui::ImplicitAnimationObserver { |
| public: |
| HideViewAnimationObserver() : target_(NULL) {} |
| |
| ~HideViewAnimationObserver() override { |
| if (target_) |
| StopObservingImplicitAnimations(); |
| } |
| |
| void SetTarget(views::View* target) { |
| if (target_) |
| StopObservingImplicitAnimations(); |
| target_ = target; |
| } |
| |
| private: |
| // Overridden from ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { |
| if (target_) { |
| target_->SetVisible(false); |
| target_ = NULL; |
| } |
| } |
| |
| views::View* target_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HideViewAnimationObserver); |
| }; |
| |
| // An animation observer to transition between states. |
| class TransitionAnimationObserver : public ui::ImplicitAnimationObserver { |
| public: |
| explicit TransitionAnimationObserver(AppListView* view) : view_(view) {} |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { |
| DCHECK(view_); |
| view_->Layout(); |
| } |
| |
| private: |
| AppListView* const view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TransitionAnimationObserver); |
| }; |
| |
| // The view for the app list background shield which changes color and radius. |
| class AppListBackgroundShieldView : public views::View { |
| public: |
| explicit AppListBackgroundShieldView(ui::LayerType layer_type) |
| : color_(AppListView::kDefaultBackgroundColor), corner_radius_(0) { |
| SetPaintToLayer(layer_type); |
| layer()->SetFillsBoundsOpaquely(false); |
| if (layer()->type() == ui::LAYER_SOLID_COLOR) |
| layer()->SetColor(color_); |
| } |
| |
| ~AppListBackgroundShieldView() override = default; |
| |
| void UpdateColor(SkColor color) { |
| if (color_ == color) |
| return; |
| |
| color_ = color; |
| if (layer()->type() == ui::LAYER_SOLID_COLOR) |
| layer()->SetColor(color); |
| else |
| SchedulePaint(); |
| } |
| |
| void UpdateCornerRadius(int corner_radius) { |
| if (corner_radius_ == corner_radius) |
| return; |
| |
| corner_radius_ = corner_radius; |
| if (!layer()) |
| SchedulePaint(); |
| } |
| |
| // Overridden from views::View: |
| void OnPaint(gfx::Canvas* canvas) override { |
| cc::PaintFlags flags; |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setColor(color_); |
| canvas->DrawRoundRect(GetContentsBounds(), corner_radius_, flags); |
| } |
| |
| SkColor GetColorForTest() const { return color_; } |
| |
| private: |
| SkColor color_; |
| int corner_radius_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AppListBackgroundShieldView); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // AppListView::TestApi |
| |
| AppListView::TestApi::TestApi(AppListView* view) : view_(view) { |
| DCHECK(view_); |
| } |
| |
| AppListView::TestApi::~TestApi() = default; |
| |
| AppsGridView* AppListView::TestApi::GetRootAppsGridView() { |
| return view_->GetRootAppsGridView(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // AppListView: |
| |
| AppListView::AppListView(AppListViewDelegate* delegate) |
| : delegate_(delegate), |
| model_(delegate->GetModel()), |
| search_model_(delegate->GetSearchModel()), |
| is_background_blur_enabled_(app_list_features::IsBackgroundBlurEnabled()), |
| hide_view_animation_observer_( |
| std::make_unique<HideViewAnimationObserver>()), |
| transition_animation_observer_( |
| std::make_unique<TransitionAnimationObserver>(this)), |
| state_animation_metrics_reporter_( |
| std::make_unique<StateAnimationMetricsReporter>(this)), |
| weak_ptr_factory_(this) { |
| CHECK(delegate); |
| } |
| |
| AppListView::~AppListView() { |
| hide_view_animation_observer_.reset(); |
| // Remove child views first to ensure no remaining dependencies on delegate_. |
| RemoveAllChildViews(true); |
| } |
| |
| // static |
| void AppListView::ExcludeWindowFromEventHandling(aura::Window* window) { |
| DCHECK(window); |
| window->SetProperty(kExcludeWindowFromEventHandling, true); |
| } |
| |
| // static |
| void AppListView::SetShortAnimationForTesting(bool enabled) { |
| short_animations_for_testing = enabled; |
| } |
| |
| // static |
| bool AppListView::ShortAnimationsForTesting() { |
| return short_animations_for_testing; |
| } |
| |
| void AppListView::Initialize(const InitParams& params) { |
| base::Time start_time = base::Time::Now(); |
| is_tablet_mode_ = params.is_tablet_mode; |
| is_side_shelf_ = params.is_side_shelf; |
| InitContents(params.initial_apps_page); |
| AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); |
| AddAccelerator(ui::Accelerator(ui::VKEY_BROWSER_BACK, ui::EF_NONE)); |
| parent_window_ = params.parent; |
| |
| InitializeFullscreen(params.parent); |
| |
| InitChildWidgets(); |
| |
| SetState(app_list_state_); |
| |
| // Ensures that the launcher won't open underneath the a11y keyboard |
| CloseKeyboardIfVisible(); |
| |
| // Tablet mode is enabled before the app list is shown, so apply the changes |
| // that should occur upon entering the tablet mode here. |
| if (is_tablet_mode()) |
| OnTabletModeChanged(is_tablet_mode_); |
| |
| UMA_HISTOGRAM_TIMES(kAppListCreationTimeHistogram, |
| base::Time::Now() - start_time); |
| RecordFolderMetrics(); |
| } |
| |
| void AppListView::SetDragAndDropHostOfCurrentAppList( |
| ApplicationDragAndDropHost* drag_and_drop_host) { |
| app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); |
| } |
| |
| void AppListView::ShowWhenReady() { |
| app_list_main_view_->ShowAppListWhenReady(); |
| } |
| |
| void AppListView::Dismiss() { |
| CloseKeyboardIfVisible(); |
| app_list_main_view_->Close(); |
| SetState(ash::mojom::AppListViewState::kClosed); |
| delegate_->DismissAppList(); |
| GetWidget()->Deactivate(); |
| } |
| |
| void AppListView::CloseOpenedPage() { |
| if (HandleCloseOpenFolder()) |
| return; |
| |
| HandleCloseOpenSearchBox(); |
| } |
| |
| bool AppListView::HandleCloseOpenFolder() { |
| if (GetAppsContainerView()->IsInFolderView()) { |
| GetAppsContainerView()->app_list_folder_view()->CloseFolderPage(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool AppListView::HandleCloseOpenSearchBox() { |
| if (app_list_main_view_ && |
| app_list_main_view_->contents_view()->IsShowingSearchResults()) { |
| return Back(); |
| } |
| return false; |
| } |
| |
| bool AppListView::Back() { |
| return app_list_main_view_->contents_view()->Back(); |
| } |
| |
| void AppListView::OnPaint(gfx::Canvas* canvas) { |
| views::WidgetDelegateView::OnPaint(canvas); |
| if (!next_paint_callback_.is_null()) { |
| next_paint_callback_.Run(); |
| next_paint_callback_.Reset(); |
| } |
| } |
| |
| const char* AppListView::GetClassName() const { |
| return "AppListView"; |
| } |
| |
| bool AppListView::CanProcessEventsWithinSubtree() const { |
| if (!delegate_->CanProcessEventsOnApplistViews()) |
| return false; |
| |
| return views::View::CanProcessEventsWithinSubtree(); |
| } |
| |
| bool AppListView::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| switch (accelerator.key_code()) { |
| case ui::VKEY_ESCAPE: |
| case ui::VKEY_BROWSER_BACK: |
| // If the ContentsView does not handle the back action, then this is the |
| // top level, so we close the app list. |
| if (!Back() && !is_tablet_mode()) |
| Dismiss(); |
| break; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| |
| // Don't let DialogClientView handle the accelerator. |
| return true; |
| } |
| |
| void AppListView::Layout() { |
| const gfx::Rect contents_bounds = GetContentsBounds(); |
| |
| // Exclude the shelf height from the contents bounds to avoid apps grid from |
| // overlapping with shelf. |
| gfx::Rect main_bounds = contents_bounds; |
| main_bounds.Inset(0, 0, 0, AppListConfig::instance().shelf_height()); |
| |
| // The AppListMainView's size is supposed to be the same as AppsContainerView. |
| const gfx::Size min_main_size = GetAppsContainerView()->GetMinimumSize(); |
| |
| if ((main_bounds.width() > 0 && main_bounds.height() > 0) && |
| (main_bounds.width() < min_main_size.width() || |
| main_bounds.height() < min_main_size.height())) { |
| // Scale down the AppListMainView if AppsContainerView does not fit in the |
| // display. |
| const float scale = std::min( |
| (main_bounds.width()) / static_cast<float>(min_main_size.width()), |
| main_bounds.height() / static_cast<float>(min_main_size.height())); |
| DCHECK_GT(scale, 0); |
| const gfx::RectF scaled_main_bounds(main_bounds.x(), main_bounds.y(), |
| main_bounds.width() / scale, |
| main_bounds.height() / scale); |
| gfx::Transform transform; |
| transform.Scale(scale, scale); |
| app_list_main_view_->SetTransform(transform); |
| app_list_main_view_->SetBoundsRect(gfx::ToEnclosedRect(scaled_main_bounds)); |
| } else { |
| app_list_main_view_->SetTransform(gfx::Transform()); |
| app_list_main_view_->SetBoundsRect(main_bounds); |
| } |
| |
| gfx::Rect app_list_background_shield_bounds = contents_bounds; |
| // Inset bottom by 2 * |kAppListBackgroundRadius| to account for the rounded |
| // corners on the top and bottom of the |app_list_background_shield_|. |
| // Only add the inset to the bottom to keep padding at the top of the AppList |
| // the same. |
| app_list_background_shield_bounds.Inset(0, 0, 0, |
| -kAppListBackgroundRadius * 2); |
| app_list_background_shield_->SetBoundsRect(app_list_background_shield_bounds); |
| app_list_background_shield_->UpdateCornerRadius(kAppListBackgroundRadius); |
| if (is_background_blur_enabled_ && app_list_background_shield_mask_ && |
| !is_tablet_mode() && |
| app_list_background_shield_->layer()->size() != |
| app_list_background_shield_mask_->layer()->size()) { |
| // Update the blur mask for the |app_list_background_shield_| with same |
| // shape and size if their bounds don't match. |
| app_list_background_shield_mask_->layer()->SetBounds( |
| app_list_background_shield_bounds); |
| } |
| |
| UpdateAppListBackgroundYPosition(); |
| } |
| |
| ax::mojom::Role AppListView::GetAccessibleWindowRole() { |
| // Default role of root view is ax::mojom::Role::kWindow which traps ChromeVox |
| // focus within the root view. Assign ax::mojom::Role::kGroup here to allow |
| // the focus to move from elements in app list view to search box. |
| return ax::mojom::Role::kGroup; |
| } |
| |
| class AppListView::FullscreenWidgetObserver : views::WidgetObserver { |
| public: |
| explicit FullscreenWidgetObserver(app_list::AppListView* view) |
| : widget_observer_(this) { |
| view_ = view; |
| widget_observer_.Add(view_->GetWidget()); |
| } |
| ~FullscreenWidgetObserver() override {} |
| |
| // Overridden from WidgetObserver: |
| void OnWidgetClosing(views::Widget* widget) override { |
| if (view_->app_list_state() != ash::mojom::AppListViewState::kClosed) |
| view_->SetState(ash::mojom::AppListViewState::kClosed); |
| widget_observer_.Remove(view_->GetWidget()); |
| } |
| |
| private: |
| app_list::AppListView* view_; |
| ScopedObserver<views::Widget, WidgetObserver> widget_observer_; |
| DISALLOW_COPY_AND_ASSIGN(FullscreenWidgetObserver); |
| }; |
| |
| views::View* AppListView::GetAppListBackgroundShieldForTest() { |
| return app_list_background_shield_; |
| } |
| |
| SkColor AppListView::GetAppListBackgroundShieldColorForTest() { |
| DCHECK(app_list_background_shield_); |
| return app_list_background_shield_->GetColorForTest(); |
| } |
| |
| void AppListView::InitContents(int initial_apps_page) { |
| // The shield view that colors/blurs the background of the app list and |
| // makes it transparent. |
| bool use_background_blur = is_background_blur_enabled_ && !is_tablet_mode(); |
| app_list_background_shield_ = new AppListBackgroundShieldView( |
| use_background_blur ? ui::LAYER_SOLID_COLOR : ui::LAYER_TEXTURED); |
| SetBackgroundShieldColor(); |
| if (use_background_blur) { |
| if (ash::features::ShouldUseShaderRoundedCorner()) { |
| app_list_background_shield_->layer()->SetRoundedCornerRadius( |
| {kAppListBackgroundRadius, kAppListBackgroundRadius, 0, 0}); |
| } else { |
| app_list_background_shield_mask_ = views::Painter::CreatePaintedLayer( |
| views::Painter::CreateSolidRoundRectPainter( |
| SK_ColorBLACK, kAppListBackgroundRadius)); |
| app_list_background_shield_mask_->layer()->SetFillsBoundsOpaquely(false); |
| app_list_background_shield_->layer()->SetMaskLayer( |
| app_list_background_shield_mask_->layer()); |
| } |
| app_list_background_shield_->layer()->SetBackgroundBlur( |
| AppListConfig::instance().blur_radius()); |
| app_list_background_shield_->layer()->SetBackdropFilterQuality( |
| kAppListBlurQuality); |
| } |
| AddChildView(app_list_background_shield_); |
| app_list_main_view_ = new AppListMainView(delegate_, this); |
| AddChildView(app_list_main_view_); |
| // This will be added to the |search_box_widget_| after the app list widget is |
| // initialized. |
| search_box_view_ = new SearchBoxView(app_list_main_view_, delegate_, this); |
| search_box_view_->Init(); |
| |
| app_list_main_view_->Init(0, search_box_view_); |
| |
| announcement_view_ = new views::View(); |
| AddChildView(announcement_view_); |
| } |
| |
| void AppListView::InitChildWidgets() { |
| DCHECK(search_box_view_); |
| |
| // Create the search box widget. |
| views::Widget::InitParams search_box_widget_params( |
| views::Widget::InitParams::TYPE_CONTROL); |
| search_box_widget_params.parent = GetWidget()->GetNativeView(); |
| search_box_widget_params.opacity = |
| views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| search_box_widget_params.name = "SearchBoxView"; |
| search_box_widget_params.delegate = search_box_view_; |
| |
| // Create a widget for the SearchBoxView to live in. This allows the |
| // SearchBoxView to be on top of the custom launcher page's WebContents |
| // (otherwise the search box events will be captured by the WebContents). |
| search_box_widget_ = new views::Widget; |
| search_box_widget_->Init(search_box_widget_params); |
| |
| // Assign an accessibility role to the native window of search box widget, so |
| // that hitting search+right could move ChromeVox focus across search box to |
| // other elements in app list view. |
| search_box_widget_->GetNativeWindow()->SetProperty( |
| ui::kAXRoleOverride, |
| static_cast<ax::mojom::Role>(ax::mojom::Role::kGroup)); |
| |
| // The search box will not naturally receive focus by itself (because it is in |
| // a separate widget). Create this SearchBoxFocusHost in the main widget to |
| // forward the focus search into to the search box. |
| search_box_focus_host_ = new SearchBoxFocusHost(search_box_widget_); |
| AddChildView(search_box_focus_host_); |
| search_box_widget_->SetFocusTraversableParentView(search_box_focus_host_); |
| search_box_widget_->SetFocusTraversableParent( |
| GetWidget()->GetFocusTraversable()); |
| |
| app_list_main_view_->contents_view()->Layout(); |
| } |
| |
| void AppListView::InitializeFullscreen(gfx::NativeView parent) { |
| fullscreen_widget_ = new views::Widget; |
| views::Widget::InitParams app_list_overlay_view_params( |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| |
| app_list_overlay_view_params.name = "AppList"; |
| app_list_overlay_view_params.parent = parent; |
| app_list_overlay_view_params.delegate = this; |
| app_list_overlay_view_params.opacity = |
| views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| app_list_overlay_view_params.layer_type = ui::LAYER_NOT_DRAWN; |
| fullscreen_widget_->Init(app_list_overlay_view_params); |
| fullscreen_widget_->GetNativeWindow()->SetEventTargeter( |
| std::make_unique<AppListEventTargeter>()); |
| |
| // The widget's initial position will be off the bottom of the display. |
| // Set native view's bounds directly to avoid screen position controller |
| // setting bounds in the display where the widget has the largest |
| // intersection. |
| // TODO(mash): Redesign this animation to position the widget to cover the |
| // entire screen, then animate the layer up into position. crbug.com/768437 |
| // The initial bounds of app list should be the same as that in closed state. |
| fullscreen_widget_->GetNativeView()->SetBounds( |
| GetPreferredWidgetBoundsForState(ash::mojom::AppListViewState::kClosed)); |
| |
| // Enable arrow key in FocusManager. Arrow left/right and up/down triggers |
| // the same focus movement as tab/shift+tab. |
| fullscreen_widget_->GetFocusManager() |
| ->set_arrow_key_traversal_enabled_for_widget(true); |
| |
| widget_observer_ = std::make_unique<FullscreenWidgetObserver>(this); |
| fullscreen_widget_->GetNativeView()->AddObserver(this); |
| } |
| |
| void AppListView::HandleClickOrTap(ui::LocatedEvent* event) { |
| // If the virtual keyboard is visible, dismiss the keyboard and return early. |
| if (CloseKeyboardIfVisible()) { |
| search_box_view_->NotifyGestureEvent(); |
| return; |
| } |
| |
| // Clear focus if the located event is not handled by any child view. |
| GetFocusManager()->ClearFocus(); |
| |
| if (GetAppsContainerView()->IsInFolderView()) { |
| // Close the folder if it is opened. |
| GetAppsContainerView()->app_list_folder_view()->CloseFolderPage(); |
| return; |
| } |
| |
| if ((event->IsGestureEvent() && |
| (event->AsGestureEvent()->type() == ui::ET_GESTURE_LONG_PRESS || |
| event->AsGestureEvent()->type() == ui::ET_GESTURE_LONG_TAP || |
| event->AsGestureEvent()->type() == ui::ET_GESTURE_TWO_FINGER_TAP)) || |
| (event->IsMouseEvent() && |
| event->AsMouseEvent()->IsOnlyRightMouseButton())) { |
| // Don't show menus on empty areas of the AppListView in clamshell mode. |
| if (!is_tablet_mode()) |
| return; |
| |
| // Home launcher is shown on top of wallpaper with trasparent background. So |
| // trigger the wallpaper context menu for the same events. |
| gfx::Point onscreen_location(event->location()); |
| ConvertPointToScreen(this, &onscreen_location); |
| delegate_->ShowWallpaperContextMenu( |
| onscreen_location, event->IsGestureEvent() ? ui::MENU_SOURCE_TOUCH |
| : ui::MENU_SOURCE_MOUSE); |
| return; |
| } |
| |
| if (!search_box_view_->is_search_box_active() && |
| model_->state() != ash::AppListState::kStateEmbeddedAssistant) { |
| if (!is_tablet_mode()) |
| Dismiss(); |
| return; |
| } |
| |
| // Reset the AppListState if the embedded Assistant UI is shown. |
| if (app_list_main_view()->contents_view()->IsShowingEmbeddedAssistantUI()) |
| Back(); |
| |
| search_box_view_->ClearSearchAndDeactivateSearchBox(); |
| } |
| |
| void AppListView::StartDrag(const gfx::Point& location) { |
| // Convert drag point from widget coordinates to screen coordinates because |
| // the widget bounds changes during the dragging. |
| initial_drag_point_ = location; |
| ConvertPointToScreen(this, &initial_drag_point_); |
| initial_window_bounds_ = fullscreen_widget_->GetWindowBoundsInScreen(); |
| } |
| |
| void AppListView::UpdateDrag(const gfx::Point& location) { |
| // Update the widget bounds based on the initial widget bounds and drag delta. |
| gfx::Point location_in_screen_coordinates = location; |
| ConvertPointToScreen(this, &location_in_screen_coordinates); |
| int new_y_position = location_in_screen_coordinates.y() - |
| initial_drag_point_.y() + initial_window_bounds_.y(); |
| |
| UpdateYPositionAndOpacity(new_y_position, |
| GetAppListBackgroundOpacityDuringDragging()); |
| } |
| |
| void AppListView::EndDrag(const gfx::Point& location) { |
| // When the SearchBoxView closes the app list, ignore the final event. |
| if (app_list_state_ == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| // Change the app list state based on where the drag ended. If fling velocity |
| // was over the threshold, snap to the next state in the direction of the |
| // fling. |
| if (std::abs(last_fling_velocity_) >= kDragVelocityThreshold) { |
| // If the user releases drag with velocity over the threshold, snap to |
| // the next state, ignoring the drag release position. |
| |
| if (last_fling_velocity_ > 0) { |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kPeeking: |
| case ash::mojom::AppListViewState::kHalf: |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| Dismiss(); |
| break; |
| case ash::mojom::AppListViewState::kClosed: |
| NOTREACHED(); |
| break; |
| } |
| } else { |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| SetState(app_list_state_); |
| break; |
| case ash::mojom::AppListViewState::kHalf: |
| SetState(ash::mojom::AppListViewState::kFullscreenSearch); |
| break; |
| case ash::mojom::AppListViewState::kPeeking: |
| UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, |
| kSwipe, kMaxPeekingToFullscreen); |
| SetState(ash::mojom::AppListViewState::kFullscreenAllApps); |
| break; |
| case ash::mojom::AppListViewState::kClosed: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } else { |
| const int fullscreen_height = GetFullscreenStateHeight(); |
| int app_list_height = 0; |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| app_list_height = fullscreen_height; |
| break; |
| case ash::mojom::AppListViewState::kHalf: |
| app_list_height = std::min(fullscreen_height, kHalfAppListHeight); |
| break; |
| case ash::mojom::AppListViewState::kPeeking: |
| app_list_height = AppListConfig::instance().peeking_app_list_height(); |
| break; |
| case ash::mojom::AppListViewState::kClosed: |
| NOTREACHED(); |
| break; |
| } |
| |
| const int app_list_threshold = |
| app_list_height / kAppListThresholdDenominator; |
| gfx::Point location_in_screen_coordinates = location; |
| ConvertPointToScreen(this, &location_in_screen_coordinates); |
| const int drag_delta = |
| initial_drag_point_.y() - location_in_screen_coordinates.y(); |
| const int location_y_in_current_work_area = |
| location_in_screen_coordinates.y() - |
| GetDisplayNearestView().work_area().y(); |
| // If the drag ended near the bezel, close the app list and return early. |
| if (location_y_in_current_work_area >= |
| (fullscreen_height - kAppListBezelMargin)) { |
| Dismiss(); |
| return; |
| } |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| if (drag_delta < -app_list_threshold) { |
| if (is_tablet_mode_ || is_side_shelf_) |
| Dismiss(); |
| else |
| SetState(ash::mojom::AppListViewState::kPeeking); |
| } else { |
| SetState(app_list_state_); |
| } |
| break; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| if (drag_delta < -app_list_threshold) |
| Dismiss(); |
| else |
| SetState(app_list_state_); |
| break; |
| case ash::mojom::AppListViewState::kHalf: |
| if (drag_delta > app_list_threshold) |
| SetState(ash::mojom::AppListViewState::kFullscreenSearch); |
| else if (drag_delta < -app_list_threshold) |
| Dismiss(); |
| else |
| SetState(app_list_state_); |
| break; |
| case ash::mojom::AppListViewState::kPeeking: |
| if (drag_delta > app_list_threshold) { |
| SetState(ash::mojom::AppListViewState::kFullscreenAllApps); |
| UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, |
| kSwipe, kMaxPeekingToFullscreen); |
| } else if (drag_delta < -app_list_threshold) { |
| Dismiss(); |
| } else { |
| SetState(app_list_state_); |
| } |
| break; |
| case ash::mojom::AppListViewState::kClosed: |
| NOTREACHED(); |
| break; |
| } |
| } |
| UpdateChildViewsYPositionAndOpacity(); |
| initial_drag_point_ = gfx::Point(); |
| } |
| |
| void AppListView::SetChildViewsForStateTransition( |
| ash::mojom::AppListViewState target_state) { |
| if (target_state != ash::mojom::AppListViewState::kPeeking && |
| target_state != ash::mojom::AppListViewState::kFullscreenAllApps && |
| target_state != ash::mojom::AppListViewState::kHalf && |
| target_state != ash::mojom::AppListViewState::kClosed) { |
| return; |
| } |
| |
| app_list_main_view_->contents_view()->OnAppListViewTargetStateChanged( |
| target_state); |
| |
| if (target_state == ash::mojom::AppListViewState::kHalf || |
| target_state == ash::mojom::AppListViewState::kClosed) { |
| return; |
| } |
| |
| if (GetAppsContainerView()->IsInFolderView()) |
| GetAppsContainerView()->ResetForShowApps(); |
| |
| app_list_main_view_->contents_view()->SetActiveState( |
| ash::AppListState::kStateApps, !is_side_shelf_); |
| |
| if (target_state == ash::mojom::AppListViewState::kPeeking) { |
| // Set the apps to the initial page when PEEKING. |
| PaginationModel* pagination_model = GetAppsPaginationModel(); |
| if (pagination_model->total_pages() > 0 && |
| pagination_model->selected_page() != 0) { |
| pagination_model->SelectPage(0, false /* animate */); |
| } |
| } |
| } |
| |
| void AppListView::ConvertAppListStateToFullscreenEquivalent( |
| ash::mojom::AppListViewState* target_state) { |
| if (!(is_side_shelf_ || is_tablet_mode_)) |
| return; |
| |
| // If side shelf or tablet mode are active, all transitions should be |
| // made to the tablet mode/side shelf friendly versions. |
| if (*target_state == ash::mojom::AppListViewState::kHalf) { |
| *target_state = ash::mojom::AppListViewState::kFullscreenSearch; |
| } else if (*target_state == ash::mojom::AppListViewState::kPeeking) { |
| // FULLSCREEN_ALL_APPS->PEEKING in tablet/side shelf mode should close |
| // instead of going to PEEKING. |
| *target_state = |
| app_list_state_ == ash::mojom::AppListViewState::kFullscreenAllApps |
| ? ash::mojom::AppListViewState::kClosed |
| : ash::mojom::AppListViewState::kFullscreenAllApps; |
| } |
| } |
| |
| void AppListView::RecordStateTransitionForUma( |
| ash::mojom::AppListViewState new_state) { |
| AppListStateTransitionSource transition = |
| GetAppListStateTransitionSource(new_state); |
| // kMaxAppListStateTransition denotes a transition we are not interested in |
| // recording (ie. PEEKING->PEEKING). |
| if (transition == kMaxAppListStateTransition) |
| return; |
| |
| UMA_HISTOGRAM_ENUMERATION(kAppListStateTransitionSourceHistogram, transition, |
| kMaxAppListStateTransition); |
| |
| switch (transition) { |
| case kPeekingToFullscreenAllApps: |
| case KHalfToFullscreenSearch: |
| base::RecordAction(base::UserMetricsAction("AppList_PeekingToFull")); |
| break; |
| |
| case kFullscreenAllAppsToPeeking: |
| base::RecordAction(base::UserMetricsAction("AppList_FullToPeeking")); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| void AppListView::MaybeCreateAccessibilityEvent( |
| ash::mojom::AppListViewState new_state) { |
| if (new_state != ash::mojom::AppListViewState::kPeeking && |
| new_state != ash::mojom::AppListViewState::kFullscreenAllApps) |
| return; |
| |
| base::string16 state_announcement; |
| |
| if (new_state == ash::mojom::AppListViewState::kPeeking) { |
| state_announcement = l10n_util::GetStringUTF16( |
| IDS_APP_LIST_SUGGESTED_APPS_ACCESSIBILITY_ANNOUNCEMENT); |
| } else { |
| state_announcement = l10n_util::GetStringUTF16( |
| IDS_APP_LIST_ALL_APPS_ACCESSIBILITY_ANNOUNCEMENT); |
| } |
| announcement_view_->GetViewAccessibility().OverrideName(state_announcement); |
| announcement_view_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true); |
| } |
| |
| display::Display AppListView::GetDisplayNearestView() const { |
| return display::Screen::GetScreen()->GetDisplayNearestView(parent_window_); |
| } |
| |
| AppsContainerView* AppListView::GetAppsContainerView() { |
| return app_list_main_view_->contents_view()->GetAppsContainerView(); |
| } |
| |
| AppsGridView* AppListView::GetRootAppsGridView() { |
| return GetAppsContainerView()->apps_grid_view(); |
| } |
| |
| AppsGridView* AppListView::GetFolderAppsGridView() { |
| return GetAppsContainerView()->app_list_folder_view()->items_grid_view(); |
| } |
| |
| AppListStateTransitionSource AppListView::GetAppListStateTransitionSource( |
| ash::mojom::AppListViewState target_state) const { |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kClosed: |
| // CLOSED->X transitions are not useful for UMA. |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kPeeking: |
| switch (target_state) { |
| case ash::mojom::AppListViewState::kClosed: |
| return kPeekingToClosed; |
| case ash::mojom::AppListViewState::kHalf: |
| return kPeekingToHalf; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| return kPeekingToFullscreenAllApps; |
| case ash::mojom::AppListViewState::kPeeking: |
| // PEEKING->PEEKING is used when resetting the widget position after a |
| // failed state transition. Not useful for UMA. |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| // PEEKING->FULLSCREEN_SEARCH is not a valid transition. |
| NOTREACHED(); |
| return kMaxAppListStateTransition; |
| } |
| case ash::mojom::AppListViewState::kHalf: |
| switch (target_state) { |
| case ash::mojom::AppListViewState::kClosed: |
| return kHalfToClosed; |
| case ash::mojom::AppListViewState::kPeeking: |
| return kHalfToPeeking; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| return KHalfToFullscreenSearch; |
| case ash::mojom::AppListViewState::kHalf: |
| // HALF->HALF is used when resetting the widget position after a |
| // failed state transition. Not useful for UMA. |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| // HALF->FULLSCREEN_ALL_APPS is not a valid transition. |
| NOTREACHED(); |
| return kMaxAppListStateTransition; |
| } |
| |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| switch (target_state) { |
| case ash::mojom::AppListViewState::kClosed: |
| return kFullscreenAllAppsToClosed; |
| case ash::mojom::AppListViewState::kPeeking: |
| return kFullscreenAllAppsToPeeking; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| return kFullscreenAllAppsToFullscreenSearch; |
| case ash::mojom::AppListViewState::kHalf: |
| // FULLSCREEN_ALL_APPS->HALF is not a valid transition. |
| NOTREACHED(); |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| // FULLSCREEN_ALL_APPS->FULLSCREEN_ALL_APPS is used when resetting the |
| // widget positon after a failed state transition. Not useful for UMA. |
| return kMaxAppListStateTransition; |
| } |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| switch (target_state) { |
| case ash::mojom::AppListViewState::kClosed: |
| return kFullscreenSearchToClosed; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| return kFullscreenSearchToFullscreenAllApps; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| // FULLSCREEN_SEARCH->FULLSCREEN_SEARCH is used when resetting the |
| // widget position after a failed state transition. Not useful for |
| // UMA. |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kPeeking: |
| // FULLSCREEN_SEARCH->PEEKING is not a valid transition. |
| NOTREACHED(); |
| return kMaxAppListStateTransition; |
| case ash::mojom::AppListViewState::kHalf: |
| // FULLSCREEN_SEARCH->HALF is not a valid transition. |
| NOTREACHED(); |
| return kMaxAppListStateTransition; |
| } |
| } |
| } |
| |
| views::View* AppListView::GetInitiallyFocusedView() { |
| return app_list_main_view_->search_box_view()->search_box(); |
| } |
| |
| void AppListView::OnScrollEvent(ui::ScrollEvent* event) { |
| if (!HandleScroll(gfx::Vector2d(event->x_offset(), event->y_offset()), |
| event->type())) { |
| return; |
| } |
| |
| event->SetHandled(); |
| event->StopPropagation(); |
| } |
| |
| void AppListView::OnMouseEvent(ui::MouseEvent* event) { |
| switch (event->type()) { |
| case ui::ET_MOUSE_PRESSED: |
| event->SetHandled(); |
| HandleClickOrTap(event); |
| break; |
| case ui::ET_MOUSEWHEEL: |
| if (HandleScroll(event->AsMouseWheelEvent()->offset(), ui::ET_MOUSEWHEEL)) |
| event->SetHandled(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void AppListView::OnGestureEvent(ui::GestureEvent* event) { |
| switch (event->type()) { |
| case ui::ET_GESTURE_TAP: |
| case ui::ET_GESTURE_LONG_PRESS: |
| case ui::ET_GESTURE_LONG_TAP: |
| case ui::ET_GESTURE_TWO_FINGER_TAP: |
| SetIsInDrag(false); |
| event->SetHandled(); |
| HandleClickOrTap(event); |
| break; |
| case ui::ET_SCROLL_FLING_START: |
| case ui::ET_GESTURE_SCROLL_BEGIN: { |
| // If the search box is active when we start our drag, let it know. |
| if (search_box_view_->is_search_box_active()) |
| search_box_view_->NotifyGestureEvent(); |
| |
| if (event->location().y() < kAppListHomeLaucherGesturesThreshold) { |
| if (delegate_->ProcessHomeLauncherGesture(event, gfx::Point())) { |
| SetIsInDrag(false); |
| event->SetHandled(); |
| HandleClickOrTap(event); |
| return; |
| } |
| } |
| |
| // Avoid scrolling events for the app list in tablet mode. |
| if (is_side_shelf_ || is_tablet_mode()) |
| return; |
| // There may be multiple scroll begin events in one drag because the |
| // relative location of the finger and widget is almost unchanged and |
| // scroll begin event occurs when the relative location changes beyond a |
| // threshold. So avoid resetting the initial drag point in drag. |
| if (!is_in_drag_) |
| StartDrag(event->location()); |
| SetIsInDrag(true); |
| event->SetHandled(); |
| break; |
| } |
| case ui::ET_GESTURE_SCROLL_UPDATE: { |
| gfx::Point location_in_screen = event->location(); |
| views::View::ConvertPointToScreen(this, &location_in_screen); |
| if (delegate_->ProcessHomeLauncherGesture(event, location_in_screen)) { |
| SetIsInDrag(true); |
| event->SetHandled(); |
| return; |
| } |
| |
| // Avoid scrolling events for the app list in tablet mode. |
| if (is_side_shelf_ || is_tablet_mode()) |
| return; |
| SetIsInDrag(true); |
| last_fling_velocity_ = event->details().scroll_y(); |
| UpdateDrag(event->location()); |
| event->SetHandled(); |
| break; |
| } |
| case ui::ET_GESTURE_END: { |
| gfx::Point location_in_screen = event->location(); |
| views::View::ConvertPointToScreen(this, &location_in_screen); |
| if (delegate_->ProcessHomeLauncherGesture(event, location_in_screen)) { |
| SetIsInDrag(false); |
| event->SetHandled(); |
| return; |
| } |
| |
| if (!is_in_drag_) |
| break; |
| // Avoid scrolling events for the app list in tablet mode. |
| if (is_side_shelf_ || is_tablet_mode()) |
| return; |
| SetIsInDrag(false); |
| EndDrag(event->location()); |
| event->SetHandled(); |
| break; |
| } |
| case ui::ET_MOUSEWHEEL: { |
| if (HandleScroll(event->AsMouseWheelEvent()->offset(), ui::ET_MOUSEWHEEL)) |
| event->SetHandled(); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void AppListView::OnKeyEvent(ui::KeyEvent* event) { |
| RedirectKeyEventToSearchBox(event); |
| } |
| |
| void AppListView::OnTabletModeChanged(bool started) { |
| is_tablet_mode_ = started; |
| |
| // Bottom shelf is enforced in tablet mode. When tablet mode ends, the |
| // AppListView is destroyed so no need to update |is_side_shelf_|. |
| if (started) |
| is_side_shelf_ = false; |
| |
| search_box_view_->OnTabletModeChanged(started); |
| search_model_->SetTabletMode(started); |
| GetAppsContainerView()->OnTabletModeChanged(started); |
| |
| if (!started) { |
| Dismiss(); |
| return; |
| } |
| |
| if (is_in_drag_) { |
| SetIsInDrag(false); |
| UpdateChildViewsYPositionAndOpacity(); |
| } |
| |
| // Set fullscreen state. When current state is fullscreen, we still need to |
| // set it again because app list may be in dragging. |
| SetState(app_list_state_ == ash::mojom::AppListViewState::kHalf || |
| app_list_state_ == |
| ash::mojom::AppListViewState::kFullscreenSearch |
| ? ash::mojom::AppListViewState::kFullscreenSearch |
| : ash::mojom::AppListViewState::kFullscreenAllApps); |
| |
| // In tablet mode, AppListView should not be moved because of the change in |
| // virtual keyboard's visibility. |
| if (started) { |
| fullscreen_widget_->GetNativeView()->ClearProperty( |
| wm::kVirtualKeyboardRestoreBoundsKey); |
| } |
| |
| // Update background color opacity. |
| SetBackgroundShieldColor(); |
| |
| // Update background blur. |
| if (is_background_blur_enabled_) |
| app_list_background_shield_->layer()->SetBackgroundBlur(0); |
| } |
| |
| void AppListView::OnWallpaperColorsChanged() { |
| SetBackgroundShieldColor(); |
| search_box_view_->OnWallpaperColorsChanged(); |
| } |
| |
| bool AppListView::HandleScroll(const gfx::Vector2d& offset, |
| ui::EventType type) { |
| // Ignore 0-offset events to prevent spurious dismissal, see crbug.com/806338 |
| // The system generates 0-offset ET_SCROLL_FLING_CANCEL events during simple |
| // touchpad mouse moves. Those may be passed via mojo APIs and handled here. |
| if ((offset.y() == 0 && offset.x() == 0) || is_in_drag() || |
| ShouldIgnoreScrollEvents()) { |
| return false; |
| } |
| |
| if (app_list_state_ != ash::mojom::AppListViewState::kPeeking && |
| app_list_state_ != ash::mojom::AppListViewState::kFullscreenAllApps) { |
| return false; |
| } |
| |
| // Let the Apps grid view handle the event first in FULLSCREEN_ALL_APPS. |
| if (app_list_state_ == ash::mojom::AppListViewState::kFullscreenAllApps) { |
| AppsGridView* apps_grid_view = GetAppsContainerView()->IsInFolderView() |
| ? GetFolderAppsGridView() |
| : GetRootAppsGridView(); |
| if (apps_grid_view->HandleScrollFromAppListView(offset, type)) |
| return true; |
| } |
| |
| // The AppList should not be dismissed with scroll in tablet mode. |
| if (is_tablet_mode()) |
| return true; |
| |
| // If the event is a mousewheel event, the offset is always large enough, |
| // otherwise the offset must be larger than the scroll threshold. |
| if (type == ui::ET_MOUSEWHEEL || |
| abs(offset.y()) > kAppListMinScrollToSwitchStates) { |
| if (app_list_state_ == ash::mojom::AppListViewState::kFullscreenAllApps) { |
| if (offset.y() > 0) |
| Dismiss(); |
| return true; |
| } |
| |
| SetState(ash::mojom::AppListViewState::kFullscreenAllApps); |
| const AppListPeekingToFullscreenSource source = |
| type == ui::ET_MOUSEWHEEL ? kMousewheelScroll : kMousepadScroll; |
| UMA_HISTOGRAM_ENUMERATION(kAppListPeekingToFullscreenHistogram, source, |
| kMaxPeekingToFullscreen); |
| } |
| return true; |
| } |
| |
| void AppListView::SetState(ash::mojom::AppListViewState new_state) { |
| // Do not allow the state to be changed once it has been set to CLOSED. |
| if (app_list_state_ == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| ash::mojom::AppListViewState new_state_override = new_state; |
| ConvertAppListStateToFullscreenEquivalent(&new_state_override); |
| MaybeCreateAccessibilityEvent(new_state_override); |
| SetChildViewsForStateTransition(new_state_override); |
| StartAnimationForState(new_state_override); |
| RecordStateTransitionForUma(new_state_override); |
| model_->SetStateFullscreen(new_state_override); |
| app_list_state_ = new_state_override; |
| |
| // Animations are skipped for side shelf mode, so trigger a layout to update |
| // children immediately. |
| if (is_side_shelf_) |
| Layout(); |
| |
| if (new_state_override == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| if (fullscreen_widget_->IsActive()) { |
| // Reset the focus to initially focused view. This should be |
| // done before updating visibility of views, because setting |
| // focused view invisible automatically moves focus to next |
| // focusable view, which potentially causes bugs. |
| GetInitiallyFocusedView()->RequestFocus(); |
| } |
| |
| // Updates the visibility of app list items according to the change of |
| // |app_list_state_|. |
| GetAppsContainerView()->UpdateControlVisibility(app_list_state_, is_in_drag_); |
| } |
| |
| void AppListView::StartAnimationForState( |
| ash::mojom::AppListViewState target_state) { |
| if (is_side_shelf_) |
| return; |
| |
| // The close animation is handled by the delegate. |
| if (target_state == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| const display::Display display = GetDisplayNearestView(); |
| const int target_state_y = GetPreferredWidgetYForState(target_state); |
| gfx::Rect target_bounds = fullscreen_widget_->GetNativeView()->bounds(); |
| const int original_state_y = target_bounds.origin().y(); |
| target_bounds.set_y(target_state_y); |
| |
| int animation_duration; |
| // If animating to or from a fullscreen state, animate over 250ms, else |
| // animate over 200 ms. |
| if (ShortAnimationsForTesting()) { |
| animation_duration = kAppListAnimationDurationTestMs; |
| } else if (is_fullscreen() || |
| target_state == ash::mojom::AppListViewState::kFullscreenAllApps || |
| target_state == ash::mojom::AppListViewState::kFullscreenSearch) { |
| animation_duration = kAppListAnimationDurationFromFullscreenMs; |
| } else { |
| animation_duration = kAppListAnimationDurationMs; |
| } |
| |
| if (fullscreen_widget_->GetNativeView()->bounds().y() == |
| display.work_area().bottom()) { |
| // If the animation start position is the bottom of the screen, activate the |
| // fade in animation. This prevents the search box from flashing at the |
| // bottom of the screen as it goes behind the shelf. |
| app_list_main_view_->contents_view()->FadeInOnOpen( |
| base::TimeDelta::FromMilliseconds(animation_duration)); |
| } |
| |
| ui::Layer* layer = fullscreen_widget_->GetLayer(); |
| layer->SetBounds(target_bounds); |
| gfx::Transform transform; |
| transform.Translate(0, original_state_y - target_state_y); |
| layer->SetTransform(transform); |
| |
| ui::LayerAnimator* animator = layer->GetAnimator(); |
| animator->StopAnimating(); |
| ui::ScopedLayerAnimationSettings settings(animator); |
| settings.SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(animation_duration)); |
| settings.SetTweenType(gfx::Tween::EASE_OUT); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| state_animation_metrics_reporter_->SetTargetState(target_state); |
| settings.SetAnimationMetricsReporter(state_animation_metrics_reporter_.get()); |
| settings.AddObserver(transition_animation_observer_.get()); |
| |
| layer->SetTransform(gfx::Transform()); |
| |
| // In transition animation, layout is only performed after it is complete, |
| // which makes the child views jump. So update y positions in advance here to |
| // avoid that. |
| app_list_main_view_->contents_view()->UpdateYPositionAndOpacity(); |
| } |
| |
| void AppListView::StartCloseAnimation(base::TimeDelta animation_duration) { |
| if (is_side_shelf_) |
| return; |
| |
| // If animating from PEEKING, animate the opacity twice as fast so the |
| // SearchBoxView does not flash behind the shelf. |
| if (app_list_state_ == ash::mojom::AppListViewState::kPeeking || |
| app_list_state_ == ash::mojom::AppListViewState::kClosed) { |
| animation_duration /= 2; |
| } |
| |
| state_animation_metrics_reporter_->SetTargetState( |
| ash::mojom::AppListViewState::kClosed); |
| SetState(ash::mojom::AppListViewState::kClosed); |
| app_list_main_view_->contents_view()->FadeOutOnClose(animation_duration); |
| } |
| |
| void AppListView::SetStateFromSearchBoxView(bool search_box_is_empty, |
| bool triggered_by_contents_change) { |
| switch (app_list_state_) { |
| case ash::mojom::AppListViewState::kPeeking: |
| if (app_list_features::IsZeroStateSuggestionsEnabled()) { |
| if (!search_box_is_empty || search_box_view()->is_search_box_active()) |
| SetState(ash::mojom::AppListViewState::kHalf); |
| } else { |
| if (!search_box_is_empty) |
| SetState(ash::mojom::AppListViewState::kHalf); |
| } |
| break; |
| case ash::mojom::AppListViewState::kHalf: |
| if (app_list_features::IsZeroStateSuggestionsEnabled()) { |
| if (search_box_is_empty && !triggered_by_contents_change) |
| SetState(ash::mojom::AppListViewState::kPeeking); |
| } else { |
| if (search_box_is_empty) |
| SetState(ash::mojom::AppListViewState::kPeeking); |
| } |
| break; |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| if (app_list_features::IsZeroStateSuggestionsEnabled()) { |
| if (search_box_is_empty && !triggered_by_contents_change) { |
| SetState(ash::mojom::AppListViewState::kFullscreenAllApps); |
| app_list_main_view()->contents_view()->SetActiveState( |
| ash::AppListState::kStateApps); |
| } |
| } else { |
| if (search_box_is_empty) { |
| SetState(ash::mojom::AppListViewState::kFullscreenAllApps); |
| app_list_main_view()->contents_view()->SetActiveState( |
| ash::AppListState::kStateApps); |
| } |
| } |
| break; |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| if (app_list_features::IsZeroStateSuggestionsEnabled()) { |
| if (!search_box_is_empty || |
| (search_box_is_empty && triggered_by_contents_change)) |
| SetState(ash::mojom::AppListViewState::kFullscreenSearch); |
| } else { |
| if (!search_box_is_empty) |
| SetState(ash::mojom::AppListViewState::kFullscreenSearch); |
| } |
| break; |
| case ash::mojom::AppListViewState::kClosed: |
| // We clean search on app list close. |
| break; |
| } |
| } |
| |
| void AppListView::UpdateYPositionAndOpacity(int y_position_in_screen, |
| float background_opacity) { |
| DCHECK(!is_side_shelf_); |
| if (app_list_state_ == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| if (fullscreen_widget_->GetLayer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::TRANSFORM)) { |
| fullscreen_widget_->GetLayer()->GetAnimator()->StopAnimatingProperty( |
| ui::LayerAnimationElement::TRANSFORM); |
| } |
| |
| SetIsInDrag(true); |
| |
| presentation_time_recorder_->RequestNext(); |
| |
| background_opacity_in_drag_ = background_opacity; |
| gfx::Rect new_widget_bounds = fullscreen_widget_->GetWindowBoundsInScreen(); |
| app_list_y_position_in_screen_ = std::min( |
| std::max(y_position_in_screen, GetDisplayNearestView().work_area().y()), |
| GetScreenBottom() - AppListConfig::instance().shelf_height()); |
| new_widget_bounds.set_y(app_list_y_position_in_screen_); |
| gfx::NativeView native_view = fullscreen_widget_->GetNativeView(); |
| ::wm::ConvertRectFromScreen(native_view->parent(), &new_widget_bounds); |
| native_view->SetBounds(new_widget_bounds); |
| UpdateChildViewsYPositionAndOpacity(); |
| } |
| |
| void AppListView::OffsetYPositionOfAppList(int offset) { |
| gfx::NativeView native_view = fullscreen_widget_->GetNativeView(); |
| gfx::Transform transform; |
| transform.Translate(0, offset); |
| native_view->SetTransform(transform); |
| } |
| |
| PaginationModel* AppListView::GetAppsPaginationModel() { |
| return GetRootAppsGridView()->pagination_model(); |
| } |
| |
| gfx::Rect AppListView::GetAppInfoDialogBounds() const { |
| gfx::Rect app_info_bounds(GetDisplayNearestView().work_area()); |
| app_info_bounds.ClampToCenteredSize( |
| gfx::Size(kAppInfoDialogWidth, kAppInfoDialogHeight)); |
| return app_info_bounds; |
| } |
| |
| void AppListView::SetIsInDrag(bool is_in_drag) { |
| // In tablet mode, |presentation_time_recorder_| is constructed/reset by |
| // HomeLauncherGestureHandler. |
| if (!is_in_drag && !is_tablet_mode_) |
| presentation_time_recorder_.reset(); |
| |
| if (app_list_state_ == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| if (is_in_drag == is_in_drag_) |
| return; |
| |
| if (is_in_drag && !is_tablet_mode_) { |
| DCHECK(!presentation_time_recorder_); |
| presentation_time_recorder_ = ash::CreatePresentationTimeHistogramRecorder( |
| GetWidget()->GetCompositor(), kAppListDragInClamshellHistogram, |
| kAppListDragInClamshellMaxLatencyHistogram); |
| } |
| |
| is_in_drag_ = is_in_drag; |
| GetAppsContainerView()->UpdateControlVisibility(app_list_state_, is_in_drag_); |
| } |
| |
| int AppListView::GetScreenBottom() const { |
| return GetDisplayNearestView().bounds().bottom(); |
| } |
| |
| int AppListView::GetCurrentAppListHeight() const { |
| if (!fullscreen_widget_) |
| return AppListConfig::instance().shelf_height(); |
| return GetScreenBottom() - fullscreen_widget_->GetWindowBoundsInScreen().y(); |
| } |
| |
| float AppListView::GetAppListTransitionProgress() const { |
| const float current_height = GetCurrentAppListHeight(); |
| const float peeking_height = |
| AppListConfig::instance().peeking_app_list_height(); |
| if (current_height <= peeking_height) { |
| // Currently transition progress is between closed and peeking state. |
| // Calculate the progress of this transition. |
| const float shelf_height = |
| GetScreenBottom() - GetDisplayNearestView().work_area().bottom(); |
| |
| // When screen is rotated, the current height might be smaller than shelf |
| // height for just one moment, which results in negative progress. So force |
| // the progress to be non-negative. |
| return std::max(0.0f, (current_height - shelf_height) / |
| (peeking_height - shelf_height)); |
| } |
| |
| // Currently transition progress is between peeking and fullscreen state. |
| // Calculate the progress of this transition. |
| const float fullscreen_height_above_peeking = |
| GetFullscreenStateHeight() - peeking_height; |
| const float current_height_above_peeking = current_height - peeking_height; |
| DCHECK_GT(fullscreen_height_above_peeking, 0); |
| DCHECK_LE(current_height_above_peeking, fullscreen_height_above_peeking); |
| return 1 + current_height_above_peeking / fullscreen_height_above_peeking; |
| } |
| |
| int AppListView::GetFullscreenStateHeight() const { |
| const display::Display display = GetDisplayNearestView(); |
| const gfx::Rect display_bounds = display.bounds(); |
| return display_bounds.height() - display.work_area().y() + display_bounds.y(); |
| } |
| |
| ash::mojom::AppListViewState AppListView::CalculateStateAfterShelfDrag( |
| const ui::GestureEvent& gesture_in_screen, |
| float launcher_above_shelf_bottom_amount) const { |
| ash::mojom::AppListViewState app_list_state = |
| ash::mojom::AppListViewState::kPeeking; |
| if (gesture_in_screen.type() == ui::ET_SCROLL_FLING_START && |
| fabs(gesture_in_screen.details().velocity_y()) > kDragVelocityThreshold) { |
| // If the scroll sequence terminates with a fling, show the fullscreen app |
| // list if the fling was fast enough and in the correct direction, otherwise |
| // close it. |
| app_list_state = gesture_in_screen.details().velocity_y() < 0 |
| ? ash::mojom::AppListViewState::kFullscreenAllApps |
| : ash::mojom::AppListViewState::kClosed; |
| } else { |
| // Snap the app list to corresponding state according to the snapping |
| // thresholds. |
| if (is_tablet_mode_) { |
| app_list_state = |
| launcher_above_shelf_bottom_amount > kDragSnapToFullscreenThreshold |
| ? ash::mojom::AppListViewState::kFullscreenAllApps |
| : ash::mojom::AppListViewState::kClosed; |
| } else { |
| if (launcher_above_shelf_bottom_amount <= kDragSnapToClosedThreshold) |
| app_list_state = ash::mojom::AppListViewState::kClosed; |
| else if (launcher_above_shelf_bottom_amount <= |
| kDragSnapToPeekingThreshold) |
| app_list_state = ash::mojom::AppListViewState::kPeeking; |
| else |
| app_list_state = ash::mojom::AppListViewState::kFullscreenAllApps; |
| } |
| } |
| |
| // Deal with the situation of dragging app list from shelf while typing in |
| // the search box. |
| if (app_list_state == ash::mojom::AppListViewState::kFullscreenAllApps) { |
| ash::AppListState active_state = |
| app_list_main_view_->contents_view()->GetActiveState(); |
| if (active_state == ash::AppListState::kStateSearchResults) |
| app_list_state = ash::mojom::AppListViewState::kFullscreenSearch; |
| } |
| |
| return app_list_state; |
| } |
| |
| ui::AnimationMetricsReporter* AppListView::GetStateTransitionMetricsReporter() { |
| state_animation_metrics_reporter_->Start(); |
| return state_animation_metrics_reporter_.get(); |
| } |
| |
| void AppListView::OnHomeLauncherDragStart() { |
| DCHECK(!presentation_time_recorder_); |
| presentation_time_recorder_ = ash::CreatePresentationTimeHistogramRecorder( |
| GetWidget()->GetCompositor(), kAppListDragInTabletHistogram, |
| kAppListDragInTabletMaxLatencyHistogram); |
| } |
| |
| void AppListView::OnHomeLauncherDragInProgress() { |
| DCHECK(presentation_time_recorder_); |
| presentation_time_recorder_->RequestNext(); |
| } |
| |
| void AppListView::OnHomeLauncherDragEnd() { |
| presentation_time_recorder_.reset(); |
| } |
| |
| void AppListView::OnWindowDestroying(aura::Window* window) { |
| DCHECK_EQ(fullscreen_widget_->GetNativeView(), window); |
| window->RemoveObserver(this); |
| } |
| |
| void AppListView::OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) { |
| DCHECK_EQ(fullscreen_widget_->GetNativeView(), window); |
| |
| // When the virtual keyboard shows, the AppListView is moved upward to avoid |
| // the overlapping area with the virtual keyboard. As a result, its bottom |
| // side may be on the display edge. Stop showing the rounded corners under |
| // this circumstance. |
| const bool hide_rounded_corners = |
| app_list_state_ == ash::mojom::AppListViewState::kHalf && |
| new_bounds.y() == 0; |
| |
| gfx::Transform transform; |
| if (hide_rounded_corners) |
| transform.Translate(0, -kAppListBackgroundRadius); |
| |
| app_list_background_shield_->SetTransform(transform); |
| app_list_background_shield_->SchedulePaint(); |
| } |
| |
| void AppListView::UpdateChildViewsYPositionAndOpacity() { |
| if (app_list_state_ == ash::mojom::AppListViewState::kClosed) |
| return; |
| |
| UpdateAppListBackgroundYPosition(); |
| |
| // Update the opacity of the background shield. |
| SetBackgroundShieldColor(); |
| |
| search_box_view_->UpdateOpacity(); |
| app_list_main_view_->contents_view()->UpdateYPositionAndOpacity(); |
| } |
| |
| void AppListView::RedirectKeyEventToSearchBox(ui::KeyEvent* event) { |
| if (event->handled()) |
| return; |
| |
| // Allow text input inside the Assistant page. |
| if (app_list_main_view()->contents_view()->IsShowingEmbeddedAssistantUI()) |
| return; |
| |
| views::Textfield* search_box = search_box_view_->search_box(); |
| const bool is_search_box_focused = search_box->HasFocus(); |
| const bool is_folder_header_view_focused = GetAppsContainerView() |
| ->app_list_folder_view() |
| ->folder_header_view() |
| ->HasTextFocus(); |
| |
| // Do not redirect the key event to the |search_box_| when focus is on a |
| // text field. |
| if (is_search_box_focused || is_folder_header_view_focused) |
| return; |
| |
| // Do not redirect the arrow keys as they are are used for focus traversal and |
| // app movement. |
| if (IsArrowKeyEvent(*event)) |
| return; |
| |
| // Redirect key event to |search_box_|. |
| search_box->OnKeyEvent(event); |
| if (event->handled()) { |
| // Set search box focused if the key event is consumed. |
| search_box->RequestFocus(); |
| return; |
| } |
| |
| // Insert it into search box if the key event is a character. Released |
| // key should not be handled to prevent inserting duplicate character. |
| if (event->type() == ui::ET_KEY_PRESSED) |
| search_box->InsertChar(*event); |
| } |
| |
| void AppListView::OnScreenKeyboardShown(bool shown) { |
| if (onscreen_keyboard_shown_ == shown) |
| return; |
| |
| onscreen_keyboard_shown_ = shown; |
| if (shown && GetAppsContainerView()->IsInFolderView()) { |
| // Move the app list up to prevent folders being blocked by the |
| // on-screen keyboard. |
| OffsetYPositionOfAppList( |
| GetAppsContainerView()->app_list_folder_view()->GetYOffsetForFolder()); |
| } else { |
| // If the keyboard is closing or a folder isn't being shown, reset |
| // the app list's position |
| OffsetYPositionOfAppList(0); |
| } |
| app_list_main_view_->contents_view()->NotifySearchBoxBoundsUpdated(); |
| } |
| |
| bool AppListView::CloseKeyboardIfVisible() { |
| // TODO(ginko) abstract this function to be in |keyboard::KeyboardController| |
| if (!keyboard::KeyboardController::HasInstance()) |
| return false; |
| auto* const keyboard_controller = keyboard::KeyboardController::Get(); |
| if (keyboard_controller->IsKeyboardVisible()) { |
| keyboard_controller->HideKeyboardByUser(); |
| return true; |
| } |
| return false; |
| } |
| |
| void AppListView::OnParentWindowBoundsChanged() { |
| // Set the |fullscreen_widget_| size to fit the new display metrics. |
| fullscreen_widget_->GetNativeView()->SetBounds( |
| GetPreferredWidgetBoundsForState(app_list_state_)); |
| |
| // Update the |fullscreen_widget_| bounds to accomodate the new work |
| // area. |
| SetState(app_list_state_); |
| } |
| |
| float AppListView::GetAppListBackgroundOpacityDuringDragging() { |
| float top_of_applist = fullscreen_widget_->GetWindowBoundsInScreen().y(); |
| const int shelf_height = AppListConfig::instance().shelf_height(); |
| float dragging_height = |
| std::max((GetScreenBottom() - shelf_height - top_of_applist), 0.f); |
| float coefficient = |
| std::min(dragging_height / (kNumOfShelfSize * shelf_height), 1.0f); |
| float shield_opacity = |
| is_background_blur_enabled_ ? kAppListOpacityWithBlur : kAppListOpacity; |
| // Assume shelf is opaque when start to drag down the launcher. |
| const float shelf_opacity = 1.0f; |
| return coefficient * shield_opacity + (1 - coefficient) * shelf_opacity; |
| } |
| |
| void AppListView::GetWallpaperProminentColors( |
| AppListViewDelegate::GetWallpaperProminentColorsCallback callback) { |
| delegate_->GetWallpaperProminentColors(std::move(callback)); |
| } |
| |
| void AppListView::SetBackgroundShieldColor() { |
| // There is a chance when AppListView::OnWallpaperColorsChanged is called |
| // from AppListViewDelegate, the |app_list_background_shield_| is not |
| // initialized. |
| if (!app_list_background_shield_) |
| return; |
| |
| // Opacity is set on the color instead of the layer because changing opacity |
| // of the layer changes opacity of the blur effect, which is not desired. |
| float color_opacity = kAppListOpacity; |
| |
| if (is_tablet_mode_) { |
| // The Homecher background should have an opacity of 0. |
| color_opacity = 0; |
| } else if (is_in_drag_) { |
| // Allow a custom opacity while the AppListView is dragging to show a |
| // gradual opacity change when dragging from the shelf. |
| color_opacity = background_opacity_in_drag_; |
| } else if (is_background_blur_enabled_) { |
| color_opacity = kAppListOpacityWithBlur; |
| } |
| |
| GetWallpaperProminentColors(base::BindOnce( |
| [](base::WeakPtr<AppListView> self, float color_opacity, |
| const std::vector<SkColor>& prominent_colors) { |
| self->app_list_background_shield_->UpdateColor( |
| GetBackgroundShieldColor(prominent_colors, color_opacity)); |
| }, |
| weak_ptr_factory_.GetWeakPtr(), color_opacity)); |
| } |
| |
| void AppListView::RecordFolderMetrics() { |
| int number_of_apps_in_folders = 0; |
| int number_of_folders = 0; |
| AppListItemList* item_list = |
| app_list_main_view_->model()->top_level_item_list(); |
| for (size_t i = 0; i < item_list->item_count(); ++i) { |
| AppListItem* item = item_list->item_at(i); |
| if (item->GetItemType() != AppListFolderItem::kItemType) |
| continue; |
| ++number_of_folders; |
| AppListFolderItem* folder = static_cast<AppListFolderItem*>(item); |
| if (folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) |
| continue; // Don't count items in OEM folders. |
| number_of_apps_in_folders += folder->item_list()->item_count(); |
| } |
| UMA_HISTOGRAM_COUNTS_100(kNumberOfFoldersHistogram, number_of_folders); |
| UMA_HISTOGRAM_COUNTS_100(kNumberOfAppsInFoldersHistogram, |
| number_of_apps_in_folders); |
| } |
| |
| bool AppListView::ShouldIgnoreScrollEvents() { |
| // When the app list is doing state change animation or the apps grid view is |
| // in transition, ignore the scroll events to prevent triggering extra state |
| // changes or transtions. |
| return fullscreen_widget_->GetLayer()->GetAnimator()->is_animating() || |
| GetRootAppsGridView()->pagination_model()->has_transition(); |
| } |
| |
| int AppListView::GetPreferredWidgetYForState( |
| ash::mojom::AppListViewState state) { |
| // Note that app list container fills the screen, so we can treat the |
| // container's y as the top of display. |
| const display::Display display = GetDisplayNearestView(); |
| const gfx::Rect work_area_bounds = display.work_area(); |
| switch (state) { |
| case ash::mojom::AppListViewState::kPeeking: |
| return display.bounds().height() - |
| AppListConfig::instance().peeking_app_list_height(); |
| case ash::mojom::AppListViewState::kHalf: |
| return std::max(work_area_bounds.y(), |
| display.bounds().height() - kHalfAppListHeight); |
| case ash::mojom::AppListViewState::kFullscreenAllApps: |
| case ash::mojom::AppListViewState::kFullscreenSearch: |
| // The ChromeVox panel as well as the Docked Magnifier viewport affect the |
| // workarea of the display. We need to account for that when applist is in |
| // fullscreen to avoid being shown below them. |
| return work_area_bounds.y() - display.bounds().y(); |
| case ash::mojom::AppListViewState::kClosed: |
| // Align the widget y with shelf y to avoid flicker in show animation. In |
| // side shelf mode, the widget y is the top of work area because the |
| // widget does not animate. |
| return (is_side_shelf_ ? work_area_bounds.y() |
| : work_area_bounds.bottom()) - |
| display.bounds().y(); |
| } |
| } |
| |
| gfx::Rect AppListView::GetPreferredWidgetBoundsForState( |
| ash::mojom::AppListViewState state) { |
| // Use parent's width instead of display width to avoid 1 px gap (See |
| // https://crbug.com/884889). |
| CHECK(fullscreen_widget_); |
| aura::Window* parent = fullscreen_widget_->GetNativeView()->parent(); |
| CHECK(parent); |
| return gfx::Rect(0, GetPreferredWidgetYForState(state), |
| parent->bounds().width(), GetFullscreenStateHeight()); |
| } |
| |
| void AppListView::UpdateAppListBackgroundYPosition() { |
| // Update the y position of the background shield. |
| gfx::Transform transform; |
| if (is_in_drag_) { |
| float app_list_transition_progress = GetAppListTransitionProgress(); |
| if (app_list_transition_progress >= 1 && |
| app_list_transition_progress <= 2) { |
| // Translate background shield so that it ends drag at y position |
| // -|kAppListBackgroundRadius| when dragging between peeking and |
| // fullscreen. |
| transform.Translate( |
| 0, -kAppListBackgroundRadius * (app_list_transition_progress - 1)); |
| } |
| } else if (is_fullscreen()) { |
| transform.Translate(0, -kAppListBackgroundRadius); |
| } |
| app_list_background_shield_->SetTransform(transform); |
| } |
| |
| void AppListView::OnStateTransitionAnimationCompleted() { |
| delegate_->OnStateTransitionAnimationCompleted(app_list_state_); |
| } |
| |
| } // namespace app_list |