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/magnifier/magnification_controller.h" 6 7 #include "ash/accelerators/accelerator_controller.h" 8 #include "ash/accessibility_delegate.h" 9 #include "ash/ash_switches.h" 10 #include "ash/display/root_window_transformers.h" 11 #include "ash/host/ash_window_tree_host.h" 12 #include "ash/host/root_window_transformer.h" 13 #include "ash/root_window_controller.h" 14 #include "ash/shell.h" 15 #include "ash/system/tray/system_tray_delegate.h" 16 #include "base/command_line.h" 17 #include "base/synchronization/waitable_event.h" 18 #include "ui/aura/client/cursor_client.h" 19 #include "ui/aura/window.h" 20 #include "ui/aura/window_property.h" 21 #include "ui/aura/window_tree_host.h" 22 #include "ui/compositor/dip_util.h" 23 #include "ui/compositor/layer.h" 24 #include "ui/compositor/layer_animation_observer.h" 25 #include "ui/compositor/scoped_layer_animation_settings.h" 26 #include "ui/events/event.h" 27 #include "ui/events/event_handler.h" 28 #include "ui/gfx/point3_f.h" 29 #include "ui/gfx/point_conversions.h" 30 #include "ui/gfx/point_f.h" 31 #include "ui/gfx/rect_conversions.h" 32 #include "ui/gfx/screen.h" 33 #include "ui/wm/core/compound_event_filter.h" 34 35 namespace { 36 37 const float kMaxMagnifiedScale = 4.0f; 38 const float kMaxMagnifiedScaleThreshold = 4.0f; 39 const float kMinMagnifiedScaleThreshold = 1.1f; 40 const float kNonMagnifiedScale = 1.0f; 41 42 const float kInitialMagnifiedScale = 2.0f; 43 const float kScrollScaleChangeFactor = 0.05f; 44 45 // Threadshold of panning. If the cursor moves to within pixels (in DIP) of 46 // |kPanningMergin| from the edge, the view-port moves. 47 const int kPanningMergin = 100; 48 49 void MoveCursorTo(aura::WindowTreeHost* host, const gfx::Point& root_location) { 50 gfx::Point3F host_location_3f(root_location); 51 host->GetRootTransform().TransformPoint(&host_location_3f); 52 host->MoveCursorToHostLocation( 53 gfx::ToCeiledPoint(host_location_3f.AsPointF())); 54 } 55 56 } // namespace 57 58 namespace ash { 59 60 //////////////////////////////////////////////////////////////////////////////// 61 // MagnificationControllerImpl: 62 63 class MagnificationControllerImpl : virtual public MagnificationController, 64 public ui::EventHandler, 65 public ui::ImplicitAnimationObserver, 66 public aura::WindowObserver { 67 public: 68 MagnificationControllerImpl(); 69 virtual ~MagnificationControllerImpl(); 70 71 // MagnificationController overrides: 72 virtual void SetEnabled(bool enabled) OVERRIDE; 73 virtual bool IsEnabled() const OVERRIDE; 74 virtual void SetScale(float scale, bool animate) OVERRIDE; 75 virtual float GetScale() const OVERRIDE { return scale_; } 76 virtual void MoveWindow(int x, int y, bool animate) OVERRIDE; 77 virtual void MoveWindow(const gfx::Point& point, bool animate) OVERRIDE; 78 virtual gfx::Point GetWindowPosition() const OVERRIDE { 79 return gfx::ToFlooredPoint(origin_); 80 } 81 virtual void SetScrollDirection(ScrollDirection direction) OVERRIDE; 82 83 // For test 84 virtual gfx::Point GetPointOfInterestForTesting() OVERRIDE { 85 return point_of_interest_; 86 } 87 88 private: 89 // ui::ImplicitAnimationObserver overrides: 90 virtual void OnImplicitAnimationsCompleted() OVERRIDE; 91 92 // aura::WindowObserver overrides: 93 virtual void OnWindowDestroying(aura::Window* root_window) OVERRIDE; 94 virtual void OnWindowBoundsChanged(aura::Window* window, 95 const gfx::Rect& old_bounds, 96 const gfx::Rect& new_bounds) OVERRIDE; 97 98 // Redraws the magnification window with the given origin position and the 99 // given scale. Returns true if the window is changed; otherwise, false. 100 // These methods should be called internally just after the scale and/or 101 // the position are changed to redraw the window. 102 bool Redraw(const gfx::PointF& position, float scale, bool animate); 103 bool RedrawDIP(const gfx::PointF& position, float scale, bool animate); 104 105 // 1) If the screen is scrolling (i.e. animating) and should scroll further, 106 // it does nothing. 107 // 2) If the screen is scrolling (i.e. animating) and the direction is NONE, 108 // it stops the scrolling animation. 109 // 3) If the direction is set to value other than NONE, it starts the 110 // scrolling/ animation towards that direction. 111 void StartOrStopScrollIfNecessary(); 112 113 // Redraw with the given zoom scale keeping the mouse cursor location. In 114 // other words, zoom (or unzoom) centering around the cursor. 115 void RedrawKeepingMousePosition(float scale, bool animate); 116 117 void OnMouseMove(const gfx::Point& location); 118 119 // Move the mouse cursot to the given point. Actual move will be done when 120 // the animation is completed. This should be called after animation is 121 // started. 122 void AfterAnimationMoveCursorTo(const gfx::Point& location); 123 124 // Switch Magnified RootWindow to |new_root_window|. This does following: 125 // - Unzoom the current root_window. 126 // - Zoom the given new root_window |new_root_window|. 127 // - Switch the target window from current window to |new_root_window|. 128 void SwitchTargetRootWindow(aura::Window* new_root_window, 129 bool redraw_original_root_window); 130 131 // Returns if the magnification scale is 1.0 or not (larger then 1.0). 132 bool IsMagnified() const; 133 134 // Returns the rect of the magnification window. 135 gfx::RectF GetWindowRectDIP(float scale) const; 136 // Returns the size of the root window. 137 gfx::Size GetHostSizeDIP() const; 138 139 // Correct the givin scale value if nessesary. 140 void ValidateScale(float* scale); 141 142 // ui::EventHandler overrides: 143 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; 144 virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; 145 virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; 146 147 // Target root window. This must not be NULL. 148 aura::Window* root_window_; 149 150 // True if the magnified window is currently animating a change. Otherwise, 151 // false. 152 bool is_on_animation_; 153 154 bool is_enabled_; 155 156 // True if the cursor needs to move the given position after the animation 157 // will be finished. When using this, set |position_after_animation_| as well. 158 bool move_cursor_after_animation_; 159 // Stores the position of cursor to be moved after animation. 160 gfx::Point position_after_animation_; 161 162 // Stores the last mouse cursor (or last touched) location. This value is 163 // used on zooming to keep this location visible. 164 gfx::Point point_of_interest_; 165 166 // Current scale, origin (left-top) position of the magnification window. 167 float scale_; 168 gfx::PointF origin_; 169 170 ScrollDirection scroll_direction_; 171 172 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl); 173 }; 174 175 //////////////////////////////////////////////////////////////////////////////// 176 // MagnificationControllerImpl: 177 178 MagnificationControllerImpl::MagnificationControllerImpl() 179 : root_window_(Shell::GetPrimaryRootWindow()), 180 is_on_animation_(false), 181 is_enabled_(false), 182 move_cursor_after_animation_(false), 183 scale_(kNonMagnifiedScale), 184 scroll_direction_(SCROLL_NONE) { 185 Shell::GetInstance()->AddPreTargetHandler(this); 186 root_window_->AddObserver(this); 187 point_of_interest_ = root_window_->bounds().CenterPoint(); 188 } 189 190 MagnificationControllerImpl::~MagnificationControllerImpl() { 191 root_window_->RemoveObserver(this); 192 193 Shell::GetInstance()->RemovePreTargetHandler(this); 194 } 195 196 void MagnificationControllerImpl::RedrawKeepingMousePosition( 197 float scale, bool animate) { 198 gfx::Point mouse_in_root = point_of_interest_; 199 200 // mouse_in_root is invalid value when the cursor is hidden. 201 if (!root_window_->bounds().Contains(mouse_in_root)) 202 mouse_in_root = root_window_->bounds().CenterPoint(); 203 204 const gfx::PointF origin = 205 gfx::PointF(mouse_in_root.x() - 206 (scale_ / scale) * (mouse_in_root.x() - origin_.x()), 207 mouse_in_root.y() - 208 (scale_ / scale) * (mouse_in_root.y() - origin_.y())); 209 bool changed = RedrawDIP(origin, scale, animate); 210 if (changed) 211 AfterAnimationMoveCursorTo(mouse_in_root); 212 } 213 214 bool MagnificationControllerImpl::Redraw(const gfx::PointF& position, 215 float scale, 216 bool animate) { 217 const gfx::PointF position_in_dip = 218 ui::ConvertPointToDIP(root_window_->layer(), position); 219 return RedrawDIP(position_in_dip, scale, animate); 220 } 221 222 bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF& position_in_dip, 223 float scale, 224 bool animate) { 225 DCHECK(root_window_); 226 227 float x = position_in_dip.x(); 228 float y = position_in_dip.y(); 229 230 ValidateScale(&scale); 231 232 if (x < 0) 233 x = 0; 234 if (y < 0) 235 y = 0; 236 237 const gfx::Size host_size_in_dip = GetHostSizeDIP(); 238 const gfx::SizeF window_size_in_dip = GetWindowRectDIP(scale).size(); 239 float max_x = host_size_in_dip.width() - window_size_in_dip.width(); 240 float max_y = host_size_in_dip.height() - window_size_in_dip.height(); 241 if (x > max_x) 242 x = max_x; 243 if (y > max_y) 244 y = max_y; 245 246 // Does nothing if both the origin and the scale are not changed. 247 if (origin_.x() == x && 248 origin_.y() == y && 249 scale == scale_) { 250 return false; 251 } 252 253 origin_.set_x(x); 254 origin_.set_y(y); 255 scale_ = scale; 256 257 // Creates transform matrix. 258 gfx::Transform transform; 259 // Flips the signs intentionally to convert them from the position of the 260 // magnification window. 261 transform.Scale(scale_, scale_); 262 transform.Translate(-origin_.x(), -origin_.y()); 263 264 ui::ScopedLayerAnimationSettings settings( 265 root_window_->layer()->GetAnimator()); 266 settings.AddObserver(this); 267 settings.SetPreemptionStrategy( 268 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 269 settings.SetTweenType(gfx::Tween::EASE_OUT); 270 settings.SetTransitionDuration( 271 base::TimeDelta::FromMilliseconds(animate ? 100 : 0)); 272 273 gfx::Display display = 274 Shell::GetScreen()->GetDisplayNearestWindow(root_window_); 275 scoped_ptr<RootWindowTransformer> transformer( 276 CreateRootWindowTransformerForDisplay(root_window_, display)); 277 GetRootWindowController(root_window_)->ash_host()->SetRootWindowTransformer( 278 transformer.Pass()); 279 280 if (animate) 281 is_on_animation_ = true; 282 283 return true; 284 } 285 286 void MagnificationControllerImpl::StartOrStopScrollIfNecessary() { 287 // This value controls the scrolling speed. 288 const int kMoveOffset = 40; 289 if (is_on_animation_) { 290 if (scroll_direction_ == SCROLL_NONE) 291 root_window_->layer()->GetAnimator()->StopAnimating(); 292 return; 293 } 294 295 gfx::PointF new_origin = origin_; 296 switch (scroll_direction_) { 297 case SCROLL_NONE: 298 // No need to take action. 299 return; 300 case SCROLL_LEFT: 301 new_origin.Offset(-kMoveOffset, 0); 302 break; 303 case SCROLL_RIGHT: 304 new_origin.Offset(kMoveOffset, 0); 305 break; 306 case SCROLL_UP: 307 new_origin.Offset(0, -kMoveOffset); 308 break; 309 case SCROLL_DOWN: 310 new_origin.Offset(0, kMoveOffset); 311 break; 312 } 313 RedrawDIP(new_origin, scale_, true); 314 } 315 316 void MagnificationControllerImpl::OnMouseMove(const gfx::Point& location) { 317 DCHECK(root_window_); 318 319 gfx::Point mouse(location); 320 321 int x = origin_.x(); 322 int y = origin_.y(); 323 bool start_zoom = false; 324 325 const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale_)); 326 const int left = window_rect.x(); 327 const int right = window_rect.right(); 328 int margin = kPanningMergin / scale_; // No need to consider DPI. 329 330 int x_diff = 0; 331 332 if (mouse.x() < left + margin) { 333 // Panning left. 334 x_diff = mouse.x() - (left + margin); 335 start_zoom = true; 336 } else if (right - margin < mouse.x()) { 337 // Panning right. 338 x_diff = mouse.x() - (right - margin); 339 start_zoom = true; 340 } 341 x = left + x_diff; 342 343 const int top = window_rect.y(); 344 const int bottom = window_rect.bottom(); 345 346 int y_diff = 0; 347 if (mouse.y() < top + margin) { 348 // Panning up. 349 y_diff = mouse.y() - (top + margin); 350 start_zoom = true; 351 } else if (bottom - margin < mouse.y()) { 352 // Panning down. 353 y_diff = mouse.y() - (bottom - margin); 354 start_zoom = true; 355 } 356 y = top + y_diff; 357 358 if (start_zoom && !is_on_animation_) { 359 // No animation on panning. 360 bool animate = false; 361 bool ret = RedrawDIP(gfx::Point(x, y), scale_, animate); 362 363 if (ret) { 364 // If the magnified region is moved, hides the mouse cursor and moves it. 365 if (x_diff != 0 || y_diff != 0) 366 MoveCursorTo(root_window_->GetHost(), mouse); 367 } 368 } 369 } 370 371 void MagnificationControllerImpl::AfterAnimationMoveCursorTo( 372 const gfx::Point& location) { 373 DCHECK(root_window_); 374 375 aura::client::CursorClient* cursor_client = 376 aura::client::GetCursorClient(root_window_); 377 if (cursor_client) { 378 // When cursor is invisible, do not move or show the cursor after the 379 // animation. 380 if (!cursor_client->IsCursorVisible()) 381 return; 382 cursor_client->DisableMouseEvents(); 383 } 384 move_cursor_after_animation_ = true; 385 position_after_animation_ = location; 386 } 387 388 gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const { 389 return root_window_->bounds().size(); 390 } 391 392 gfx::RectF MagnificationControllerImpl::GetWindowRectDIP(float scale) const { 393 const gfx::Size size_in_dip = root_window_->bounds().size(); 394 const float width = size_in_dip.width() / scale; 395 const float height = size_in_dip.height() / scale; 396 397 return gfx::RectF(origin_.x(), origin_.y(), width, height); 398 } 399 400 bool MagnificationControllerImpl::IsMagnified() const { 401 return scale_ >= kMinMagnifiedScaleThreshold; 402 } 403 404 void MagnificationControllerImpl::ValidateScale(float* scale) { 405 // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than 406 // |kMinMagnifiedScaleThreshold|; 407 if (*scale < kMinMagnifiedScaleThreshold) 408 *scale = kNonMagnifiedScale; 409 410 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than 411 // |kMinMagnifiedScaleThreshold|; 412 if (*scale > kMaxMagnifiedScaleThreshold) 413 *scale = kMaxMagnifiedScale; 414 415 DCHECK(kNonMagnifiedScale <= *scale && *scale <= kMaxMagnifiedScale); 416 } 417 418 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() { 419 if (!is_on_animation_) 420 return; 421 422 if (move_cursor_after_animation_) { 423 MoveCursorTo(root_window_->GetHost(), position_after_animation_); 424 move_cursor_after_animation_ = false; 425 426 aura::client::CursorClient* cursor_client = 427 aura::client::GetCursorClient(root_window_); 428 if (cursor_client) 429 cursor_client->EnableMouseEvents(); 430 } 431 432 is_on_animation_ = false; 433 434 StartOrStopScrollIfNecessary(); 435 } 436 437 void MagnificationControllerImpl::OnWindowDestroying( 438 aura::Window* root_window) { 439 if (root_window == root_window_) { 440 // There must be at least one root window because this controller is 441 // destroyed before the root windows get destroyed. 442 DCHECK(root_window); 443 444 aura::Window* target_root_window = Shell::GetTargetRootWindow(); 445 CHECK(target_root_window); 446 447 // The destroyed root window must not be target. 448 CHECK_NE(target_root_window, root_window); 449 // Don't redraw the old root window as it's being destroyed. 450 SwitchTargetRootWindow(target_root_window, false); 451 point_of_interest_ = target_root_window->bounds().CenterPoint(); 452 } 453 } 454 455 void MagnificationControllerImpl::OnWindowBoundsChanged( 456 aura::Window* window, 457 const gfx::Rect& old_bounds, 458 const gfx::Rect& new_bounds) { 459 // TODO(yoshiki): implement here. crbug.com/230979 460 } 461 462 void MagnificationControllerImpl::SwitchTargetRootWindow( 463 aura::Window* new_root_window, 464 bool redraw_original_root_window) { 465 DCHECK(new_root_window); 466 467 if (new_root_window == root_window_) 468 return; 469 470 // Stores the previous scale. 471 float scale = GetScale(); 472 473 // Unmagnify the previous root window. 474 root_window_->RemoveObserver(this); 475 if (redraw_original_root_window) 476 RedrawKeepingMousePosition(1.0f, true); 477 478 root_window_ = new_root_window; 479 RedrawKeepingMousePosition(scale, true); 480 root_window_->AddObserver(this); 481 } 482 483 //////////////////////////////////////////////////////////////////////////////// 484 // MagnificationControllerImpl: MagnificationController implementation 485 486 void MagnificationControllerImpl::SetScale(float scale, bool animate) { 487 if (!is_enabled_) 488 return; 489 490 ValidateScale(&scale); 491 Shell::GetInstance()->accessibility_delegate()-> 492 SaveScreenMagnifierScale(scale); 493 RedrawKeepingMousePosition(scale, animate); 494 } 495 496 void MagnificationControllerImpl::MoveWindow(int x, int y, bool animate) { 497 if (!is_enabled_) 498 return; 499 500 Redraw(gfx::Point(x, y), scale_, animate); 501 } 502 503 void MagnificationControllerImpl::MoveWindow(const gfx::Point& point, 504 bool animate) { 505 if (!is_enabled_) 506 return; 507 508 Redraw(point, scale_, animate); 509 } 510 511 void MagnificationControllerImpl::SetScrollDirection( 512 ScrollDirection direction) { 513 scroll_direction_ = direction; 514 StartOrStopScrollIfNecessary(); 515 } 516 517 void MagnificationControllerImpl::SetEnabled(bool enabled) { 518 Shell* shell = Shell::GetInstance(); 519 if (enabled) { 520 float scale = 521 Shell::GetInstance()->accessibility_delegate()-> 522 GetSavedScreenMagnifierScale(); 523 if (scale <= 0.0f) 524 scale = kInitialMagnifiedScale; 525 ValidateScale(&scale); 526 527 // Do nothing, if already enabled with same scale. 528 if (is_enabled_ && scale == scale_) 529 return; 530 531 is_enabled_ = enabled; 532 RedrawKeepingMousePosition(scale, true); 533 shell->accessibility_delegate()->SaveScreenMagnifierScale(scale); 534 } else { 535 // Do nothing, if already disabled. 536 if (!is_enabled_) 537 return; 538 539 RedrawKeepingMousePosition(kNonMagnifiedScale, true); 540 is_enabled_ = enabled; 541 } 542 } 543 544 bool MagnificationControllerImpl::IsEnabled() const { 545 return is_enabled_; 546 } 547 548 //////////////////////////////////////////////////////////////////////////////// 549 // MagnificationControllerImpl: aura::EventFilter implementation 550 551 void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent* event) { 552 aura::Window* target = static_cast<aura::Window*>(event->target()); 553 aura::Window* current_root = target->GetRootWindow(); 554 gfx::Rect root_bounds = current_root->bounds(); 555 556 if (root_bounds.Contains(event->root_location())) { 557 // This must be before |SwitchTargetRootWindow()|. 558 if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) 559 point_of_interest_ = event->root_location(); 560 561 if (current_root != root_window_) { 562 DCHECK(current_root); 563 SwitchTargetRootWindow(current_root, true); 564 } 565 566 if (IsMagnified() && event->type() == ui::ET_MOUSE_MOVED) 567 OnMouseMove(event->root_location()); 568 } 569 } 570 571 void MagnificationControllerImpl::OnScrollEvent(ui::ScrollEvent* event) { 572 if (event->IsAltDown() && event->IsControlDown()) { 573 if (event->type() == ui::ET_SCROLL_FLING_START || 574 event->type() == ui::ET_SCROLL_FLING_CANCEL) { 575 event->StopPropagation(); 576 return; 577 } 578 579 if (event->type() == ui::ET_SCROLL) { 580 ui::ScrollEvent* scroll_event = static_cast<ui::ScrollEvent*>(event); 581 float scale = GetScale(); 582 scale += scroll_event->y_offset() * kScrollScaleChangeFactor; 583 SetScale(scale, true); 584 event->StopPropagation(); 585 return; 586 } 587 } 588 } 589 590 void MagnificationControllerImpl::OnTouchEvent(ui::TouchEvent* event) { 591 aura::Window* target = static_cast<aura::Window*>(event->target()); 592 aura::Window* current_root = target->GetRootWindow(); 593 if (current_root == root_window_) { 594 gfx::Rect root_bounds = current_root->bounds(); 595 if (root_bounds.Contains(event->root_location())) 596 point_of_interest_ = event->root_location(); 597 } 598 } 599 600 //////////////////////////////////////////////////////////////////////////////// 601 // MagnificationController: 602 603 // static 604 MagnificationController* MagnificationController::CreateInstance() { 605 return new MagnificationControllerImpl(); 606 } 607 608 } // namespace ash 609