| // Copyright 2019 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/home_screen/drag_window_from_shelf_controller.h" |
| |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/home_screen/home_screen_controller.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/scoped_animation_disabler.h" |
| #include "ash/screen_util.h" |
| #include "ash/shell.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/window_state.h" |
| #include "base/numerics/ranges.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/display/screen.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The minimum window scale factor when dragging a window from shelf. |
| constexpr float kMinimumWindowScaleDuringDragging = 0.2f; |
| |
| } // namespace |
| |
| DragWindowFromShelfController::DragWindowFromShelfController( |
| aura::Window* window, |
| const std::vector<aura::Window*>& hidden_windows) |
| : window_(window), hidden_windows_(hidden_windows) {} |
| |
| DragWindowFromShelfController::~DragWindowFromShelfController() { |
| CancelDrag(); |
| } |
| |
| void DragWindowFromShelfController::Drag(const gfx::Point& location_in_screen, |
| float scroll_y) { |
| if (!drag_started_) { |
| // Do not start drag until the drag goes above the shelf. |
| const gfx::Rect work_area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| window_); |
| if (location_in_screen.y() > work_area.bottom()) |
| return; |
| OnDragStarted(location_in_screen); |
| } |
| UpdateDraggedWindow(location_in_screen); |
| |
| // Open overview if the window has been dragged far enough and the scroll |
| // delta has decreased to kShowOverviewThreshold or less. |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (std::abs(scroll_y) <= kShowOverviewThreshold && |
| !overview_controller->InOverviewSession()) { |
| overview_controller->StartOverview( |
| OverviewSession::EnterExitOverviewType::kImmediateEnter); |
| overview_controller->overview_session()->OnWindowDragStarted( |
| window_, /*animate=*/false); |
| } |
| |
| // If overview is active, update its splitview indicator during dragging if |
| // splitview is allowed in current configuration. |
| if (overview_controller->InOverviewSession() && ShouldAllowSplitView()) { |
| OverviewSession* overview_session = overview_controller->overview_session(); |
| IndicatorState indicator_state = GetIndicatorState(location_in_screen); |
| overview_session->SetSplitViewDragIndicatorsIndicatorState( |
| indicator_state, location_in_screen); |
| overview_session->OnWindowDragContinued( |
| window_, gfx::PointF(location_in_screen), indicator_state); |
| } |
| |
| previous_location_in_screen_ = location_in_screen; |
| } |
| |
| void DragWindowFromShelfController::EndDrag( |
| const gfx::Point& location_in_screen, |
| base::Optional<float> velocity_y) { |
| if (!drag_started_) { |
| ReshowHiddenWindowsOnDragEnd(); |
| return; |
| } |
| |
| drag_started_ = false; |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| SplitViewController* split_view_controller = SplitViewController::Get(); |
| const bool in_overview = overview_controller->InOverviewSession(); |
| const bool in_splitview = split_view_controller->InSplitViewMode(); |
| |
| if (ShouldGoToHomeScreen(velocity_y)) { |
| DCHECK(!in_splitview); |
| if (in_overview) { |
| overview_controller->EndOverview( |
| OverviewSession::EnterExitOverviewType::kImmediateExit); |
| } |
| // TODO(crbug.com/997885): Add animation. |
| WindowState::Get(window_)->Minimize(); |
| } else if (ShouldRestoreToOriginalBounds(location_in_screen)) { |
| // TODO(crbug.com/997885): Add animation. |
| SetTransform(window_, gfx::Transform()); |
| if (!in_splitview && in_overview) { |
| overview_controller->EndOverview( |
| OverviewSession::EnterExitOverviewType::kImmediateExit); |
| } |
| ReshowHiddenWindowsOnDragEnd(); |
| } else if (!in_overview) { |
| // if overview is not active during the entire drag process, go to home |
| // screen. |
| WindowState::Get(window_)->Minimize(); |
| } |
| |
| OnDragEnded(location_in_screen, |
| ShouldDropWindowInOverview(location_in_screen, velocity_y), |
| GetSnapPositionOnDragEnd(location_in_screen, velocity_y)); |
| } |
| |
| void DragWindowFromShelfController::CancelDrag() { |
| if (!drag_started_) { |
| ReshowHiddenWindowsOnDragEnd(); |
| return; |
| } |
| |
| drag_started_ = false; |
| // Reset the window's transform to identity transform. |
| window_->SetTransform(gfx::Transform()); |
| ReshowHiddenWindowsOnDragEnd(); |
| |
| OnDragEnded(previous_location_in_screen_, |
| /*should_drop_window_in_overview=*/false, |
| /*snap_position=*/SplitViewController::NONE); |
| } |
| |
| void DragWindowFromShelfController::OnDragStarted( |
| const gfx::Point& location_in_screen) { |
| drag_started_ = true; |
| initial_location_in_screen_ = location_in_screen; |
| previous_location_in_screen_ = location_in_screen; |
| WindowState::Get(window_)->CreateDragDetails( |
| initial_location_in_screen_, HTCLIENT, ::wm::WINDOW_MOVE_SOURCE_TOUCH); |
| |
| // Disable the backdrop on the dragged window during dragging. |
| original_backdrop_mode_ = window_->GetProperty(kBackdropWindowMode); |
| window_->SetProperty(kBackdropWindowMode, BackdropWindowMode::kDisabled); |
| |
| // Hide the home launcher until it's eligible to show it. |
| Shell::Get()->home_screen_controller()->OnWindowDragStarted(); |
| |
| // If the dragged window is one of the snapped window in splitview, it needs |
| // to be detached from splitview before start dragging. |
| SplitViewController* split_view_controller = SplitViewController::Get(); |
| split_view_controller->OnWindowDragStarted(window_); |
| // Note SplitViewController::OnWindowDragStarted() may open overview. |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (overview_controller->InOverviewSession()) { |
| overview_controller->overview_session()->OnWindowDragStarted( |
| window_, /*animate=*/false); |
| } |
| } |
| |
| void DragWindowFromShelfController::OnDragEnded( |
| const gfx::Point& location_in_screen, |
| bool should_drop_window_in_overview, |
| SplitViewController::SnapPosition snap_position) { |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (overview_controller->InOverviewSession()) { |
| OverviewSession* overview_session = overview_controller->overview_session(); |
| overview_session->SetSplitViewDragIndicatorsIndicatorState( |
| IndicatorState::kNone, location_in_screen); |
| overview_session->OnWindowDragEnded( |
| window_, gfx::PointF(location_in_screen), |
| should_drop_window_in_overview, |
| /*snap=*/snap_position != SplitViewController::NONE); |
| } |
| SplitViewController* split_view_controller = SplitViewController::Get(); |
| if (split_view_controller->InSplitViewMode() || |
| snap_position != SplitViewController::NONE) { |
| split_view_controller->OnWindowDragEnded(window_, snap_position, |
| location_in_screen); |
| } |
| Shell::Get()->home_screen_controller()->OnWindowDragEnded(); |
| |
| WindowState::Get(window_)->DeleteDragDetails(); |
| window_->SetProperty(kBackdropWindowMode, original_backdrop_mode_); |
| hidden_windows_.clear(); |
| } |
| |
| void DragWindowFromShelfController::UpdateDraggedWindow( |
| const gfx::Point& location_in_screen) { |
| gfx::Rect bounds = window_->bounds(); |
| ::wm::ConvertRectToScreen(window_->parent(), &bounds); |
| |
| // Calculate the window's transform based on the location. |
| // For scale, at |initial_location_in_screen_|, the scale is 1.0, and at the |
| // middle y position of its bounds, it reaches to its minimum scale 0.2. |
| // Calculate the desired scale based on the current y position. |
| int y_full = bounds.bottom() - bounds.CenterPoint().y(); |
| int y_diff = location_in_screen.y() - bounds.CenterPoint().y(); |
| float scale = (1.0f - kMinimumWindowScaleDuringDragging) * y_diff / y_full + |
| kMinimumWindowScaleDuringDragging; |
| scale = base::ClampToRange(scale, /*min=*/kMinimumWindowScaleDuringDragging, |
| /*max=*/1.f); |
| |
| // Calculate the desired translation so that the dragged window stays under |
| // the finger during the dragging. |
| gfx::Transform transform; |
| transform.Translate( |
| (location_in_screen.x() - bounds.x()) - |
| (initial_location_in_screen_.x() - bounds.x()) * scale, |
| (location_in_screen.y() - bounds.y()) - |
| (initial_location_in_screen_.y() - bounds.y()) * scale); |
| transform.Scale(scale, scale); |
| |
| SetTransform(window_, transform); |
| } |
| |
| SplitViewController::SnapPosition |
| DragWindowFromShelfController::GetSnapPosition( |
| const gfx::Point& location_in_screen) const { |
| // if |location_in_screen| is close to the bottom of the screen and is |
| // inside of kReturnToMaximizedThreshold threshold, we should not try to |
| // snap the window. |
| if (ShouldRestoreToOriginalBounds(location_in_screen)) |
| return SplitViewController::NONE; |
| |
| const gfx::Rect work_area = display::Screen::GetScreen() |
| ->GetDisplayNearestPoint(location_in_screen) |
| .work_area(); |
| SplitViewController::SnapPosition snap_position = |
| ::ash::GetSnapPosition(window_, location_in_screen, work_area); |
| |
| // For portrait mode, since the drag starts from the bottom of the screen, |
| // we should only allow the window to snap to the top of the screen. |
| const bool is_landscape = IsCurrentScreenOrientationLandscape(); |
| const bool is_primary = IsCurrentScreenOrientationPrimary(); |
| if (!is_landscape && |
| ((is_primary && snap_position == SplitViewController::RIGHT) || |
| (!is_primary && snap_position == SplitViewController::LEFT))) { |
| snap_position = SplitViewController::NONE; |
| } |
| |
| return snap_position; |
| } |
| |
| IndicatorState DragWindowFromShelfController::GetIndicatorState( |
| const gfx::Point& location_in_screen) const { |
| if (!drag_started_) |
| return IndicatorState::kNone; |
| |
| // if |location_in_screen| is close to the bottom of the screen and is |
| // inside of kReturnToMaximizedThreshold threshold, we do not show the |
| // indicators. |
| if (ShouldRestoreToOriginalBounds(location_in_screen)) |
| return IndicatorState::kNone; |
| |
| IndicatorState indicator_state = |
| ::ash::GetIndicatorState(window_, GetSnapPosition(location_in_screen)); |
| // For portrait mode, since the drag starts from the bottom of the |
| // there is no bottom drag indicator. |
| if (!IsCurrentScreenOrientationLandscape()) { |
| if (indicator_state == IndicatorState::kDragArea) |
| indicator_state = IndicatorState::kDragAreaLeft; |
| else if (indicator_state == IndicatorState::kCannotSnap) |
| indicator_state = IndicatorState::kCannotSnapLeft; |
| } |
| |
| return indicator_state; |
| } |
| |
| bool DragWindowFromShelfController::ShouldRestoreToOriginalBounds( |
| const gfx::Point& location_in_screen) const { |
| const gfx::Rect work_area = display::Screen::GetScreen() |
| ->GetDisplayNearestPoint(location_in_screen) |
| .work_area(); |
| return location_in_screen.y() > |
| work_area.bottom() - kReturnToMaximizedThreshold; |
| } |
| |
| bool DragWindowFromShelfController::ShouldGoToHomeScreen( |
| base::Optional<float> velocity_y) const { |
| return velocity_y.has_value() && *velocity_y < 0 && |
| std::abs(*velocity_y) >= kVelocityToHomeScreenThreshold && |
| !SplitViewController::Get()->InSplitViewMode(); |
| } |
| |
| SplitViewController::SnapPosition |
| DragWindowFromShelfController::GetSnapPositionOnDragEnd( |
| const gfx::Point& location_in_screen, |
| base::Optional<float> velocity_y) const { |
| if (ShouldRestoreToOriginalBounds(location_in_screen) || |
| ShouldGoToHomeScreen(velocity_y)) { |
| return SplitViewController::NONE; |
| } |
| |
| return GetSnapPosition(location_in_screen); |
| } |
| |
| bool DragWindowFromShelfController::ShouldDropWindowInOverview( |
| const gfx::Point& location_in_screen, |
| base::Optional<float> velocity_y) const { |
| if (!Shell::Get()->overview_controller()->InOverviewSession()) |
| return false; |
| |
| if (ShouldGoToHomeScreen(velocity_y)) |
| return false; |
| |
| const bool in_splitview = SplitViewController::Get()->InSplitViewMode(); |
| if (!in_splitview && ShouldRestoreToOriginalBounds(location_in_screen)) { |
| return false; |
| } |
| |
| if (in_splitview) { |
| if (velocity_y.has_value() && *velocity_y < 0 && |
| std::abs(*velocity_y) >= kVelocityToOverviewThreshold) { |
| return true; |
| } |
| if (ShouldRestoreToOriginalBounds(location_in_screen)) |
| return false; |
| } |
| |
| return GetSnapPositionOnDragEnd(location_in_screen, velocity_y) == |
| SplitViewController::NONE; |
| } |
| |
| void DragWindowFromShelfController::ReshowHiddenWindowsOnDragEnd() { |
| for (auto* window : hidden_windows_) { |
| ScopedAnimationDisabler disable(window); |
| window->Show(); |
| } |
| hidden_windows_.clear(); |
| } |
| |
| } // namespace ash |