// Copyright 2015 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 "mash/wm/frame/move_loop.h" #include "base/auto_reset.h" #include "components/mus/public/cpp/window.h" #include "components/mus/public/interfaces/input_event_constants.mojom.h" #include "mash/wm/property_util.h" #include "ui/base/hit_test.h" #include "ui/events/event.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rect.h" namespace mash { namespace wm { namespace { int MouseOnlyEventFlags(int flags) { return flags & (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON); } } // namespace MoveLoop::~MoveLoop() { if (target_) target_->RemoveObserver(this); } // static scoped_ptr MoveLoop::Create(mus::Window* target, int ht_location, const ui::PointerEvent& event) { DCHECK_EQ(event.type(), ui::ET_POINTER_DOWN); // Start a move on left mouse, or any other type of pointer. if (event.IsMousePointerEvent() && MouseOnlyEventFlags(event.flags()) != ui::EF_LEFT_MOUSE_BUTTON) { return nullptr; } Type type; HorizontalLocation h_loc; VerticalLocation v_loc; if (!DetermineType(ht_location, &type, &h_loc, &v_loc)) return nullptr; return make_scoped_ptr(new MoveLoop(target, event, type, h_loc, v_loc)); } MoveLoop::MoveResult MoveLoop::Move(const ui::PointerEvent& event) { switch (event.type()) { case ui::ET_POINTER_CANCELLED: if (event.pointer_id() == pointer_id_) { if (target_) Revert(); return MoveResult::DONE; } return MoveResult::CONTINUE; case ui::ET_POINTER_MOVED: if (target_ && event.pointer_id() == pointer_id_) MoveImpl(event); return MoveResult::CONTINUE; case ui::ET_POINTER_UP: if (event.pointer_id() == pointer_id_) { // TODO(sky): need to support changed_flags. if (target_) MoveImpl(event); return MoveResult::DONE; } return MoveResult::CONTINUE; default: break; } return MoveResult::CONTINUE; } void MoveLoop::Revert() { if (!target_) return; base::AutoReset resetter(&changing_bounds_, true); target_->SetBounds(initial_window_bounds_); SetWindowUserSetBounds(target_, initial_user_set_bounds_); } MoveLoop::MoveLoop(mus::Window* target, const ui::PointerEvent& event, Type type, HorizontalLocation h_loc, VerticalLocation v_loc) : target_(target), type_(type), h_loc_(h_loc), v_loc_(v_loc), pointer_id_(event.pointer_id()), initial_event_screen_location_(event.root_location()), initial_window_bounds_(target->bounds()), initial_user_set_bounds_(GetWindowUserSetBounds(target)), changing_bounds_(false) { target->AddObserver(this); } // static bool MoveLoop::DetermineType(int ht_location, Type* type, HorizontalLocation* h_loc, VerticalLocation* v_loc) { *h_loc = HorizontalLocation::OTHER; *v_loc = VerticalLocation::OTHER; switch (ht_location) { case HTCAPTION: *type = Type::MOVE; *v_loc = VerticalLocation::TOP; return true; case HTTOPLEFT: *type = Type::RESIZE; *v_loc = VerticalLocation::TOP; *h_loc = HorizontalLocation::LEFT; return true; case HTTOP: *type = Type::RESIZE; *v_loc = VerticalLocation::TOP; return true; case HTTOPRIGHT: *type = Type::RESIZE; *v_loc = VerticalLocation::TOP; *h_loc = HorizontalLocation::RIGHT; return true; case HTRIGHT: *type = Type::RESIZE; *h_loc = HorizontalLocation::RIGHT; return true; case HTBOTTOMRIGHT: *type = Type::RESIZE; *v_loc = VerticalLocation::BOTTOM; *h_loc = HorizontalLocation::RIGHT; return true; case HTBOTTOM: *type = Type::RESIZE; *v_loc = VerticalLocation::BOTTOM; return true; case HTBOTTOMLEFT: *type = Type::RESIZE; *v_loc = VerticalLocation::BOTTOM; *h_loc = HorizontalLocation::LEFT; return true; case HTLEFT: *type = Type::RESIZE; *h_loc = HorizontalLocation::LEFT; return true; default: break; } return false; } void MoveLoop::MoveImpl(const ui::PointerEvent& event) { const gfx::Vector2d delta = event.root_location() - initial_event_screen_location_; const gfx::Rect new_bounds(DetermineBoundsFromDelta(delta)); base::AutoReset resetter(&changing_bounds_, true); target_->SetBounds(new_bounds); SetWindowUserSetBounds(target_, new_bounds); } void MoveLoop::Cancel() { target_->RemoveObserver(this); target_ = nullptr; } gfx::Rect MoveLoop::DetermineBoundsFromDelta(const gfx::Vector2d& delta) { if (type_ == Type::MOVE) { return gfx::Rect(initial_window_bounds_.origin() + delta, initial_window_bounds_.size()); } // TODO(sky): support better min sizes, make sure doesn't get bigger than // screen and max. Also make sure keep some portion on screen. gfx::Rect bounds(initial_window_bounds_); if (h_loc_ == HorizontalLocation::LEFT) { const int x = std::min(bounds.right() - 1, bounds.x() + delta.x()); const int width = bounds.right() - x; bounds.set_x(x); bounds.set_width(width); } else if (h_loc_ == HorizontalLocation::RIGHT) { bounds.set_width(std::max(1, bounds.width() + delta.x())); } if (v_loc_ == VerticalLocation::TOP) { const int y = std::min(bounds.bottom() - 1, bounds.y() + delta.y()); const int height = bounds.bottom() - y; bounds.set_y(y); bounds.set_height(height); } else if (v_loc_ == VerticalLocation::BOTTOM) { bounds.set_height(std::max(1, bounds.height() + delta.y())); } return bounds; } void MoveLoop::OnTreeChanged(const TreeChangeParams& params) { if (params.target == target_) Cancel(); } void MoveLoop::OnWindowBoundsChanged(mus::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) { DCHECK_EQ(window, target_); if (!changing_bounds_) Cancel(); } void MoveLoop::OnWindowVisibilityChanged(mus::Window* window) { DCHECK_EQ(window, target_); Cancel(); } } // namespace wm } // namespace mash