1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ash/wm/dock/docked_window_resizer.h" 6 7#include "ash/display/display_controller.h" 8#include "ash/root_window_controller.h" 9#include "ash/screen_util.h" 10#include "ash/shelf/shelf.h" 11#include "ash/shelf/shelf_types.h" 12#include "ash/shelf/shelf_widget.h" 13#include "ash/shell.h" 14#include "ash/shell_window_ids.h" 15#include "ash/wm/dock/docked_window_layout_manager.h" 16#include "ash/wm/window_state.h" 17#include "ash/wm/window_util.h" 18#include "ash/wm/workspace/magnetism_matcher.h" 19#include "ash/wm/workspace/workspace_window_resizer.h" 20#include "base/command_line.h" 21#include "base/memory/weak_ptr.h" 22#include "ui/aura/client/aura_constants.h" 23#include "ui/aura/client/window_tree_client.h" 24#include "ui/aura/env.h" 25#include "ui/aura/window.h" 26#include "ui/aura/window_delegate.h" 27#include "ui/aura/window_event_dispatcher.h" 28#include "ui/base/hit_test.h" 29#include "ui/base/ui_base_types.h" 30#include "ui/gfx/screen.h" 31#include "ui/views/widget/widget.h" 32#include "ui/wm/core/coordinate_conversion.h" 33 34namespace ash { 35namespace { 36 37DockedWindowLayoutManager* GetDockedLayoutManagerAtPoint( 38 const gfx::Point& point) { 39 gfx::Display display = ScreenUtil::FindDisplayContainingPoint(point); 40 if (!display.is_valid()) 41 return NULL; 42 aura::Window* root = Shell::GetInstance()->display_controller()-> 43 GetRootWindowForDisplayId(display.id()); 44 aura::Window* dock_container = Shell::GetContainer( 45 root, kShellWindowId_DockedContainer); 46 return static_cast<DockedWindowLayoutManager*>( 47 dock_container->layout_manager()); 48} 49 50} // namespace 51 52DockedWindowResizer::~DockedWindowResizer() { 53} 54 55// static 56DockedWindowResizer* 57DockedWindowResizer::Create(WindowResizer* next_window_resizer, 58 wm::WindowState* window_state) { 59 return new DockedWindowResizer(next_window_resizer, window_state); 60} 61 62void DockedWindowResizer::Drag(const gfx::Point& location, int event_flags) { 63 last_location_ = location; 64 ::wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_); 65 if (!did_move_or_resize_) { 66 did_move_or_resize_ = true; 67 StartedDragging(); 68 } 69 gfx::Point offset; 70 gfx::Rect bounds(CalculateBoundsForDrag(location)); 71 MaybeSnapToEdge(bounds, &offset); 72 gfx::Point modified_location(location); 73 modified_location.Offset(offset.x(), offset.y()); 74 75 base::WeakPtr<DockedWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr()); 76 next_window_resizer_->Drag(modified_location, event_flags); 77 if (!resizer) 78 return; 79 80 DockedWindowLayoutManager* new_dock_layout = 81 GetDockedLayoutManagerAtPoint(last_location_); 82 if (new_dock_layout && new_dock_layout != dock_layout_) { 83 // The window is being dragged to a new display. If the previous 84 // container is the current parent of the window it will be informed of 85 // the end of drag when the window is reparented, otherwise let the 86 // previous container know the drag is complete. If we told the 87 // window's parent that the drag was complete it would begin 88 // positioning the window. 89 if (is_docked_ && dock_layout_->is_dragged_window_docked()) 90 dock_layout_->UndockDraggedWindow(); 91 if (dock_layout_ != initial_dock_layout_) 92 dock_layout_->FinishDragging( 93 DOCKED_ACTION_NONE, 94 details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ? 95 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH); 96 is_docked_ = false; 97 dock_layout_ = new_dock_layout; 98 // The window's initial layout manager already knows that the drag is 99 // in progress for this window. 100 if (new_dock_layout != initial_dock_layout_) 101 new_dock_layout->StartDragging(GetTarget()); 102 } 103 // Window could get docked by the WorkspaceWindowResizer, update the state. 104 is_docked_ = dock_layout_->is_dragged_window_docked(); 105 // Whenever a window is dragged out of the dock it will be auto-sized 106 // in the dock if it gets docked again. 107 if (!is_docked_) 108 was_bounds_changed_by_user_ = false; 109} 110 111void DockedWindowResizer::CompleteDrag() { 112 // The root window can change when dragging into a different screen. 113 next_window_resizer_->CompleteDrag(); 114 FinishedDragging(aura::client::MOVE_SUCCESSFUL); 115} 116 117void DockedWindowResizer::RevertDrag() { 118 next_window_resizer_->RevertDrag(); 119 // Restore docked state to what it was before the drag if necessary. 120 if (is_docked_ != was_docked_) { 121 is_docked_ = was_docked_; 122 if (is_docked_) 123 dock_layout_->DockDraggedWindow(GetTarget()); 124 else 125 dock_layout_->UndockDraggedWindow(); 126 } 127 FinishedDragging(aura::client::MOVE_CANCELED); 128} 129 130DockedWindowResizer::DockedWindowResizer(WindowResizer* next_window_resizer, 131 wm::WindowState* window_state) 132 : WindowResizer(window_state), 133 next_window_resizer_(next_window_resizer), 134 dock_layout_(NULL), 135 initial_dock_layout_(NULL), 136 did_move_or_resize_(false), 137 was_docked_(false), 138 is_docked_(false), 139 was_bounds_changed_by_user_(window_state->bounds_changed_by_user()), 140 weak_ptr_factory_(this) { 141 DCHECK(details().is_resizable); 142 aura::Window* dock_container = Shell::GetContainer( 143 GetTarget()->GetRootWindow(), 144 kShellWindowId_DockedContainer); 145 dock_layout_ = static_cast<DockedWindowLayoutManager*>( 146 dock_container->layout_manager()); 147 initial_dock_layout_ = dock_layout_; 148 was_docked_ = GetTarget()->parent() == dock_container; 149 is_docked_ = was_docked_; 150} 151 152void DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds, 153 gfx::Point* offset) { 154 // Windows only snap magnetically when they were previously docked. 155 if (!was_docked_) 156 return; 157 DockedAlignment dock_alignment = dock_layout_->CalculateAlignment(); 158 gfx::Rect dock_bounds = ScreenUtil::ConvertRectFromScreen( 159 GetTarget()->parent(), 160 dock_layout_->dock_container()->GetBoundsInScreen()); 161 162 // Short-range magnetism when retaining docked state. Same constant as in 163 // MagnetismMatcher is used for consistency. 164 const int kSnapToDockDistance = MagnetismMatcher::kMagneticDistance; 165 166 if (dock_alignment == DOCKED_ALIGNMENT_LEFT || 167 dock_alignment == DOCKED_ALIGNMENT_NONE) { 168 const int distance = bounds.x() - dock_bounds.x(); 169 if (distance < kSnapToDockDistance && distance > 0) { 170 offset->set_x(-distance); 171 return; 172 } 173 } 174 if (dock_alignment == DOCKED_ALIGNMENT_RIGHT || 175 dock_alignment == DOCKED_ALIGNMENT_NONE) { 176 const int distance = dock_bounds.right() - bounds.right(); 177 if (distance < kSnapToDockDistance && distance > 0) 178 offset->set_x(distance); 179 } 180} 181 182void DockedWindowResizer::StartedDragging() { 183 // During resizing the window width is preserved by DockedwindowLayoutManager. 184 if (is_docked_ && 185 (details().bounds_change & WindowResizer::kBoundsChange_Resizes)) { 186 window_state_->set_bounds_changed_by_user(true); 187 } 188 189 // Tell the dock layout manager that we are dragging this window. 190 // At this point we are not yet animating the window as it may not be 191 // inside the docked area. 192 dock_layout_->StartDragging(GetTarget()); 193 // Reparent workspace windows during the drag to elevate them above workspace. 194 // Other windows for which the DockedWindowResizer is instantiated include 195 // panels and windows that are already docked. Those do not need reparenting. 196 if (GetTarget()->type() != ui::wm::WINDOW_TYPE_PANEL && 197 GetTarget()->parent()->id() == kShellWindowId_DefaultContainer) { 198 // Reparent the window into the docked windows container in order to get it 199 // on top of other docked windows. 200 aura::Window* docked_container = Shell::GetContainer( 201 GetTarget()->GetRootWindow(), 202 kShellWindowId_DockedContainer); 203 wm::ReparentChildWithTransientChildren(GetTarget(), 204 GetTarget()->parent(), 205 docked_container); 206 } 207 if (is_docked_) 208 dock_layout_->DockDraggedWindow(GetTarget()); 209} 210 211void DockedWindowResizer::FinishedDragging( 212 aura::client::WindowMoveResult move_result) { 213 if (!did_move_or_resize_) 214 return; 215 did_move_or_resize_ = false; 216 aura::Window* window = GetTarget(); 217 const bool is_attached_panel = window->type() == ui::wm::WINDOW_TYPE_PANEL && 218 window_state_->panel_attached(); 219 const bool is_resized = 220 (details().bounds_change & WindowResizer::kBoundsChange_Resizes) != 0; 221 222 // Undock the window if it is not in the normal or minimized state type. This 223 // happens if a user snaps or maximizes a window using a keyboard shortcut 224 // while it is being dragged. 225 if (!window_state_->IsMinimized() && !window_state_->IsNormalStateType()) 226 is_docked_ = false; 227 228 // When drag is completed the dragged docked window is resized to the bounds 229 // calculated by the layout manager that conform to other docked windows. 230 if (!is_attached_panel && is_docked_ && !is_resized) { 231 gfx::Rect bounds = ScreenUtil::ConvertRectFromScreen( 232 window->parent(), dock_layout_->dragged_bounds()); 233 if (!bounds.IsEmpty() && bounds.width() != window->bounds().width()) { 234 window->SetBounds(bounds); 235 } 236 } 237 // If a window has restore bounds, update the restore origin and width but not 238 // the height (since the height is auto-calculated for the docked windows). 239 if (is_resized && is_docked_ && window_state_->HasRestoreBounds()) { 240 gfx::Rect restore_bounds = window->GetBoundsInScreen(); 241 restore_bounds.set_height( 242 window_state_->GetRestoreBoundsInScreen().height()); 243 window_state_->SetRestoreBoundsInScreen(restore_bounds); 244 } 245 246 // Check if the window needs to be docked or returned to workspace. 247 DockedAction action = MaybeReparentWindowOnDragCompletion(is_resized, 248 is_attached_panel); 249 dock_layout_->FinishDragging( 250 move_result == aura::client::MOVE_CANCELED ? DOCKED_ACTION_NONE : action, 251 details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ? 252 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH); 253 254 // If we started the drag in one root window and moved into another root 255 // but then canceled the drag we may need to inform the original layout 256 // manager that the drag is finished. 257 if (initial_dock_layout_ != dock_layout_) 258 initial_dock_layout_->FinishDragging( 259 DOCKED_ACTION_NONE, 260 details().source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ? 261 DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH); 262 is_docked_ = false; 263} 264 265DockedAction DockedWindowResizer::MaybeReparentWindowOnDragCompletion( 266 bool is_resized, bool is_attached_panel) { 267 aura::Window* window = GetTarget(); 268 269 // Check if the window needs to be docked or returned to workspace. 270 DockedAction action = DOCKED_ACTION_NONE; 271 aura::Window* dock_container = Shell::GetContainer( 272 window->GetRootWindow(), 273 kShellWindowId_DockedContainer); 274 if ((is_resized || !is_attached_panel) && 275 is_docked_ != (window->parent() == dock_container)) { 276 if (is_docked_) { 277 wm::ReparentChildWithTransientChildren(window, 278 window->parent(), 279 dock_container); 280 action = DOCKED_ACTION_DOCK; 281 } else if (window->parent()->id() == kShellWindowId_DockedContainer) { 282 // Reparent the window back to workspace. 283 // We need to be careful to give ParentWindowWithContext a location in 284 // the right root window (matching the logic in DragWindowResizer) based 285 // on which root window a mouse pointer is in. We want to undock into the 286 // right screen near the edge of a multiscreen setup (based on where the 287 // mouse is). 288 gfx::Rect near_last_location(last_location_, gfx::Size()); 289 // Reparenting will cause Relayout and possible dock shrinking. 290 aura::Window* previous_parent = window->parent(); 291 aura::client::ParentWindowWithContext(window, window, near_last_location); 292 if (window->parent() != previous_parent) { 293 wm::ReparentTransientChildrenOfChild(window, 294 previous_parent, 295 window->parent()); 296 } 297 action = was_docked_ ? DOCKED_ACTION_UNDOCK : DOCKED_ACTION_NONE; 298 } 299 } else { 300 // Docked state was not changed but still need to record a UMA action. 301 if (is_resized && is_docked_ && was_docked_) 302 action = DOCKED_ACTION_RESIZE; 303 else if (is_docked_ && was_docked_) 304 action = DOCKED_ACTION_REORDER; 305 else if (is_docked_ && !was_docked_) 306 action = DOCKED_ACTION_DOCK; 307 else 308 action = DOCKED_ACTION_NONE; 309 } 310 // When a window is newly docked it is auto-sized by docked layout adjusting 311 // to other windows. If it is just dragged (but not resized) while being 312 // docked it is auto-sized unless it has been resized while being docked 313 // before. 314 if (is_docked_) { 315 wm::GetWindowState(window)->set_bounds_changed_by_user( 316 was_docked_ && (is_resized || was_bounds_changed_by_user_)); 317 } 318 return action; 319} 320 321} // namespace ash 322