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.
5 #include "ash/magnifier/magnification_controller.h"
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/shell.h"
12 #include "ash/system/tray/system_tray_delegate.h"
13 #include "base/command_line.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "ui/aura/client/cursor_client.h"
16 #include "ui/aura/root_window.h"
17 #include "ui/aura/root_window_transformer.h"
18 #include "ui/aura/window.h"
19 #include "ui/aura/window_property.h"
20 #include "ui/compositor/dip_util.h"
21 #include "ui/compositor/layer.h"
22 #include "ui/compositor/layer_animation_observer.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/events/event.h"
25 #include "ui/events/event_handler.h"
26 #include "ui/gfx/point3_f.h"
27 #include "ui/gfx/point_conversions.h"
28 #include "ui/gfx/point_f.h"
29 #include "ui/gfx/rect_conversions.h"
30 #include "ui/gfx/screen.h"
31 #include "ui/views/corewm/compound_event_filter.h"
35 const float kMaxMagnifiedScale = 4.0f;
36 const float kMaxMagnifiedScaleThreshold = 4.0f;
37 const float kMinMagnifiedScaleThreshold = 1.1f;
38 const float kNonMagnifiedScale = 1.0f;
40 const float kInitialMagnifiedScale = 2.0f;
41 const float kScrollScaleChangeFactor = 0.05f;
43 // Threadshold of panning. If the cursor moves to within pixels (in DIP) of
44 // |kPanningMergin| from the edge, the view-port moves.
45 const int kPanningMergin = 100;
47 void MoveCursorTo(aura::RootWindow* root_window,
48 const gfx::Point& root_location) {
49 gfx::Point3F host_location_3f(root_location);
50 root_window->host()->GetRootTransform().TransformPoint(&host_location_3f);
51 root_window->host()->MoveCursorToHostLocation(
52 gfx::ToCeiledPoint(host_location_3f.AsPointF()));
59 ////////////////////////////////////////////////////////////////////////////////
60 // MagnificationControllerImpl:
62 class MagnificationControllerImpl : virtual public MagnificationController,
63 public ui::EventHandler,
64 public ui::ImplicitAnimationObserver,
65 public aura::WindowObserver {
67 MagnificationControllerImpl();
68 virtual ~MagnificationControllerImpl();
70 // MagnificationController overrides:
71 virtual void SetEnabled(bool enabled) OVERRIDE;
72 virtual bool IsEnabled() const OVERRIDE;
73 virtual void SetScale(float scale, bool animate) OVERRIDE;
74 virtual float GetScale() const OVERRIDE { return scale_; }
75 virtual void MoveWindow(int x, int y, bool animate) OVERRIDE;
76 virtual void MoveWindow(const gfx::Point& point, bool animate) OVERRIDE;
77 virtual gfx::Point GetWindowPosition() const OVERRIDE {
78 return gfx::ToFlooredPoint(origin_);
80 virtual void SetScrollDirection(ScrollDirection direction) OVERRIDE;
83 virtual gfx::Point GetPointOfInterestForTesting() OVERRIDE {
84 return point_of_interest_;
88 // ui::ImplicitAnimationObserver overrides:
89 virtual void OnImplicitAnimationsCompleted() OVERRIDE;
91 // aura::WindowObserver overrides:
92 virtual void OnWindowDestroying(aura::Window* root_window) OVERRIDE;
93 virtual void OnWindowBoundsChanged(aura::Window* window,
94 const gfx::Rect& old_bounds,
95 const gfx::Rect& new_bounds) OVERRIDE;
97 // Redraws the magnification window with the given origin position and the
98 // given scale. Returns true if the window is changed; otherwise, false.
99 // These methods should be called internally just after the scale and/or
100 // the position are changed to redraw the window.
101 bool Redraw(const gfx::PointF& position, float scale, bool animate);
102 bool RedrawDIP(const gfx::PointF& position, float scale, bool animate);
104 // 1) If the screen is scrolling (i.e. animating) and should scroll further,
106 // 2) If the screen is scrolling (i.e. animating) and the direction is NONE,
107 // it stops the scrolling animation.
108 // 3) If the direction is set to value other than NONE, it starts the
109 // scrolling/ animation towards that direction.
110 void StartOrStopScrollIfNecessary();
112 // Redraw with the given zoom scale keeping the mouse cursor location. In
113 // other words, zoom (or unzoom) centering around the cursor.
114 void RedrawKeepingMousePosition(float scale, bool animate);
116 void OnMouseMove(const gfx::Point& location);
118 // Move the mouse cursot to the given point. Actual move will be done when
119 // the animation is completed. This should be called after animation is
121 void AfterAnimationMoveCursorTo(const gfx::Point& location);
123 // Switch Magnified RootWindow to |new_root_window|. This does following:
124 // - Unzoom the current root_window.
125 // - Zoom the given new root_window |new_root_window|.
126 // - Switch the target window from current window to |new_root_window|.
127 void SwitchTargetRootWindow(aura::Window* new_root_window,
128 bool redraw_original_root_window);
130 // Returns if the magnification scale is 1.0 or not (larger then 1.0).
131 bool IsMagnified() const;
133 // Returns the rect of the magnification window.
134 gfx::RectF GetWindowRectDIP(float scale) const;
135 // Returns the size of the root window.
136 gfx::Size GetHostSizeDIP() const;
138 // Correct the givin scale value if nessesary.
139 void ValidateScale(float* scale);
141 // ui::EventHandler overrides:
142 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
143 virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
144 virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
146 // Target root window. This must not be NULL.
147 aura::Window* root_window_;
149 // True if the magnified window is currently animating a change. Otherwise,
151 bool is_on_animation_;
155 // True if the cursor needs to move the given position after the animation
156 // will be finished. When using this, set |position_after_animation_| as well.
157 bool move_cursor_after_animation_;
158 // Stores the position of cursor to be moved after animation.
159 gfx::Point position_after_animation_;
161 // Stores the last mouse cursor (or last touched) location. This value is
162 // used on zooming to keep this location visible.
163 gfx::Point point_of_interest_;
165 // Current scale, origin (left-top) position of the magnification window.
169 ScrollDirection scroll_direction_;
171 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl);
174 ////////////////////////////////////////////////////////////////////////////////
175 // MagnificationControllerImpl:
177 MagnificationControllerImpl::MagnificationControllerImpl()
178 : root_window_(Shell::GetPrimaryRootWindow()),
179 is_on_animation_(false),
181 move_cursor_after_animation_(false),
182 scale_(kNonMagnifiedScale),
183 scroll_direction_(SCROLL_NONE) {
184 Shell::GetInstance()->AddPreTargetHandler(this);
185 root_window_->AddObserver(this);
186 point_of_interest_ = root_window_->bounds().CenterPoint();
189 MagnificationControllerImpl::~MagnificationControllerImpl() {
190 root_window_->RemoveObserver(this);
192 Shell::GetInstance()->RemovePreTargetHandler(this);
195 void MagnificationControllerImpl::RedrawKeepingMousePosition(
196 float scale, bool animate) {
197 gfx::Point mouse_in_root = point_of_interest_;
199 // mouse_in_root is invalid value when the cursor is hidden.
200 if (!root_window_->bounds().Contains(mouse_in_root))
201 mouse_in_root = root_window_->bounds().CenterPoint();
203 const gfx::PointF origin =
204 gfx::PointF(mouse_in_root.x() -
205 (scale_ / scale) * (mouse_in_root.x() - origin_.x()),
207 (scale_ / scale) * (mouse_in_root.y() - origin_.y()));
208 bool changed = RedrawDIP(origin, scale, animate);
210 AfterAnimationMoveCursorTo(mouse_in_root);
213 bool MagnificationControllerImpl::Redraw(const gfx::PointF& position,
216 const gfx::PointF position_in_dip =
217 ui::ConvertPointToDIP(root_window_->layer(), position);
218 return RedrawDIP(position_in_dip, scale, animate);
221 bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF& position_in_dip,
224 DCHECK(root_window_);
226 float x = position_in_dip.x();
227 float y = position_in_dip.y();
229 ValidateScale(&scale);
236 const gfx::Size host_size_in_dip = GetHostSizeDIP();
237 const gfx::SizeF window_size_in_dip = GetWindowRectDIP(scale).size();
238 float max_x = host_size_in_dip.width() - window_size_in_dip.width();
239 float max_y = host_size_in_dip.height() - window_size_in_dip.height();
245 // Does nothing if both the origin and the scale are not changed.
246 if (origin_.x() == x &&
256 // Creates transform matrix.
257 gfx::Transform transform;
258 // Flips the signs intentionally to convert them from the position of the
259 // magnification window.
260 transform.Scale(scale_, scale_);
261 transform.Translate(-origin_.x(), -origin_.y());
263 ui::ScopedLayerAnimationSettings settings(
264 root_window_->layer()->GetAnimator());
265 settings.AddObserver(this);
266 settings.SetPreemptionStrategy(
267 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
268 settings.SetTweenType(gfx::Tween::EASE_OUT);
269 settings.SetTransitionDuration(
270 base::TimeDelta::FromMilliseconds(animate ? 100 : 0));
272 gfx::Display display =
273 Shell::GetScreen()->GetDisplayNearestWindow(root_window_);
274 scoped_ptr<aura::RootWindowTransformer> transformer(
275 internal::CreateRootWindowTransformerForDisplay(root_window_, display));
276 root_window_->GetDispatcher()->host()->SetRootWindowTransformer(
280 is_on_animation_ = true;
285 void MagnificationControllerImpl::StartOrStopScrollIfNecessary() {
286 // This value controls the scrolling speed.
287 const int kMoveOffset = 40;
288 if (is_on_animation_) {
289 if (scroll_direction_ == SCROLL_NONE)
290 root_window_->layer()->GetAnimator()->StopAnimating();
294 gfx::PointF new_origin = origin_;
295 switch (scroll_direction_) {
297 // No need to take action.
300 new_origin.Offset(-kMoveOffset, 0);
303 new_origin.Offset(kMoveOffset, 0);
306 new_origin.Offset(0, -kMoveOffset);
309 new_origin.Offset(0, kMoveOffset);
312 RedrawDIP(new_origin, scale_, true);
315 void MagnificationControllerImpl::OnMouseMove(const gfx::Point& location) {
316 DCHECK(root_window_);
318 gfx::Point mouse(location);
322 bool start_zoom = false;
324 const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale_));
325 const int left = window_rect.x();
326 const int right = window_rect.right();
327 int margin = kPanningMergin / scale_; // No need to consider DPI.
331 if (mouse.x() < left + margin) {
333 x_diff = mouse.x() - (left + margin);
335 } else if (right - margin < mouse.x()) {
337 x_diff = mouse.x() - (right - margin);
342 const int top = window_rect.y();
343 const int bottom = window_rect.bottom();
346 if (mouse.y() < top + margin) {
348 y_diff = mouse.y() - (top + margin);
350 } else if (bottom - margin < mouse.y()) {
352 y_diff = mouse.y() - (bottom - margin);
357 if (start_zoom && !is_on_animation_) {
358 // No animation on panning.
359 bool animate = false;
360 bool ret = RedrawDIP(gfx::Point(x, y), scale_, animate);
363 // If the magnified region is moved, hides the mouse cursor and moves it.
364 if (x_diff != 0 || y_diff != 0)
365 MoveCursorTo(root_window_->GetDispatcher(), mouse);
370 void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
371 const gfx::Point& location) {
372 DCHECK(root_window_);
374 aura::client::CursorClient* cursor_client =
375 aura::client::GetCursorClient(root_window_);
377 // When cursor is invisible, do not move or show the cursor after the
379 if (!cursor_client->IsCursorVisible())
381 cursor_client->DisableMouseEvents();
383 move_cursor_after_animation_ = true;
384 position_after_animation_ = location;
387 gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const {
388 return root_window_->bounds().size();
391 gfx::RectF MagnificationControllerImpl::GetWindowRectDIP(float scale) const {
392 const gfx::Size size_in_dip = root_window_->bounds().size();
393 const float width = size_in_dip.width() / scale;
394 const float height = size_in_dip.height() / scale;
396 return gfx::RectF(origin_.x(), origin_.y(), width, height);
399 bool MagnificationControllerImpl::IsMagnified() const {
400 return scale_ >= kMinMagnifiedScaleThreshold;
403 void MagnificationControllerImpl::ValidateScale(float* scale) {
404 // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than
405 // |kMinMagnifiedScaleThreshold|;
406 if (*scale < kMinMagnifiedScaleThreshold)
407 *scale = kNonMagnifiedScale;
409 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
410 // |kMinMagnifiedScaleThreshold|;
411 if (*scale > kMaxMagnifiedScaleThreshold)
412 *scale = kMaxMagnifiedScale;
414 DCHECK(kNonMagnifiedScale <= *scale && *scale <= kMaxMagnifiedScale);
417 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
418 if (!is_on_animation_)
421 if (move_cursor_after_animation_) {
422 MoveCursorTo(root_window_->GetDispatcher(), position_after_animation_);
423 move_cursor_after_animation_ = false;
425 aura::client::CursorClient* cursor_client =
426 aura::client::GetCursorClient(root_window_);
428 cursor_client->EnableMouseEvents();
431 is_on_animation_ = false;
433 StartOrStopScrollIfNecessary();
436 void MagnificationControllerImpl::OnWindowDestroying(
437 aura::Window* root_window) {
438 if (root_window == root_window_) {
439 // There must be at least one root window because this controller is
440 // destroyed before the root windows get destroyed.
443 aura::Window* target_root_window = Shell::GetTargetRootWindow();
444 CHECK(target_root_window);
446 // The destroyed root window must not be target.
447 CHECK_NE(target_root_window, root_window);
448 // Don't redraw the old root window as it's being destroyed.
449 SwitchTargetRootWindow(target_root_window, false);
450 point_of_interest_ = target_root_window->bounds().CenterPoint();
454 void MagnificationControllerImpl::OnWindowBoundsChanged(
455 aura::Window* window,
456 const gfx::Rect& old_bounds,
457 const gfx::Rect& new_bounds) {
458 // TODO(yoshiki): implement here. crbug.com/230979
461 void MagnificationControllerImpl::SwitchTargetRootWindow(
462 aura::Window* new_root_window,
463 bool redraw_original_root_window) {
464 DCHECK(new_root_window);
466 if (new_root_window == root_window_)
469 // Stores the previous scale.
470 float scale = GetScale();
472 // Unmagnify the previous root window.
473 root_window_->RemoveObserver(this);
474 if (redraw_original_root_window)
475 RedrawKeepingMousePosition(1.0f, true);
477 root_window_ = new_root_window;
478 RedrawKeepingMousePosition(scale, true);
479 root_window_->AddObserver(this);
482 ////////////////////////////////////////////////////////////////////////////////
483 // MagnificationControllerImpl: MagnificationController implementation
485 void MagnificationControllerImpl::SetScale(float scale, bool animate) {
489 ValidateScale(&scale);
490 Shell::GetInstance()->accessibility_delegate()->
491 SaveScreenMagnifierScale(scale);
492 RedrawKeepingMousePosition(scale, animate);
495 void MagnificationControllerImpl::MoveWindow(int x, int y, bool animate) {
499 Redraw(gfx::Point(x, y), scale_, animate);
502 void MagnificationControllerImpl::MoveWindow(const gfx::Point& point,
507 Redraw(point, scale_, animate);
510 void MagnificationControllerImpl::SetScrollDirection(
511 ScrollDirection direction) {
512 scroll_direction_ = direction;
513 StartOrStopScrollIfNecessary();
516 void MagnificationControllerImpl::SetEnabled(bool enabled) {
517 Shell* shell = Shell::GetInstance();
520 Shell::GetInstance()->accessibility_delegate()->
521 GetSavedScreenMagnifierScale();
523 scale = kInitialMagnifiedScale;
524 ValidateScale(&scale);
526 // Do nothing, if already enabled with same scale.
527 if (is_enabled_ && scale == scale_)
530 is_enabled_ = enabled;
531 RedrawKeepingMousePosition(scale, true);
532 shell->accessibility_delegate()->SaveScreenMagnifierScale(scale);
534 // Do nothing, if already disabled.
538 RedrawKeepingMousePosition(kNonMagnifiedScale, true);
539 is_enabled_ = enabled;
543 bool MagnificationControllerImpl::IsEnabled() const {
547 ////////////////////////////////////////////////////////////////////////////////
548 // MagnificationControllerImpl: aura::EventFilter implementation
550 void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
551 aura::Window* target = static_cast<aura::Window*>(event->target());
552 aura::Window* current_root = target->GetRootWindow();
553 gfx::Rect root_bounds = current_root->bounds();
555 if (root_bounds.Contains(event->root_location())) {
556 // This must be before |SwitchTargetRootWindow()|.
557 if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)
558 point_of_interest_ = event->root_location();
560 if (current_root != root_window_) {
561 DCHECK(current_root);
562 SwitchTargetRootWindow(current_root, true);
565 if (IsMagnified() && event->type() == ui::ET_MOUSE_MOVED)
566 OnMouseMove(event->root_location());
570 void MagnificationControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
571 if (event->IsAltDown() && event->IsControlDown()) {
572 if (event->type() == ui::ET_SCROLL_FLING_START ||
573 event->type() == ui::ET_SCROLL_FLING_CANCEL) {
574 event->StopPropagation();
578 if (event->type() == ui::ET_SCROLL) {
579 ui::ScrollEvent* scroll_event = static_cast<ui::ScrollEvent*>(event);
580 float scale = GetScale();
581 scale += scroll_event->y_offset() * kScrollScaleChangeFactor;
582 SetScale(scale, true);
583 event->StopPropagation();
589 void MagnificationControllerImpl::OnTouchEvent(ui::TouchEvent* event) {
590 aura::Window* target = static_cast<aura::Window*>(event->target());
591 aura::Window* current_root = target->GetRootWindow();
592 if (current_root == root_window_) {
593 gfx::Rect root_bounds = current_root->bounds();
594 if (root_bounds.Contains(event->root_location()))
595 point_of_interest_ = event->root_location();
599 ////////////////////////////////////////////////////////////////////////////////
600 // MagnificationController:
603 MagnificationController* MagnificationController::CreateInstance() {
604 return new MagnificationControllerImpl();