Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / ash / wm / workspace / multi_window_resize_controller.cc
blob067275b59bc64efafb619d5e2f0a62bdd8f5a927
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.
5 #include "ash/wm/workspace/multi_window_resize_controller.h"
7 #include "ash/screen_util.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/window_animations.h"
11 #include "ash/wm/workspace/workspace_event_handler.h"
12 #include "ash/wm/workspace/workspace_window_resizer.h"
13 #include "grit/ash_resources.h"
14 #include "ui/aura/client/screen_position_client.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_delegate.h"
17 #include "ui/aura/window_event_dispatcher.h"
18 #include "ui/base/hit_test.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/events/event_targeter.h"
21 #include "ui/events/event_utils.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/gfx/screen.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/widget/widget_delegate.h"
28 #include "ui/wm/core/compound_event_filter.h"
29 #include "ui/wm/core/coordinate_conversion.h"
31 using aura::Window;
33 namespace ash {
34 namespace {
36 // Delay before showing.
37 const int kShowDelayMS = 400;
39 // Delay before hiding.
40 const int kHideDelayMS = 500;
42 // Padding from the bottom/right edge the resize widget is shown at.
43 const int kResizeWidgetPadding = 15;
45 bool ContainsX(Window* window, int x) {
46 return x >= 0 && x <= window->bounds().width();
49 bool ContainsScreenX(Window* window, int x_in_screen) {
50 gfx::Point window_loc(x_in_screen, 0);
51 ::wm::ConvertPointFromScreen(window, &window_loc);
52 return ContainsX(window, window_loc.x());
55 bool ContainsY(Window* window, int y) {
56 return y >= 0 && y <= window->bounds().height();
59 bool ContainsScreenY(Window* window, int y_in_screen) {
60 gfx::Point window_loc(0, y_in_screen);
61 ::wm::ConvertPointFromScreen(window, &window_loc);
62 return ContainsY(window, window_loc.y());
65 bool Intersects(int x1, int max_1, int x2, int max_2) {
66 return x2 <= max_1 && max_2 > x1;
69 } // namespace
71 // View contained in the widget. Passes along mouse events to the
72 // MultiWindowResizeController so that it can start/stop the resize loop.
73 class MultiWindowResizeController::ResizeView : public views::View {
74 public:
75 explicit ResizeView(MultiWindowResizeController* controller,
76 Direction direction)
77 : controller_(controller),
78 direction_(direction),
79 image_(NULL) {
80 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
81 int image_id =
82 direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
83 IDR_AURA_MULTI_WINDOW_RESIZE_V;
84 image_ = rb.GetImageNamed(image_id).ToImageSkia();
87 // views::View overrides:
88 gfx::Size GetPreferredSize() const override {
89 return gfx::Size(image_->width(), image_->height());
91 void OnPaint(gfx::Canvas* canvas) override {
92 canvas->DrawImageInt(*image_, 0, 0);
94 bool OnMousePressed(const ui::MouseEvent& event) override {
95 gfx::Point location(event.location());
96 views::View::ConvertPointToScreen(this, &location);
97 controller_->StartResize(location);
98 return true;
100 bool OnMouseDragged(const ui::MouseEvent& event) override {
101 gfx::Point location(event.location());
102 views::View::ConvertPointToScreen(this, &location);
103 controller_->Resize(location, event.flags());
104 return true;
106 void OnMouseReleased(const ui::MouseEvent& event) override {
107 controller_->CompleteResize();
109 void OnMouseCaptureLost() override { controller_->CancelResize(); }
110 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
111 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
112 return ::wm::CompoundEventFilter::CursorForWindowComponent(
113 component);
116 private:
117 MultiWindowResizeController* controller_;
118 const Direction direction_;
119 const gfx::ImageSkia* image_;
121 DISALLOW_COPY_AND_ASSIGN(ResizeView);
124 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
125 // Contains() to MultiWindowResizeController.
126 class MultiWindowResizeController::ResizeMouseWatcherHost :
127 public views::MouseWatcherHost {
128 public:
129 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
131 // MouseWatcherHost overrides:
132 bool Contains(const gfx::Point& point_in_screen,
133 MouseEventType type) override {
134 return (type == MOUSE_PRESS)
135 ? host_->IsOverResizeWidget(point_in_screen)
136 : host_->IsOverWindows(point_in_screen);
139 private:
140 MultiWindowResizeController* host_;
142 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
145 MultiWindowResizeController::ResizeWindows::ResizeWindows()
146 : window1(NULL),
147 window2(NULL),
148 direction(TOP_BOTTOM){
151 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
154 bool MultiWindowResizeController::ResizeWindows::Equals(
155 const ResizeWindows& other) const {
156 return window1 == other.window1 &&
157 window2 == other.window2 &&
158 direction == other.direction;
161 MultiWindowResizeController::MultiWindowResizeController() {
164 MultiWindowResizeController::~MultiWindowResizeController() {
165 window_resizer_.reset();
166 Hide();
169 void MultiWindowResizeController::Show(Window* window,
170 int component,
171 const gfx::Point& point_in_window) {
172 // When the resize widget is showing we ignore Show() requests. Instead we
173 // only care about mouse movements from MouseWatcher. This is necessary as
174 // WorkspaceEventHandler only sees mouse movements over the windows, not all
175 // windows or over the desktop.
176 if (resize_widget_)
177 return;
179 ResizeWindows windows(DetermineWindows(window, component, point_in_window));
180 if (IsShowing() && windows_.Equals(windows))
181 return;
183 Hide();
184 if (!windows.is_valid()) {
185 windows_ = ResizeWindows();
186 return;
189 windows_ = windows;
190 windows_.window1->AddObserver(this);
191 windows_.window2->AddObserver(this);
192 show_location_in_parent_ = point_in_window;
193 Window::ConvertPointToTarget(
194 window, window->parent(), &show_location_in_parent_);
195 show_timer_.Start(
196 FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
197 this, &MultiWindowResizeController::ShowIfValidMouseLocation);
200 void MultiWindowResizeController::Hide() {
201 if (window_resizer_)
202 return; // Ignore hides while actively resizing.
204 if (windows_.window1) {
205 windows_.window1->RemoveObserver(this);
206 windows_.window1 = NULL;
208 if (windows_.window2) {
209 windows_.window2->RemoveObserver(this);
210 windows_.window2 = NULL;
213 show_timer_.Stop();
215 if (!resize_widget_)
216 return;
218 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
219 windows_.other_windows[i]->RemoveObserver(this);
220 mouse_watcher_.reset();
221 resize_widget_.reset();
222 windows_ = ResizeWindows();
225 void MultiWindowResizeController::MouseMovedOutOfHost() {
226 Hide();
229 void MultiWindowResizeController::OnWindowDestroying(
230 aura::Window* window) {
231 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
232 window_resizer_.reset();
233 Hide();
236 MultiWindowResizeController::ResizeWindows
237 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
238 aura::Window* window) const {
239 gfx::Point mouse_location(
240 gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
241 ::wm::ConvertPointFromScreen(window, &mouse_location);
242 const int component =
243 window->delegate()->GetNonClientComponent(mouse_location);
244 return DetermineWindows(window, component, mouse_location);
247 void MultiWindowResizeController::CreateMouseWatcher() {
248 mouse_watcher_.reset(new views::MouseWatcher(
249 new ResizeMouseWatcherHost(this), this));
250 mouse_watcher_->set_notify_on_exit_time(
251 base::TimeDelta::FromMilliseconds(kHideDelayMS));
252 mouse_watcher_->Start();
255 MultiWindowResizeController::ResizeWindows
256 MultiWindowResizeController::DetermineWindows(
257 Window* window,
258 int window_component,
259 const gfx::Point& point) const {
260 ResizeWindows result;
261 gfx::Point point_in_parent(point);
262 Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
263 switch (window_component) {
264 case HTRIGHT:
265 result.direction = LEFT_RIGHT;
266 result.window1 = window;
267 result.window2 = FindWindowByEdge(
268 window, HTLEFT, window->bounds().right(), point_in_parent.y());
269 break;
270 case HTLEFT:
271 result.direction = LEFT_RIGHT;
272 result.window1 = FindWindowByEdge(
273 window, HTRIGHT, window->bounds().x(), point_in_parent.y());
274 result.window2 = window;
275 break;
276 case HTTOP:
277 result.direction = TOP_BOTTOM;
278 result.window1 = FindWindowByEdge(
279 window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
280 result.window2 = window;
281 break;
282 case HTBOTTOM:
283 result.direction = TOP_BOTTOM;
284 result.window1 = window;
285 result.window2 = FindWindowByEdge(
286 window, HTTOP, point_in_parent.x(), window->bounds().bottom());
287 break;
288 default:
289 break;
291 return result;
294 Window* MultiWindowResizeController::FindWindowByEdge(
295 Window* window_to_ignore,
296 int edge_want,
297 int x_in_parent,
298 int y_in_parent) const {
299 Window* parent = window_to_ignore->parent();
300 const Window::Windows& windows(parent->children());
301 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
302 i != windows.rend(); ++i) {
303 Window* window = *i;
304 if (window == window_to_ignore || !window->IsVisible())
305 continue;
307 // Ignore windows without a delegate. A delegate is necessary to query the
308 // non-client component.
309 if (!window->delegate())
310 continue;
312 gfx::Point p(x_in_parent, y_in_parent);
313 aura::Window::ConvertPointToTarget(parent, window, &p);
314 switch (edge_want) {
315 case HTLEFT:
316 if (ContainsY(window, p.y()) && p.x() == 0)
317 return window;
318 break;
319 case HTRIGHT:
320 if (ContainsY(window, p.y()) && p.x() == window->bounds().width())
321 return window;
322 break;
323 case HTTOP:
324 if (ContainsX(window, p.x()) && p.y() == 0)
325 return window;
326 break;
327 case HTBOTTOM:
328 if (ContainsX(window, p.x()) && p.y() == window->bounds().height())
329 return window;
330 break;
331 default:
332 NOTREACHED();
334 // Window doesn't contain the edge, but if window contains |point|
335 // it's obscuring any other window that could be at the location.
336 if (window->bounds().Contains(x_in_parent, y_in_parent))
337 return NULL;
339 return NULL;
342 aura::Window* MultiWindowResizeController::FindWindowTouching(
343 aura::Window* window,
344 Direction direction) const {
345 int right = window->bounds().right();
346 int bottom = window->bounds().bottom();
347 Window* parent = window->parent();
348 const Window::Windows& windows(parent->children());
349 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
350 i != windows.rend(); ++i) {
351 Window* other = *i;
352 if (other == window || !other->IsVisible())
353 continue;
354 switch (direction) {
355 case TOP_BOTTOM:
356 if (other->bounds().y() == bottom &&
357 Intersects(other->bounds().x(), other->bounds().right(),
358 window->bounds().x(), window->bounds().right())) {
359 return other;
361 break;
362 case LEFT_RIGHT:
363 if (other->bounds().x() == right &&
364 Intersects(other->bounds().y(), other->bounds().bottom(),
365 window->bounds().y(), window->bounds().bottom())) {
366 return other;
368 break;
369 default:
370 NOTREACHED();
373 return NULL;
376 void MultiWindowResizeController::FindWindowsTouching(
377 aura::Window* start,
378 Direction direction,
379 std::vector<aura::Window*>* others) const {
380 while (start) {
381 start = FindWindowTouching(start, direction);
382 if (start)
383 others->push_back(start);
387 void MultiWindowResizeController::ShowIfValidMouseLocation() {
388 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
389 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
390 ShowNow();
391 } else {
392 Hide();
396 void MultiWindowResizeController::ShowNow() {
397 DCHECK(!resize_widget_.get());
398 DCHECK(windows_.is_valid());
399 show_timer_.Stop();
400 resize_widget_.reset(new views::Widget);
401 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
402 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
403 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
404 params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
405 kShellWindowId_AlwaysOnTopContainer);
406 ResizeView* view = new ResizeView(this, windows_.direction);
407 resize_widget_->set_focus_on_creation(false);
408 resize_widget_->Init(params);
409 ::wm::SetWindowVisibilityAnimationType(
410 resize_widget_->GetNativeWindow(),
411 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
412 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
413 resize_widget_->SetContentsView(view);
414 show_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen(
415 windows_.window1->parent(),
416 CalculateResizeWidgetBounds(show_location_in_parent_));
417 resize_widget_->SetBounds(show_bounds_in_screen_);
418 resize_widget_->Show();
419 CreateMouseWatcher();
422 bool MultiWindowResizeController::IsShowing() const {
423 return resize_widget_.get() || show_timer_.IsRunning();
426 void MultiWindowResizeController::StartResize(
427 const gfx::Point& location_in_screen) {
428 DCHECK(!window_resizer_.get());
429 DCHECK(windows_.is_valid());
430 gfx::Point location_in_parent(location_in_screen);
431 aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
432 ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
433 std::vector<aura::Window*> windows;
434 windows.push_back(windows_.window2);
435 DCHECK(windows_.other_windows.empty());
436 FindWindowsTouching(windows_.window2, windows_.direction,
437 &windows_.other_windows);
438 for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
439 windows_.other_windows[i]->AddObserver(this);
440 windows.push_back(windows_.other_windows[i]);
442 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
443 wm::WindowState* window_state = wm::GetWindowState(windows_.window1);
444 window_state->CreateDragDetails(windows_.window1,
445 location_in_parent,
446 component,
447 aura::client::WINDOW_MOVE_SOURCE_MOUSE);
448 window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows));
450 // Do not hide the resize widget while a drag is active.
451 mouse_watcher_.reset();
454 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
455 int event_flags) {
456 gfx::Point location_in_parent(location_in_screen);
457 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
458 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
459 window_resizer_->Drag(location_in_parent, event_flags);
460 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen(
461 windows_.window1->parent(),
462 CalculateResizeWidgetBounds(location_in_parent));
464 if (windows_.direction == LEFT_RIGHT)
465 bounds.set_y(show_bounds_in_screen_.y());
466 else
467 bounds.set_x(show_bounds_in_screen_.x());
468 resize_widget_->SetBounds(bounds);
471 void MultiWindowResizeController::CompleteResize() {
472 window_resizer_->CompleteDrag();
473 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
474 window_resizer_.reset();
476 // Mouse may still be over resizer, if not hide.
477 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
478 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
479 Hide();
480 } else {
481 // If the mouse is over the resizer we need to remove observers on any of
482 // the |other_windows|. If we start another resize we'll recalculate the
483 // |other_windows| and invoke AddObserver() as necessary.
484 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
485 windows_.other_windows[i]->RemoveObserver(this);
486 windows_.other_windows.clear();
488 CreateMouseWatcher();
492 void MultiWindowResizeController::CancelResize() {
493 if (!window_resizer_)
494 return; // Happens if window was destroyed and we nuked the WindowResizer.
495 window_resizer_->RevertDrag();
496 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
497 window_resizer_.reset();
498 Hide();
501 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
502 const gfx::Point& location_in_parent) const {
503 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
504 int x = 0, y = 0;
505 if (windows_.direction == LEFT_RIGHT) {
506 x = windows_.window1->bounds().right() - pref.width() / 2;
507 y = location_in_parent.y() + kResizeWidgetPadding;
508 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
509 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
510 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
512 } else {
513 x = location_in_parent.x() + kResizeWidgetPadding;
514 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
515 x + pref.height() / 2 > windows_.window2->bounds().right()) {
516 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
518 y = windows_.window1->bounds().bottom() - pref.height() / 2;
520 return gfx::Rect(x, y, pref.width(), pref.height());
523 bool MultiWindowResizeController::IsOverResizeWidget(
524 const gfx::Point& location_in_screen) const {
525 return resize_widget_->GetWindowBoundsInScreen().Contains(
526 location_in_screen);
529 bool MultiWindowResizeController::IsOverWindows(
530 const gfx::Point& location_in_screen) const {
531 if (IsOverResizeWidget(location_in_screen))
532 return true;
534 if (windows_.direction == TOP_BOTTOM) {
535 if (!ContainsScreenX(windows_.window1, location_in_screen.x()) ||
536 !ContainsScreenX(windows_.window2, location_in_screen.x())) {
537 return false;
539 } else {
540 if (!ContainsScreenY(windows_.window1, location_in_screen.y()) ||
541 !ContainsScreenY(windows_.window2, location_in_screen.y())) {
542 return false;
546 // Check whether |location_in_screen| is in the event target's resize region.
547 // This is tricky because a window's resize region can extend outside a
548 // window's bounds.
549 gfx::Point location_in_root(location_in_screen);
550 aura::Window* root = windows_.window1->GetRootWindow();
551 ::wm::ConvertPointFromScreen(root, &location_in_root);
552 ui::MouseEvent test_event(ui::ET_MOUSE_MOVED, location_in_root,
553 location_in_root, ui::EventTimeForNow(),
554 ui::EF_NONE, ui::EF_NONE);
555 ui::EventTarget* event_handler = static_cast<ui::EventTarget*>(root)
556 ->GetEventTargeter()
557 ->FindTargetForEvent(root, &test_event);
558 if (event_handler == windows_.window1) {
559 return IsOverComponent(
560 windows_.window1,
561 location_in_screen,
562 windows_.direction == TOP_BOTTOM ? HTBOTTOM : HTRIGHT);
563 } else if (event_handler == windows_.window2) {
564 return IsOverComponent(
565 windows_.window2,
566 location_in_screen,
567 windows_.direction == TOP_BOTTOM ? HTTOP : HTLEFT);
569 return false;
572 bool MultiWindowResizeController::IsOverComponent(
573 aura::Window* window,
574 const gfx::Point& location_in_screen,
575 int component) const {
576 gfx::Point window_loc(location_in_screen);
577 ::wm::ConvertPointFromScreen(window, &window_loc);
578 return window->delegate()->GetNonClientComponent(window_loc) == component;
581 } // namespace ash