blob: 5f8746a2789731a57d1737c91e2c835761eb948f [file] [log] [blame]
// Copyright 2016 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 "ui/aura/mus/drag_drop_controller_mus.h"
#include <map>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/map.h"
#include "services/ws/public/mojom/window_tree.mojom.h"
#include "services/ws/public/mojom/window_tree_constants.mojom.h"
#include "ui/aura/client/drag_drop_client_observer.h"
#include "ui/aura/client/drag_drop_delegate.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/mus/drag_drop_controller_host.h"
#include "ui/aura/mus/mus_types.h"
#include "ui/aura/mus/os_exchange_data_provider_mus.h"
#include "ui/aura/mus/window_mus.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/dragdrop/drop_target_event.h"
// Interaction with DragDropDelegate assumes constants are the same.
static_assert(ui::DragDropTypes::DRAG_NONE == ws::mojom::kDropEffectNone,
"Drag constants must be the same");
static_assert(ui::DragDropTypes::DRAG_MOVE == ws::mojom::kDropEffectMove,
"Drag constants must be the same");
static_assert(ui::DragDropTypes::DRAG_COPY == ws::mojom::kDropEffectCopy,
"Drag constants must be the same");
static_assert(ui::DragDropTypes::DRAG_LINK == ws::mojom::kDropEffectLink,
"Drag constants must be the same");
namespace aura {
// State related to a drag initiated by this client.
struct DragDropControllerMus::CurrentDragState {
ws::Id window_id;
// The change id of the drag. Used to identify the drag on the server.
uint32_t change_id;
// The result of the drag. This is set on completion and returned to the
// caller.
uint32_t completed_action;
// OSExchangeData supplied to StartDragAndDrop().
const ui::OSExchangeData& drag_data;
// StartDragDrop() runs a nested run loop. This closure is used to quit
// the run loop when the drag completes.
base::Closure runloop_quit_closure;
// The starting location of the drag gesture in screen coordinates.
gfx::Point start_screen_location;
// A tracker for the window that received the initial drag and drop events,
// used to send ET_GESTURE_LONG_TAP when the user presses, pauses, and
// releases a touch without any movement; it is reset if movement is detected.
WindowTracker source_window_tracker;
};
DragDropControllerMus::DragDropControllerMus(
DragDropControllerHost* drag_drop_controller_host,
ws::mojom::WindowTree* window_tree)
: drag_drop_controller_host_(drag_drop_controller_host),
window_tree_(window_tree) {}
DragDropControllerMus::~DragDropControllerMus() {}
bool DragDropControllerMus::DoesChangeIdMatchDragChangeId(uint32_t id) const {
return current_drag_state_ && current_drag_state_->change_id == id;
}
void DragDropControllerMus::OnDragDropStart(
std::map<std::string, std::vector<uint8_t>> data) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>(
std::make_unique<aura::OSExchangeDataProviderMus>(std::move(data)));
}
uint32_t DragDropControllerMus::OnDragEnter(WindowMus* window,
uint32_t event_flags,
const gfx::PointF& location_in_root,
const gfx::PointF& location,
uint32_t effect_bitmask) {
return HandleDragEnterOrOver(window, event_flags, location_in_root, location,
effect_bitmask, true);
}
uint32_t DragDropControllerMus::OnDragOver(WindowMus* window,
uint32_t event_flags,
const gfx::PointF& location_in_root,
const gfx::PointF& location,
uint32_t effect_bitmask) {
return HandleDragEnterOrOver(window, event_flags, location_in_root, location,
effect_bitmask, false);
}
void DragDropControllerMus::OnDragLeave(WindowMus* window) {
if (drop_target_window_tracker_.windows().empty())
return;
DCHECK(window);
Window* current_target = drop_target_window_tracker_.Pop();
DCHECK_EQ(window->GetWindow(), current_target);
client::GetDragDropDelegate(current_target)->OnDragExited();
}
uint32_t DragDropControllerMus::OnCompleteDrop(
WindowMus* window,
uint32_t event_flags,
const gfx::PointF& location_in_root,
const gfx::PointF& location,
uint32_t effect_bitmask) {
if (drop_target_window_tracker_.windows().empty())
return ws::mojom::kDropEffectNone;
DCHECK(window);
Window* current_target = drop_target_window_tracker_.Pop();
DCHECK_EQ(window->GetWindow(), current_target);
std::unique_ptr<ui::DropTargetEvent> event =
CreateDropTargetEvent(window->GetWindow(), event_flags, location_in_root,
location, effect_bitmask);
return client::GetDragDropDelegate(current_target)->OnPerformDrop(*event);
}
void DragDropControllerMus::OnPerformDragDropCompleted(uint32_t action_taken) {
DCHECK(current_drag_state_);
for (client::DragDropClientObserver& observer : observers_)
observer.OnDragEnded();
// When the user presses, pauses, and releases a touch without any movement,
// that gesture should be interpreted as a long tap and show a menu, etc.
// See Classic Ash's long tap event handling in ash::DragDropController.
if (action_taken == ws::mojom::kDropEffectNone &&
!current_drag_state_->source_window_tracker.windows().empty()) {
auto* window = current_drag_state_->source_window_tracker.windows()[0];
gfx::Point location = current_drag_state_->start_screen_location;
if (auto* client = client::GetScreenPositionClient(window->GetRootWindow()))
client->ConvertPointFromScreen(window, &location);
ui::GestureEventDetails details(ui::ET_GESTURE_LONG_TAP);
details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent long_tap(location.x(), location.y(), ui::EF_NONE,
base::TimeTicks::Now(), details);
window->delegate()->OnEvent(&long_tap);
}
current_drag_state_->completed_action = action_taken;
current_drag_state_->runloop_quit_closure.Run();
current_drag_state_ = nullptr;
}
void DragDropControllerMus::OnDragDropDone() {
os_exchange_data_.reset();
}
int DragDropControllerMus::StartDragAndDrop(
const ui::OSExchangeData& data,
Window* root_window,
Window* source_window,
const gfx::Point& screen_location,
int drag_operations,
ui::DragDropTypes::DragEventSource source) {
DCHECK(!current_drag_state_);
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
WindowMus* source_window_mus = WindowMus::Get(source_window);
const uint32_t change_id =
drag_drop_controller_host_->CreateChangeIdForDrag(source_window_mus);
CurrentDragState current_drag_state = {
source_window_mus->server_id(), change_id,
ws::mojom::kDropEffectNone, data,
run_loop.QuitClosure(), screen_location};
// current_drag_state_ will be reset in |OnPerformDragDropCompleted| before
// run_loop.Run() quits.
current_drag_state_ = &current_drag_state;
ui::mojom::PointerKind mojo_source = ui::mojom::PointerKind::MOUSE;
if (source != ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
// TODO(erg): This collapses both touch and pen events to touch.
mojo_source = ui::mojom::PointerKind::TOUCH;
// Track the source window, to possibly send ET_GESTURE_LONG_TAP later.
current_drag_state.source_window_tracker.Add(source_window);
}
std::map<std::string, std::vector<uint8_t>> drag_data =
static_cast<const aura::OSExchangeDataProviderMus&>(data.provider())
.GetData();
for (client::DragDropClientObserver& observer : observers_)
observer.OnDragStarted();
window_tree_->PerformDragDrop(
change_id, source_window_mus->server_id(), screen_location,
mojo::MapToFlatMap(drag_data), data.provider().GetDragImage(),
data.provider().GetDragImageOffset(), drag_operations, mojo_source);
run_loop.Run();
return current_drag_state.completed_action;
}
void DragDropControllerMus::DragCancel() {
DCHECK(current_drag_state_);
// Server will clean up drag and fail the in-flight change.
window_tree_->CancelDragDrop(current_drag_state_->window_id);
}
bool DragDropControllerMus::IsDragDropInProgress() {
return current_drag_state_ != nullptr;
}
void DragDropControllerMus::AddObserver(
client::DragDropClientObserver* observer) {
observers_.AddObserver(observer);
}
void DragDropControllerMus::RemoveObserver(
client::DragDropClientObserver* observer) {
observers_.RemoveObserver(observer);
}
uint32_t DragDropControllerMus::HandleDragEnterOrOver(
WindowMus* window,
uint32_t event_flags,
const gfx::PointF& location_in_root,
const gfx::PointF& location,
uint32_t effect_bitmask,
bool is_enter) {
// Reset the tracker on drag movement, ET_GESTURE_LONG_TAP will not be needed.
current_drag_state_->source_window_tracker.RemoveAll();
client::DragDropDelegate* drag_drop_delegate =
window ? client::GetDragDropDelegate(window->GetWindow()) : nullptr;
WindowTreeHost* window_tree_host =
window ? window->GetWindow()->GetHost() : nullptr;
if ((!is_enter && drop_target_window_tracker_.windows().empty()) ||
!drag_drop_delegate || !window_tree_host) {
drop_target_window_tracker_.RemoveAll();
return ws::mojom::kDropEffectNone;
}
drop_target_window_tracker_.Add(window->GetWindow());
std::unique_ptr<ui::DropTargetEvent> event =
CreateDropTargetEvent(window->GetWindow(), event_flags, location_in_root,
location, effect_bitmask);
if (is_enter)
drag_drop_delegate->OnDragEntered(*event);
return drag_drop_delegate->OnDragUpdated(*event);
}
std::unique_ptr<ui::DropTargetEvent>
DragDropControllerMus::CreateDropTargetEvent(
Window* window,
uint32_t event_flags,
const gfx::PointF& location_in_root,
const gfx::PointF& location,
uint32_t effect_bitmask) {
std::unique_ptr<ui::DropTargetEvent> event =
std::make_unique<ui::DropTargetEvent>(
current_drag_state_ ? current_drag_state_->drag_data
: *(os_exchange_data_.get()),
location, location_in_root, effect_bitmask);
event->set_flags(event_flags);
ui::Event::DispatcherApi(event.get()).set_target(window);
return event;
}
} // namespace aura