• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MoveCursorTo(aura::WindowTreeHost * host,const gfx::Point & root_location)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;
GetScale() const75   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;
GetWindowPosition() const78   virtual gfx::Point GetWindowPosition() const OVERRIDE {
79     return gfx::ToFlooredPoint(origin_);
80   }
81   virtual void SetScrollDirection(ScrollDirection direction) OVERRIDE;
82 
83   // For test
GetPointOfInterestForTesting()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 
MagnificationControllerImpl()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 
~MagnificationControllerImpl()190 MagnificationControllerImpl::~MagnificationControllerImpl() {
191   root_window_->RemoveObserver(this);
192 
193   Shell::GetInstance()->RemovePreTargetHandler(this);
194 }
195 
RedrawKeepingMousePosition(float scale,bool animate)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 
Redraw(const gfx::PointF & position,float scale,bool animate)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 
RedrawDIP(const gfx::PointF & position_in_dip,float scale,bool animate)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 
StartOrStopScrollIfNecessary()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 
OnMouseMove(const gfx::Point & location)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 
AfterAnimationMoveCursorTo(const gfx::Point & location)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 
GetHostSizeDIP() const388 gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const {
389   return root_window_->bounds().size();
390 }
391 
GetWindowRectDIP(float scale) const392 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 
IsMagnified() const400 bool MagnificationControllerImpl::IsMagnified() const {
401   return scale_ >= kMinMagnifiedScaleThreshold;
402 }
403 
ValidateScale(float * scale)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 
OnImplicitAnimationsCompleted()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 
OnWindowDestroying(aura::Window * root_window)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 
OnWindowBoundsChanged(aura::Window * window,const gfx::Rect & old_bounds,const gfx::Rect & new_bounds)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 
SwitchTargetRootWindow(aura::Window * new_root_window,bool redraw_original_root_window)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 
SetScale(float scale,bool animate)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 
MoveWindow(int x,int y,bool animate)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 
MoveWindow(const gfx::Point & point,bool animate)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 
SetScrollDirection(ScrollDirection direction)511 void MagnificationControllerImpl::SetScrollDirection(
512     ScrollDirection direction) {
513   scroll_direction_ = direction;
514   StartOrStopScrollIfNecessary();
515 }
516 
SetEnabled(bool enabled)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 
IsEnabled() const544 bool MagnificationControllerImpl::IsEnabled() const {
545   return is_enabled_;
546 }
547 
548 ////////////////////////////////////////////////////////////////////////////////
549 // MagnificationControllerImpl: aura::EventFilter implementation
550 
OnMouseEvent(ui::MouseEvent * event)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 
OnScrollEvent(ui::ScrollEvent * event)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 
OnTouchEvent(ui::TouchEvent * event)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
CreateInstance()604 MagnificationController* MagnificationController::CreateInstance() {
605   return new MagnificationControllerImpl();
606 }
607 
608 }  // namespace ash
609