summaryrefslogtreecommitdiffstats
path: root/chromium/ash/wm/dock/docked_window_resizer.cc
blob: cd06beec370f437dd5d0809a98b93dd56b034113 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// 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/display/display_controller.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/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace/magnetism_matcher.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_tree_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/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) {
  gfx::Display display = ScreenAsh::FindDisplayContainingPoint(point);
  if (!display.is_valid())
    return NULL;
  aura::Window* root = Shell::GetInstance()->display_controller()->
      GetRootWindowForDisplayId(display.id());
  aura::Window* dock_container = Shell::GetContainer(
      root, kShellWindowId_DockedContainer);
  return static_cast<DockedWindowLayoutManager*>(
      dock_container->layout_manager());
}

}  // namespace

DockedWindowResizer::~DockedWindowResizer() {
}

// 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_);
  if (!did_move_or_resize_) {
    did_move_or_resize_ = true;
    StartedDragging();
  }
  gfx::Point offset;
  gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
  MaybeSnapToEdge(bounds, &offset);
  gfx::Point modified_location(location);
  modified_location.Offset(offset.x(), offset.y());

  base::WeakPtr<DockedWindowResizer> resizer(weak_ptr_factory_.GetWeakPtr());
  next_window_resizer_->Drag(modified_location, event_flags);
  if (!resizer)
    return;

  DockedWindowLayoutManager* new_dock_layout =
      GetDockedLayoutManagerAtPoint(last_location_);
  if (new_dock_layout && 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_->is_dragged_window_docked())
      dock_layout_->UndockDraggedWindow();
    if (dock_layout_ != initial_dock_layout_)
      dock_layout_->FinishDragging(
          DOCKED_ACTION_NONE,
          details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
              DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
    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());
  }
  // Window could get docked by the WorkspaceWindowResizer, update the state.
  is_docked_ = dock_layout_->is_dragged_window_docked();
  // Whenever a window is dragged out of the dock it will be auto-sized
  // in the dock if it gets docked again.
  if (!is_docked_)
    was_bounds_changed_by_user_ = false;
}

void DockedWindowResizer::CompleteDrag(int event_flags) {
  // The root window can change when dragging into a different screen.
  next_window_resizer_->CompleteDrag(event_flags);
  FinishedDragging();
}

void DockedWindowResizer::RevertDrag() {
  next_window_resizer_->RevertDrag();
  // Restore docked state to what it was before the drag if necessary.
  if (is_docked_ != was_docked_) {
    is_docked_ = was_docked_;
    if (is_docked_)
      dock_layout_->DockDraggedWindow(GetTarget());
    else
      dock_layout_->UndockDraggedWindow();
  }
  FinishedDragging();
}

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),
      was_bounds_changed_by_user_(
          wm::GetWindowState(details.window)->bounds_changed_by_user()),
      weak_ptr_factory_(this) {
  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_;
}

void DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds,
                                          gfx::Point* offset) {
  // Windows only snap magnetically when they were previously docked.
  if (!was_docked_)
    return;
  DockedAlignment dock_alignment = dock_layout_->CalculateAlignment();
  gfx::Rect dock_bounds = ScreenAsh::ConvertRectFromScreen(
      GetTarget()->parent(),
      dock_layout_->dock_container()->GetBoundsInScreen());

  // 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) {
    const int distance = bounds.x() - dock_bounds.x();
    if (distance < kSnapToDockDistance && distance > 0) {
      offset->set_x(-distance);
      return;
    }
  }
  if (dock_alignment == DOCKED_ALIGNMENT_RIGHT ||
      dock_alignment == DOCKED_ALIGNMENT_NONE) {
    const int distance = dock_bounds.right() - bounds.right();
    if (distance < kSnapToDockDistance && distance > 0)
      offset->set_x(distance);
  }
}

void DockedWindowResizer::StartedDragging() {
  // During resizing the window width is preserved by DockedwindowLayoutManager.
  wm::WindowState* window_state = wm::GetWindowState(GetTarget());
  if (is_docked_ &&
      (details_.bounds_change & WindowResizer::kBoundsChange_Resizes)) {
    window_state->set_bounds_changed_by_user(true);
  }

  // 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.
    window_state->set_continue_drag_after_reparent(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);
    wm::ReparentChildWithTransientChildren(GetTarget(),
                                           GetTarget()->parent(),
                                           docked_container);
  }
  if (is_docked_)
    dock_layout_->DockDraggedWindow(GetTarget());
}

