| // 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/drag_drop/drag_drop_controller.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/drag_drop/drag_drop_tracker.h" |
| #include "ash/drag_drop/drag_image_view.h" |
| #include "ash/drag_drop/toplevel_window_drag_delegate.h" |
| #include "ash/shell.h" |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "ui/aura/client/capture_client.h" |
| #include "ui/aura/client/drag_drop_client_observer.h" |
| #include "ui/aura/client/drag_drop_delegate.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" |
| #include "ui/base/data_transfer_policy/data_transfer_endpoint.h" |
| #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h" |
| #include "ui/base/dragdrop/drag_drop_types.h" |
| #include "ui/base/dragdrop/os_exchange_data.h" |
| #include "ui/base/dragdrop/os_exchange_data_provider.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/gfx/animation/animation_delegate_notifier.h" |
| #include "ui/gfx/animation/linear_animation.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/views/animation/animation_delegate_views.h" |
| #include "ui/views/widget/native_widget_aura.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The duration of the drag cancel animation in millisecond. |
| constexpr base::TimeDelta kCancelAnimationDuration = |
| base::TimeDelta::FromMilliseconds(250); |
| constexpr base::TimeDelta kTouchCancelAnimationDuration = |
| base::TimeDelta::FromMilliseconds(20); |
| // The frame rate of the drag cancel animation in hertz. |
| const int kCancelAnimationFrameRate = 60; |
| |
| // For touch initiated dragging, we scale and shift drag image by the following: |
| static const float kTouchDragImageScale = 1.2f; |
| static const int kTouchDragImageVerticalOffset = -25; |
| |
| // Adjusts the drag image bounds such that the new bounds are scaled by |scale| |
| // and translated by the |drag_image_offset| and additional |vertical_offset|. |
| gfx::Rect AdjustDragImageBoundsForScaleAndOffset( |
| const gfx::Rect& drag_image_bounds, |
| int vertical_offset, |
| float scale, |
| gfx::Vector2d* drag_image_offset) { |
| gfx::Point final_origin = drag_image_bounds.origin(); |
| gfx::SizeF final_size = gfx::SizeF(drag_image_bounds.size()); |
| final_size.Scale(scale); |
| drag_image_offset->set_x(drag_image_offset->x() * scale); |
| drag_image_offset->set_y(drag_image_offset->y() * scale); |
| int total_x_offset = drag_image_offset->x(); |
| int total_y_offset = drag_image_offset->y() - vertical_offset; |
| final_origin.Offset(-total_x_offset, -total_y_offset); |
| return gfx::ToEnclosingRect( |
| gfx::RectF(gfx::PointF(final_origin), final_size)); |
| } |
| |
| void DispatchGestureEndToWindow(aura::Window* window) { |
| if (window && window->delegate()) { |
| ui::GestureEventDetails details(ui::ET_GESTURE_END); |
| details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_end(0, 0, 0, ui::EventTimeForNow(), details); |
| window->delegate()->OnGestureEvent(&gesture_end); |
| } |
| } |
| |
| bool IsDragDropAllowed(const ui::OSExchangeData* drag_data, |
| aura::client::DragUpdateInfo& drag_info, |
| bool is_drop) { |
| DCHECK(drag_data); |
| |
| return ui::DataTransferPolicyController::HasInstance() |
| ? ui::DataTransferPolicyController::Get()->IsDragDropAllowed( |
| drag_data->GetSource(), &drag_info.data_endpoint, is_drop) |
| : true; |
| } |
| |
| } // namespace |
| |
| class DragDropTrackerDelegate : public aura::WindowDelegate { |
| public: |
| explicit DragDropTrackerDelegate(DragDropController* controller) |
| : drag_drop_controller_(controller) {} |
| ~DragDropTrackerDelegate() override = default; |
| |
| // Overridden from WindowDelegate: |
| gfx::Size GetMinimumSize() const override { return gfx::Size(); } |
| |
| gfx::Size GetMaximumSize() const override { return gfx::Size(); } |
| |
| void OnBoundsChanged(const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds) override {} |
| gfx::NativeCursor GetCursor(const gfx::Point& point) override { |
| return gfx::kNullCursor; |
| } |
| int GetNonClientComponent(const gfx::Point& point) const override { |
| return HTCAPTION; |
| } |
| bool ShouldDescendIntoChildForEventHandling( |
| aura::Window* child, |
| const gfx::Point& location) override { |
| return true; |
| } |
| bool CanFocus() override { return true; } |
| void OnCaptureLost() override { |
| if (drag_drop_controller_->IsDragDropInProgress()) |
| drag_drop_controller_->DragCancel(); |
| } |
| void OnPaint(const ui::PaintContext& context) override {} |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| void OnWindowDestroying(aura::Window* window) override {} |
| void OnWindowDestroyed(aura::Window* window) override {} |
| void OnWindowTargetVisibilityChanged(bool visible) override {} |
| bool HasHitTestMask() const override { return true; } |
| void GetHitTestMask(SkPath* mask) const override { DCHECK(mask->isEmpty()); } |
| |
| private: |
| DragDropController* drag_drop_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DragDropController, public: |
| |
| DragDropController::DragDropController() |
| : drag_drop_window_delegate_(new DragDropTrackerDelegate(this)) { |
| Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem); |
| Shell::Get()->window_tree_host_manager()->AddObserver(this); |
| } |
| |
| DragDropController::~DragDropController() { |
| Shell::Get()->window_tree_host_manager()->RemoveObserver(this); |
| Shell::Get()->RemovePreTargetHandler(this); |
| Cleanup(); |
| if (cancel_animation_) |
| cancel_animation_->End(); |
| drag_image_widget_.reset(); |
| } |
| |
| int DragDropController::StartDragAndDrop( |
| std::unique_ptr<ui::OSExchangeData> data, |
| aura::Window* root_window, |
| aura::Window* source_window, |
| const gfx::Point& screen_location, |
| int operation, |
| ui::mojom::DragEventSource source) { |
| if (!enabled_ || IsDragDropInProgress()) |
| return 0; |
| |
| const ui::OSExchangeDataProvider* provider = &data->provider(); |
| // We do not support touch drag/drop without a drag image. |
| if (source == ui::mojom::DragEventSource::kTouch && |
| provider->GetDragImage().size().IsEmpty()) |
| return 0; |
| |
| current_drag_event_source_ = source; |
| DragDropTracker* tracker = |
| new DragDropTracker(root_window, drag_drop_window_delegate_.get()); |
| if (source == ui::mojom::DragEventSource::kTouch) { |
| // We need to transfer the current gesture sequence and the GR's touch event |
| // queue to the |drag_drop_tracker_|'s capture window so that when it takes |
| // capture, it still gets a valid gesture state. |
| aura::Env::GetInstance()->gesture_recognizer()->TransferEventsTo( |
| source_window, tracker->capture_window(), |
| ui::TransferTouchesBehavior::kCancel); |
| // We also send a gesture end to the source window so it can clear state. |
| // TODO(varunjain): Remove this whole block when gesture sequence |
| // transferring is properly done in the GR (http://crbug.com/160558) |
| DispatchGestureEndToWindow(source_window); |
| } |
| tracker->TakeCapture(); |
| drag_drop_tracker_.reset(tracker); |
| drag_source_window_ = source_window; |
| if (drag_source_window_) |
| drag_source_window_->AddObserver(this); |
| pending_long_tap_.reset(); |
| |
| drag_data_ = std::move(data); |
| drag_operation_ = operation; |
| current_drag_info_ = aura::client::DragUpdateInfo(); |
| |
| start_location_ = screen_location; |
| current_location_ = screen_location; |
| |
| SetDragImage(provider->GetDragImage(), provider->GetDragImageOffset()); |
| |
| drag_window_ = nullptr; |
| |
| // Ends cancel animation if it's in progress. |
| if (cancel_animation_) |
| cancel_animation_->End(); |
| |
| for (aura::client::DragDropClientObserver& observer : observers_) |
| observer.OnDragStarted(); |
| |
| if (toplevel_window_drag_delegate_) { |
| toplevel_window_drag_delegate_->OnToplevelWindowDragStarted( |
| gfx::PointF(start_location_), source); |
| } |
| |
| if (TabDragDropDelegate::IsChromeTabDrag(*drag_data_)) { |
| DCHECK(!tab_drag_drop_delegate_); |
| tab_drag_drop_delegate_.emplace(root_window, drag_source_window_, |
| start_location_); |
| static_cast<DragImageView*>(drag_image_widget_->GetContentsView()) |
| ->SetTouchDragOperationHintOff(); |
| } |
| |
| if (should_block_during_drag_drop_) { |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| if (!cancel_animation_.get() || !cancel_animation_->is_animating() || |
| !pending_long_tap_.get()) { |
| // If drag cancel animation is running, this cleanup is done when the |
| // animation completes. |
| if (drag_source_window_) |
| drag_source_window_->RemoveObserver(this); |
| drag_source_window_ = nullptr; |
| } |
| |
| return drag_operation_; |
| } |
| |
| void DragDropController::SetDragImage(const gfx::ImageSkia& image, |
| const gfx::Vector2d& image_offset) { |
| auto source = current_drag_event_source_; |
| auto* source_window = drag_source_window_; |
| |
| float drag_image_scale = 1; |
| int drag_image_vertical_offset = 0; |
| if (source == ui::mojom::DragEventSource::kTouch) { |
| drag_image_scale = kTouchDragImageScale; |
| drag_image_vertical_offset = kTouchDragImageVerticalOffset; |
| } |
| drag_image_final_bounds_for_cancel_animation_ = |
| gfx::Rect(start_location_ - image_offset, image.size()); |
| |
| if (!drag_image_widget_) { |
| drag_image_widget_ = |
| DragImageView::Create(source_window->GetRootWindow(), source); |
| } |
| |
| DragImageView* drag_image = |
| static_cast<DragImageView*>(drag_image_widget_->GetContentsView()); |
| drag_image->SetImage(image); |
| drag_image_offset_ = image_offset; |
| gfx::Rect drag_image_bounds(current_location_, |
| drag_image->GetPreferredSize()); |
| drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset( |
| drag_image_bounds, drag_image_vertical_offset, drag_image_scale, |
| &drag_image_offset_); |
| drag_image->SetBoundsInScreen(drag_image_bounds); |
| drag_image->SetWidgetVisible(true); |
| if (source == ui::mojom::DragEventSource::kTouch) { |
| drag_image->SetTouchDragOperationHintPosition( |
| gfx::Point(drag_image_offset_.x(), |
| drag_image_offset_.y() + drag_image_vertical_offset)); |
| } |
| } |
| |
| void DragDropController::DragCancel() { |
| DCHECK(enabled_); |
| DoDragCancel(kCancelAnimationDuration); |
| } |
| |
| bool DragDropController::IsDragDropInProgress() { |
| return !!drag_drop_tracker_ && !!drag_data_; |
| } |
| |
| void DragDropController::AddObserver( |
| aura::client::DragDropClientObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void DragDropController::RemoveObserver( |
| aura::client::DragDropClientObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void DragDropController::OnKeyEvent(ui::KeyEvent* event) { |
| if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) { |
| DragCancel(); |
| event->StopPropagation(); |
| } |
| } |
| |
| void DragDropController::OnMouseEvent(ui::MouseEvent* event) { |
| if (!IsDragDropInProgress()) |
| return; |
| |
| // If current drag session was not started by mouse, dont process this mouse |
| // event, but consume it so it does not interfere with current drag session. |
| if (current_drag_event_source_ != ui::mojom::DragEventSource::kMouse) { |
| event->StopPropagation(); |
| return; |
| } |
| |
| aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event); |
| if (!translated_target) { |
| DragCancel(); |
| event->StopPropagation(); |
| return; |
| } |
| std::unique_ptr<ui::LocatedEvent> translated_event( |
| drag_drop_tracker_->ConvertEvent(translated_target, *event)); |
| switch (translated_event->type()) { |
| case ui::ET_MOUSE_DRAGGED: |
| DragUpdate(translated_target, *translated_event.get()); |
| break; |
| case ui::ET_MOUSE_RELEASED: |
| Drop(translated_target, *translated_event.get()); |
| break; |
| default: |
| // We could also reach here because RootWindow may sometimes generate a |
| // bunch of fake mouse events |
| // (aura::RootWindow::PostMouseMoveEventAfterWindowChange). |
| break; |
| } |
| |
| if (toplevel_window_drag_delegate_) |
| toplevel_window_drag_delegate_->OnToplevelWindowDragEvent(event); |
| |
| event->StopPropagation(); |
| } |
| |
| void DragDropController::OnTouchEvent(ui::TouchEvent* event) { |
| if (!IsDragDropInProgress()) |
| return; |
| |
| // If current drag session was not started by touch, dont process this touch |
| // event, but consume it so it does not interfere with current drag session. |
| if (current_drag_event_source_ != ui::mojom::DragEventSource::kTouch) |
| event->StopPropagation(); |
| |
| if (event->handled()) |
| return; |
| |
| if (event->type() == ui::ET_TOUCH_CANCELLED) |
| DragCancel(); |
| } |
| |
| void DragDropController::OnGestureEvent(ui::GestureEvent* event) { |
| if (!IsDragDropInProgress()) |
| return; |
| |
| // No one else should handle gesture events when in drag drop. Note that it is |
| // not enough to just set ER_HANDLED because the dispatcher only stops |
| // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the |
| // event will still be dispatched to other handlers and we depend on |
| // individual handlers' kindness to not touch events marked ER_HANDLED (not |
| // all handlers are so kind and may cause bugs like crbug.com/236493). |
| event->StopPropagation(); |
| |
| // If current drag session was not started by touch, dont process this event. |
| if (current_drag_event_source_ != ui::mojom::DragEventSource::kTouch) |
| return; |
| |
| // Apply kTouchDragImageVerticalOffset to the location. |
| ui::GestureEvent touch_offset_event(*event, |
| static_cast<aura::Window*>(nullptr), |
| static_cast<aura::Window*>(nullptr)); |
| gfx::PointF touch_offset_location = touch_offset_event.location_f(); |
| gfx::PointF touch_offset_root_location = touch_offset_event.root_location_f(); |
| touch_offset_location.Offset(0, kTouchDragImageVerticalOffset); |
| touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset); |
| touch_offset_event.set_location_f(touch_offset_location); |
| touch_offset_event.set_root_location_f(touch_offset_root_location); |
| |
| aura::Window* translated_target = |
| drag_drop_tracker_->GetTarget(touch_offset_event); |
| if (!translated_target) { |
| DragCancel(); |
| event->SetHandled(); |
| return; |
| } |
| std::unique_ptr<ui::LocatedEvent> translated_event( |
| drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event)); |
| |
| switch (event->type()) { |
| case ui::ET_GESTURE_SCROLL_UPDATE: |
| DragUpdate(translated_target, *translated_event.get()); |
| break; |
| case ui::ET_GESTURE_SCROLL_END: |
| case ui::ET_SCROLL_FLING_START: |
| Drop(translated_target, *translated_event.get()); |
| break; |
| case ui::ET_GESTURE_LONG_TAP: |
| // Ideally we would want to just forward this long tap event to the |
| // |drag_source_window_|. However, webkit does not accept events while a |
| // drag drop is still in progress. The drag drop ends only when the nested |
| // message loop ends. Due to this stupidity, we have to defer forwarding |
| // the long tap. |
| pending_long_tap_.reset(new ui::GestureEvent( |
| *event, |
| static_cast<aura::Window*>(drag_drop_tracker_->capture_window()), |
| static_cast<aura::Window*>(drag_source_window_))); |
| DoDragCancel(kTouchCancelAnimationDuration); |
| break; |
| default: |
| break; |
| } |
| event->SetHandled(); |
| } |
| |
| void DragDropController::OnWindowDestroyed(aura::Window* window) { |
| if (drag_window_ == window) |
| drag_window_ = nullptr; |
| if (drag_source_window_ == window) |
| drag_source_window_ = nullptr; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DragDropController, protected: |
| |
| gfx::LinearAnimation* DragDropController::CreateCancelAnimation( |
| base::TimeDelta duration, |
| int frame_rate, |
| gfx::AnimationDelegate* delegate) { |
| return new gfx::LinearAnimation(duration, frame_rate, delegate); |
| } |
| |
| void DragDropController::DragUpdate(aura::Window* target, |
| const ui::LocatedEvent& event) { |
| aura::client::DragUpdateInfo drag_info; |
| if (target != drag_window_) { |
| if (drag_window_) { |
| aura::client::DragDropDelegate* delegate = |
| aura::client::GetDragDropDelegate(drag_window_); |
| if (delegate) |
| delegate->OnDragExited(); |
| if (drag_window_ != drag_source_window_) |
| drag_window_->RemoveObserver(this); |
| } |
| drag_window_ = target; |
| // We are already an observer of |drag_source_window_| so no need to add. |
| if (drag_window_ != drag_source_window_) |
| drag_window_->AddObserver(this); |
| aura::client::DragDropDelegate* delegate = |
| aura::client::GetDragDropDelegate(drag_window_); |
| if (delegate) { |
| ui::DropTargetEvent e(*drag_data_.get(), event.location_f(), |
| event.root_location_f(), drag_operation_); |
| e.set_flags(event.flags()); |
| ui::Event::DispatcherApi(&e).set_target(target); |
| delegate->OnDragEntered(e); |
| } |
| } else { |
| aura::client::DragDropDelegate* delegate = |
| aura::client::GetDragDropDelegate(drag_window_); |
| if (delegate) { |
| ui::DropTargetEvent e(*drag_data_.get(), event.location_f(), |
| event.root_location_f(), drag_operation_); |
| e.set_flags(event.flags()); |
| ui::Event::DispatcherApi(&e).set_target(target); |
| drag_info = delegate->OnDragUpdated(e); |
| bool is_drop_allowed = IsDragDropAllowed(drag_data_.get(), drag_info, |
| /*is_drop=*/false); |
| gfx::NativeCursor cursor = ui::mojom::CursorType::kNoDrop; |
| if (is_drop_allowed) { |
| if (drag_info.drag_operation & ui::DragDropTypes::DRAG_COPY) |
| cursor = ui::mojom::CursorType::kCopy; |
| else if (drag_info.drag_operation & ui::DragDropTypes::DRAG_LINK) |
| cursor = ui::mojom::CursorType::kAlias; |
| else if (drag_info.drag_operation & ui::DragDropTypes::DRAG_MOVE) |
| cursor = ui::mojom::CursorType::kGrabbing; |
| } else { |
| drag_info.drag_operation = ui::DragDropTypes::DRAG_NONE; |
| } |
| Shell::Get()->cursor_manager()->SetCursor(cursor); |
| } |
| } |
| |
| if (drag_info.drag_operation != current_drag_info_.drag_operation) { |
| for (aura::client::DragDropClientObserver& observer : observers_) |
| observer.OnDragActionsChanged(drag_info.drag_operation); |
| } |
| current_drag_info_ = drag_info; |
| |
| gfx::Point root_location_in_screen = event.root_location(); |
| ::wm::ConvertPointToScreen(target->GetRootWindow(), &root_location_in_screen); |
| |
| DCHECK(drag_image_widget_); |
| DragImageView* drag_image = |
| static_cast<DragImageView*>(drag_image_widget_->GetContentsView()); |
| if (drag_image->GetVisible()) { |
| current_location_ = root_location_in_screen; |
| drag_image->SetScreenPosition(root_location_in_screen - drag_image_offset_); |
| drag_image->SetTouchDragOperation(drag_info.drag_operation); |
| } |
| |
| if (tab_drag_drop_delegate_) { |
| // TabDragDropDelegate assumes the root window doesn't change. Tab drags are |
| // only seen in tablet mode which precludes dragging between displays. |
| // DCHECK just to make sure. |
| DCHECK_EQ(target->GetRootWindow(), tab_drag_drop_delegate_->root_window()); |
| |
| tab_drag_drop_delegate_->DragUpdate(root_location_in_screen); |
| } |
| } |
| |
| void DragDropController::Drop(aura::Window* target, |
| const ui::LocatedEvent& event) { |
| // We must guarantee that a target gets a OnDragEntered before Drop. WebKit |
| // depends on not getting a Drop without DragEnter. This behavior is |
| // consistent with drag/drop on other platforms. |
| if (target != drag_window_) |
| DragUpdate(target, event); |
| DCHECK(target == drag_window_); |
| |
| if (!IsDragDropAllowed(drag_data_.get(), current_drag_info_, |
| /*is_drop=*/true)) { |
| DragCancel(); |
| return; |
| } |
| |
| Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kPointer); |
| |
| aura::client::DragDropDelegate* delegate = |
| aura::client::GetDragDropDelegate(target); |
| if (delegate) { |
| ui::DropTargetEvent e(*drag_data_.get(), event.location_f(), |
| event.root_location_f(), drag_operation_); |
| e.set_flags(event.flags()); |
| ui::Event::DispatcherApi(&e).set_target(target); |
| |
| ui::OSExchangeData copied_data(drag_data_->provider().Clone()); |
| drag_operation_ = delegate->OnPerformDrop(e, std::move(drag_data_)); |
| if (drag_operation_ == 0 && tab_drag_drop_delegate_) { |
| gfx::Point location_in_screen = event.root_location(); |
| ::wm::ConvertPointToScreen(target->GetRootWindow(), &location_in_screen); |
| tab_drag_drop_delegate_->Drop(location_in_screen, copied_data); |
| // Override the drag event's drop effect as a move to inform the front-end |
| // that the tab or group was moved. Otherwise, the WebUI tab strip does |
| // not know that a drop resulted in a tab being moved and will temporarily |
| // visually return the tab to its original position. (crbug.com/1081905) |
| drag_operation_ = ui::DragDropTypes::DragOperation::DRAG_MOVE; |
| StartCanceledAnimation(kCancelAnimationDuration); |
| } else if (drag_operation_ == 0) { |
| StartCanceledAnimation(kCancelAnimationDuration); |
| } else { |
| drag_image_widget_.reset(); |
| } |
| } else { |
| drag_image_widget_.reset(); |
| } |
| |
| if (toplevel_window_drag_delegate_) { |
| drag_operation_ = |
| toplevel_window_drag_delegate_->OnToplevelWindowDragDropped(); |
| } |
| |
| Cleanup(); |
| if (should_block_during_drag_drop_) |
| std::move(quit_closure_).Run(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DragDropController, private: |
| |
| void DragDropController::AnimationEnded(const gfx::Animation* animation) { |
| cancel_animation_.reset(); |
| cancel_animation_notifier_.reset(); |
| |
| // By the time we finish animation, another drag/drop session may have |
| // started. We do not want to destroy the drag image in that case. |
| if (!IsDragDropInProgress()) |
| drag_image_widget_.reset(); |
| if (pending_long_tap_) { |
| // If not in a nested run loop, we can forward the long tap right now. |
| if (!should_block_during_drag_drop_) { |
| ForwardPendingLongTap(); |
| } else { |
| // See comment about this in OnGestureEvent(). |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&DragDropController::ForwardPendingLongTap, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| } |
| |
| void DragDropController::DoDragCancel( |
| base::TimeDelta drag_cancel_animation_duration) { |
| Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kPointer); |
| |
| // |drag_window_| can be NULL if we have just started the drag and have not |
| // received any DragUpdates, or, if the |drag_window_| gets destroyed during |
| // a drag/drop. |
| aura::client::DragDropDelegate* delegate = |
| drag_window_ ? aura::client::GetDragDropDelegate(drag_window_) : nullptr; |
| if (delegate) |
| delegate->OnDragExited(); |
| |
| if (toplevel_window_drag_delegate_) |
| toplevel_window_drag_delegate_->OnToplevelWindowDragCancelled(); |
| |
| Cleanup(); |
| drag_operation_ = 0; |
| StartCanceledAnimation(drag_cancel_animation_duration); |
| if (should_block_during_drag_drop_) |
| std::move(quit_closure_).Run(); |
| } |
| |
| void DragDropController::AnimationProgressed(const gfx::Animation* animation) { |
| gfx::Rect current_bounds = animation->CurrentValueBetween( |
| drag_image_initial_bounds_for_cancel_animation_, |
| drag_image_final_bounds_for_cancel_animation_); |
| static_cast<DragImageView*>(drag_image_widget_->GetContentsView()) |
| ->SetBoundsInScreen(current_bounds); |
| } |
| |
| void DragDropController::AnimationCanceled(const gfx::Animation* animation) { |
| AnimationEnded(animation); |
| } |
| |
| void DragDropController::OnDisplayConfigurationChanging() { |
| // Abort in-progress drags if a monitor is added or removed because the drag |
| // image widget's container may be destroyed. |
| if (IsDragDropInProgress()) |
| DragCancel(); |
| } |
| |
| void DragDropController::StartCanceledAnimation( |
| base::TimeDelta animation_duration) { |
| DCHECK(drag_image_widget_); |
| DragImageView* drag_image = |
| static_cast<DragImageView*>(drag_image_widget_->GetContentsView()); |
| drag_image->SetTouchDragOperationHintOff(); |
| drag_image_initial_bounds_for_cancel_animation_ = |
| drag_image->GetBoundsInScreen(); |
| cancel_animation_notifier_ = std::make_unique< |
| gfx::AnimationDelegateNotifier<views::AnimationDelegateViews>>( |
| this, drag_image); |
| cancel_animation_.reset( |
| CreateCancelAnimation(animation_duration, kCancelAnimationFrameRate, |
| cancel_animation_notifier_.get())); |
| cancel_animation_->Start(); |
| } |
| |
| void DragDropController::ForwardPendingLongTap() { |
| if (drag_source_window_ && drag_source_window_->delegate()) { |
| drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get()); |
| DispatchGestureEndToWindow(drag_source_window_); |
| } |
| pending_long_tap_.reset(); |
| if (drag_source_window_) |
| drag_source_window_->RemoveObserver(this); |
| drag_source_window_ = nullptr; |
| } |
| |
| void DragDropController::Cleanup() { |
| for (aura::client::DragDropClientObserver& observer : observers_) |
| observer.OnDragEnded(); |
| if (drag_window_) |
| drag_window_->RemoveObserver(this); |
| drag_window_ = nullptr; |
| drag_data_.reset(); |
| |
| tab_drag_drop_delegate_.reset(); |
| |
| // Cleanup can be called again while deleting DragDropTracker, so delete |
| // the pointer with a local variable to avoid double free. |
| std::unique_ptr<DragDropTracker> holder = std::move(drag_drop_tracker_); |
| } |
| |
| } // namespace ash |