blob: 3e1ef29b0ad76f1ee1e6e8cad4eff34d0a61d55f [file] [log] [blame]
// Copyright 2013 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/dock/docked_window_resizer.h"
#include "ash/ash_switches.h"
#include "ash/launcher/launcher.h"
#include "ash/root_window_controller.h"
#include "ash/screen_ash.h"
#include "ash/shelf/shelf_types.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/coordinate_conversion.h"
#include "ash/wm/dock/docked_window_layout_manager.h"
#include "ash/wm/property_util.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/workspace/magnetism_matcher.h"
#include "ash/wm/workspace/phantom_window_controller.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "base/command_line.h"
#include "ui/aura/client/aura_constants.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/base/hit_test.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/screen.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace internal {
namespace {
DockedWindowLayoutManager* GetDockedLayoutManagerAtPoint(
const gfx::Point& point) {
aura::Window* dock_container = Shell::GetContainer(
wm::GetRootWindowAt(point),
kShellWindowId_DockedContainer);
return static_cast<DockedWindowLayoutManager*>(
dock_container->layout_manager());
}
} // namespace
DockedWindowResizer::~DockedWindowResizer() {
if (destroyed_)
*destroyed_ = true;
}
// static
DockedWindowResizer*
DockedWindowResizer::Create(WindowResizer* next_window_resizer,
aura::Window* window,
const gfx::Point& location,
int window_component,
aura::client::WindowMoveSource source) {
Details details(window, location, window_component, source);
return details.is_resizable ?
new DockedWindowResizer(next_window_resizer, details) : NULL;
}
void DockedWindowResizer::Drag(const gfx::Point& location, int event_flags) {
last_location_ = location;
wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_);
bool destroyed = false;
if (!did_move_or_resize_) {
did_move_or_resize_ = true;
StartedDragging();
}
gfx::Point offset;
gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
bool set_tracked_by_workspace = MaybeSnapToEdge(bounds, &offset);
// Temporarily clear kWindowTrackedByWorkspaceKey for windows that are snapped
// to screen edges e.g. when they are docked. This prevents the windows from
// getting snapped to other nearby windows during the drag.
bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), false);
gfx::Point modified_location(location.x() + offset.x(),
location.y() + offset.y());
destroyed_ = &destroyed;
next_window_resizer_->Drag(modified_location, event_flags);
// TODO(varkha): Refactor the way WindowResizer calls into other window
// resizers to avoid the awkward pattern here for checking if
// next_window_resizer_ destroys the resizer object.
if (destroyed)
return;
destroyed_ = NULL;
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
DockedWindowLayoutManager* new_dock_layout =
GetDockedLayoutManagerAtPoint(last_location_);
if (new_dock_layout != dock_layout_) {
// The window is being dragged to a new display. If the previous
// container is the current parent of the window it will be informed of
// the end of drag when the window is reparented, otherwise let the
// previous container know the drag is complete. If we told the
// window's parent that the drag was complete it would begin
// positioning the window.
if (is_docked_)
dock_layout_->UndockDraggedWindow();
if (dock_layout_ != initial_dock_layout_)
dock_layout_->FinishDragging();
is_docked_ = false;
dock_layout_ = new_dock_layout;
// The window's initial layout manager already knows that the drag is
// in progress for this window.
if (new_dock_layout != initial_dock_layout_)
new_dock_layout->StartDragging(GetTarget());
}
// Show snapping animation when a window touches a screen edge or when
// it is about to get docked.
DockedAlignment new_docked_alignment = GetDraggedWindowAlignment();
if (new_docked_alignment != DOCKED_ALIGNMENT_NONE) {
if (!is_docked_) {
dock_layout_->DockDraggedWindow(GetTarget());
is_docked_ = true;
}
UpdateSnapPhantomWindow();
} else {
if (is_docked_) {
dock_layout_->UndockDraggedWindow();
is_docked_ = false;
}
// Clear phantom window when a window gets undocked.
snap_phantom_window_controller_.reset();
}
}
void DockedWindowResizer::CompleteDrag(int event_flags) {
snap_phantom_window_controller_.reset();
// Temporarily clear kWindowTrackedByWorkspaceKey for panels so that they
// don't get forced into the workspace that may be shrunken because of docked
// windows.
bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
bool set_tracked_by_workspace = was_docked_;
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), false);
// The root window can change when dragging into a different screen.
next_window_resizer_->CompleteDrag(event_flags);
FinishedDragging();
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
}
void DockedWindowResizer::RevertDrag() {
snap_phantom_window_controller_.reset();
// Temporarily clear kWindowTrackedByWorkspaceKey for panels so that they
// don't get forced into the workspace that may be shrunken because of docked
// windows.
bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
bool set_tracked_by_workspace = was_docked_;
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), false);
next_window_resizer_->RevertDrag();
FinishedDragging();
if (set_tracked_by_workspace)
SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
}
aura::Window* DockedWindowResizer::GetTarget() {
return next_window_resizer_->GetTarget();
}
const gfx::Point& DockedWindowResizer::GetInitialLocation() const {
return details_.initial_location_in_parent;
}
DockedWindowResizer::DockedWindowResizer(WindowResizer* next_window_resizer,
const Details& details)
: details_(details),
next_window_resizer_(next_window_resizer),
dock_layout_(NULL),
initial_dock_layout_(NULL),
did_move_or_resize_(false),
was_docked_(false),
is_docked_(false),
destroyed_(NULL) {
DCHECK(details_.is_resizable);
aura::Window* dock_container = Shell::GetContainer(
details.window->GetRootWindow(),
kShellWindowId_DockedContainer);
dock_layout_ = static_cast<DockedWindowLayoutManager*>(
dock_container->layout_manager());
initial_dock_layout_ = dock_layout_;
was_docked_ = details.window->parent() == dock_container;
is_docked_ = was_docked_;
}
DockedAlignment DockedWindowResizer::GetDraggedWindowAlignment() {
aura::Window* window = GetTarget();
DockedWindowLayoutManager* layout_manager =
GetDockedLayoutManagerAtPoint(last_location_);
const DockedAlignment alignment = layout_manager->CalculateAlignment();
const gfx::Rect& bounds(window->GetBoundsInScreen());
// Check if the window is touching the edge - it may need to get docked.
if (alignment == DOCKED_ALIGNMENT_NONE)
return layout_manager->GetAlignmentOfWindow(window);
// Both bounds and pointer location are checked because some drags involve
// stickiness at the workspace-to-dock boundary and so the |location| may be
// outside of the |bounds|.
// It is also possible that all the docked windows are minimized or hidden
// in which case the dragged window needs to be exactly touching the same
// edge that those docked windows were aligned before they got minimized.
// TODO(varkha): Consider eliminating sticky behavior on that boundary when
// a pointer enters docked area.
if ((layout_manager->docked_bounds().Intersects(bounds) &&
layout_manager->docked_bounds().Contains(last_location_)) ||
alignment == layout_manager->GetAlignmentOfWindow(window)) {
// A window is being added to other docked windows (on the same side).
return alignment;
}
return DOCKED_ALIGNMENT_NONE;
}
bool DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds,
gfx::Point* offset) {
aura::Window* dock_container = Shell::GetContainer(
wm::GetRootWindowAt(last_location_),
kShellWindowId_DockedContainer);
DockedAlignment dock_alignment =
GetDockedLayoutManagerAtPoint(last_location_)->CalculateAlignment();
gfx::Rect dock_bounds = ScreenAsh::ConvertRectFromScreen(
GetTarget()->parent(), dock_container->GetBoundsInScreen());
// Windows only snap magnetically when they are close to the edge of the
// screen and when the cursor is over other docked windows.
// When a window being dragged is the last window that was previously
// docked it is still allowed to magnetically snap to either side.
bool can_snap = was_docked_ ||
(GetDraggedWindowAlignment() != DOCKED_ALIGNMENT_NONE);
if (!can_snap)
return false;
// Distance in pixels that the cursor must move past an edge for a window
// to move beyond that edge. Same constant as in WorkspaceWindowResizer
// is used for consistency.
const int kStickyDistance = WorkspaceWindowResizer::kStickyDistancePixels;
// Short-range magnetism when retaining docked state. Same constant as in
// MagnetismMatcher is used for consistency.
const int kSnapToDockDistance = MagnetismMatcher::kMagneticDistance;
if (dock_alignment == DOCKED_ALIGNMENT_LEFT ||
(dock_alignment == DOCKED_ALIGNMENT_NONE && was_docked_)) {
const int distance = bounds.x() - dock_bounds.x();
if (distance < (was_docked_ ? kSnapToDockDistance : 0) &&
distance > -kStickyDistance) {
offset->set_x(-distance);
return true;
}
}
if (dock_alignment == DOCKED_ALIGNMENT_RIGHT ||
(dock_alignment == DOCKED_ALIGNMENT_NONE && was_docked_)) {
const int distance = dock_bounds.right() - bounds.right();
if (distance < (was_docked_ ? kSnapToDockDistance : 0) &&
distance > -kStickyDistance) {
offset->set_x(distance);
return true;
}
}
return false;
}
void DockedWindowResizer::StartedDragging() {
// Tell the dock layout manager that we are dragging this window.
// At this point we are not yet animating the window as it may not be
// inside the docked area.
dock_layout_->StartDragging(GetTarget());
// Reparent workspace windows during the drag to elevate them above workspace.
// Other windows for which the DockedWindowResizer is instantiated include
// panels and windows that are already docked. Those do not need reparenting.
if (GetTarget()->type() != aura::client::WINDOW_TYPE_PANEL &&
GetTarget()->parent()->id() == kShellWindowId_DefaultContainer) {
// The window is going to be reparented - avoid completing the drag.
GetTarget()->SetProperty(kContinueDragAfterReparent, true);
// Reparent the window into the docked windows container in order to get it
// on top of other docked windows.
aura::Window* docked_container = Shell::GetContainer(
GetTarget()->GetRootWindow(),
kShellWindowId_DockedContainer);
docked_container->AddChild(GetTarget());
}
if (is_docked_)
dock_layout_->DockDraggedWindow(GetTarget());
}
void DockedWindowResizer::FinishedDragging() {
if (!did_move_or_resize_)
return;
aura::Window* window = GetTarget();
bool should_dock = was_docked_;
const bool attached_panel =
window->type() == aura::client::WINDOW_TYPE_PANEL &&
window->GetProperty(kPanelAttachedKey);
// If a window was previously docked then keep it docked if it is resized and
// still aligned at the screen edge.
if ((was_docked_ ||
((details_.bounds_change & WindowResizer::kBoundsChange_Repositions) &&
!(details_.bounds_change & WindowResizer::kBoundsChange_Resizes)))) {
should_dock = GetDraggedWindowAlignment() != DOCKED_ALIGNMENT_NONE;
}
// Check if the window needs to be docked or returned to workspace.
aura::Window* dock_container = Shell::GetContainer(
window->GetRootWindow(),
kShellWindowId_DockedContainer);
if (!attached_panel &&
should_dock != (window->parent() == dock_container)) {
if (should_dock) {
dock_container->AddChild(window);
} else if (window->parent()->id() == kShellWindowId_DockedContainer) {
// Reparent the window back to workspace.
// We need to be careful to give SetDefaultParentByRootWindow location in
// the right root window (matching the logic in DragWindowResizer) based
// on which root window a mouse pointer is in. We want to undock into the
// right screen near the edge of a multiscreen setup (based on where the
// mouse is).
gfx::Rect near_last_location(last_location_, gfx::Size());
// Reparenting will cause Relayout and possible dock shrinking.
window->SetDefaultParentByRootWindow(window->GetRootWindow(),
near_last_location);
}
}
dock_layout_->FinishDragging();
// If we started the drag in one root window and moved into another root
// but then canceled the drag we may need to inform the original layout
// manager that the drag is finished.
if (initial_dock_layout_ != dock_layout_)
initial_dock_layout_->FinishDragging();
is_docked_ = false;
}
void DockedWindowResizer::UpdateSnapPhantomWindow() {
if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
return;
if (!snap_phantom_window_controller_) {
snap_phantom_window_controller_.reset(
new PhantomWindowController(GetTarget()));
}
snap_phantom_window_controller_->Show(dock_layout_->dragged_bounds());
}
} // namespace internal
} // namespace ash