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