| // Copyright (c) 2012 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 "ash/wm/toplevel_window_event_handler.h" |
| |
| #include "ash/shell.h" |
| #include "ash/wm/resize_shadow_controller.h" |
| #include "ash/wm/window_resizer.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_state_observer.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/workspace/snap_sizer.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "ui/aura/client/cursor_client.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/root_window.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/gestures/gesture_recognizer.h" |
| #include "ui/gfx/screen.h" |
| |
| namespace { |
| const double kMinHorizVelocityForWindowSwipe = 1100; |
| const double kMinVertVelocityForWindowMinimize = 1000; |
| } |
| |
| namespace ash { |
| |
| namespace { |
| |
| gfx::Point ConvertPointToParent(aura::Window* window, |
| const gfx::Point& point) { |
| gfx::Point result(point); |
| aura::Window::ConvertPointToTarget(window, window->parent(), &result); |
| return result; |
| } |
| |
| } // namespace |
| |
| // ScopedWindowResizer --------------------------------------------------------- |
| |
| // Wraps a WindowResizer and installs an observer on its target window. When |
| // the window is destroyed ResizerWindowDestroyed() is invoked back on the |
| // ToplevelWindowEventHandler to clean up. |
| class ToplevelWindowEventHandler::ScopedWindowResizer |
| : public aura::WindowObserver, |
| public wm::WindowStateObserver { |
| public: |
| ScopedWindowResizer(ToplevelWindowEventHandler* handler, |
| WindowResizer* resizer); |
| virtual ~ScopedWindowResizer(); |
| |
| WindowResizer* resizer() { return resizer_.get(); } |
| |
| // WindowObserver overrides: |
| virtual void OnWindowHierarchyChanging( |
| const HierarchyChangeParams& params) OVERRIDE; |
| virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; |
| |
| // WindowStateObserver overrides: |
| virtual void OnWindowShowTypeChanged(wm::WindowState* window_state, |
| wm::WindowShowType type) OVERRIDE; |
| |
| private: |
| void AddHandlers(aura::Window* container); |
| void RemoveHandlers(); |
| |
| ToplevelWindowEventHandler* handler_; |
| scoped_ptr<WindowResizer> resizer_; |
| |
| // If not NULL, this is an additional container that the dragged window has |
| // moved to which ScopedWindowResizer has temporarily added observers on. |
| aura::Window* target_container_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer); |
| }; |
| |
| ToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer( |
| ToplevelWindowEventHandler* handler, |
| WindowResizer* resizer) |
| : handler_(handler), |
| resizer_(resizer), |
| target_container_(NULL) { |
| if (resizer_) { |
| resizer_->GetTarget()->AddObserver(this); |
| wm::GetWindowState(resizer_->GetTarget())->AddObserver(this); |
| } |
| } |
| |
| ToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() { |
| RemoveHandlers(); |
| if (resizer_) { |
| resizer_->GetTarget()->RemoveObserver(this); |
| wm::GetWindowState(resizer_->GetTarget())->RemoveObserver(this); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowHierarchyChanging( |
| const HierarchyChangeParams& params) { |
| if (params.receiver != resizer_->GetTarget()) |
| return; |
| wm::WindowState* state = wm::GetWindowState(params.receiver); |
| if (state->continue_drag_after_reparent()) { |
| state->set_continue_drag_after_reparent(false); |
| AddHandlers(params.new_parent); |
| } else { |
| handler_->CompleteDrag(DRAG_COMPLETE, 0); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowShowTypeChanged( |
| wm::WindowState* window_state, |
| wm::WindowShowType old) { |
| if (!window_state->IsNormalShowState()) |
| handler_->CompleteDrag(DRAG_COMPLETE, 0); |
| } |
| |
| void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying( |
| aura::Window* window) { |
| DCHECK(resizer_.get()); |
| DCHECK_EQ(resizer_->GetTarget(), window); |
| handler_->ResizerWindowDestroyed(); |
| } |
| |
| void ToplevelWindowEventHandler::ScopedWindowResizer::AddHandlers( |
| aura::Window* container) { |
| RemoveHandlers(); |
| if (!handler_->owner()->Contains(container)) { |
| container->AddPreTargetHandler(handler_); |
| container->AddPostTargetHandler(handler_); |
| target_container_ = container; |
| } |
| } |
| |
| void ToplevelWindowEventHandler::ScopedWindowResizer::RemoveHandlers() { |
| if (target_container_) { |
| target_container_->RemovePreTargetHandler(handler_); |
| target_container_->RemovePostTargetHandler(handler_); |
| target_container_ = NULL; |
| } |
| } |
| |
| |
| // ToplevelWindowEventHandler -------------------------------------------------- |
| |
| ToplevelWindowEventHandler::ToplevelWindowEventHandler(aura::Window* owner) |
| : owner_(owner), |
| in_move_loop_(false), |
| move_cancelled_(false), |
| in_gesture_drag_(false), |
| destroyed_(NULL) { |
| aura::client::SetWindowMoveClient(owner, this); |
| Shell::GetInstance()->display_controller()->AddObserver(this); |
| owner->AddPreTargetHandler(this); |
| owner->AddPostTargetHandler(this); |
| } |
| |
| ToplevelWindowEventHandler::~ToplevelWindowEventHandler() { |
| Shell::GetInstance()->display_controller()->RemoveObserver(this); |
| if (destroyed_) |
| *destroyed_ = true; |
| } |
| |
| void ToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) { |
| if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED && |
| event->key_code() == ui::VKEY_ESCAPE) { |
| CompleteDrag(DRAG_REVERT, event->flags()); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::OnMouseEvent( |
| ui::MouseEvent* event) { |
| if ((event->flags() & |
| (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0) |
| return; |
| |
| if (in_gesture_drag_) |
| return; |
| |
| aura::Window* target = static_cast<aura::Window*>(event->target()); |
| switch (event->type()) { |
| case ui::ET_MOUSE_PRESSED: |
| HandleMousePressed(target, event); |
| break; |
| case ui::ET_MOUSE_DRAGGED: |
| HandleDrag(target, event); |
| break; |
| case ui::ET_MOUSE_CAPTURE_CHANGED: |
| case ui::ET_MOUSE_RELEASED: |
| HandleMouseReleased(target, event); |
| break; |
| case ui::ET_MOUSE_MOVED: |
| HandleMouseMoved(target, event); |
| break; |
| case ui::ET_MOUSE_EXITED: |
| HandleMouseExited(target, event); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) { |
| aura::Window* target = static_cast<aura::Window*>(event->target()); |
| if (!target->delegate()) |
| return; |
| |
| if (in_move_loop_ && !in_gesture_drag_) |
| return; |
| |
| switch (event->type()) { |
| case ui::ET_GESTURE_TAP_DOWN: { |
| int component = |
| target->delegate()->GetNonClientComponent(event->location()); |
| if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) & |
| WindowResizer::kBoundsChange_Resizes)) |
| return; |
| internal::ResizeShadowController* controller = |
| Shell::GetInstance()->resize_shadow_controller(); |
| if (controller) |
| controller->ShowShadow(target, component); |
| return; |
| } |
| case ui::ET_GESTURE_END: { |
| internal::ResizeShadowController* controller = |
| Shell::GetInstance()->resize_shadow_controller(); |
| if (controller) |
| controller->HideShadow(target); |
| return; |
| } |
| case ui::ET_GESTURE_SCROLL_BEGIN: { |
| if (in_gesture_drag_) |
| return; |
| int component = |
| target->delegate()->GetNonClientComponent(event->location()); |
| if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0) { |
| window_resizer_.reset(); |
| return; |
| } |
| in_gesture_drag_ = true; |
| pre_drag_window_bounds_ = target->bounds(); |
| gfx::Point location_in_parent( |
| ConvertPointToParent(target, event->location())); |
| CreateScopedWindowResizer(target, location_in_parent, component, |
| aura::client::WINDOW_MOVE_SOURCE_TOUCH); |
| break; |
| } |
| case ui::ET_GESTURE_SCROLL_UPDATE: { |
| if (!in_gesture_drag_) |
| return; |
| if (window_resizer_.get() && |
| window_resizer_->resizer()->GetTarget() != target) { |
| return; |
| } |
| HandleDrag(target, event); |
| break; |
| } |
| case ui::ET_GESTURE_SCROLL_END: |
| case ui::ET_SCROLL_FLING_START: { |
| if (!in_gesture_drag_) |
| return; |
| if (window_resizer_.get() && |
| window_resizer_->resizer()->GetTarget() != target) { |
| return; |
| } |
| |
| CompleteDrag(DRAG_COMPLETE, event->flags()); |
| if (in_move_loop_) { |
| quit_closure_.Run(); |
| in_move_loop_ = false; |
| } |
| in_gesture_drag_ = false; |
| |
| if (event->type() == ui::ET_GESTURE_SCROLL_END) { |
| event->StopPropagation(); |
| return; |
| } |
| |
| int component = |
| target->delegate()->GetNonClientComponent(event->location()); |
| if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0) |
| return; |
| |
| wm::WindowState* window_state = wm::GetWindowState(target); |
| if (!window_state->IsNormalShowState()) |
| return; |
| |
| if (fabs(event->details().velocity_y()) > |
| kMinVertVelocityForWindowMinimize) { |
| // Minimize/maximize. |
| if (event->details().velocity_y() > 0 && |
| window_state->CanMinimize()) { |
| window_state->Minimize(); |
| window_state->set_always_restores_to_restore_bounds(true); |
| window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); |
| } else if (window_state->CanMaximize()) { |
| window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); |
| window_state->Maximize(); |
| } |
| } else if (window_state->CanSnap() && |
| fabs(event->details().velocity_x()) > |
| kMinHorizVelocityForWindowSwipe) { |
| // Snap left/right. |
| ui::ScopedLayerAnimationSettings scoped_setter( |
| target->layer()->GetAnimator()); |
| scoped_setter.SetPreemptionStrategy( |
| ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); |
| internal::SnapSizer::SnapWindow(window_state, |
| event->details().velocity_x() < 0 ? |
| internal::SnapSizer::LEFT_EDGE : internal::SnapSizer::RIGHT_EDGE); |
| } |
| break; |
| } |
| default: |
| return; |
| } |
| |
| event->StopPropagation(); |
| } |
| |
| aura::client::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop( |
| aura::Window* source, |
| const gfx::Vector2d& drag_offset, |
| aura::client::WindowMoveSource move_source) { |
| DCHECK(!in_move_loop_); // Can only handle one nested loop at a time. |
| in_move_loop_ = true; |
| move_cancelled_ = false; |
| aura::Window* root_window = source->GetRootWindow(); |
| DCHECK(root_window); |
| gfx::Point drag_location; |
| if (move_source == aura::client::WINDOW_MOVE_SOURCE_TOUCH && |
| aura::Env::GetInstance()->is_touch_down()) { |
| in_gesture_drag_ = true; |
| bool has_point = ui::GestureRecognizer::Get()-> |
| GetLastTouchPointForTarget(source, &drag_location); |
| DCHECK(has_point); |
| } else { |
| drag_location = root_window->GetDispatcher()->GetLastMouseLocationInRoot(); |
| aura::Window::ConvertPointToTarget( |
| root_window, source->parent(), &drag_location); |
| } |
| // Set the cursor before calling CreateScopedWindowResizer(), as that will |
| // eventually call LockCursor() and prevent the cursor from changing. |
| aura::client::CursorClient* cursor_client = |
| aura::client::GetCursorClient(root_window); |
| if (cursor_client) |
| cursor_client->SetCursor(ui::kCursorPointer); |
| CreateScopedWindowResizer(source, drag_location, HTCAPTION, move_source); |
| bool destroyed = false; |
| destroyed_ = &destroyed; |
| base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); |
| base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); |
| base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher()); |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| if (destroyed) |
| return aura::client::MOVE_CANCELED; |
| destroyed_ = NULL; |
| in_gesture_drag_ = in_move_loop_ = false; |
| return move_cancelled_ ? aura::client::MOVE_CANCELED : |
| aura::client::MOVE_SUCCESSFUL; |
| } |
| |
| void ToplevelWindowEventHandler::EndMoveLoop() { |
| if (!in_move_loop_) |
| return; |
| |
| in_move_loop_ = false; |
| if (window_resizer_) { |
| window_resizer_->resizer()->RevertDrag(); |
| window_resizer_.reset(); |
| } |
| quit_closure_.Run(); |
| } |
| |
| void ToplevelWindowEventHandler::OnDisplayConfigurationChanging() { |
| if (in_move_loop_) { |
| move_cancelled_ = true; |
| EndMoveLoop(); |
| } else if (window_resizer_) { |
| window_resizer_->resizer()->RevertDrag(); |
| window_resizer_.reset(); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::CreateScopedWindowResizer( |
| aura::Window* window, |
| const gfx::Point& point_in_parent, |
| int window_component, |
| aura::client::WindowMoveSource source) { |
| window_resizer_.reset(); |
| WindowResizer* resizer = |
| CreateWindowResizer(window, point_in_parent, window_component, |
| source).release(); |
| if (resizer) |
| window_resizer_.reset(new ScopedWindowResizer(this, resizer)); |
| } |
| |
| void ToplevelWindowEventHandler::CompleteDrag(DragCompletionStatus status, |
| int event_flags) { |
| scoped_ptr<ScopedWindowResizer> resizer(window_resizer_.release()); |
| if (resizer) { |
| if (status == DRAG_COMPLETE) |
| resizer->resizer()->CompleteDrag(event_flags); |
| else |
| resizer->resizer()->RevertDrag(); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::HandleMousePressed( |
| aura::Window* target, |
| ui::MouseEvent* event) { |
| // Move/size operations are initiated post-target handling to give the target |
| // an opportunity to cancel this default behavior by returning ER_HANDLED. |
| if (ui::EventCanceledDefaultHandling(*event)) |
| return; |
| |
| // We also update the current window component here because for the |
| // mouse-drag-release-press case, where the mouse is released and |
| // pressed without mouse move event. |
| int component = |
| target->delegate()->GetNonClientComponent(event->location()); |
| if ((event->flags() & |
| (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK)) == 0 && |
| WindowResizer::GetBoundsChangeForWindowComponent(component)) { |
| gfx::Point location_in_parent( |
| ConvertPointToParent(target, event->location())); |
| CreateScopedWindowResizer(target, location_in_parent, component, |
| aura::client::WINDOW_MOVE_SOURCE_MOUSE); |
| } else { |
| window_resizer_.reset(); |
| } |
| if (WindowResizer::GetBoundsChangeForWindowComponent(component) != 0) |
| event->StopPropagation(); |
| } |
| |
| void ToplevelWindowEventHandler::HandleMouseReleased( |
| aura::Window* target, |
| ui::MouseEvent* event) { |
| if (event->phase() != ui::EP_PRETARGET) |
| return; |
| |
| CompleteDrag(event->type() == ui::ET_MOUSE_RELEASED ? |
| DRAG_COMPLETE : DRAG_REVERT, |
| event->flags()); |
| if (in_move_loop_) { |
| quit_closure_.Run(); |
| in_move_loop_ = false; |
| } |
| // Completing the drag may result in hiding the window. If this happens |
| // return true so no other handlers/observers see the event. Otherwise |
| // they see the event on a hidden window. |
| if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED && |
| !target->IsVisible()) { |
| event->StopPropagation(); |
| } |
| } |
| |
| void ToplevelWindowEventHandler::HandleDrag( |
| aura::Window* target, |
| ui::LocatedEvent* event) { |
| // This function only be triggered to move window |
| // by mouse drag or touch move event. |
| DCHECK(event->type() == ui::ET_MOUSE_DRAGGED || |
| event->type() == ui::ET_TOUCH_MOVED || |
| event->type() == ui::ET_GESTURE_SCROLL_UPDATE); |
| |
| // Drag actions are performed pre-target handling to prevent spurious mouse |
| // moves from the move/size operation from being sent to the target. |
| if (event->phase() != ui::EP_PRETARGET) |
| return; |
| |
| if (!window_resizer_) |
| return; |
| window_resizer_->resizer()->Drag( |
| ConvertPointToParent(target, event->location()), event->flags()); |
| event->StopPropagation(); |
| } |
| |
| void ToplevelWindowEventHandler::HandleMouseMoved( |
| aura::Window* target, |
| ui::LocatedEvent* event) { |
| // Shadow effects are applied after target handling. Note that we don't |
| // respect ER_HANDLED here right now since we have not had a reason to allow |
| // the target to cancel shadow rendering. |
| if (event->phase() != ui::EP_POSTTARGET) |
| return; |
| |
| // TODO(jamescook): Move the resize cursor update code into here from |
| // CompoundEventFilter? |
| internal::ResizeShadowController* controller = |
| Shell::GetInstance()->resize_shadow_controller(); |
| if (controller) { |
| if (event->flags() & ui::EF_IS_NON_CLIENT) { |
| int component = |
| target->delegate()->GetNonClientComponent(event->location()); |
| controller->ShowShadow(target, component); |
| } else { |
| controller->HideShadow(target); |
| } |
| } |
| } |
| |
| void ToplevelWindowEventHandler::HandleMouseExited( |
| aura::Window* target, |
| ui::LocatedEvent* event) { |
| // Shadow effects are applied after target handling. Note that we don't |
| // respect ER_HANDLED here right now since we have not had a reason to allow |
| // the target to cancel shadow rendering. |
| if (event->phase() != ui::EP_POSTTARGET) |
| return; |
| |
| internal::ResizeShadowController* controller = |
| Shell::GetInstance()->resize_shadow_controller(); |
| if (controller) |
| controller->HideShadow(target); |
| } |
| |
| void ToplevelWindowEventHandler::ResizerWindowDestroyed() { |
| // We explicitly don't invoke RevertDrag() since that may do things to window. |
| // Instead we destroy the resizer. |
| window_resizer_.reset(); |
| |
| // End the move loop. This does nothing if we're not in a move loop. |
| EndMoveLoop(); |
| } |
| |
| } // namespace ash |