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