| // Copyright 2018 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/home_launcher_gesture_handler.h" |
| |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/app_list/model/app_list_view_state.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/scoped_animation_disabler.h" |
| #include "ash/screen_util.h" |
| #include "ash/shell.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/overview/window_selector.h" |
| #include "ash/wm/overview/window_selector_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_divider.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_transient_descendant_iterator.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/workspace/backdrop_controller.h" |
| #include "ash/wm/workspace/workspace_layout_manager.h" |
| #include "ash/wm/workspace_controller.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/numerics/ranges.h" |
| #include "ui/aura/client/window_types.h" |
| #include "ui/aura/null_window_targeter.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/transient_window_manager.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The animation speed at which the window moves when the gesture is released. |
| constexpr base::TimeDelta kAnimationDurationMs = |
| base::TimeDelta::FromMilliseconds(250); |
| |
| // The animation speed at which the window moves when a window is acitvated from |
| // the shelf, or deacitvated via home launcher button minimize. |
| constexpr base::TimeDelta kActivationChangedAnimationDurationMs = |
| base::TimeDelta::FromMilliseconds(350); |
| |
| // The velocity the app list or shelf must be dragged in order to transition to |
| // the next state regardless of where the gesture ends, measured in DIPs/event. |
| constexpr int kScrollVelocityThreshold = 6; |
| |
| // The width of the target of screen bounds will be the work area width times |
| // this ratio. |
| constexpr float kWidthRatio = 0.8f; |
| |
| bool IsTabletMode() { |
| return Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| } |
| |
| // Checks if |window| can be hidden or shown with a gesture. |
| bool CanProcessWindow(aura::Window* window, |
| HomeLauncherGestureHandler::Mode mode) { |
| if (!window) |
| return false; |
| |
| if (!window->IsVisible() && |
| mode == HomeLauncherGestureHandler::Mode::kSlideUpToShow) { |
| return false; |
| } |
| |
| if (window->IsVisible() && |
| mode == HomeLauncherGestureHandler::Mode::kSlideDownToHide) { |
| return false; |
| } |
| |
| if (!IsTabletMode()) |
| return false; |
| |
| if (window->type() == aura::client::WINDOW_TYPE_POPUP) |
| return false; |
| |
| // Do not process if |window| is not the root of a transient tree. |
| if (::wm::GetTransientParent(window)) |
| return false; |
| |
| return true; |
| } |
| |
| // Find the transform that will convert |src| to |dst|. |
| gfx::Transform CalculateTransform(const gfx::RectF& src, |
| const gfx::RectF& dst) { |
| return gfx::Transform(dst.width() / src.width(), 0, 0, |
| dst.height() / src.height(), dst.x() - src.x(), |
| dst.y() - src.y()); |
| } |
| |
| // Get the target offscreen workspace bounds. |
| gfx::RectF GetOffscreenWorkspaceBounds(const gfx::RectF& work_area) { |
| gfx::RectF new_work_area; |
| new_work_area.set_x(((1.f - kWidthRatio) / 2.f) * work_area.width() + |
| work_area.x()); |
| new_work_area.set_width(kWidthRatio * work_area.width()); |
| new_work_area.set_height(kWidthRatio * work_area.height()); |
| new_work_area.set_y(work_area.y() - work_area.height()); |
| return new_work_area; |
| } |
| |
| // Get the target bounds of a window. It should maintain the same ratios |
| // relative the work area. |
| gfx::RectF GetOffscreenWindowBounds(aura::Window* window, |
| const gfx::RectF& src_work_area, |
| const gfx::RectF& dst_work_area) { |
| gfx::RectF bounds = gfx::RectF(window->GetTargetBounds()); |
| float ratio = dst_work_area.width() / src_work_area.width(); |
| |
| gfx::RectF dst_bounds; |
| dst_bounds.set_x(bounds.x() * ratio + dst_work_area.x()); |
| dst_bounds.set_y(bounds.y() * ratio + dst_work_area.y()); |
| dst_bounds.set_width(bounds.width() * ratio); |
| dst_bounds.set_height(bounds.height() * ratio); |
| return dst_bounds; |
| } |
| |
| // Given a |location_in_screen|, find out where it lies as a ratio in the |
| // work area, where the top of the work area is 0.f and the bottom is 1.f. |
| double GetHeightInWorkAreaAsRatio(const gfx::Point& location_in_screen, |
| const gfx::Rect& work_area) { |
| int clamped_y = base::ClampToRange(location_in_screen.y(), work_area.y(), |
| work_area.bottom()); |
| double ratio = |
| static_cast<double>(clamped_y) / static_cast<double>(work_area.height()); |
| return 1.0 - ratio; |
| } |
| |
| bool IsLastEventInTopHalf(const gfx::Point& location_in_screen, |
| const gfx::Rect& work_area) { |
| return GetHeightInWorkAreaAsRatio(location_in_screen, work_area) > 0.5; |
| } |
| |
| // Returns the window of the widget which contains the workspace backdrop. May |
| // be nullptr if the backdrop is not shown. |
| aura::Window* GetBackdropWindow(aura::Window* window) { |
| WorkspaceLayoutManager* layout_manager = |
| RootWindowController::ForWindow(window->GetRootWindow()) |
| ->workspace_controller() |
| ->layout_manager(); |
| return layout_manager |
| ? layout_manager->backdrop_controller()->backdrop_window() |
| : nullptr; |
| } |
| |
| // Returns the window of the widget of the split view divider. May be nullptr if |
| // split view is not active. |
| aura::Window* GetDividerWindow() { |
| SplitViewController* split_view_controller = |
| Shell::Get()->split_view_controller(); |
| if (!split_view_controller->IsSplitViewModeActive()) |
| return nullptr; |
| return split_view_controller->split_view_divider() |
| ->divider_widget() |
| ->GetNativeWindow(); |
| } |
| |
| } // namespace |
| |
| // Class which allows us to make modifications to a window, and removes those |
| // modifications on destruction. |
| // TODO(sammiequon): Move to separate file and add test for |
| // ComputeWindowValues. |
| class HomeLauncherGestureHandler::ScopedWindowModifier |
| : public aura::WindowObserver { |
| public: |
| explicit ScopedWindowModifier(aura::Window* window) : window_(window) { |
| DCHECK(window_); |
| original_targeter_ = |
| window_->SetEventTargeter(std::make_unique<aura::NullWindowTargeter>()); |
| } |
| ~ScopedWindowModifier() override { |
| for (const auto& descendant : transient_descendants_values_) |
| descendant.first->RemoveObserver(this); |
| |
| ResetOpacityAndTransform(); |
| window_->SetEventTargeter(std::move(original_targeter_)); |
| } |
| |
| bool IsAnimating() const { |
| if (window_->layer()->GetAnimator()->is_animating()) |
| return true; |
| |
| for (const auto& descendant : transient_descendants_values_) { |
| if (descendant.first->layer()->GetAnimator()->is_animating()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void StopAnimating() { |
| window_->layer()->GetAnimator()->StopAnimating(); |
| for (const auto& descendant : transient_descendants_values_) |
| descendant.first->layer()->GetAnimator()->StopAnimating(); |
| } |
| |
| void ResetOpacityAndTransform() { |
| window_->SetTransform(window_values_.initial_transform); |
| window_->layer()->SetOpacity(window_values_.initial_opacity); |
| for (const auto& descendant : transient_descendants_values_) { |
| descendant.first->SetTransform(descendant.second.initial_transform); |
| descendant.first->layer()->SetOpacity(descendant.second.initial_opacity); |
| } |
| } |
| |
| // Calculates the values for |window_| and its transient descendants. |
| void ComputeWindowValues(const gfx::RectF& work_area, |
| const gfx::RectF& target_work_area) { |
| transient_descendants_values_.clear(); |
| for (auto* window : wm::GetTransientTreeIterator(window_)) { |
| WindowValues values; |
| values.initial_opacity = window->layer()->opacity(); |
| values.initial_transform = window->transform(); |
| values.target_opacity = 0.f; |
| values.target_transform = CalculateTransform( |
| gfx::RectF(window->GetTargetBounds()), |
| GetOffscreenWindowBounds(window, work_area, target_work_area)); |
| if (window == window_) { |
| window_values_ = values; |
| continue; |
| } |
| |
| window->AddObserver(this); |
| transient_descendants_values_[window] = values; |
| } |
| } |
| |
| // aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override { |
| auto it = transient_descendants_values_.find(window); |
| DCHECK(it != transient_descendants_values_.end()); |
| |
| window->RemoveObserver(this); |
| transient_descendants_values_.erase(it); |
| } |
| |
| aura::Window* window() { return window_; } |
| WindowValues window_values() const { return window_values_; } |
| const std::map<aura::Window*, WindowValues>& transient_descendants_values() |
| const { |
| return transient_descendants_values_; |
| } |
| |
| private: |
| aura::Window* window_; |
| |
| // Original and target transform and opacity of |window_|. |
| WindowValues window_values_; |
| |
| // Tracks the transient descendants of |window_| and their initial and |
| // target opacities and transforms. |
| std::map<aura::Window*, WindowValues> transient_descendants_values_; |
| |
| std::unique_ptr<aura::WindowTargeter> original_targeter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedWindowModifier); |
| }; |
| |
| HomeLauncherGestureHandler::HomeLauncherGestureHandler( |
| AppListControllerImpl* app_list_controller) |
| : app_list_controller_(app_list_controller) { |
| tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller()); |
| } |
| |
| HomeLauncherGestureHandler::~HomeLauncherGestureHandler() { |
| StopObservingImplicitAnimations(); |
| } |
| |
| bool HomeLauncherGestureHandler::OnPressEvent(Mode mode, |
| const gfx::Point& location) { |
| // Do not start a new session if a window is currently being processed. |
| if (!IsIdle()) |
| return false; |
| |
| display_ = display::Screen::GetScreen()->GetDisplayNearestPoint(location); |
| if (!display_.is_valid()) |
| return false; |
| |
| if (!SetUpWindows(mode, /*window=*/nullptr)) |
| return false; |
| |
| mode_ = mode; |
| last_event_location_ = base::make_optional(location); |
| |
| UpdateWindows(0.0, /*animate=*/false); |
| return true; |
| } |
| |
| bool HomeLauncherGestureHandler::OnScrollEvent(const gfx::Point& location, |
| float scroll_y) { |
| if (IsAnimating()) |
| return false; |
| |
| if (!IsDragInProgress()) |
| return false; |
| |
| last_event_location_ = base::make_optional(location); |
| last_scroll_y_ = scroll_y; |
| |
| DCHECK(display_.is_valid()); |
| UpdateWindows(GetHeightInWorkAreaAsRatio(location, display_.work_area()), |
| /*animate=*/false); |
| return true; |
| } |
| |
| bool HomeLauncherGestureHandler::OnReleaseEvent(const gfx::Point& location) { |
| if (IsAnimating()) |
| return false; |
| |
| if (!IsDragInProgress()) { |
| if (GetWindow1()) { |
| // |window1_| may not be nullptr when this release event is triggered |
| // by opening |window1_| with modal dialog in OnPressEvent(). In that |
| // case, just leave the |window1_| in show state and stop tracking. |
| AnimateToFinalState(); |
| RemoveObserversAndStopTracking(); |
| return true; |
| } |
| return false; |
| } |
| |
| last_event_location_ = base::make_optional(location); |
| AnimateToFinalState(); |
| return true; |
| } |
| |
| void HomeLauncherGestureHandler::Cancel() { |
| if (!IsDragInProgress()) |
| return; |
| |
| AnimateToFinalState(); |
| return; |
| } |
| |
| bool HomeLauncherGestureHandler::ShowHomeLauncher( |
| const display::Display& display) { |
| if (!IsIdle()) |
| return false; |
| |
| if (!display.is_valid()) |
| return false; |
| |
| if (!SetUpWindows(Mode::kSlideUpToShow, /*window=*/nullptr)) |
| return false; |
| |
| display_ = display; |
| mode_ = Mode::kSlideUpToShow; |
| |
| UpdateWindows(0.0, /*animate=*/false); |
| AnimateToFinalState(); |
| return true; |
| } |
| |
| bool HomeLauncherGestureHandler::HideHomeLauncherForWindow( |
| const display::Display& display, |
| aura::Window* window) { |
| if (!IsIdle()) |
| return false; |
| |
| if (!display.is_valid()) |
| return false; |
| |
| if (!SetUpWindows(Mode::kSlideDownToHide, window)) |
| return false; |
| |
| display_ = display; |
| mode_ = Mode::kSlideDownToHide; |
| |
| UpdateWindows(1.0, /*animate=*/false); |
| AnimateToFinalState(); |
| return true; |
| } |
| |
| aura::Window* HomeLauncherGestureHandler::GetWindow1() { |
| if (!window1_) |
| return nullptr; |
| return window1_->window(); |
| } |
| |
| aura::Window* HomeLauncherGestureHandler::GetWindow2() { |
| if (!window2_) |
| return nullptr; |
| return window2_->window(); |
| } |
| |
| void HomeLauncherGestureHandler::OnWindowDestroying(aura::Window* window) { |
| if (window1_ && window == GetWindow1()) { |
| for (auto* hidden_window : hidden_windows_) |
| hidden_window->Show(); |
| |
| RemoveObserversAndStopTracking(); |
| return; |
| } |
| |
| if (window2_ && window == GetWindow2()) { |
| DCHECK(window1_); |
| window->RemoveObserver(this); |
| window2_.reset(); |
| return; |
| } |
| |
| DCHECK(base::ContainsValue(hidden_windows_, window)); |
| window->RemoveObserver(this); |
| hidden_windows_.erase( |
| std::find(hidden_windows_.begin(), hidden_windows_.end(), window)); |
| } |
| |
| void HomeLauncherGestureHandler::OnTabletModeEnded() { |
| if (IsIdle()) |
| return; |
| |
| // When leaving tablet mode advance to the end of the in progress scroll |
| // session or animation. |
| StopObservingImplicitAnimations(); |
| if (window1_) |
| window1_->StopAnimating(); |
| if (window2_) |
| window2_->StopAnimating(); |
| UpdateWindows(IsFinalStateShow() ? 1.0 : 0.0, /*animate=*/false); |
| OnImplicitAnimationsCompleted(); |
| } |
| |
| void HomeLauncherGestureHandler::OnImplicitAnimationsCompleted() { |
| float app_list_opacity = 1.f; |
| const bool is_final_state_show = IsFinalStateShow(); |
| if (Shell::Get()->window_selector_controller()->IsSelecting()) { |
| if (overview_active_on_gesture_start_ && is_final_state_show) { |
| // Exit overview if event is released on the top half. This will also |
| // end splitview if it is active as SplitViewController observes |
| // overview mode ends. |
| Shell::Get()->window_selector_controller()->ToggleOverview( |
| WindowSelector::EnterExitOverviewType::kSwipeFromShelf); |
| } else { |
| app_list_opacity = 0.f; |
| } |
| } |
| |
| // Return the app list to its original opacity and transform without |
| // animation. |
| DCHECK(display_.is_valid()); |
| app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher( |
| display_.work_area().y(), app_list_opacity, base::NullCallback()); |
| |
| if (!window1_) { |
| RemoveObserversAndStopTracking(); |
| return; |
| } |
| |
| // Explicitly exit split view if two windows are snapped. |
| if (is_final_state_show && Shell::Get()->split_view_controller()->state() == |
| SplitViewController::BOTH_SNAPPED) { |
| Shell::Get()->split_view_controller()->EndSplitView(); |
| } |
| |
| window1_->ResetOpacityAndTransform(); |
| if (window2_) |
| window2_->ResetOpacityAndTransform(); |
| |
| if (is_final_state_show) { |
| ScopedAnimationDisabler disable(GetWindow1()); |
| GetWindow1()->Hide(); |
| wm::GetWindowState(GetWindow1())->Minimize(); |
| |
| if (window2_) { |
| ScopedAnimationDisabler disable(GetWindow2()); |
| GetWindow2()->Hide(); |
| wm::GetWindowState(GetWindow2())->Minimize(); |
| } |
| |
| // Minimize the hidden windows so they can be used normally with alt+tab |
| // and overview. Minimize in reverse order to preserve mru ordering. |
| std::reverse(hidden_windows_.begin(), hidden_windows_.end()); |
| for (auto* window : hidden_windows_) { |
| ScopedAnimationDisabler disable(window); |
| window->Hide(); |
| wm::GetWindowState(window)->Minimize(); |
| } |
| } else { |
| // Reshow all windows previously hidden. |
| for (auto* window : hidden_windows_) { |
| ScopedAnimationDisabler disable(window); |
| window->Show(); |
| } |
| } |
| |
| // Update the backdrop last as the backdrop controller listens for some |
| // state changes like minimizing above which may also alter the backdrop. |
| aura::Window* backdrop_window = GetBackdropWindow(GetWindow1()); |
| if (backdrop_window) { |
| backdrop_window->SetTransform(gfx::Transform()); |
| backdrop_window->layer()->SetOpacity(1.f); |
| } |
| |
| RemoveObserversAndStopTracking(); |
| } |
| |
| void HomeLauncherGestureHandler::AnimateToFinalState() { |
| const bool is_final_state_show = IsFinalStateShow(); |
| UpdateWindows(is_final_state_show ? 1.0 : 0.0, /*animate=*/true); |
| |
| if (!is_final_state_show && mode_ == Mode::kSlideDownToHide) { |
| base::RecordAction( |
| base::UserMetricsAction("AppList_HomeLauncherToMRUWindow")); |
| } else if (is_final_state_show && mode_ == Mode::kSlideUpToShow) { |
| base::RecordAction( |
| base::UserMetricsAction("AppList_CurrentWindowToHomeLauncher")); |
| } |
| } |
| |
| void HomeLauncherGestureHandler::UpdateSettings( |
| ui::ScopedLayerAnimationSettings* settings, |
| bool observe) { |
| settings->SetTransitionDuration(IsDragInProgress() |
| ? kAnimationDurationMs |
| : kActivationChangedAnimationDurationMs); |
| settings->SetTweenType(IsDragInProgress() ? gfx::Tween::LINEAR |
| : gfx::Tween::FAST_OUT_SLOW_IN); |
| settings->SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| |
| if (observe) |
| settings->AddObserver(this); |
| } |
| |
| void HomeLauncherGestureHandler::UpdateWindows(double progress, bool animate) { |
| // Update full screen applist. |
| DCHECK(display_.is_valid()); |
| const gfx::Rect work_area = display_.work_area(); |
| const int y_position = |
| gfx::Tween::IntValueBetween(progress, work_area.bottom(), work_area.y()); |
| const float opacity = gfx::Tween::FloatValueBetween(progress, 0.f, 1.f); |
| app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher( |
| y_position, opacity, |
| animate ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings, |
| base::Unretained(this)) |
| : base::NullCallback()); |
| |
| // Update the overview grid if needed. |
| WindowSelectorController* controller = |
| Shell::Get()->window_selector_controller(); |
| if (overview_active_on_gesture_start_ && controller->IsSelecting()) { |
| DCHECK_EQ(mode_, Mode::kSlideUpToShow); |
| controller->window_selector()->UpdateGridAtLocationYPositionAndOpacity( |
| display_.id(), y_position - work_area.height(), 1.f - opacity, |
| work_area, |
| animate |
| ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings, |
| base::Unretained(this)) |
| : base::NullCallback()); |
| } |
| |
| if (!window1_) |
| return; |
| |
| // Helper to update a single windows opacity and transform based on by |
| // calculating the in between values using |value| and |values|. |
| auto update_windows_helper = [this](double progress, bool animate, |
| aura::Window* window, |
| const WindowValues& values) { |
| float opacity = gfx::Tween::FloatValueBetween( |
| progress, values.initial_opacity, values.target_opacity); |
| gfx::Transform transform = gfx::Tween::TransformValueBetween( |
| progress, values.initial_transform, values.target_transform); |
| |
| std::unique_ptr<ui::ScopedLayerAnimationSettings> settings; |
| if (animate) { |
| settings = std::make_unique<ui::ScopedLayerAnimationSettings>( |
| window->layer()->GetAnimator()); |
| // There are multiple animations run on a release event (app list, |
| // overview and the stored windows). We only want to act on one |
| // animation end, so only observe one of the animations. If overview |
| // is active, observe the shield widget of the grid, else observe |
| // |window1_|. |
| UpdateSettings( |
| settings.get(), |
| this->GetWindow1() == window && |
| !(overview_active_on_gesture_start_ && |
| Shell::Get()->window_selector_controller()->IsSelecting())); |
| } |
| window->layer()->SetOpacity(opacity); |
| window->SetTransform(transform); |
| }; |
| |
| aura::Window* backdrop_window = GetBackdropWindow(GetWindow1()); |
| if (backdrop_window && backdrop_values_) { |
| update_windows_helper(progress, animate, backdrop_window, |
| *backdrop_values_); |
| } |
| |
| aura::Window* divider_window = GetDividerWindow(); |
| if (divider_window && divider_values_) { |
| update_windows_helper(progress, animate, divider_window, *divider_values_); |
| } |
| |
| if (window2_) { |
| for (const auto& descendant : window2_->transient_descendants_values()) { |
| update_windows_helper(progress, animate, descendant.first, |
| descendant.second); |
| } |
| update_windows_helper(progress, animate, GetWindow2(), |
| window2_->window_values()); |
| } |
| |
| for (const auto& descendant : window1_->transient_descendants_values()) { |
| update_windows_helper(progress, animate, descendant.first, |
| descendant.second); |
| } |
| update_windows_helper(progress, animate, GetWindow1(), |
| window1_->window_values()); |
| } |
| |
| void HomeLauncherGestureHandler::RemoveObserversAndStopTracking() { |
| display_.set_id(display::kInvalidDisplayId); |
| backdrop_values_ = base::nullopt; |
| divider_values_ = base::nullopt; |
| last_event_location_ = base::nullopt; |
| last_scroll_y_ = 0.f; |
| mode_ = Mode::kNone; |
| |
| for (auto* window : hidden_windows_) |
| window->RemoveObserver(this); |
| hidden_windows_.clear(); |
| |
| if (window1_) |
| GetWindow1()->RemoveObserver(this); |
| window1_.reset(); |
| |
| if (window2_) |
| GetWindow2()->RemoveObserver(this); |
| window2_.reset(); |
| } |
| |
| bool HomeLauncherGestureHandler::IsIdle() { |
| return !IsDragInProgress() && !IsAnimating(); |
| } |
| |
| bool HomeLauncherGestureHandler::IsAnimating() { |
| if (window1_ && window1_->IsAnimating()) |
| return true; |
| |
| if (window2_ && window2_->IsAnimating()) |
| return true; |
| |
| if (overview_active_on_gesture_start_ && |
| Shell::Get()->window_selector_controller()->IsSelecting() && |
| Shell::Get() |
| ->window_selector_controller() |
| ->window_selector() |
| ->IsWindowGridAnimating()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool HomeLauncherGestureHandler::IsFinalStateShow() { |
| DCHECK_NE(Mode::kNone, mode_); |
| DCHECK(display_.is_valid()); |
| |
| // If fling velocity is greater than the threshold, show the launcher if |
| // sliding up, or hide the launcher if sliding down, irregardless of |
| // |last_event_location_|. |
| if (mode_ == Mode::kSlideUpToShow && |
| last_scroll_y_ < -kScrollVelocityThreshold) { |
| return true; |
| } |
| |
| if (mode_ == Mode::kSlideDownToHide && |
| last_scroll_y_ > kScrollVelocityThreshold) { |
| return false; |
| } |
| |
| return last_event_location_ |
| ? IsLastEventInTopHalf(*last_event_location_, display_.work_area()) |
| : mode_ == Mode::kSlideUpToShow; |
| } |
| |
| bool HomeLauncherGestureHandler::SetUpWindows(Mode mode, aura::Window* window) { |
| SplitViewController* split_view_controller = |
| Shell::Get()->split_view_controller(); |
| overview_active_on_gesture_start_ = |
| Shell::Get()->window_selector_controller()->IsSelecting(); |
| const bool split_view_active = split_view_controller->IsSplitViewModeActive(); |
| auto windows = Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(); |
| if (window && (mode != Mode::kSlideDownToHide || |
| overview_active_on_gesture_start_ || split_view_active)) { |
| window1_.reset(); |
| return false; |
| } |
| |
| if (window && !windows.empty() && windows[0] != window && |
| windows[0]->IsVisible()) { |
| // Do not run slide down animation for the |window| if another active |
| // window in mru list exists. Windows minimized in clamshell mode may |
| // have opacity of 0, so set them to 1 to ensure visibility. |
| if (wm::GetWindowState(window)->IsMinimized()) |
| window->layer()->SetOpacity(1.f); |
| window1_.reset(); |
| return false; |
| } |
| |
| if (IsTabletMode() && overview_active_on_gesture_start_ && |
| !split_view_active) { |
| DCHECK_EQ(Mode::kSlideUpToShow, mode); |
| window1_.reset(); |
| return true; |
| } |
| |
| // Always hide split view windows if they exist. Otherwise, hide the |
| // specified window if it is not null. If none of above is true, we want |
| // the first window in the mru list, if it exists and is usable. |
| aura::Window* first_window = |
| split_view_active |
| ? split_view_controller->GetDefaultSnappedWindow() |
| : (window ? window : (windows.empty() ? nullptr : windows[0])); |
| if (!CanProcessWindow(first_window, mode)) { |
| window1_.reset(); |
| return false; |
| } |
| |
| DCHECK(base::ContainsValue(windows, first_window)); |
| DCHECK_NE(Mode::kNone, mode); |
| base::RecordAction(base::UserMetricsAction( |
| mode == Mode::kSlideDownToHide |
| ? "AppList_HomeLauncherToMRUWindowAttempt" |
| : "AppList_CurrentWindowToHomeLauncherAttempt")); |
| window1_ = std::make_unique<ScopedWindowModifier>(first_window); |
| GetWindow1()->AddObserver(this); |
| base::EraseIf(windows, [this](aura::Window* elem) { |
| return elem == this->GetWindow1(); |
| }); |
| |
| // Alter a second window if we are in split view mode with two windows |
| // snapped. |
| if (mode == Mode::kSlideUpToShow && |
| split_view_controller->state() == SplitViewController::BOTH_SNAPPED) { |
| DCHECK_GT(windows.size(), 0u); |
| aura::Window* second_window = |
| split_view_controller->default_snap_position() == |
| SplitViewController::LEFT |
| ? split_view_controller->right_window() |
| : split_view_controller->left_window(); |
| DCHECK(base::ContainsValue(windows, second_window)); |
| window2_ = std::make_unique<ScopedWindowModifier>(second_window); |
| GetWindow2()->AddObserver(this); |
| base::EraseIf(windows, [this](aura::Window* elem) { |
| return elem == this->GetWindow2(); |
| }); |
| } |
| |
| // Show |window1_| if we are swiping down to hide. |
| if (mode == Mode::kSlideDownToHide) { |
| ScopedAnimationDisabler disable(GetWindow1()); |
| GetWindow1()->Show(); |
| |
| // When |window1_| has a modal dialog child, window1_->Show() above would |
| // cancel the current gesture and trigger OnReleaseEvent() to reset |
| // |window1_|. |
| if (!window1_ || !GetWindow1()) |
| return false; |
| |
| wm::ActivateWindow(GetWindow1()); |
| GetWindow1()->layer()->SetOpacity(1.f); |
| } |
| |
| const gfx::RectF work_area = |
| gfx::RectF(screen_util::GetDisplayWorkAreaBoundsInParent(GetWindow1())); |
| const gfx::RectF target_work_area = GetOffscreenWorkspaceBounds(work_area); |
| |
| window1_->ComputeWindowValues(work_area, target_work_area); |
| if (window2_) |
| window2_->ComputeWindowValues(work_area, target_work_area); |
| |
| aura::Window* backdrop_window = GetBackdropWindow(GetWindow1()); |
| if (backdrop_window) { |
| // Store the values needed to transform the backdrop. The backdrop |
| // actually covers the area behind the shelf as well, so initially |
| // transform it to be sized to the work area. Without the transform |
| // tweak, there is an extra shelf sized black area under |window1_|. Go |
| // to 0.01 opacity instead of 0 opacity otherwise animation end code |
| // will attempt to update the backdrop which will try to show a 0 |
| // opacity window which causes a crash. |
| backdrop_values_ = base::make_optional(WindowValues()); |
| backdrop_values_->initial_opacity = 1.f; |
| backdrop_values_->initial_transform = gfx::Transform( |
| 1.f, 0.f, 0.f, |
| work_area.height() / |
| static_cast<float>(backdrop_window->bounds().height()), |
| 0.f, 0.f); |
| backdrop_values_->target_opacity = 0.01f; |
| backdrop_values_->target_transform = CalculateTransform( |
| gfx::RectF(backdrop_window->bounds()), target_work_area); |
| } |
| |
| // Stores values needed to transform the split view divider if it exists. |
| aura::Window* divider_window = GetDividerWindow(); |
| if (divider_window) { |
| divider_values_ = base::make_optional(WindowValues()); |
| divider_values_->initial_opacity = 1.f; |
| divider_values_->initial_transform = gfx::Transform(); |
| divider_values_->target_opacity = 0.f; |
| divider_values_->target_transform = CalculateTransform( |
| gfx::RectF(divider_window->bounds()), |
| GetOffscreenWindowBounds(divider_window, work_area, target_work_area)); |
| } |
| |
| // Hide all visible windows which are behind our window so that when we |
| // scroll, the home launcher will be visible. This is only needed when |
| // swiping up, and not when overview mode is active. |
| hidden_windows_.clear(); |
| if (mode == Mode::kSlideUpToShow && !overview_active_on_gesture_start_) { |
| for (auto* window : windows) { |
| if (window->IsVisible()) { |
| hidden_windows_.push_back(window); |
| window->AddObserver(this); |
| ScopedAnimationDisabler disable(window); |
| window->Hide(); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace ash |