blob: 827f401c8bc2285e7042a5e3565baff816cef015 [file] [log] [blame]
// 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 "ui/aura/mus/client_side_window_move_handler.h"
#include "base/bind.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/mus/window_tree_host_mus.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/events/gestures/gesture_recognizer.h"
namespace aura {
namespace {
// TODO(estade,mukai): De-dupe this constant and the following logic with
// WmToplevelWindowEventHandler.
constexpr int kDragStartTopEdgeInset = 8;
Window* GetToplevelTargetForEvent(ui::LocatedEvent* event, int* component) {
DCHECK(!event->handled());
auto* window = static_cast<Window*>(event->target());
if (!window || !window->delegate())
return nullptr;
*component = window->delegate()->GetNonClientComponent(event->location());
if (ui::CanPerformDragOrResize(*component)) {
DCHECK_EQ(window, window->GetToplevelWindow());
return window;
}
// Gestures can sometimes trigger drags from the client area. All other events
// and hit locations will not trigger a drag.
if (event->type() != ui::ET_GESTURE_SCROLL_BEGIN || *component != HTCLIENT)
return nullptr;
window = window->GetToplevelWindow();
if (!window->GetRootWindow()->GetProperty(
client::kGestureDragFromClientAreaTopMovesWindow)) {
return nullptr;
}
if (event->AsGestureEvent()->details().scroll_y_hint() < 0)
return nullptr;
const gfx::Point location_in_screen =
event->target()->GetScreenLocation(*event);
const gfx::Rect work_area_bounds =
display::Screen::GetScreen()->GetDisplayNearestWindow(window).work_area();
gfx::Rect hit_bounds_in_screen(work_area_bounds);
hit_bounds_in_screen.set_height(kDragStartTopEdgeInset);
// There may be a bezel sensor off screen logically above
// |hit_bounds_in_screen|. Handles the ET_GESTURE_SCROLL_BEGIN event
// triggered in the bezel area too.
bool in_bezel = location_in_screen.y() < hit_bounds_in_screen.y() &&
location_in_screen.x() >= hit_bounds_in_screen.x() &&
location_in_screen.x() < hit_bounds_in_screen.right();
if (hit_bounds_in_screen.Contains(location_in_screen) || in_bezel)
return window;
return nullptr;
}
int GetHitTestComponent(Window* window, const gfx::Point& location) {
if (!window->delegate())
return HTNOWHERE;
int component = window->delegate()->GetNonClientComponent(location);
if (!ui::IsResizingComponent(component))
return HTNOWHERE;
return component;
}
} // namespace
ClientSideWindowMoveHandler::ClientSideWindowMoveHandler(
Env* env,
WindowTreeClient* client)
: env_(env), client_(client) {
env_->AddPreTargetHandler(this);
}
ClientSideWindowMoveHandler::~ClientSideWindowMoveHandler() {
env_->RemovePreTargetHandler(this);
}
void ClientSideWindowMoveHandler::MaybeSetupLastTarget(
ui::LocatedEvent* event) {
// Do nothing in the middle of a dragging session, otherwise some properties
// (e.g. |last_component_| or |dragging_window_|) might get wrong. See:
// https://crbug.com/940545.
if (dragging_window_)
return;
last_target_.RemoveAll();
Window* window = GetToplevelTargetForEvent(event, &last_component_);
if (!window) {
last_component_ = HTNOWHERE;
return;
}
last_target_.Add(window);
last_location_ = event->location();
UpdateWindowResizeShadow(window, ui::IsResizingComponent(last_component_)
? last_component_
: HTNOWHERE);
}
void ClientSideWindowMoveHandler::UpdateWindowResizeShadow(Window* window,
int component) {
// Do nothing in the middle of a dragging session. See:
// https://crbug.com/940545.
if (dragging_window_)
return;
client_->SetWindowResizeShadow(window->GetRootWindow(), component);
last_shadow_target_.RemoveAll();
last_shadow_target_.Add(window);
}
void ClientSideWindowMoveHandler::MaybePerformWindowMove(
ui::LocatedEvent* event,
ws::mojom::MoveLoopSource source) {
// Do nothing in the middle of a dragging session. See:
// https://crbug.com/940545.
if (dragging_window_)
return;
Window* target = static_cast<Window*>(event->target());
if (!last_target_.Contains(target->GetToplevelWindow()))
return;
gfx::Point screen_location = last_location_;
auto* screen_position_client =
aura::client::GetScreenPositionClient(target->GetRootWindow());
// screen_position_client may be null in tests.
if (screen_position_client)
screen_position_client->ConvertPointToScreen(target, &screen_location);
WindowTreeHostMus::ForWindow(target)->PerformWindowMove(
target, source, screen_location, last_component_,
base::BindOnce(&ClientSideWindowMoveHandler::OnWindowMoveDone,
base::Unretained(this)));
// Clear |last_target_| so that event->target() won't match with
// |last_target_| anymore.
last_target_.RemoveAll();
dragging_window_ = target;
event->SetHandled();
}
void ClientSideWindowMoveHandler::OnMouseEvent(ui::MouseEvent* event) {
// The logic here should be aligned with ash::WmToplevelWindowEventHandler.
// TODO(mukai): create a common class in ash/public/cpp to share the logic.
if (event->handled())
return;
if ((event->flags() &
(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0) {
return;
}
switch (event->type()) {
case ui::ET_MOUSE_EXITED:
if (!last_shadow_target_.Contains(
static_cast<Window*>(event->target())) ||
last_component_ == HTNOWHERE) {
return;
}
client_->SetWindowResizeShadow(last_shadow_target_.Pop()->GetRootWindow(),
HTNOWHERE);
last_component_ = HTNOWHERE;
break;
case ui::ET_MOUSE_MOVED: {
if (!event->target()) {
last_component_ = HTNOWHERE;
last_shadow_target_.RemoveAll();
return;
}
Window* window = static_cast<Window*>(event->target());
int component = GetHitTestComponent(window, event->location());
if (component == last_component_)
return;
last_component_ = component;
UpdateWindowResizeShadow(window, last_component_);
break;
}
case ui::ET_MOUSE_PRESSED:
MaybeSetupLastTarget(event);
break;
case ui::ET_MOUSE_DRAGGED:
MaybePerformWindowMove(event, ws::mojom::MoveLoopSource::MOUSE);
break;
default:
// Do nothing.
break;
}
}
void ClientSideWindowMoveHandler::OnGestureEvent(ui::GestureEvent* event) {
// The logic here should be aligned with ash::WmToplevelWindowEventHandler.
// TODO(mukai): create a common class in ash/public/cpp to share the logic.
if (event->handled())
return;
// A drag may have already been started and gesture events transferred, but
// in-flight gesture events may still come through, targetting the original
// window. These need to be set to handled so that the client area doesn't
// complain about unexpected scroll updates or ends.
if (dragging_window_) {
event->SetHandled();
return;
}
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_BEGIN:
MaybeSetupLastTarget(event);
if (!last_target_.windows().empty())
event->SetHandled();
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
MaybePerformWindowMove(event, ws::mojom::MoveLoopSource::TOUCH);
break;
default:
// Do nothing.
break;
}
}
void ClientSideWindowMoveHandler::OnWindowMoveDone(bool success) {
dragging_window_ = nullptr;
}
} // namespace aura