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
35namespace {
36
37const float kMaxMagnifiedScale = 4.0f;
38const float kMaxMagnifiedScaleThreshold = 4.0f;
39const float kMinMagnifiedScaleThreshold = 1.1f;
40const float kNonMagnifiedScale = 1.0f;
41
42const float kInitialMagnifiedScale = 2.0f;
43const 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.
47const int kPanningMergin = 100;
48
49void 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
58namespace ash {
59
60////////////////////////////////////////////////////////////////////////////////
61// MagnificationControllerImpl:
62
63class 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
178MagnificationControllerImpl::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
190MagnificationControllerImpl::~MagnificationControllerImpl() {
191  root_window_->RemoveObserver(this);
192
193  Shell::GetInstance()->RemovePreTargetHandler(this);
194}
195
196void 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
214bool 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
222bool 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
286void 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
316void 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
371void 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
388gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const {
389  return root_window_->bounds().size();
390}
391
392gfx::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
400bool MagnificationControllerImpl::IsMagnified() const {
401  return scale_ >= kMinMagnifiedScaleThreshold;
402}
403
404void 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
418void 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
437void 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
455void 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
462void 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
486void 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
496void 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
503void MagnificationControllerImpl::MoveWindow(const gfx::Point& point,
504                                             bool animate) {
505  if (!is_enabled_)
506    return;
507
508  Redraw(point, scale_, animate);
509}
510
511void MagnificationControllerImpl::SetScrollDirection(
512    ScrollDirection direction) {
513  scroll_direction_ = direction;
514  StartOrStopScrollIfNecessary();
515}
516
517void 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
544bool MagnificationControllerImpl::IsEnabled() const {
545  return is_enabled_;
546}
547
548////////////////////////////////////////////////////////////////////////////////
549// MagnificationControllerImpl: aura::EventFilter implementation
550
551void 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
571void 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
590void 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
604MagnificationController* MagnificationController::CreateInstance() {
605  return new MagnificationControllerImpl();
606}
607
608}  // namespace ash
609