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/display/root_window_transformers.h"
9 #include "ash/shell_delegate.h"
10 #include "ash/system/tray/system_tray_delegate.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "ui/aura/client/cursor_client.h"
13 #include "ui/aura/root_window.h"
14 #include "ui/aura/root_window_transformer.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_property.h"
17 #include "ui/base/events/event.h"
18 #include "ui/base/events/event_handler.h"
19 #include "ui/compositor/dip_util.h"
20 #include "ui/compositor/layer.h"
21 #include "ui/compositor/layer_animation_observer.h"
22 #include "ui/compositor/scoped_layer_animation_settings.h"
23 #include "ui/gfx/point3_f.h"
24 #include "ui/gfx/point_conversions.h"
25 #include "ui/gfx/point_f.h"
26 #include "ui/gfx/rect_conversions.h"
27 #include "ui/gfx/screen.h"
28 #include "ui/views/corewm/compound_event_filter.h"
32 const float kMaxMagnifiedScale
= 4.0f
;
33 const float kMaxMagnifiedScaleThreshold
= 4.0f
;
34 const float kMinMagnifiedScaleThreshold
= 1.1f
;
35 const float kNonMagnifiedScale
= 1.0f
;
37 const float kInitialMagnifiedScale
= 2.0f
;
38 const float kScrollScaleChangeFactor
= 0.05f
;
40 // Threadshold of panning. If the cursor moves to within pixels (in DIP) of
41 // |kPanningMergin| from the edge, the view-port moves.
42 const int kPanningMergin
= 100;
44 void MoveCursorTo(aura::RootWindow
* root_window
,
45 const gfx::Point
& root_location
) {
46 gfx::Point3F
host_location_3f(root_location
);
47 root_window
->GetRootTransform().TransformPoint(host_location_3f
);
48 root_window
->MoveCursorToHostLocation(
49 gfx::ToCeiledPoint(host_location_3f
.AsPointF()));
56 ////////////////////////////////////////////////////////////////////////////////
57 // MagnificationControllerImpl:
59 class MagnificationControllerImpl
: virtual public MagnificationController
,
60 public ui::EventHandler
,
61 public ui::ImplicitAnimationObserver
,
62 public aura::WindowObserver
{
64 MagnificationControllerImpl();
65 virtual ~MagnificationControllerImpl();
67 // MagnificationController overrides:
68 virtual void SetEnabled(bool enabled
) OVERRIDE
;
69 virtual bool IsEnabled() const OVERRIDE
;
70 virtual void SetScale(float scale
, bool animate
) OVERRIDE
;
71 virtual float GetScale() const OVERRIDE
{ return scale_
; }
72 virtual void MoveWindow(int x
, int y
, bool animate
) OVERRIDE
;
73 virtual void MoveWindow(const gfx::Point
& point
, bool animate
) OVERRIDE
;
74 virtual gfx::Point
GetWindowPosition() const OVERRIDE
{
75 return gfx::ToFlooredPoint(origin_
);
77 virtual void EnsureRectIsVisible(const gfx::Rect
& rect
,
78 bool animate
) OVERRIDE
;
79 virtual void EnsurePointIsVisible(const gfx::Point
& point
,
80 bool animate
) OVERRIDE
;
82 virtual gfx::Point
GetPointOfInterestForTesting() OVERRIDE
{
83 return point_of_interest_
;
87 // ui::ImplicitAnimationObserver overrides:
88 virtual void OnImplicitAnimationsCompleted() OVERRIDE
;
90 // aura::WindowObserver overrides:
91 virtual void OnWindowDestroying(aura::Window
* root_window
) OVERRIDE
;
92 virtual void OnWindowBoundsChanged(aura::Window
* window
,
93 const gfx::Rect
& old_bounds
,
94 const gfx::Rect
& new_bounds
) OVERRIDE
;
96 // Redraws the magnification window with the given origin position and the
97 // given scale. Returns true if the window is changed; otherwise, false.
98 // These methods should be called internally just after the scale and/or
99 // the position are changed to redraw the window.
100 bool Redraw(const gfx::PointF
& position
, float scale
, bool animate
);
101 bool RedrawDIP(const gfx::PointF
& position
, float scale
, bool animate
);
103 // Redraw with the given zoom scale keeping the mouse cursor location. In
104 // other words, zoom (or unzoom) centering around the cursor.
105 void RedrawKeepingMousePosition(float scale
, bool animate
);
107 // Ensures that the given point, rect or last mouse location is inside
108 // magnification window. If not, the controller moves the window to contain
109 // the given point/rect.
110 void EnsureRectIsVisibleWithScale(const gfx::Rect
& target_rect
,
113 void EnsureRectIsVisibleDIP(const gfx::Rect
& target_rect_in_dip
,
116 void EnsurePointIsVisibleWithScale(const gfx::Point
& point
,
119 void OnMouseMove(const gfx::Point
& location
);
121 // Move the mouse cursot to the given point. Actual move will be done when
122 // the animation is completed. This should be called after animation is
124 void AfterAnimationMoveCursorTo(const gfx::Point
& location
);
126 // Switch Magnified RootWindow to |new_root_window|. This does following:
127 // - Unzoom the current root_window.
128 // - Zoom the given new root_window |new_root_window|.
129 // - Switch the target window from current window to |new_root_window|.
130 void SwitchTargetRootWindow(aura::RootWindow
* new_root_window
,
131 bool redraw_original_root_window
);
133 // Returns if the magnification scale is 1.0 or not (larger then 1.0).
134 bool IsMagnified() const;
136 // Returns the rect of the magnification window.
137 gfx::RectF
GetWindowRectDIP(float scale
) const;
138 // Returns the size of the root window.
139 gfx::Size
GetHostSizeDIP() const;
141 // Correct the givin scale value if nessesary.
142 void ValidateScale(float* scale
);
144 // ui::EventHandler overrides:
145 virtual void OnMouseEvent(ui::MouseEvent
* event
) OVERRIDE
;
146 virtual void OnScrollEvent(ui::ScrollEvent
* event
) OVERRIDE
;
147 virtual void OnTouchEvent(ui::TouchEvent
* event
) OVERRIDE
;
149 // Target root window. This must not be NULL.
150 aura::RootWindow
* root_window_
;
152 // True if the magnified window is currently animating a change. Otherwise,
154 bool is_on_animation_
;
158 // True if the cursor needs to move the given position after the animation
159 // will be finished. When using this, set |position_after_animation_| as well.
160 bool move_cursor_after_animation_
;
161 // Stores the position of cursor to be moved after animation.
162 gfx::Point position_after_animation_
;
164 // Stores the last mouse cursor (or last touched) location. This value is
165 // used on zooming to keep this location visible.
166 gfx::Point point_of_interest_
;
168 // Current scale, origin (left-top) position of the magnification window.
172 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl
);
175 ////////////////////////////////////////////////////////////////////////////////
176 // MagnificationControllerImpl:
178 MagnificationControllerImpl::MagnificationControllerImpl()
179 : root_window_(ash::Shell::GetPrimaryRootWindow()),
180 is_on_animation_(false),
182 move_cursor_after_animation_(false),
183 scale_(kNonMagnifiedScale
) {
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(ui::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_
->SetRootWindowTransformer(transformer
.Pass());
279 is_on_animation_
= true;
284 void MagnificationControllerImpl::EnsureRectIsVisibleWithScale(
285 const gfx::Rect
& target_rect
,
288 const gfx::Rect target_rect_in_dip
=
289 ui::ConvertRectToDIP(root_window_
->layer(), target_rect
);
290 EnsureRectIsVisibleDIP(target_rect_in_dip
, scale
, animate
);
293 void MagnificationControllerImpl::EnsureRectIsVisibleDIP(
294 const gfx::Rect
& target_rect
,
297 ValidateScale(&scale
);
299 const gfx::Rect window_rect
= gfx::ToEnclosingRect(GetWindowRectDIP(scale
));
300 if (scale
== scale_
&& window_rect
.Contains(target_rect
))
303 // TODO(yoshiki): Un-zoom and change the scale if the magnification window
304 // can't contain the whole given rect.
306 gfx::Rect rect
= window_rect
;
307 if (target_rect
.width() > rect
.width())
308 rect
.set_x(target_rect
.CenterPoint().x() - rect
.x() / 2);
309 else if (target_rect
.right() < rect
.x())
310 rect
.set_x(target_rect
.right());
311 else if (rect
.right() < target_rect
.x())
312 rect
.set_x(target_rect
.x() - rect
.width());
314 if (rect
.height() > window_rect
.height())
315 rect
.set_y(target_rect
.CenterPoint().y() - rect
.y() / 2);
316 else if (target_rect
.bottom() < rect
.y())
317 rect
.set_y(target_rect
.bottom());
318 else if (rect
.bottom() < target_rect
.y())
319 rect
.set_y(target_rect
.y() - rect
.height());
321 RedrawDIP(rect
.origin(), scale
, animate
);
324 void MagnificationControllerImpl::EnsurePointIsVisibleWithScale(
325 const gfx::Point
& point
,
328 EnsureRectIsVisibleWithScale(gfx::Rect(point
, gfx::Size(0, 0)),
333 void MagnificationControllerImpl::OnMouseMove(const gfx::Point
& location
) {
334 DCHECK(root_window_
);
336 gfx::Point
mouse(location
);
340 bool start_zoom
= false;
342 const gfx::Rect window_rect
= gfx::ToEnclosingRect(GetWindowRectDIP(scale_
));
343 const int left
= window_rect
.x();
344 const int right
= window_rect
.right();
345 int margin
= kPanningMergin
/ scale_
; // No need to consider DPI.
349 if (mouse
.x() < left
+ margin
) {
351 x_diff
= mouse
.x() - (left
+ margin
);
353 } else if (right
- margin
< mouse
.x()) {
355 x_diff
= mouse
.x() - (right
- margin
);
360 const int top
= window_rect
.y();
361 const int bottom
= window_rect
.bottom();
364 if (mouse
.y() < top
+ margin
) {
366 y_diff
= mouse
.y() - (top
+ margin
);
368 } else if (bottom
- margin
< mouse
.y()) {
370 y_diff
= mouse
.y() - (bottom
- margin
);
375 if (start_zoom
&& !is_on_animation_
) {
376 // No animation on panning.
377 bool animate
= false;
378 bool ret
= RedrawDIP(gfx::Point(x
, y
), scale_
, animate
);
381 // If the magnified region is moved, hides the mouse cursor and moves it.
382 if (x_diff
!= 0 || y_diff
!= 0)
383 MoveCursorTo(root_window_
, mouse
);
388 void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
389 const gfx::Point
& location
) {
390 DCHECK(root_window_
);
392 aura::client::CursorClient
* cursor_client
=
393 aura::client::GetCursorClient(root_window_
);
395 // When cursor is invisible, do not move or show the cursor after the
397 if (!cursor_client
->IsCursorVisible())
399 cursor_client
->DisableMouseEvents();
401 move_cursor_after_animation_
= true;
402 position_after_animation_
= location
;
405 gfx::Size
MagnificationControllerImpl::GetHostSizeDIP() const {
406 return root_window_
->bounds().size();
409 gfx::RectF
MagnificationControllerImpl::GetWindowRectDIP(float scale
) const {
410 const gfx::Size size_in_dip
= root_window_
->bounds().size();
411 const float width
= size_in_dip
.width() / scale
;
412 const float height
= size_in_dip
.height() / scale
;
414 return gfx::RectF(origin_
.x(), origin_
.y(), width
, height
);
417 bool MagnificationControllerImpl::IsMagnified() const {
418 return scale_
>= kMinMagnifiedScaleThreshold
;
421 void MagnificationControllerImpl::ValidateScale(float* scale
) {
422 // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than
423 // |kMinMagnifiedScaleThreshold|;
424 if (*scale
< kMinMagnifiedScaleThreshold
)
425 *scale
= kNonMagnifiedScale
;
427 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
428 // |kMinMagnifiedScaleThreshold|;
429 if (*scale
> kMaxMagnifiedScaleThreshold
)
430 *scale
= kMaxMagnifiedScale
;
432 DCHECK(kNonMagnifiedScale
<= *scale
&& *scale
<= kMaxMagnifiedScale
);
435 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
436 if (!is_on_animation_
)
439 if (move_cursor_after_animation_
) {
440 MoveCursorTo(root_window_
, position_after_animation_
);
441 move_cursor_after_animation_
= false;
443 aura::client::CursorClient
* cursor_client
=
444 aura::client::GetCursorClient(root_window_
);
446 cursor_client
->EnableMouseEvents();
449 is_on_animation_
= false;
452 void MagnificationControllerImpl::OnWindowDestroying(
453 aura::Window
* root_window
) {
454 if (root_window
== root_window_
) {
455 // There must be at least one root window because this controller is
456 // destroyed before the root windows get destroyed.
459 aura::RootWindow
* active_root_window
= Shell::GetActiveRootWindow();
460 CHECK(active_root_window
);
462 // The destroyed root window must not be active.
463 CHECK_NE(active_root_window
, root_window
);
464 // Don't redraw the old root window as it's being destroyed.
465 SwitchTargetRootWindow(active_root_window
, false);
466 point_of_interest_
= active_root_window
->bounds().CenterPoint();
470 void MagnificationControllerImpl::OnWindowBoundsChanged(
471 aura::Window
* window
,
472 const gfx::Rect
& old_bounds
,
473 const gfx::Rect
& new_bounds
) {
474 // TODO(yoshiki): implement here. crbug.com/230979
477 void MagnificationControllerImpl::SwitchTargetRootWindow(
478 aura::RootWindow
* new_root_window
,
479 bool redraw_original_root_window
) {
480 DCHECK(new_root_window
);
482 if (new_root_window
== root_window_
)
485 // Stores the previous scale.
486 float scale
= GetScale();
488 // Unmagnify the previous root window.
489 root_window_
->RemoveObserver(this);
490 if (redraw_original_root_window
)
491 RedrawKeepingMousePosition(1.0f
, true);
493 root_window_
= new_root_window
;
494 RedrawKeepingMousePosition(scale
, true);
495 root_window_
->AddObserver(this);
498 ////////////////////////////////////////////////////////////////////////////////
499 // MagnificationControllerImpl: MagnificationController implementation
501 void MagnificationControllerImpl::SetScale(float scale
, bool animate
) {
505 ValidateScale(&scale
);
506 ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale
);
507 RedrawKeepingMousePosition(scale
, animate
);
510 void MagnificationControllerImpl::MoveWindow(int x
, int y
, bool animate
) {
514 Redraw(gfx::Point(x
, y
), scale_
, animate
);
517 void MagnificationControllerImpl::MoveWindow(const gfx::Point
& point
,
522 Redraw(point
, scale_
, animate
);
525 void MagnificationControllerImpl::EnsureRectIsVisible(
526 const gfx::Rect
& target_rect
,
531 EnsureRectIsVisibleWithScale(target_rect
, scale_
, animate
);
534 void MagnificationControllerImpl::EnsurePointIsVisible(
535 const gfx::Point
& point
,
540 EnsurePointIsVisibleWithScale(point
, scale_
, animate
);
543 void MagnificationControllerImpl::SetEnabled(bool enabled
) {
546 ash::Shell::GetInstance()->delegate()->GetSavedScreenMagnifierScale();
548 scale
= kInitialMagnifiedScale
;
549 ValidateScale(&scale
);
551 // Do nothing, if already enabled with same scale.
552 if (is_enabled_
&& scale
== scale_
)
555 is_enabled_
= enabled
;
556 RedrawKeepingMousePosition(scale
, true);
557 ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale
);
559 // Do nothing, if already disabled.
563 RedrawKeepingMousePosition(kNonMagnifiedScale
, true);
564 is_enabled_
= enabled
;
568 bool MagnificationControllerImpl::IsEnabled() const {
572 ////////////////////////////////////////////////////////////////////////////////
573 // MagnificationControllerImpl: aura::EventFilter implementation
575 void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent
* event
) {
576 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
577 aura::RootWindow
* current_root
= target
->GetRootWindow();
578 gfx::Rect root_bounds
= current_root
->bounds();
580 if (root_bounds
.Contains(event
->root_location())) {
581 // This must be before |SwitchTargetRootWindow()|.
582 point_of_interest_
= event
->root_location();
584 if (current_root
!= root_window_
) {
585 DCHECK(current_root
);
586 SwitchTargetRootWindow(current_root
, true);
589 if (IsMagnified() && event
->type() == ui::ET_MOUSE_MOVED
)
590 OnMouseMove(event
->root_location());
594 void MagnificationControllerImpl::OnScrollEvent(ui::ScrollEvent
* event
) {
595 if (event
->IsAltDown() && event
->IsControlDown()) {
596 if (event
->type() == ui::ET_SCROLL_FLING_START
||
597 event
->type() == ui::ET_SCROLL_FLING_CANCEL
) {
598 event
->StopPropagation();
602 if (event
->type() == ui::ET_SCROLL
) {
603 ui::ScrollEvent
* scroll_event
= static_cast<ui::ScrollEvent
*>(event
);
604 float scale
= GetScale();
605 scale
+= scroll_event
->y_offset() * kScrollScaleChangeFactor
;
606 SetScale(scale
, true);
607 event
->StopPropagation();
613 void MagnificationControllerImpl::OnTouchEvent(ui::TouchEvent
* event
) {
614 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
615 aura::RootWindow
* current_root
= target
->GetRootWindow();
616 if (current_root
== root_window_
) {
617 gfx::Rect root_bounds
= current_root
->bounds();
618 if (root_bounds
.Contains(event
->root_location()))
619 point_of_interest_
= event
->root_location();
623 ////////////////////////////////////////////////////////////////////////////////
624 // MagnificationController:
627 MagnificationController
* MagnificationController::CreateInstance() {
628 return new MagnificationControllerImpl();