// 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 "mash/simple_wm/move_loop.h" #include "base/auto_reset.h" #include "base/memory/ptr_util.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/base/hit_test.h" #include "ui/base/ui_base_types.h" #include "ui/events/event.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rect.h" namespace simple_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 std::unique_ptr MoveLoop::Create(aura::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 base::WrapUnique(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_details().id == pointer_id_) { if (target_) Revert(); return MoveResult::DONE; } return MoveResult::CONTINUE; case ui::ET_POINTER_MOVED: if (target_ && event.pointer_details().id == pointer_id_) MoveImpl(event); return MoveResult::CONTINUE; case ui::ET_POINTER_UP: if (event.pointer_details().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_); } MoveLoop::MoveLoop(aura::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_details().id), initial_event_screen_location_(event.root_location()), initial_window_bounds_(target->bounds()), initial_user_set_bounds_(target->bounds()), 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) { ui::WindowShowState show_state = target_->GetProperty(aura::client::kShowStateKey); // TODO(beng): figure out if there might not be another place to put this, // perhaps prior to move loop creation. if (show_state == ui::SHOW_STATE_MAXIMIZED) { base::AutoReset resetter(&changing_bounds_, true); target_->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); gfx::Rect restored_bounds = *target_->GetProperty(aura::client::kRestoreBoundsKey); // TODO(beng): Not just enough to adjust width and height, probably also // need to take some action to recenter the window relative to // the pointer position within the titlebar. initial_window_bounds_.set_width(restored_bounds.width()); initial_window_bounds_.set_height(restored_bounds.height()); } 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); } 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::OnWindowHierarchyChanged(const HierarchyChangeParams& params) { if (params.target == target_) Cancel(); } void MoveLoop::OnWindowBoundsChanged(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) { DCHECK_EQ(window, target_); if (!changing_bounds_) Cancel(); } void MoveLoop::OnWindowVisibilityChanged(aura::Window* window, bool visible) { DCHECK_EQ(window, target_); Cancel(); } } // namespace simple_wm