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/wm/workspace/multi_window_resize_controller.h" |
| 6 |
| 7 #include "ash/shell.h" |
| 8 #include "ash/shell_window_ids.h" |
| 9 #include "ash/wm/root_window_event_filter.h" |
| 10 #include "ash/wm/workspace/workspace_event_filter.h" |
| 11 #include "ash/wm/workspace/workspace_window_resizer.h" |
| 12 #include "ui/aura/event_filter.h" |
| 13 #include "ui/aura/root_window.h" |
| 14 #include "ui/aura/window.h" |
| 15 #include "ui/aura/window_delegate.h" |
| 16 #include "ui/base/hit_test.h" |
| 17 #include "ui/gfx/canvas.h" |
| 18 #include "ui/gfx/canvas_skia.h" |
| 19 #include "ui/gfx/screen.h" |
| 20 #include "ui/views/view.h" |
| 21 #include "ui/views/widget/widget.h" |
| 22 #include "ui/views/widget/widget_delegate.h" |
| 23 |
| 24 using aura::Window; |
| 25 |
| 26 namespace ash { |
| 27 namespace internal { |
| 28 |
| 29 namespace { |
| 30 |
| 31 const int kShowDelayMS = 100; |
| 32 |
| 33 // Padding from the bottom/right edge the resize widget is shown at. |
| 34 const int kResizeWidgetPadding = 40; |
| 35 |
| 36 bool ContainsX(Window* window, int x) { |
| 37 return window->bounds().x() <= x && window->bounds().right() >= x; |
| 38 } |
| 39 |
| 40 bool ContainsY(Window* window, int y) { |
| 41 return window->bounds().y() <= y && window->bounds().bottom() >= y; |
| 42 } |
| 43 |
| 44 } // namespace |
| 45 |
| 46 // View contained in the widget. Passes along mouse events to the |
| 47 // MultiWindowResizeController so that it can start/stop the resize loop. |
| 48 class MultiWindowResizeController::ResizeView : public views::View { |
| 49 public: |
| 50 explicit ResizeView(MultiWindowResizeController* controller, |
| 51 Direction direction) |
| 52 : controller_(controller), |
| 53 direction_(direction) { |
| 54 } |
| 55 |
| 56 // views::View overrides: |
| 57 virtual gfx::Size GetPreferredSize() OVERRIDE { |
| 58 return gfx::Size(30, 30); |
| 59 } |
| 60 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { |
| 61 // TODO: replace with real assets. |
| 62 SkPaint paint; |
| 63 paint.setColor(SkColorSetARGB(128, 0, 0, 0)); |
| 64 paint.setStyle(SkPaint::kFill_Style); |
| 65 paint.setAntiAlias(true); |
| 66 canvas->AsCanvasSkia()->GetSkCanvas()->drawCircle( |
| 67 SkIntToScalar(15), SkIntToScalar(15), SkIntToScalar(15), paint); |
| 68 } |
| 69 virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE { |
| 70 gfx::Point location(event.location()); |
| 71 views::View::ConvertPointToScreen(this, &location); |
| 72 controller_->StartResize(location); |
| 73 return true; |
| 74 } |
| 75 virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE { |
| 76 gfx::Point location(event.location()); |
| 77 views::View::ConvertPointToScreen(this, &location); |
| 78 controller_->Resize(location); |
| 79 return true; |
| 80 } |
| 81 virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE { |
| 82 controller_->CompleteResize(); |
| 83 } |
| 84 virtual void OnMouseCaptureLost() OVERRIDE { |
| 85 controller_->CancelResize(); |
| 86 } |
| 87 virtual gfx::NativeCursor GetCursor( |
| 88 const views::MouseEvent& event) OVERRIDE { |
| 89 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM; |
| 90 return RootWindowEventFilter::CursorForWindowComponent(component); |
| 91 } |
| 92 |
| 93 private: |
| 94 MultiWindowResizeController* controller_; |
| 95 const Direction direction_; |
| 96 |
| 97 DISALLOW_COPY_AND_ASSIGN(ResizeView); |
| 98 }; |
| 99 |
| 100 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards |
| 101 // Contains() to MultiWindowResizeController. |
| 102 class MultiWindowResizeController::ResizeMouseWatcherHost : |
| 103 public views::MouseWatcherHost { |
| 104 public: |
| 105 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {} |
| 106 |
| 107 // MouseWatcherHost overrides: |
| 108 virtual bool Contains(const gfx::Point& screen_point, |
| 109 MouseEventType type) OVERRIDE { |
| 110 return !host_->IsOverWindows(screen_point); |
| 111 } |
| 112 |
| 113 private: |
| 114 MultiWindowResizeController* host_; |
| 115 |
| 116 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost); |
| 117 }; |
| 118 |
| 119 MultiWindowResizeController::ResizeWindows::ResizeWindows() |
| 120 : window1(NULL), |
| 121 window2(NULL), |
| 122 direction(TOP_BOTTOM){ |
| 123 } |
| 124 |
| 125 MultiWindowResizeController::ResizeWindows::~ResizeWindows() { |
| 126 } |
| 127 |
| 128 bool MultiWindowResizeController::ResizeWindows::Equals( |
| 129 const ResizeWindows& other) const { |
| 130 return window1 == other.window1 && |
| 131 window2 == other.window2 && |
| 132 direction == other.direction; |
| 133 } |
| 134 |
| 135 MultiWindowResizeController::MultiWindowResizeController() |
| 136 : resize_widget_(NULL), |
| 137 grid_size_(0) { |
| 138 } |
| 139 |
| 140 MultiWindowResizeController::~MultiWindowResizeController() { |
| 141 Hide(); |
| 142 } |
| 143 |
| 144 void MultiWindowResizeController::Show(Window* window, |
| 145 int component, |
| 146 const gfx::Point& point) { |
| 147 ResizeWindows windows(DetermineWindows(window, component, point)); |
| 148 if (IsShowing()) { |
| 149 if (windows_.Equals(windows)) |
| 150 return; |
| 151 Hide(); |
| 152 // Fall through to see if there is another hot spot we should show at. |
| 153 } |
| 154 |
| 155 windows_ = windows; |
| 156 if (!windows_.is_valid()) |
| 157 return; |
| 158 // TODO: need to listen for windows to be closed/destroyed, maybe window |
| 159 // moved too. |
| 160 show_timer_.Start(FROM_HERE, |
| 161 base::TimeDelta::FromMilliseconds(kShowDelayMS), |
| 162 this, &MultiWindowResizeController::ShowNow); |
| 163 } |
| 164 |
| 165 void MultiWindowResizeController::Hide() { |
| 166 if (window_resizer_.get()) |
| 167 return; // Ignore hides while actively resizing. |
| 168 |
| 169 show_timer_.Stop(); |
| 170 if (!resize_widget_) |
| 171 return; |
| 172 resize_widget_->Close(); |
| 173 resize_widget_ = NULL; |
| 174 windows_ = ResizeWindows(); |
| 175 } |
| 176 |
| 177 void MultiWindowResizeController::MouseMovedOutOfHost() { |
| 178 Hide(); |
| 179 } |
| 180 |
| 181 MultiWindowResizeController::ResizeWindows |
| 182 MultiWindowResizeController::DetermineWindows( |
| 183 Window* window, |
| 184 int window_component, |
| 185 const gfx::Point& point) const { |
| 186 ResizeWindows result; |
| 187 gfx::Point point_in_parent(point); |
| 188 Window::ConvertPointToWindow(window, window->parent(), &point_in_parent); |
| 189 switch (window_component) { |
| 190 case HTRIGHT: |
| 191 result.direction = LEFT_RIGHT; |
| 192 result.window1 = window; |
| 193 result.window2 = FindWindowByEdge( |
| 194 window, HTLEFT, window->bounds().right(), point_in_parent.y()); |
| 195 break; |
| 196 case HTLEFT: |
| 197 result.direction = LEFT_RIGHT; |
| 198 result.window1 = FindWindowByEdge( |
| 199 window, HTRIGHT, window->bounds().x(), point_in_parent.y()); |
| 200 result.window2 = window; |
| 201 break; |
| 202 case HTTOP: |
| 203 result.direction = TOP_BOTTOM; |
| 204 result.window1 = FindWindowByEdge( |
| 205 window, HTBOTTOM, point_in_parent.x(), window->bounds().y()); |
| 206 result.window2 = window; |
| 207 break; |
| 208 case HTBOTTOM: |
| 209 result.direction = TOP_BOTTOM; |
| 210 result.window1 = window; |
| 211 result.window2 = FindWindowByEdge( |
| 212 window, HTTOP, point_in_parent.x(), window->bounds().bottom()); |
| 213 break; |
| 214 default: |
| 215 break; |
| 216 } |
| 217 return result; |
| 218 } |
| 219 |
| 220 Window* MultiWindowResizeController::FindWindowByEdge( |
| 221 Window* window_to_ignore, |
| 222 int edge_want, |
| 223 int x, |
| 224 int y) const { |
| 225 Window* parent = window_to_ignore->parent(); |
| 226 const Window::Windows& windows(parent->children()); |
| 227 for (Window::Windows::const_reverse_iterator i = windows.rbegin(); |
| 228 i != windows.rend(); ++i) { |
| 229 Window* window = *i; |
| 230 if (window == window_to_ignore || !window->IsVisible()) |
| 231 continue; |
| 232 switch (edge_want) { |
| 233 case HTLEFT: |
| 234 if (ContainsY(window, y) && window->bounds().x() == x) |
| 235 return window; |
| 236 break; |
| 237 case HTRIGHT: |
| 238 if (ContainsY(window, y) && window->bounds().right() == x) |
| 239 return window; |
| 240 break; |
| 241 case HTTOP: |
| 242 if (ContainsX(window, x) && window->bounds().y() == y) |
| 243 return window; |
| 244 break; |
| 245 case HTBOTTOM: |
| 246 if (ContainsX(window, x) && window->bounds().bottom() == y) |
| 247 return window; |
| 248 break; |
| 249 default: |
| 250 NOTREACHED(); |
| 251 } |
| 252 // Window doesn't contain the edge, but if window contains |point| |
| 253 // it's obscuring any other window that could be at the location. |
| 254 if (window->bounds().Contains(x, y)) |
| 255 return NULL; |
| 256 } |
| 257 return NULL; |
| 258 } |
| 259 |
| 260 void MultiWindowResizeController::ShowNow() { |
| 261 DCHECK(!resize_widget_); |
| 262 DCHECK(windows_.is_valid()); |
| 263 show_timer_.Stop(); |
| 264 resize_widget_ = new views::Widget; |
| 265 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); |
| 266 params.transparent = true; |
| 267 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| 268 params.parent = Shell::GetInstance()->GetContainer( |
| 269 ash::internal::kShellWindowId_AlwaysOnTopContainer); |
| 270 params.can_activate = false; |
| 271 ResizeView* view = new ResizeView(this, windows_.direction); |
| 272 params.delegate = new views::WidgetDelegateView; |
| 273 resize_widget_->set_focus_on_creation(false); |
| 274 resize_widget_->Init(params); |
| 275 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController"); |
| 276 resize_widget_->SetContentsView(view); |
| 277 show_bounds_ = CalculateResizeWidgetBounds(); |
| 278 resize_widget_->SetBounds(show_bounds_); |
| 279 resize_widget_->Show(); |
| 280 } |
| 281 |
| 282 bool MultiWindowResizeController::IsShowing() const { |
| 283 return resize_widget_ || show_timer_.IsRunning(); |
| 284 } |
| 285 |
| 286 void MultiWindowResizeController::StartResize( |
| 287 const gfx::Point& screen_location) { |
| 288 DCHECK(!window_resizer_.get()); |
| 289 DCHECK(windows_.is_valid()); |
| 290 gfx::Point parent_location(screen_location); |
| 291 aura::Window::ConvertPointToWindow( |
| 292 windows_.window1->GetRootWindow(), windows_.window1->parent(), |
| 293 &parent_location); |
| 294 std::vector<aura::Window*> windows; |
| 295 windows.push_back(windows_.window2); |
| 296 // TODO: search for other windows. |
| 297 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM; |
| 298 window_resizer_.reset(WorkspaceWindowResizer::Create( |
| 299 windows_.window1, parent_location, component, grid_size_, windows)); |
| 300 } |
| 301 |
| 302 void MultiWindowResizeController::Resize(const gfx::Point& screen_location) { |
| 303 gfx::Point parent_location(screen_location); |
| 304 aura::Window::ConvertPointToWindow(windows_.window1->GetRootWindow(), |
| 305 windows_.window1->parent(), |
| 306 &parent_location); |
| 307 window_resizer_->Drag(parent_location); |
| 308 gfx::Rect bounds = CalculateResizeWidgetBounds(); |
| 309 if (windows_.direction == LEFT_RIGHT) |
| 310 bounds.set_y(show_bounds_.y()); |
| 311 else |
| 312 bounds.set_x(show_bounds_.x()); |
| 313 resize_widget_->SetBounds(bounds); |
| 314 } |
| 315 |
| 316 void MultiWindowResizeController::CompleteResize() { |
| 317 window_resizer_->CompleteDrag(); |
| 318 window_resizer_.reset(); |
| 319 |
| 320 // Mouse may still be over resizer, if not hide. |
| 321 gfx::Point screen_loc = gfx::Screen::GetCursorScreenPoint(); |
| 322 if (!resize_widget_->GetWindowScreenBounds().Contains(screen_loc)) |
| 323 Hide(); |
| 324 } |
| 325 |
| 326 void MultiWindowResizeController::CancelResize() { |
| 327 window_resizer_->RevertDrag(); |
| 328 window_resizer_.reset(); |
| 329 Hide(); |
| 330 } |
| 331 |
| 332 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds() const { |
| 333 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize(); |
| 334 int x = 0, y = 0; |
| 335 if (windows_.direction == LEFT_RIGHT) { |
| 336 x = windows_.window1->bounds().right() - pref.width() / 2; |
| 337 y = std::min(windows_.window1->bounds().bottom(), |
| 338 windows_.window2->bounds().bottom()) - kResizeWidgetPadding; |
| 339 y -= pref.height() / 2; |
| 340 } else { |
| 341 x = std::min(windows_.window1->bounds().right(), |
| 342 windows_.window2->bounds().right()) - kResizeWidgetPadding; |
| 343 x -= pref.width() / 2; |
| 344 y = windows_.window1->bounds().bottom() - pref.height() / 2; |
| 345 } |
| 346 return gfx::Rect(x, y, pref.width(), pref.height()); |
| 347 } |
| 348 |
| 349 bool MultiWindowResizeController::IsOverWindows( |
| 350 const gfx::Point& screen_location) const { |
| 351 if (window_resizer_.get()) |
| 352 return true; // Ignore hides while actively resizing. |
| 353 |
| 354 if (resize_widget_->GetWindowScreenBounds().Contains(screen_location)) |
| 355 return true; |
| 356 |
| 357 return IsOverWindow(windows_.window1, screen_location) || |
| 358 IsOverWindow(windows_.window2, screen_location); |
| 359 } |
| 360 |
| 361 bool MultiWindowResizeController::IsOverWindow( |
| 362 aura::Window* window, |
| 363 const gfx::Point& screen_location) const { |
| 364 if (!window->GetScreenBounds().Contains(screen_location)) |
| 365 return false; |
| 366 |
| 367 gfx::Point window_loc(screen_location); |
| 368 aura::Window::ConvertPointToWindow( |
| 369 window->GetRootWindow(), window->parent(), &window_loc); |
| 370 int component = window->delegate()->GetNonClientComponent(window_loc); |
| 371 // TODO: this needs to make sure no other window is obscuring window. |
| 372 return windows_.Equals(DetermineWindows(window, component, window_loc)); |
| 373 } |
| 374 |
| 375 } // namespace internal |
| 376 } // namespace ash |
OLD | NEW |