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/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"
37 const float kMaxMagnifiedScale
= 4.0f
;
38 const float kMaxMagnifiedScaleThreshold
= 4.0f
;
39 const float kMinMagnifiedScaleThreshold
= 1.1f
;
40 const float kNonMagnifiedScale
= 1.0f
;
42 const float kInitialMagnifiedScale
= 2.0f
;
43 const float kScrollScaleChangeFactor
= 0.05f
;
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;
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()));
60 ////////////////////////////////////////////////////////////////////////////////
61 // MagnificationControllerImpl:
63 class MagnificationControllerImpl
: virtual public MagnificationController
,
64 public ui::EventHandler
,
65 public ui::ImplicitAnimationObserver
,
66 public aura::WindowObserver
{
68 MagnificationControllerImpl();
69 virtual ~MagnificationControllerImpl();
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_
);
81 virtual void SetScrollDirection(ScrollDirection direction
) OVERRIDE
;
84 virtual gfx::Point
GetPointOfInterestForTesting() OVERRIDE
{
85 return point_of_interest_
;
89 // ui::ImplicitAnimationObserver overrides:
90 virtual void OnImplicitAnimationsCompleted() OVERRIDE
;
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
;
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
);
105 // 1) If the screen is scrolling (i.e. animating) and should scroll further,
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();
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
);
117 void OnMouseMove(const gfx::Point
& location
);
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
122 void AfterAnimationMoveCursorTo(const gfx::Point
& location
);
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
);
131 // Returns if the magnification scale is 1.0 or not (larger then 1.0).
132 bool IsMagnified() const;
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;
139 // Correct the givin scale value if nessesary.
140 void ValidateScale(float* scale
);
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
;
147 // Target root window. This must not be NULL.
148 aura::Window
* root_window_
;
150 // True if the magnified window is currently animating a change. Otherwise,
152 bool is_on_animation_
;
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_
;
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_
;
166 // Current scale, origin (left-top) position of the magnification window.
170 ScrollDirection scroll_direction_
;
172 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl
);
175 ////////////////////////////////////////////////////////////////////////////////
176 // MagnificationControllerImpl:
178 MagnificationControllerImpl::MagnificationControllerImpl()
179 : root_window_(Shell::GetPrimaryRootWindow()),
180 is_on_animation_(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();
190 MagnificationControllerImpl::~MagnificationControllerImpl() {
191 root_window_
->RemoveObserver(this);
193 Shell::GetInstance()->RemovePreTargetHandler(this);
196 void MagnificationControllerImpl::RedrawKeepingMousePosition(
197 float scale
, bool animate
) {
198 gfx::Point mouse_in_root
= point_of_interest_
;
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();
204 const gfx::PointF origin
=
205 gfx::PointF(mouse_in_root
.x() -
206 (scale_
/ scale
) * (mouse_in_root
.x() - origin_
.x()),
208 (scale_
/ scale
) * (mouse_in_root
.y() - origin_
.y()));
209 bool changed
= RedrawDIP(origin
, scale
, animate
);
211 AfterAnimationMoveCursorTo(mouse_in_root
);
214 bool MagnificationControllerImpl::Redraw(const gfx::PointF
& position
,
217 const gfx::PointF position_in_dip
=
218 ui::ConvertPointToDIP(root_window_
->layer(), position
);
219 return RedrawDIP(position_in_dip
, scale
, animate
);
222 bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF
& position_in_dip
,
225 DCHECK(root_window_
);
227 float x
= position_in_dip
.x();
228 float y
= position_in_dip
.y();
230 ValidateScale(&scale
);
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();
246 // Does nothing if both the origin and the scale are not changed.
247 if (origin_
.x() == x
&&
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());
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));
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(
281 is_on_animation_
= true;
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();
295 gfx::PointF new_origin
= origin_
;
296 switch (scroll_direction_
) {
298 // No need to take action.
301 new_origin
.Offset(-kMoveOffset
, 0);
304 new_origin
.Offset(kMoveOffset
, 0);
307 new_origin
.Offset(0, -kMoveOffset
);
310 new_origin
.Offset(0, kMoveOffset
);
313 RedrawDIP(new_origin
, scale_
, true);
316 void MagnificationControllerImpl::OnMouseMove(const gfx::Point
& location
) {
317 DCHECK(root_window_
);
319 gfx::Point
mouse(location
);
323 bool start_zoom
= false;
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.
332 if (mouse
.x() < left
+ margin
) {
334 x_diff
= mouse
.x() - (left
+ margin
);
336 } else if (right
- margin
< mouse
.x()) {
338 x_diff
= mouse
.x() - (right
- margin
);
343 const int top
= window_rect
.y();
344 const int bottom
= window_rect
.bottom();
347 if (mouse
.y() < top
+ margin
) {
349 y_diff
= mouse
.y() - (top
+ margin
);
351 } else if (bottom
- margin
< mouse
.y()) {
353 y_diff
= mouse
.y() - (bottom
- margin
);
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
);
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
);
371 void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
372 const gfx::Point
& location
) {
373 DCHECK(root_window_
);
375 aura::client::CursorClient
* cursor_client
=
376 aura::client::GetCursorClient(root_window_
);
378 // When cursor is invisible, do not move or show the cursor after the
380 if (!cursor_client
->IsCursorVisible())
382 cursor_client
->DisableMouseEvents();
384 move_cursor_after_animation_
= true;
385 position_after_animation_
= location
;
388 gfx::Size
MagnificationControllerImpl::GetHostSizeDIP() const {
389 return root_window_
->bounds().size();
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
;
397 return gfx::RectF(origin_
.x(), origin_
.y(), width
, height
);
400 bool MagnificationControllerImpl::IsMagnified() const {
401 return scale_
>= kMinMagnifiedScaleThreshold
;
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
;
410 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
411 // |kMinMagnifiedScaleThreshold|;
412 if (*scale
> kMaxMagnifiedScaleThreshold
)
413 *scale
= kMaxMagnifiedScale
;
415 DCHECK(kNonMagnifiedScale
<= *scale
&& *scale
<= kMaxMagnifiedScale
);
418 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
419 if (!is_on_animation_
)
422 if (move_cursor_after_animation_
) {
423 MoveCursorTo(root_window_
->GetHost(), position_after_animation_
);
424 move_cursor_after_animation_
= false;
426 aura::client::CursorClient
* cursor_client
=
427 aura::client::GetCursorClient(root_window_
);
429 cursor_client
->EnableMouseEvents();
432 is_on_animation_
= false;
434 StartOrStopScrollIfNecessary();
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.
444 aura::Window
* target_root_window
= Shell::GetTargetRootWindow();
445 CHECK(target_root_window
);
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();
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
462 void MagnificationControllerImpl::SwitchTargetRootWindow(
463 aura::Window
* new_root_window
,
464 bool redraw_original_root_window
) {
465 DCHECK(new_root_window
);
467 if (new_root_window
== root_window_
)
470 // Stores the previous scale.
471 float scale
= GetScale();
473 // Unmagnify the previous root window.
474 root_window_
->RemoveObserver(this);
475 if (redraw_original_root_window
)
476 RedrawKeepingMousePosition(1.0f
, true);
478 root_window_
= new_root_window
;
479 RedrawKeepingMousePosition(scale
, true);
480 root_window_
->AddObserver(this);
483 ////////////////////////////////////////////////////////////////////////////////
484 // MagnificationControllerImpl: MagnificationController implementation
486 void MagnificationControllerImpl::SetScale(float scale
, bool animate
) {
490 ValidateScale(&scale
);
491 Shell::GetInstance()->accessibility_delegate()->
492 SaveScreenMagnifierScale(scale
);
493 RedrawKeepingMousePosition(scale
, animate
);
496 void MagnificationControllerImpl::MoveWindow(int x
, int y
, bool animate
) {
500 Redraw(gfx::Point(x
, y
), scale_
, animate
);
503 void MagnificationControllerImpl::MoveWindow(const gfx::Point
& point
,
508 Redraw(point
, scale_
, animate
);
511 void MagnificationControllerImpl::SetScrollDirection(
512 ScrollDirection direction
) {
513 scroll_direction_
= direction
;
514 StartOrStopScrollIfNecessary();
517 void MagnificationControllerImpl::SetEnabled(bool enabled
) {
518 Shell
* shell
= Shell::GetInstance();
521 Shell::GetInstance()->accessibility_delegate()->
522 GetSavedScreenMagnifierScale();
524 scale
= kInitialMagnifiedScale
;
525 ValidateScale(&scale
);
527 // Do nothing, if already enabled with same scale.
528 if (is_enabled_
&& scale
== scale_
)
531 is_enabled_
= enabled
;
532 RedrawKeepingMousePosition(scale
, true);
533 shell
->accessibility_delegate()->SaveScreenMagnifierScale(scale
);
535 // Do nothing, if already disabled.
539 RedrawKeepingMousePosition(kNonMagnifiedScale
, true);
540 is_enabled_
= enabled
;
544 bool MagnificationControllerImpl::IsEnabled() const {
548 ////////////////////////////////////////////////////////////////////////////////
549 // MagnificationControllerImpl: aura::EventFilter implementation
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();
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();
561 if (current_root
!= root_window_
) {
562 DCHECK(current_root
);
563 SwitchTargetRootWindow(current_root
, true);
566 if (IsMagnified() && event
->type() == ui::ET_MOUSE_MOVED
)
567 OnMouseMove(event
->root_location());
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();
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();
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();
600 ////////////////////////////////////////////////////////////////////////////////
601 // MagnificationController:
604 MagnificationController
* MagnificationController::CreateInstance() {
605 return new MagnificationControllerImpl();