123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- // Copyright 2020 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/drag_drop/tab_drag_drop_delegate.h"
- #include "ash/constants/app_types.h"
- #include "ash/constants/ash_features.h"
- #include "ash/public/cpp/new_window_delegate.h"
- #include "ash/public/cpp/window_properties.h"
- #include "ash/screen_util.h"
- #include "ash/shell.h"
- #include "ash/shell_delegate.h"
- #include "ash/wm/overview/overview_controller.h"
- #include "ash/wm/overview/overview_session.h"
- #include "ash/wm/splitview/split_view_constants.h"
- #include "ash/wm/splitview/split_view_controller.h"
- #include "ash/wm/splitview/split_view_drag_indicators.h"
- #include "ash/wm/splitview/split_view_utils.h"
- #include "ash/wm/tablet_mode/tablet_mode_browser_window_drag_session_windows_hider.h"
- #include "ash/wm/wm_metrics.h"
- #include "base/pickle.h"
- #include "base/strings/utf_string_conversions.h"
- #include "chromeos/crosapi/cpp/lacros_startup_state.h"
- #include "ui/aura/client/aura_constants.h"
- #include "ui/base/clipboard/clipboard_format_type.h"
- #include "ui/base/clipboard/custom_data_helper.h"
- #include "ui/base/dragdrop/os_exchange_data.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/layer_animator.h"
- #include "ui/compositor/presentation_time_recorder.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- #include "ui/gfx/geometry/rect.h"
- #include "ui/wm/core/coordinate_conversion.h"
- namespace ash {
- namespace {
- // The following distances are copied from tablet_mode_window_drag_delegate.cc.
- // TODO(https://crbug.com/1069869): share these constants.
- // Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped
- // even if they have not moved by |kMinimumDragToSnapDistanceDp|.
- constexpr float kDistanceFromEdgeDp = 16.f;
- // The minimum distance that an item must be moved before it is snapped. This
- // prevents accidental snaps.
- constexpr float kMinimumDragToSnapDistanceDp = 96.f;
- // The scale factor that the source window should scale if the source window is
- // not the dragged window && is not in splitscreen when drag starts && the user
- // has dragged the window to pass the |kIndicatorThresholdRatio| vertical
- // threshold.
- constexpr float kSourceWindowScale = 0.85;
- // The UMA histogram that records presentation time for tab dragging in
- // tablet mode with webui tab strip enable.
- constexpr char kTabDraggingInTabletModeHistogram[] =
- "Ash.TabDrag.PresentationTime.TabletMode";
- constexpr char kTabDraggingInTabletModeMaxLatencyHistogram[] =
- "Ash.TabDrag.PresentationTime.MaxLatency.TabletMode";
- DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsSourceWindowForDrag, false)
- bool IsLacrosWindow(const aura::Window* window) {
- auto app_type =
- static_cast<AppType>(window->GetProperty(aura::client::kAppType));
- return app_type == AppType::LACROS;
- }
- // Returns the overview session if overview mode is active, otherwise returns
- // nullptr.
- OverviewSession* GetOverviewSession() {
- return Shell::Get()->overview_controller()->InOverviewSession()
- ? Shell::Get()->overview_controller()->overview_session()
- : nullptr;
- }
- } // namespace
- // static
- bool TabDragDropDelegate::IsChromeTabDrag(const ui::OSExchangeData& drag_data) {
- if (!features::IsWebUITabStripTabDragIntegrationEnabled())
- return false;
- return Shell::Get()->shell_delegate()->IsTabDrag(drag_data);
- }
- // static
- bool TabDragDropDelegate::IsSourceWindowForDrag(const aura::Window* window) {
- return window->GetProperty(kIsSourceWindowForDrag);
- }
- TabDragDropDelegate::TabDragDropDelegate(
- aura::Window* root_window,
- aura::Window* source_window,
- const gfx::Point& start_location_in_screen)
- : root_window_(root_window),
- source_window_(source_window->GetToplevelWindow()),
- start_location_in_screen_(start_location_in_screen) {
- DCHECK(root_window_);
- DCHECK(source_window_);
- source_window_->SetProperty(kIsSourceWindowForDrag, true);
- split_view_drag_indicators_ =
- std::make_unique<SplitViewDragIndicators>(root_window_);
- tab_dragging_recorder_ = CreatePresentationTimeHistogramRecorder(
- source_window_->layer()->GetCompositor(),
- kTabDraggingInTabletModeHistogram,
- kTabDraggingInTabletModeMaxLatencyHistogram);
- }
- TabDragDropDelegate::~TabDragDropDelegate() {
- tab_dragging_recorder_.reset();
- if (source_window_->is_destroying())
- return;
- if (!source_window_->GetProperty(kIsSourceWindowForDrag))
- return;
- // If we didn't drop to a new window, we must restore the original window.
- RestoreSourceWindowBounds();
- source_window_->ClearProperty(kIsSourceWindowForDrag);
- }
- void TabDragDropDelegate::DragUpdate(const gfx::Point& location_in_screen) {
- const gfx::Rect area =
- screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
- root_window_);
- SplitViewController::SnapPosition snap_position =
- ::ash::GetSnapPositionForLocation(
- Shell::GetPrimaryRootWindow(), location_in_screen,
- start_location_in_screen_,
- /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
- /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
- /*horizontal_edge_inset=*/area.width() *
- kHighlightScreenPrimaryAxisRatio +
- kHighlightScreenEdgePaddingDp,
- /*vertical_edge_inset=*/area.height() *
- kHighlightScreenPrimaryAxisRatio +
- kHighlightScreenEdgePaddingDp);
- if (ShouldPreventSnapToTheEdge(location_in_screen))
- snap_position = SplitViewController::SnapPosition::NONE;
- split_view_drag_indicators_->SetWindowDraggingState(
- SplitViewDragIndicators::ComputeWindowDraggingState(
- true, SplitViewDragIndicators::WindowDraggingState::kFromTop,
- snap_position));
- UpdateSourceWindowBoundsIfNecessary(snap_position, location_in_screen);
- tab_dragging_recorder_->RequestNext();
- }
- void TabDragDropDelegate::DropAndDeleteSelf(
- const gfx::Point& location_in_screen,
- const ui::OSExchangeData& drop_data) {
- tab_dragging_recorder_.reset();
- auto closure = base::BindOnce(&TabDragDropDelegate::OnNewBrowserWindowCreated,
- base::Owned(this), location_in_screen);
- NewWindowDelegate::GetPrimary()->NewWindowForDetachingTab(
- source_window_, drop_data, std::move(closure));
- }
- void TabDragDropDelegate::OnNewBrowserWindowCreated(
- const gfx::Point& location_in_screen,
- aura::Window* new_window) {
- auto is_lacros = IsLacrosWindow(source_window_);
- // https://crbug.com/1286203:
- // It's possible new window is created when the dragged WebContents
- // closes itself during the drag session.
- if (!new_window) {
- if (is_lacros && !crosapi::lacros_startup_state::IsLacrosPrimaryEnabled()) {
- LOG(ERROR)
- << "New browser window creation for tab detaching failed.\n"
- << "Check whether about:flags#lacros-primary is enabled or "
- << "--enable-features=LacrosPrimary is passed in when launching Ash";
- }
- return;
- }
- const gfx::Rect area =
- screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
- root_window_);
- SplitViewController::SnapPosition snap_position_in_snapping_zone =
- ash::GetSnapPosition(
- root_window_, new_window, location_in_screen,
- start_location_in_screen_,
- /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
- /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
- /*horizontal_edge_inset=*/area.width() *
- kHighlightScreenPrimaryAxisRatio +
- kHighlightScreenEdgePaddingDp,
- /*vertical_edge_inset=*/area.height() *
- kHighlightScreenPrimaryAxisRatio +
- kHighlightScreenEdgePaddingDp);
- if (ShouldPreventSnapToTheEdge(location_in_screen))
- snap_position_in_snapping_zone = SplitViewController::SnapPosition::NONE;
- if (snap_position_in_snapping_zone == SplitViewController::SnapPosition::NONE)
- RestoreSourceWindowBounds();
- // This must be done after restoring the source window's bounds since
- // otherwise the SetBounds() call may have no effect.
- source_window_->ClearProperty(kIsSourceWindowForDrag);
- SplitViewController* const split_view_controller =
- SplitViewController::Get(new_window);
- // If it's already in split view mode, either snap the new window
- // to the left or the right depending on the drop location.
- const bool in_split_view_mode = split_view_controller->InSplitViewMode();
- SplitViewController::SnapPosition snap_position =
- snap_position_in_snapping_zone;
- if (in_split_view_mode) {
- snap_position =
- split_view_controller->ComputeSnapPosition(location_in_screen);
- }
- if (snap_position == SplitViewController::SnapPosition::NONE)
- return;
- OverviewSession* overview_session = GetOverviewSession();
- // If overview session is present on the other side and the new window is
- // about to snap to that side but not in the snapping zone then drop the new
- // window into overview.
- if (overview_session &&
- snap_position_in_snapping_zone ==
- SplitViewController::SnapPosition::NONE &&
- split_view_controller->GetPositionOfSnappedWindow(source_window_) !=
- snap_position) {
- overview_session->MergeWindowIntoOverviewForWebUITabStrip(new_window);
- } else {
- WindowState::Get(new_window)
- ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
- split_view_controller->SnapWindow(new_window, snap_position,
- /*activate_window=*/true);
- }
- // Do not snap the source window if already in split view mode.
- if (in_split_view_mode)
- return;
- // The tab drag source window is the last window the user was
- // interacting with. When dropping into split view, it makes the most
- // sense to snap this window to the opposite side. Do this.
- SplitViewController::SnapPosition opposite_position =
- (snap_position == SplitViewController::SnapPosition::LEFT)
- ? SplitViewController::SnapPosition::RIGHT
- : SplitViewController::SnapPosition::LEFT;
- // |source_window_| is itself a child window of the browser since it
- // hosts web content (specifically, the tab strip WebUI). Snap its
- // toplevel window which is the browser window.
- WindowState::Get(new_window)
- ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
- split_view_controller->SnapWindow(source_window_, opposite_position);
- }
- bool TabDragDropDelegate::ShouldPreventSnapToTheEdge(
- const gfx::Point& location_in_screen) {
- SplitViewController* const split_view_controller =
- SplitViewController::Get(source_window_);
- return !split_view_controller->InSplitViewMode() &&
- split_view_controller->IsLayoutHorizontal(source_window_) &&
- location_in_screen.y() <
- Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight();
- }
- void TabDragDropDelegate::UpdateSourceWindowBoundsIfNecessary(
- SplitViewController::SnapPosition candidate_snap_position,
- const gfx::Point& location_in_screen) {
- SplitViewController* const split_view_controller =
- SplitViewController::Get(source_window_);
- if (split_view_controller->IsWindowInSplitView(source_window_))
- return;
- if (!windows_hider_) {
- windows_hider_ =
- std::make_unique<TabletModeBrowserWindowDragSessionWindowsHider>(
- source_window_, nullptr);
- }
- gfx::Rect new_source_window_bounds;
- if (candidate_snap_position == SplitViewController::SnapPosition::NONE) {
- const gfx::Rect area =
- screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
- root_window_);
- new_source_window_bounds = area;
- // Only shrink the window when the tab is dragged out of WebUI tab strip.
- if (location_in_screen.y() >
- Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight()) {
- new_source_window_bounds.ClampToCenteredSize(
- gfx::Size(area.width() * kSourceWindowScale,
- area.height() * kSourceWindowScale));
- }
- } else {
- const SplitViewController::SnapPosition opposite_position =
- (candidate_snap_position == SplitViewController::SnapPosition::LEFT)
- ? SplitViewController::SnapPosition::RIGHT
- : SplitViewController::SnapPosition::LEFT;
- new_source_window_bounds =
- SplitViewController::Get(source_window_)
- ->GetSnappedWindowBoundsInScreen(opposite_position, source_window_);
- }
- wm::ConvertRectFromScreen(source_window_->parent(),
- &new_source_window_bounds);
- if (new_source_window_bounds != source_window_->GetTargetBounds()) {
- ui::ScopedLayerAnimationSettings settings(
- source_window_->layer()->GetAnimator());
- settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
- source_window_->SetBounds(new_source_window_bounds);
- }
- }
- void TabDragDropDelegate::RestoreSourceWindowBounds() {
- if (SplitViewController::Get(source_window_)
- ->IsWindowInSplitView(source_window_))
- return;
- const gfx::Rect area =
- screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
- root_window_);
- source_window_->SetBounds(area);
- }
- } // namespace ash
|