void DockedWindowResizer::FinishedDragging() {
  if (!did_move_or_resize_)
    return;
  did_move_or_resize_ = false;
  aura::Window* window = GetTarget();
  wm::WindowState* window_state = wm::GetWindowState(window);
  const bool is_attached_panel =
      window->type() == aura::client::WINDOW_TYPE_PANEL &&
      window_state->panel_attached();
  const bool is_resized =
      (details_.bounds_change & WindowResizer::kBoundsChange_Resizes) != 0;

  // When drag is completed the dragged docked window is resized to the bounds
  // calculated by the layout manager that conform to other docked windows.
  if (!is_attached_panel && is_docked_ && !is_resized) {
    gfx::Rect bounds = ScreenAsh::ConvertRectFromScreen(
        window->parent(), dock_layout_->dragged_bounds());
    if (!bounds.IsEmpty() && bounds.width() != window->bounds().width()) {
      window->SetBounds(bounds);
    }
  }
  // If a window has restore bounds, update the restore origin and width but not
  // the height (since the height is auto-calculated for the docked windows).
  if (is_resized && is_docked_ && window_state->HasRestoreBounds()) {
    gfx::Rect restore_bounds = window->GetBoundsInScreen();
    restore_bounds.set_height(
        window_state->GetRestoreBoundsInScreen().height());
    window_state->SetRestoreBoundsInScreen(restore_bounds);
  }

  // Check if the window needs to be docked or returned to workspace.
  DockedAction action = MaybeReparentWindowOnDragCompletion(is_resized,
                                                            is_attached_panel);
  dock_layout_->FinishDragging(
      action,
      details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
          DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);

  // 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(
        DOCKED_ACTION_NONE,
        details_.source == aura::client::WINDOW_MOVE_SOURCE_MOUSE ?
            DOCKED_ACTION_SOURCE_MOUSE : DOCKED_ACTION_SOURCE_TOUCH);
  is_docked_ = false;
}

DockedAction DockedWindowResizer::MaybeReparentWindowOnDragCompletion(
    bool is_resized, bool is_attached_panel) {
  aura::Window* window = GetTarget();

  // Check if the window needs to be docked or returned to workspace.
  DockedAction action = DOCKED_ACTION_NONE;
  aura::Window* dock_container = Shell::GetContainer(
      window->GetRootWindow(),
      kShellWindowId_DockedContainer);
  if ((is_resized || !is_attached_panel) &&
      is_docked_ != (window->parent() == dock_container)) {
    if (is_docked_) {
      wm::ReparentChildWithTransientChildren(window,
                                             window->parent(),
                                             dock_container);
      action = DOCKED_ACTION_DOCK;
    } else if (window->parent()->id() == kShellWindowId_DockedContainer) {
      // Reparent the window back to workspace.
      // We need to be careful to give ParentWindowWithContext a 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.
      aura::Window* previous_parent = window->parent();
      aura::client::ParentWindowWithContext(window, window, near_last_location);
      if (window->parent() != previous_parent) {
        wm::ReparentTransientChildrenOfChild(window,
                                             previous_parent,
                                             window->parent());
      }
      action = was_docked_ ? DOCKED_ACTION_UNDOCK : DOCKED_ACTION_NONE;
    }
  } else {
    // Docked state was not changed but still need to record a UMA action.
    if (is_resized && is_docked_ && was_docked_)
      action = DOCKED_ACTION_RESIZE;
    else if (is_docked_ && was_docked_)
      action = DOCKED_ACTION_REORDER;
    else if (is_docked_ && !was_docked_)
      action = DOCKED_ACTION_DOCK;
    else
      action = DOCKED_ACTION_NONE;
  }
  // When a window is newly docked it is auto-sized by docked layout adjusting
  // to other windows. If it is just dragged (but not resized) while being
  // docked it is auto-sized unless it has been resized while being docked
  // before.
  if (is_docked_) {
    wm::GetWindowState(window)->set_bounds_changed_by_user(
        was_docked_ && (is_resized || was_bounds_changed_by_user_));
  }
  return action;
}

}  // namespace internal
}  // namespace ash