Home | History | Annotate | Download | only in magnifier
      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