blob: 583c3d7d2f9d374bbf09ff496b9518433eaf7b2f [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/drop_helper.h"
#include <memory>
#include <set>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
using ::ui::mojom::DragOperation;
const View* g_drag_entered_callback_view = nullptr;
base::RepeatingClosure* GetDragEnteredCallback() {
static base::NoDestructor<base::RepeatingClosure> callback;
return callback.get();
}
} // namespace
DropHelper::DropHelper(View* root_view) : root_view_(root_view) {}
DropHelper::~DropHelper() = default;
// static
void DropHelper::SetDragEnteredCallbackForTesting(
const View* view,
base::RepeatingClosure callback) {
g_drag_entered_callback_view = view;
*GetDragEnteredCallback() = std::move(callback);
}
void DropHelper::ResetTargetViewIfEquals(View* view) {
if (target_view_ == view) {
target_view_ = nullptr;
}
if (deepest_view_ == view) {
deepest_view_ = nullptr;
}
}
int DropHelper::OnDragOver(const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
const View* old_deepest_view = deepest_view_;
View* view =
CalculateTargetViewImpl(root_view_location, data, true, &deepest_view_);
if (view != target_view_) {
// Target changed. Notify old drag exited, then new drag entered.
NotifyDragExit();
target_view_ = view;
NotifyDragEntered(data, root_view_location, drag_operation);
}
// Notify testing callback if the drag newly moved over the target view.
if (g_drag_entered_callback_view &&
g_drag_entered_callback_view->Contains(deepest_view_) &&
!g_drag_entered_callback_view->Contains(old_deepest_view)) {
auto* callback = GetDragEnteredCallback();
if (!callback->is_null()) {
callback->Run();
}
}
return NotifyDragOver(data, root_view_location, drag_operation);
}
void DropHelper::OnDragExit() {
NotifyDragExit();
deepest_view_ = target_view_ = nullptr;
}
DragOperation DropHelper::OnDrop(const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
View* drop_view = target_view_;
deepest_view_ = target_view_ = nullptr;
if (!drop_view) {
return DragOperation::kNone;
}
if (drag_operation == ui::DragDropTypes::DRAG_NONE) {
drop_view->OnDragExited();
return DragOperation::kNone;
}
gfx::Point view_location(root_view_location);
View* root_view = drop_view->GetWidget()->GetRootView();
View::ConvertPointToTarget(root_view, drop_view, &view_location);
ui::DropTargetEvent drop_event(data, gfx::PointF(view_location),
gfx::PointF(root_view_location),
drag_operation);
auto output_drag_op = ui::mojom::DragOperation::kNone;
auto drop_cb = drop_view->GetDropCallback(drop_event);
std::move(drop_cb).Run(drop_event, output_drag_op,
/*drag_image_layer_owner=*/nullptr);
return output_drag_op;
}
DropHelper::DropCallback DropHelper::GetDropCallback(
const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
View* drop_view = target_view_;
deepest_view_ = target_view_ = nullptr;
if (!drop_view) {
return base::NullCallback();
}
if (drag_operation == ui::DragDropTypes::DRAG_NONE) {
drop_view->OnDragExited();
return base::NullCallback();
}
gfx::Point view_location(root_view_location);
View* root_view = drop_view->GetWidget()->GetRootView();
View::ConvertPointToTarget(root_view, drop_view, &view_location);
ui::DropTargetEvent drop_event(data, gfx::PointF(view_location),
gfx::PointF(root_view_location),
drag_operation);
auto drop_view_cb = drop_view->GetDropCallback(drop_event);
if (!drop_view_cb) {
return base::NullCallback();
}
return base::BindOnce(
[](const ui::DropTargetEvent& drop_event, View::DropCallback drop_cb,
std::unique_ptr<ui::OSExchangeData> data,
ui::mojom::DragOperation& output_drag_op,
std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
// Bind the drop_event here instead of using the one that the callback
// is invoked with as that event is in window coordinates and callbacks
// expect View coordinates.
std::move(drop_cb).Run(drop_event, output_drag_op,
std::move(drag_image_layer_owner));
},
drop_event, std::move(drop_view_cb));
}
View* DropHelper::CalculateTargetView(const gfx::Point& root_view_location,
const OSExchangeData& data,
bool check_can_drop) {
return CalculateTargetViewImpl(root_view_location, data, check_can_drop,
nullptr);
}
View* DropHelper::CalculateTargetViewImpl(const gfx::Point& root_view_location,
const OSExchangeData& data,
bool check_can_drop,
raw_ptr<View>* deepest_view) {
View* view = root_view_->GetEventHandlerForPoint(root_view_location);
if (view == deepest_view_) {
// The view the mouse is over hasn't changed; reuse the target.
return target_view_;
}
if (deepest_view) {
*deepest_view = view;
}
// TODO(sky): for the time being these are separate. Once I port chrome menu
// I can switch to the #else implementation and nuke the OS_WIN
// implementation.
#if BUILDFLAG(IS_WIN)
// View under mouse changed, which means a new view may want the drop.
// Walk the tree, stopping at target_view_ as we know it'll accept the
// drop.
while (view && view != target_view_ &&
(!view->GetEnabled() || !view->CanDrop(data))) {
view = view->parent();
}
#else
int formats = 0;
std::set<ui::ClipboardFormatType> format_types;
while (view && view != target_view_) {
if (view->GetEnabled() && view->GetDropFormats(&formats, &format_types) &&
data.HasAnyFormat(formats, format_types) &&
(!check_can_drop || view->CanDrop(data))) {
// Found the view.
return view;
}
formats = 0;
format_types.clear();
view = view->parent();
}
#endif
return view;
}
void DropHelper::NotifyDragEntered(const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
if (!target_view_) {
return;
}
gfx::Point target_view_location(root_view_location);
View::ConvertPointToTarget(root_view_, target_view_, &target_view_location);
ui::DropTargetEvent enter_event(data, gfx::PointF(target_view_location),
gfx::PointF(root_view_location),
drag_operation);
target_view_->OnDragEntered(enter_event);
}
int DropHelper::NotifyDragOver(const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
if (!target_view_) {
return ui::DragDropTypes::DRAG_NONE;
}
gfx::Point target_view_location(root_view_location);
View::ConvertPointToTarget(root_view_, target_view_, &target_view_location);
ui::DropTargetEvent enter_event(data, gfx::PointF(target_view_location),
gfx::PointF(root_view_location),
drag_operation);
return target_view_->OnDragUpdated(enter_event);
}
void DropHelper::NotifyDragExit() {
if (target_view_) {
target_view_->OnDragExited();
}
}
} // namespace views