OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/common/wm/workspace/multi_window_resize_controller.h" | |
6 | |
7 #include "ash/common/wm/workspace/workspace_window_resizer.h" | |
8 #include "ash/common/wm_window.h" | |
9 #include "ash/public/cpp/shell_window_ids.h" | |
10 #include "ash/resources/grit/ash_resources.h" | |
11 #include "ash/root_window_controller.h" | |
12 #include "ui/base/cursor/cursor.h" | |
13 #include "ui/base/hit_test.h" | |
14 #include "ui/base/resource/resource_bundle.h" | |
15 #include "ui/display/screen.h" | |
16 #include "ui/gfx/canvas.h" | |
17 #include "ui/gfx/image/image.h" | |
18 #include "ui/views/view.h" | |
19 #include "ui/views/widget/widget.h" | |
20 #include "ui/views/widget/widget_delegate.h" | |
21 #include "ui/wm/core/compound_event_filter.h" | |
22 | |
23 namespace ash { | |
24 namespace { | |
25 | |
26 // Delay before showing. | |
27 const int kShowDelayMS = 400; | |
28 | |
29 // Delay before hiding. | |
30 const int kHideDelayMS = 500; | |
31 | |
32 // Padding from the bottom/right edge the resize widget is shown at. | |
33 const int kResizeWidgetPadding = 15; | |
34 | |
35 bool ContainsX(WmWindow* window, int x) { | |
36 return x >= 0 && x <= window->GetBounds().width(); | |
37 } | |
38 | |
39 bool ContainsScreenX(WmWindow* window, int x_in_screen) { | |
40 gfx::Point window_loc = | |
41 window->ConvertPointFromScreen(gfx::Point(x_in_screen, 0)); | |
42 return ContainsX(window, window_loc.x()); | |
43 } | |
44 | |
45 bool ContainsY(WmWindow* window, int y) { | |
46 return y >= 0 && y <= window->GetBounds().height(); | |
47 } | |
48 | |
49 bool ContainsScreenY(WmWindow* window, int y_in_screen) { | |
50 gfx::Point window_loc = | |
51 window->ConvertPointFromScreen(gfx::Point(0, y_in_screen)); | |
52 return ContainsY(window, window_loc.y()); | |
53 } | |
54 | |
55 bool Intersects(int x1, int max_1, int x2, int max_2) { | |
56 return x2 <= max_1 && max_2 > x1; | |
57 } | |
58 | |
59 } // namespace | |
60 | |
61 // View contained in the widget. Passes along mouse events to the | |
62 // MultiWindowResizeController so that it can start/stop the resize loop. | |
63 class MultiWindowResizeController::ResizeView : public views::View { | |
64 public: | |
65 explicit ResizeView(MultiWindowResizeController* controller, | |
66 Direction direction) | |
67 : controller_(controller), direction_(direction), image_(NULL) { | |
68 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
69 int image_id = direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H | |
70 : IDR_AURA_MULTI_WINDOW_RESIZE_V; | |
71 image_ = rb.GetImageNamed(image_id).ToImageSkia(); | |
72 } | |
73 | |
74 // views::View overrides: | |
75 gfx::Size GetPreferredSize() const override { | |
76 return gfx::Size(image_->width(), image_->height()); | |
77 } | |
78 void OnPaint(gfx::Canvas* canvas) override { | |
79 canvas->DrawImageInt(*image_, 0, 0); | |
80 } | |
81 bool OnMousePressed(const ui::MouseEvent& event) override { | |
82 gfx::Point location(event.location()); | |
83 views::View::ConvertPointToScreen(this, &location); | |
84 controller_->StartResize(location); | |
85 return true; | |
86 } | |
87 bool OnMouseDragged(const ui::MouseEvent& event) override { | |
88 gfx::Point location(event.location()); | |
89 views::View::ConvertPointToScreen(this, &location); | |
90 controller_->Resize(location, event.flags()); | |
91 return true; | |
92 } | |
93 void OnMouseReleased(const ui::MouseEvent& event) override { | |
94 controller_->CompleteResize(); | |
95 } | |
96 void OnMouseCaptureLost() override { controller_->CancelResize(); } | |
97 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override { | |
98 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM; | |
99 return ::wm::CompoundEventFilter::CursorForWindowComponent(component); | |
100 } | |
101 | |
102 private: | |
103 MultiWindowResizeController* controller_; | |
104 const Direction direction_; | |
105 const gfx::ImageSkia* image_; | |
106 | |
107 DISALLOW_COPY_AND_ASSIGN(ResizeView); | |
108 }; | |
109 | |
110 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards | |
111 // Contains() to MultiWindowResizeController. | |
112 class MultiWindowResizeController::ResizeMouseWatcherHost | |
113 : public views::MouseWatcherHost { | |
114 public: | |
115 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {} | |
116 | |
117 // MouseWatcherHost overrides: | |
118 bool Contains(const gfx::Point& point_in_screen, | |
119 MouseEventType type) override { | |
120 return (type == MOUSE_PRESS) ? host_->IsOverResizeWidget(point_in_screen) | |
121 : host_->IsOverWindows(point_in_screen); | |
122 } | |
123 | |
124 private: | |
125 MultiWindowResizeController* host_; | |
126 | |
127 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost); | |
128 }; | |
129 | |
130 MultiWindowResizeController::ResizeWindows::ResizeWindows() | |
131 : window1(nullptr), window2(nullptr), direction(TOP_BOTTOM) {} | |
132 | |
133 MultiWindowResizeController::ResizeWindows::ResizeWindows( | |
134 const ResizeWindows& other) = default; | |
135 | |
136 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {} | |
137 | |
138 bool MultiWindowResizeController::ResizeWindows::Equals( | |
139 const ResizeWindows& other) const { | |
140 return window1 == other.window1 && window2 == other.window2 && | |
141 direction == other.direction; | |
142 } | |
143 | |
144 MultiWindowResizeController::MultiWindowResizeController() {} | |
145 | |
146 MultiWindowResizeController::~MultiWindowResizeController() { | |
147 window_resizer_.reset(); | |
148 Hide(); | |
149 } | |
150 | |
151 void MultiWindowResizeController::Show(WmWindow* window, | |
152 int component, | |
153 const gfx::Point& point_in_window) { | |
154 // When the resize widget is showing we ignore Show() requests. Instead we | |
155 // only care about mouse movements from MouseWatcher. This is necessary as | |
156 // WorkspaceEventHandler only sees mouse movements over the windows, not all | |
157 // windows or over the desktop. | |
158 if (resize_widget_) | |
159 return; | |
160 | |
161 ResizeWindows windows(DetermineWindows(window, component, point_in_window)); | |
162 if (IsShowing() && windows_.Equals(windows)) | |
163 return; | |
164 | |
165 Hide(); | |
166 if (!windows.is_valid()) { | |
167 windows_ = ResizeWindows(); | |
168 return; | |
169 } | |
170 | |
171 windows_ = windows; | |
172 windows_.window1->aura_window()->AddObserver(this); | |
173 windows_.window2->aura_window()->AddObserver(this); | |
174 show_location_in_parent_ = | |
175 window->ConvertPointToTarget(window->GetParent(), point_in_window); | |
176 show_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS), | |
177 this, | |
178 &MultiWindowResizeController::ShowIfValidMouseLocation); | |
179 } | |
180 | |
181 void MultiWindowResizeController::Hide() { | |
182 if (window_resizer_) | |
183 return; // Ignore hides while actively resizing. | |
184 | |
185 if (windows_.window1) { | |
186 windows_.window1->aura_window()->RemoveObserver(this); | |
187 windows_.window1 = NULL; | |
188 } | |
189 if (windows_.window2) { | |
190 windows_.window2->aura_window()->RemoveObserver(this); | |
191 windows_.window2 = NULL; | |
192 } | |
193 | |
194 show_timer_.Stop(); | |
195 | |
196 if (!resize_widget_) | |
197 return; | |
198 | |
199 for (size_t i = 0; i < windows_.other_windows.size(); ++i) | |
200 windows_.other_windows[i]->aura_window()->RemoveObserver(this); | |
201 mouse_watcher_.reset(); | |
202 resize_widget_.reset(); | |
203 windows_ = ResizeWindows(); | |
204 } | |
205 | |
206 void MultiWindowResizeController::MouseMovedOutOfHost() { | |
207 Hide(); | |
208 } | |
209 | |
210 void MultiWindowResizeController::OnWindowDestroying(aura::Window* window) { | |
211 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing. | |
212 window_resizer_.reset(); | |
213 Hide(); | |
214 } | |
215 | |
216 MultiWindowResizeController::ResizeWindows | |
217 MultiWindowResizeController::DetermineWindowsFromScreenPoint( | |
218 WmWindow* window) const { | |
219 gfx::Point mouse_location( | |
220 display::Screen::GetScreen()->GetCursorScreenPoint()); | |
221 mouse_location = window->ConvertPointFromScreen(mouse_location); | |
222 const int component = window->GetNonClientComponent(mouse_location); | |
223 return DetermineWindows(window, component, mouse_location); | |
224 } | |
225 | |
226 void MultiWindowResizeController::CreateMouseWatcher() { | |
227 mouse_watcher_.reset( | |
228 new views::MouseWatcher(new ResizeMouseWatcherHost(this), this)); | |
229 mouse_watcher_->set_notify_on_exit_time( | |
230 base::TimeDelta::FromMilliseconds(kHideDelayMS)); | |
231 mouse_watcher_->Start(); | |
232 } | |
233 | |
234 MultiWindowResizeController::ResizeWindows | |
235 MultiWindowResizeController::DetermineWindows(WmWindow* window, | |
236 int window_component, | |
237 const gfx::Point& point) const { | |
238 ResizeWindows result; | |
239 gfx::Point point_in_parent = | |
240 window->ConvertPointToTarget(window->GetParent(), point); | |
241 switch (window_component) { | |
242 case HTRIGHT: | |
243 result.direction = LEFT_RIGHT; | |
244 result.window1 = window; | |
245 result.window2 = FindWindowByEdge( | |
246 window, HTLEFT, window->GetBounds().right(), point_in_parent.y()); | |
247 break; | |
248 case HTLEFT: | |
249 result.direction = LEFT_RIGHT; | |
250 result.window1 = FindWindowByEdge( | |
251 window, HTRIGHT, window->GetBounds().x(), point_in_parent.y()); | |
252 result.window2 = window; | |
253 break; | |
254 case HTTOP: | |
255 result.direction = TOP_BOTTOM; | |
256 result.window1 = FindWindowByEdge(window, HTBOTTOM, point_in_parent.x(), | |
257 window->GetBounds().y()); | |
258 result.window2 = window; | |
259 break; | |
260 case HTBOTTOM: | |
261 result.direction = TOP_BOTTOM; | |
262 result.window1 = window; | |
263 result.window2 = FindWindowByEdge(window, HTTOP, point_in_parent.x(), | |
264 window->GetBounds().bottom()); | |
265 break; | |
266 default: | |
267 break; | |
268 } | |
269 return result; | |
270 } | |
271 | |
272 WmWindow* MultiWindowResizeController::FindWindowByEdge( | |
273 WmWindow* window_to_ignore, | |
274 int edge_want, | |
275 int x_in_parent, | |
276 int y_in_parent) const { | |
277 WmWindow* parent = window_to_ignore->GetParent(); | |
278 std::vector<WmWindow*> windows = parent->GetChildren(); | |
279 for (auto i = windows.rbegin(); i != windows.rend(); ++i) { | |
280 WmWindow* window = *i; | |
281 if (window == window_to_ignore || !window->IsVisible()) | |
282 continue; | |
283 | |
284 // Ignore windows without a non-client area. | |
285 if (!window->HasNonClientArea()) | |
286 continue; | |
287 | |
288 gfx::Point p = parent->ConvertPointToTarget( | |
289 window, gfx::Point(x_in_parent, y_in_parent)); | |
290 switch (edge_want) { | |
291 case HTLEFT: | |
292 if (ContainsY(window, p.y()) && p.x() == 0) | |
293 return window; | |
294 break; | |
295 case HTRIGHT: | |
296 if (ContainsY(window, p.y()) && p.x() == window->GetBounds().width()) | |
297 return window; | |
298 break; | |
299 case HTTOP: | |
300 if (ContainsX(window, p.x()) && p.y() == 0) | |
301 return window; | |
302 break; | |
303 case HTBOTTOM: | |
304 if (ContainsX(window, p.x()) && p.y() == window->GetBounds().height()) | |
305 return window; | |
306 break; | |
307 default: | |
308 NOTREACHED(); | |
309 } | |
310 // Window doesn't contain the edge, but if window contains |point| | |
311 // it's obscuring any other window that could be at the location. | |
312 if (window->GetBounds().Contains(x_in_parent, y_in_parent)) | |
313 return NULL; | |
314 } | |
315 return NULL; | |
316 } | |
317 | |
318 WmWindow* MultiWindowResizeController::FindWindowTouching( | |
319 WmWindow* window, | |
320 Direction direction) const { | |
321 int right = window->GetBounds().right(); | |
322 int bottom = window->GetBounds().bottom(); | |
323 WmWindow* parent = window->GetParent(); | |
324 std::vector<WmWindow*> windows = parent->GetChildren(); | |
325 for (auto i = windows.rbegin(); i != windows.rend(); ++i) { | |
326 WmWindow* other = *i; | |
327 if (other == window || !other->IsVisible()) | |
328 continue; | |
329 switch (direction) { | |
330 case TOP_BOTTOM: | |
331 if (other->GetBounds().y() == bottom && | |
332 Intersects(other->GetBounds().x(), other->GetBounds().right(), | |
333 window->GetBounds().x(), window->GetBounds().right())) { | |
334 return other; | |
335 } | |
336 break; | |
337 case LEFT_RIGHT: | |
338 if (other->GetBounds().x() == right && | |
339 Intersects(other->GetBounds().y(), other->GetBounds().bottom(), | |
340 window->GetBounds().y(), window->GetBounds().bottom())) { | |
341 return other; | |
342 } | |
343 break; | |
344 default: | |
345 NOTREACHED(); | |
346 } | |
347 } | |
348 return NULL; | |
349 } | |
350 | |
351 void MultiWindowResizeController::FindWindowsTouching( | |
352 WmWindow* start, | |
353 Direction direction, | |
354 std::vector<WmWindow*>* others) const { | |
355 while (start) { | |
356 start = FindWindowTouching(start, direction); | |
357 if (start) | |
358 others->push_back(start); | |
359 } | |
360 } | |
361 | |
362 void MultiWindowResizeController::ShowIfValidMouseLocation() { | |
363 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) || | |
364 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) { | |
365 ShowNow(); | |
366 } else { | |
367 Hide(); | |
368 } | |
369 } | |
370 | |
371 void MultiWindowResizeController::ShowNow() { | |
372 DCHECK(!resize_widget_.get()); | |
373 DCHECK(windows_.is_valid()); | |
374 show_timer_.Stop(); | |
375 resize_widget_.reset(new views::Widget); | |
376 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); | |
377 params.name = "MultiWindowResizeController"; | |
378 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
379 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
380 windows_.window1->GetRootWindowController() | |
381 ->ConfigureWidgetInitParamsForContainer( | |
382 resize_widget_.get(), kShellWindowId_AlwaysOnTopContainer, ¶ms); | |
383 ResizeView* view = new ResizeView(this, windows_.direction); | |
384 resize_widget_->set_focus_on_creation(false); | |
385 resize_widget_->Init(params); | |
386 WmWindow::Get(resize_widget_->GetNativeWindow()) | |
387 ->SetVisibilityAnimationType(::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); | |
388 resize_widget_->SetContentsView(view); | |
389 show_bounds_in_screen_ = windows_.window1->GetParent()->ConvertRectToScreen( | |
390 CalculateResizeWidgetBounds(show_location_in_parent_)); | |
391 resize_widget_->SetBounds(show_bounds_in_screen_); | |
392 resize_widget_->Show(); | |
393 CreateMouseWatcher(); | |
394 } | |
395 | |
396 bool MultiWindowResizeController::IsShowing() const { | |
397 return resize_widget_.get() || show_timer_.IsRunning(); | |
398 } | |
399 | |
400 void MultiWindowResizeController::StartResize( | |
401 const gfx::Point& location_in_screen) { | |
402 DCHECK(!window_resizer_.get()); | |
403 DCHECK(windows_.is_valid()); | |
404 gfx::Point location_in_parent = | |
405 windows_.window2->GetParent()->ConvertPointFromScreen(location_in_screen); | |
406 std::vector<WmWindow*> windows; | |
407 windows.push_back(windows_.window2); | |
408 DCHECK(windows_.other_windows.empty()); | |
409 FindWindowsTouching(windows_.window2, windows_.direction, | |
410 &windows_.other_windows); | |
411 for (size_t i = 0; i < windows_.other_windows.size(); ++i) { | |
412 windows_.other_windows[i]->aura_window()->AddObserver(this); | |
413 windows.push_back(windows_.other_windows[i]); | |
414 } | |
415 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM; | |
416 wm::WindowState* window_state = windows_.window1->GetWindowState(); | |
417 window_state->CreateDragDetails(location_in_parent, component, | |
418 aura::client::WINDOW_MOVE_SOURCE_MOUSE); | |
419 window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows)); | |
420 | |
421 // Do not hide the resize widget while a drag is active. | |
422 mouse_watcher_.reset(); | |
423 } | |
424 | |
425 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen, | |
426 int event_flags) { | |
427 gfx::Point location_in_parent = | |
428 windows_.window1->GetParent()->ConvertPointFromScreen(location_in_screen); | |
429 window_resizer_->Drag(location_in_parent, event_flags); | |
430 gfx::Rect bounds = windows_.window1->GetParent()->ConvertRectToScreen( | |
431 CalculateResizeWidgetBounds(location_in_parent)); | |
432 | |
433 if (windows_.direction == LEFT_RIGHT) | |
434 bounds.set_y(show_bounds_in_screen_.y()); | |
435 else | |
436 bounds.set_x(show_bounds_in_screen_.x()); | |
437 resize_widget_->SetBounds(bounds); | |
438 } | |
439 | |
440 void MultiWindowResizeController::CompleteResize() { | |
441 window_resizer_->CompleteDrag(); | |
442 window_resizer_->GetTarget()->GetWindowState()->DeleteDragDetails(); | |
443 window_resizer_.reset(); | |
444 | |
445 // Mouse may still be over resizer, if not hide. | |
446 gfx::Point screen_loc = display::Screen::GetScreen()->GetCursorScreenPoint(); | |
447 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) { | |
448 Hide(); | |
449 } else { | |
450 // If the mouse is over the resizer we need to remove observers on any of | |
451 // the |other_windows|. If we start another resize we'll recalculate the | |
452 // |other_windows| and invoke AddObserver() as necessary. | |
453 for (size_t i = 0; i < windows_.other_windows.size(); ++i) | |
454 windows_.other_windows[i]->aura_window()->RemoveObserver(this); | |
455 windows_.other_windows.clear(); | |
456 | |
457 CreateMouseWatcher(); | |
458 } | |
459 } | |
460 | |
461 void MultiWindowResizeController::CancelResize() { | |
462 if (!window_resizer_) | |
463 return; // Happens if window was destroyed and we nuked the WindowResizer. | |
464 window_resizer_->RevertDrag(); | |
465 window_resizer_->GetTarget()->GetWindowState()->DeleteDragDetails(); | |
466 window_resizer_.reset(); | |
467 Hide(); | |
468 } | |
469 | |
470 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds( | |
471 const gfx::Point& location_in_parent) const { | |
472 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize(); | |
473 int x = 0, y = 0; | |
474 if (windows_.direction == LEFT_RIGHT) { | |
475 x = windows_.window1->GetBounds().right() - pref.width() / 2; | |
476 y = location_in_parent.y() + kResizeWidgetPadding; | |
477 if (y + pref.height() / 2 > windows_.window1->GetBounds().bottom() && | |
478 y + pref.height() / 2 > windows_.window2->GetBounds().bottom()) { | |
479 y = location_in_parent.y() - kResizeWidgetPadding - pref.height(); | |
480 } | |
481 } else { | |
482 x = location_in_parent.x() + kResizeWidgetPadding; | |
483 if (x + pref.height() / 2 > windows_.window1->GetBounds().right() && | |
484 x + pref.height() / 2 > windows_.window2->GetBounds().right()) { | |
485 x = location_in_parent.x() - kResizeWidgetPadding - pref.width(); | |
486 } | |
487 y = windows_.window1->GetBounds().bottom() - pref.height() / 2; | |
488 } | |
489 return gfx::Rect(x, y, pref.width(), pref.height()); | |
490 } | |
491 | |
492 bool MultiWindowResizeController::IsOverResizeWidget( | |
493 const gfx::Point& location_in_screen) const { | |
494 return resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen); | |
495 } | |
496 | |
497 bool MultiWindowResizeController::IsOverWindows( | |
498 const gfx::Point& location_in_screen) const { | |
499 if (IsOverResizeWidget(location_in_screen)) | |
500 return true; | |
501 | |
502 if (windows_.direction == TOP_BOTTOM) { | |
503 if (!ContainsScreenX(windows_.window1, location_in_screen.x()) || | |
504 !ContainsScreenX(windows_.window2, location_in_screen.x())) { | |
505 return false; | |
506 } | |
507 } else { | |
508 if (!ContainsScreenY(windows_.window1, location_in_screen.y()) || | |
509 !ContainsScreenY(windows_.window2, location_in_screen.y())) { | |
510 return false; | |
511 } | |
512 } | |
513 | |
514 // Check whether |location_in_screen| is in the event target's resize region. | |
515 // This is tricky because a window's resize region can extend outside a | |
516 // window's bounds. | |
517 WmWindow* target = | |
518 windows_.window1->GetRootWindowController()->FindEventTarget( | |
519 location_in_screen); | |
520 if (target == windows_.window1) { | |
521 return IsOverComponent( | |
522 windows_.window1, location_in_screen, | |
523 windows_.direction == TOP_BOTTOM ? HTBOTTOM : HTRIGHT); | |
524 } | |
525 if (target == windows_.window2) { | |
526 return IsOverComponent(windows_.window2, location_in_screen, | |
527 windows_.direction == TOP_BOTTOM ? HTTOP : HTLEFT); | |
528 } | |
529 return false; | |
530 } | |
531 | |
532 bool MultiWindowResizeController::IsOverComponent( | |
533 WmWindow* window, | |
534 const gfx::Point& location_in_screen, | |
535 int component) const { | |
536 gfx::Point window_loc = window->ConvertPointFromScreen(location_in_screen); | |
537 return window->GetNonClientComponent(window_loc) == component; | |
538 } | |
539 | |
540 } // namespace ash | |
OLD | NEW |