Only fsync leveldb's directory when the manifest is being updated.
[chromium-blink-merge.git] / ash / wm / workspace / multi_window_resize_controller.cc
blob798b5c46d8e9562dc2a1d4fcbaf8ebe4e58c8293
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_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"
29 using aura::Window;
31 namespace ash {
32 namespace internal {
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 window->bounds().x() <= x && window->bounds().right() >= x;
49 bool ContainsY(Window* window, int y) {
50 return window->bounds().y() <= y && window->bounds().bottom() >= y;
53 bool Intersects(int x1, int max_1, int x2, int max_2) {
54 return x2 <= max_1 && max_2 > x1;
57 } // namespace
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();
75 // views::View overrides:
76 virtual gfx::Size GetPreferredSize() OVERRIDE {
77 return gfx::Size(image_->width(), image_->height());
79 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
80 canvas->DrawImageInt(*image_, 0, 0);
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;
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;
94 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
95 controller_->CompleteResize(event.flags());
97 virtual void OnMouseCaptureLost() OVERRIDE {
98 controller_->CancelResize();
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);
107 private:
108 MultiWindowResizeController* controller_;
109 const Direction direction_;
110 const gfx::ImageSkia* image_;
112 DISALLOW_COPY_AND_ASSIGN(ResizeView);
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) {}
122 // MouseWatcherHost overrides:
123 virtual bool Contains(const gfx::Point& point_in_screen,
124 MouseEventType type) OVERRIDE {
125 return host_->IsOverWindows(point_in_screen);
128 private:
129 MultiWindowResizeController* host_;
131 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
134 MultiWindowResizeController::ResizeWindows::ResizeWindows()
135 : window1(NULL),
136 window2(NULL),
137 direction(TOP_BOTTOM){
140 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
143 bool MultiWindowResizeController::ResizeWindows::Equals(
144 const ResizeWindows& other) const {
145 return window1 == other.window1 &&
146 window2 == other.window2 &&
147 direction == other.direction;
150 MultiWindowResizeController::MultiWindowResizeController() {
153 MultiWindowResizeController::~MultiWindowResizeController() {
154 window_resizer_.reset();
155 Hide();
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;
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();
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);
191 void MultiWindowResizeController::Hide() {
192 hide_timer_.Stop();
193 if (window_resizer_)
194 return; // Ignore hides while actively resizing.
196 if (windows_.window1) {
197 windows_.window1->RemoveObserver(this);
198 windows_.window1 = NULL;
200 if (windows_.window2) {
201 windows_.window2->RemoveObserver(this);
202 windows_.window2 = NULL;
205 show_timer_.Stop();
207 if (!resize_widget_)
208 return;
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();
217 void MultiWindowResizeController::MouseMovedOutOfHost() {
218 Hide();
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();
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);
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;
275 return result;
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();
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;
315 return NULL;
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;
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;
344 break;
345 default:
346 NOTREACHED();
349 return NULL;
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);
363 void MultiWindowResizeController::DelayedHide() {
364 if (hide_timer_.IsRunning())
365 return;
367 hide_timer_.Start(FROM_HERE,
368 base::TimeDelta::FromMilliseconds(kHideDelayMS),
369 this, &MultiWindowResizeController::Hide);
372 void MultiWindowResizeController::ShowIfValidMouseLocation() {
373 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
374 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
375 ShowNow();
376 } else {
377 Hide();
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.transparent = true;
388 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
389 params.parent = Shell::GetContainer(
390 Shell::GetActiveRootWindow(),
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();
414 bool MultiWindowResizeController::IsShowing() const {
415 return resize_widget_.get() || show_timer_.IsRunning();
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]);
435 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
436 window_resizer_.reset(WorkspaceWindowResizer::Create(
437 windows_.window1, location_in_parent, component, windows));
440 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
441 int event_flags) {
442 gfx::Point location_in_parent(location_in_screen);
443 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
444 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
445 window_resizer_->Drag(location_in_parent, event_flags);
446 gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
447 windows_.window1->parent(),
448 CalculateResizeWidgetBounds(location_in_parent));
450 if (windows_.direction == LEFT_RIGHT)
451 bounds.set_y(show_bounds_in_screen_.y());
452 else
453 bounds.set_x(show_bounds_in_screen_.x());
454 resize_widget_->SetBounds(bounds);
457 void MultiWindowResizeController::CompleteResize(int event_flags) {
458 window_resizer_->CompleteDrag(event_flags);
459 window_resizer_.reset();
461 // Mouse may still be over resizer, if not hide.
462 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
463 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
464 Hide();
465 } else {
466 // If the mouse is over the resizer we need to remove observers on any of
467 // the |other_windows|. If we start another resize we'll recalculate the
468 // |other_windows| and invoke AddObserver() as necessary.
469 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
470 windows_.other_windows[i]->RemoveObserver(this);
471 windows_.other_windows.clear();
475 void MultiWindowResizeController::CancelResize() {
476 if (!window_resizer_)
477 return; // Happens if window was destroyed and we nuked the WindowResizer.
478 window_resizer_->RevertDrag();
479 window_resizer_.reset();
480 Hide();
483 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
484 const gfx::Point& location_in_parent) const {
485 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
486 int x = 0, y = 0;
487 if (windows_.direction == LEFT_RIGHT) {
488 x = windows_.window1->bounds().right() - pref.width() / 2;
489 y = location_in_parent.y() + kResizeWidgetPadding;
490 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
491 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
492 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
494 } else {
495 x = location_in_parent.x() + kResizeWidgetPadding;
496 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
497 x + pref.height() / 2 > windows_.window2->bounds().right()) {
498 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
500 y = windows_.window1->bounds().bottom() - pref.height() / 2;
502 return gfx::Rect(x, y, pref.width(), pref.height());
505 bool MultiWindowResizeController::IsOverWindows(
506 const gfx::Point& location_in_screen) const {
507 if (window_resizer_)
508 return true; // Ignore hides while actively resizing.
510 if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
511 return true;
513 int hit1, hit2;
514 if (windows_.direction == TOP_BOTTOM) {
515 hit1 = HTBOTTOM;
516 hit2 = HTTOP;
517 } else {
518 hit1 = HTRIGHT;
519 hit2 = HTLEFT;
522 return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
523 IsOverWindow(windows_.window2, location_in_screen, hit2);
526 bool MultiWindowResizeController::IsOverWindow(
527 aura::Window* window,
528 const gfx::Point& location_in_screen,
529 int component) const {
530 if (!window->delegate())
531 return false;
533 gfx::Point window_loc(location_in_screen);
534 aura::Window::ConvertPointToTarget(
535 window->GetRootWindow(), window, &window_loc);
536 return window->HitTest(window_loc) &&
537 window->delegate()->GetNonClientComponent(window_loc) == component;
540 } // namespace internal
541 } // namespace ash