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"
8 #include "ash/shell_delegate.h"
9 #include "ash/system/tray/system_tray_delegate.h"
10 #include "ui/aura/client/cursor_client.h"
11 #include "ui/aura/root_window.h"
12 #include "ui/aura/window.h"
13 #include "ui/aura/window_property.h"
14 #include "ui/base/events/event.h"
15 #include "ui/base/events/event_handler.h"
16 #include "ui/compositor/dip_util.h"
17 #include "ui/compositor/layer.h"
18 #include "ui/compositor/layer_animation_observer.h"
19 #include "ui/compositor/scoped_layer_animation_settings.h"
20 #include "ui/gfx/point_conversions.h"
21 #include "ui/gfx/point_f.h"
22 #include "ui/gfx/rect_conversions.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/views/corewm/compound_event_filter.h"
28 const float kMaxMagnifiedScale
= 4.0f
;
29 const float kMaxMagnifiedScaleThreshold
= 4.0f
;
30 const float kMinMagnifiedScaleThreshold
= 1.1f
;
31 const float kNonMagnifiedScale
= 1.0f
;
33 const float kInitialMagnifiedScale
= 2.0f
;
34 const float kScrollScaleChangeFactor
= 0.05f
;
36 // Threadshold of panning. If the cursor moves to within pixels (in DIP) of
37 // |kPanningMergin| from the edge, the view-port moves.
38 const int kPanningMergin
= 100;
44 ////////////////////////////////////////////////////////////////////////////////
45 // MagnificationControllerImpl:
47 class MagnificationControllerImpl
: virtual public MagnificationController
,
48 public ui::EventHandler
,
49 public ui::ImplicitAnimationObserver
{
51 MagnificationControllerImpl();
52 virtual ~MagnificationControllerImpl();
54 // MagnificationController overrides:
55 virtual void SetEnabled(bool enabled
) OVERRIDE
;
56 virtual bool IsEnabled() const OVERRIDE
;
57 virtual void SetScale(float scale
, bool animate
) OVERRIDE
;
58 virtual float GetScale() const OVERRIDE
{ return scale_
; }
59 virtual void MoveWindow(int x
, int y
, bool animate
) OVERRIDE
;
60 virtual void MoveWindow(const gfx::Point
& point
, bool animate
) OVERRIDE
;
61 virtual gfx::Point
GetWindowPosition() const OVERRIDE
{
62 return gfx::ToFlooredPoint(origin_
);
64 virtual void EnsureRectIsVisible(const gfx::Rect
& rect
,
65 bool animate
) OVERRIDE
;
66 virtual void EnsurePointIsVisible(const gfx::Point
& point
,
67 bool animate
) OVERRIDE
;
70 // ui::ImplicitAnimationObserver overrides:
71 virtual void OnImplicitAnimationsCompleted() OVERRIDE
;
73 // Redraws the magnification window with the given origin position and the
74 // given scale. Returns true if the window is changed; otherwise, false.
75 // These methods should be called internally just after the scale and/or
76 // the position are changed to redraw the window.
77 bool Redraw(const gfx::PointF
& position
, float scale
, bool animate
);
78 bool RedrawDIP(const gfx::PointF
& position
, float scale
, bool animate
);
80 // Redraw with the given zoom scale keeping the mouse cursor location. In
81 // other words, zoom (or unzoom) centering around the cursor.
82 void RedrawKeepingMousePosition(float scale
, bool animate
);
84 // Ensures that the given point, rect or last mouse location is inside
85 // magnification window. If not, the controller moves the window to contain
86 // the given point/rect.
87 void EnsureRectIsVisibleWithScale(const gfx::Rect
& target_rect
,
90 void EnsureRectIsVisibleDIP(const gfx::Rect
& target_rect_in_dip
,
93 void EnsurePointIsVisibleWithScale(const gfx::Point
& point
,
96 void OnMouseMove(const gfx::Point
& location
);
98 // Move the mouse cursot to the given point. Actual move will be done when
99 // the animation is completed. This should be called after animation is
101 void AfterAnimationMoveCursorTo(const gfx::Point
& location
);
103 // Switch Magnified RootWindow to |new_root_window|. This does following:
104 // - Unzoom the current root_window.
105 // - Zoom the given new root_window |new_root_window|.
106 // - Switch the target window from current window to |new_root_window|.
107 void SwitchTargetRootWindow(aura::RootWindow
* new_root_window
);
109 // Returns if the magnification scale is 1.0 or not (larger then 1.0).
110 bool IsMagnified() const;
112 // Returns the rect of the magnification window.
113 gfx::RectF
GetWindowRectDIP(float scale
) const;
114 // Returns the size of the root window.
115 gfx::Size
GetHostSizeDIP() const;
117 // Correct the givin scale value if nessesary.
118 void ValidateScale(float* scale
);
120 // ui::EventHandler overrides:
121 virtual void OnMouseEvent(ui::MouseEvent
* event
) OVERRIDE
;
122 virtual void OnScrollEvent(ui::ScrollEvent
* event
) OVERRIDE
;
124 aura::RootWindow
* root_window_
;
126 // True if the magnified window is currently animating a change. Otherwise,
128 bool is_on_animation_
;
132 // True if the cursor needs to move the given position after the animation
133 // will be finished. When using this, set |position_after_animation_| as well.
134 bool move_cursor_after_animation_
;
135 // Stores the position of cursor to be moved after animation.
136 gfx::Point position_after_animation_
;
138 // Current scale, origin (left-top) position of the magnification window.
142 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl
);
145 ////////////////////////////////////////////////////////////////////////////////
146 // MagnificationControllerImpl:
148 MagnificationControllerImpl::MagnificationControllerImpl()
149 : root_window_(ash::Shell::GetPrimaryRootWindow()),
150 is_on_animation_(false),
152 move_cursor_after_animation_(false),
153 scale_(kNonMagnifiedScale
) {
154 Shell::GetInstance()->AddPreTargetHandler(this);
157 MagnificationControllerImpl::~MagnificationControllerImpl() {
158 Shell::GetInstance()->RemovePreTargetHandler(this);
161 void MagnificationControllerImpl::RedrawKeepingMousePosition(
162 float scale
, bool animate
) {
163 gfx::Point mouse_in_root
= root_window_
->GetLastMouseLocationInRoot();
165 // mouse_in_root is invalid value when the cursor is hidden.
166 if (!root_window_
->bounds().Contains(mouse_in_root
))
167 mouse_in_root
= root_window_
->bounds().CenterPoint();
169 const gfx::PointF origin
=
170 gfx::PointF(mouse_in_root
.x() -
171 (scale_
/ scale
) * (mouse_in_root
.x() - origin_
.x()),
173 (scale_
/ scale
) * (mouse_in_root
.y() - origin_
.y()));
174 bool changed
= Redraw(origin
, scale
, animate
);
176 AfterAnimationMoveCursorTo(mouse_in_root
);
179 bool MagnificationControllerImpl::Redraw(const gfx::PointF
& position
,
182 const gfx::PointF position_in_dip
=
183 ui::ConvertPointToDIP(root_window_
->layer(), position
);
184 return RedrawDIP(position_in_dip
, scale
, animate
);
187 bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF
& position_in_dip
,
190 float x
= position_in_dip
.x();
191 float y
= position_in_dip
.y();
193 ValidateScale(&scale
);
200 const gfx::Size host_size_in_dip
= GetHostSizeDIP();
201 const gfx::SizeF window_size_in_dip
= GetWindowRectDIP(scale
).size();
202 float max_x
= host_size_in_dip
.width() - window_size_in_dip
.width();
203 float max_y
= host_size_in_dip
.height() - window_size_in_dip
.height();
209 // Does nothing if both the origin and the scale are not changed.
210 if (origin_
.x() == x
&&
220 // Creates transform matrix.
221 gfx::Transform transform
;
222 // Flips the signs intentionally to convert them from the position of the
223 // magnification window.
224 transform
.Scale(scale_
, scale_
);
225 transform
.Translate(-origin_
.x(), -origin_
.y());
227 ui::ScopedLayerAnimationSettings
settings(
228 root_window_
->layer()->GetAnimator());
229 settings
.AddObserver(this);
230 settings
.SetPreemptionStrategy(
231 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
232 settings
.SetTweenType(ui::Tween::EASE_OUT
);
233 settings
.SetTransitionDuration(
234 base::TimeDelta::FromMilliseconds(animate
? 100 : 0));
236 root_window_
->layer()->SetTransform(transform
);
239 is_on_animation_
= true;
244 void MagnificationControllerImpl::EnsureRectIsVisibleWithScale(
245 const gfx::Rect
& target_rect
,
248 const gfx::Rect target_rect_in_dip
=
249 ui::ConvertRectToDIP(root_window_
->layer(), target_rect
);
250 EnsureRectIsVisibleDIP(target_rect_in_dip
, scale
, animate
);
253 void MagnificationControllerImpl::EnsureRectIsVisibleDIP(
254 const gfx::Rect
& target_rect
,
257 ValidateScale(&scale
);
259 const gfx::Rect window_rect
= gfx::ToEnclosingRect(GetWindowRectDIP(scale
));
260 if (scale
== scale_
&& window_rect
.Contains(target_rect
))
263 // TODO(yoshiki): Un-zoom and change the scale if the magnification window
264 // can't contain the whole given rect.
266 gfx::Rect rect
= window_rect
;
267 if (target_rect
.width() > rect
.width())
268 rect
.set_x(target_rect
.CenterPoint().x() - rect
.x() / 2);
269 else if (target_rect
.right() < rect
.x())
270 rect
.set_x(target_rect
.right());
271 else if (rect
.right() < target_rect
.x())
272 rect
.set_x(target_rect
.x() - rect
.width());
274 if (rect
.height() > window_rect
.height())
275 rect
.set_y(target_rect
.CenterPoint().y() - rect
.y() / 2);
276 else if (target_rect
.bottom() < rect
.y())
277 rect
.set_y(target_rect
.bottom());
278 else if (rect
.bottom() < target_rect
.y())
279 rect
.set_y(target_rect
.y() - rect
.height());
281 RedrawDIP(rect
.origin(), scale
, animate
);
284 void MagnificationControllerImpl::EnsurePointIsVisibleWithScale(
285 const gfx::Point
& point
,
288 EnsureRectIsVisibleWithScale(gfx::Rect(point
, gfx::Size(0, 0)),
293 void MagnificationControllerImpl::OnMouseMove(const gfx::Point
& location
) {
294 gfx::Point
mouse(location
);
298 bool start_zoom
= false;
300 const gfx::Rect window_rect
= gfx::ToEnclosingRect(GetWindowRectDIP(scale_
));
301 const int left
= window_rect
.x();
302 const int right
= window_rect
.right();
303 int margin
= kPanningMergin
/ scale_
; // No need to consider DPI.
307 if (mouse
.x() < left
+ margin
) {
309 x_diff
= mouse
.x() - (left
+ margin
);
311 } else if (right
- margin
< mouse
.x()) {
313 x_diff
= mouse
.x() - (right
- margin
);
318 const int top
= window_rect
.y();
319 const int bottom
= window_rect
.bottom();
322 if (mouse
.y() < top
+ margin
) {
324 y_diff
= mouse
.y() - (top
+ margin
);
326 } else if (bottom
- margin
< mouse
.y()) {
328 y_diff
= mouse
.y() - (bottom
- margin
);
333 if (start_zoom
&& !is_on_animation_
) {
334 // No animation on panning.
335 bool animate
= false;
336 bool ret
= RedrawDIP(gfx::Point(x
, y
), scale_
, animate
);
339 // If the magnified region is moved, hides the mouse cursor and moves it.
340 if (x_diff
!= 0 || y_diff
!= 0)
341 root_window_
->MoveCursorTo(mouse
);
346 void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
347 const gfx::Point
& location
) {
348 aura::client::CursorClient
* cursor_client
=
349 aura::client::GetCursorClient(root_window_
);
351 // When cursor is invisible, do not move or show the cursor after the
353 if (!cursor_client
->IsCursorVisible())
355 cursor_client
->DisableMouseEvents();
357 move_cursor_after_animation_
= true;
358 position_after_animation_
= location
;
361 gfx::Size
MagnificationControllerImpl::GetHostSizeDIP() const {
362 return ui::ConvertSizeToDIP(root_window_
->layer(),
363 root_window_
->GetHostSize());
366 gfx::RectF
MagnificationControllerImpl::GetWindowRectDIP(float scale
) const {
367 const gfx::Size size_in_dip
=
368 ui::ConvertSizeToDIP(root_window_
->layer(),
369 root_window_
->GetHostSize());
370 const float width
= size_in_dip
.width() / scale
;
371 const float height
= size_in_dip
.height() / scale
;
373 return gfx::RectF(origin_
.x(), origin_
.y(), width
, height
);
376 bool MagnificationControllerImpl::IsMagnified() const {
377 return scale_
>= kMinMagnifiedScaleThreshold
;
380 void MagnificationControllerImpl::ValidateScale(float* scale
) {
381 // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than
382 // |kMinMagnifiedScaleThreshold|;
383 if (*scale
< kMinMagnifiedScaleThreshold
)
384 *scale
= kNonMagnifiedScale
;
386 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
387 // |kMinMagnifiedScaleThreshold|;
388 if (*scale
> kMaxMagnifiedScaleThreshold
)
389 *scale
= kMaxMagnifiedScale
;
391 DCHECK(kNonMagnifiedScale
<= *scale
&& *scale
<= kMaxMagnifiedScale
);
394 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
395 if (!is_on_animation_
)
398 if (move_cursor_after_animation_
) {
399 root_window_
->MoveCursorTo(position_after_animation_
);
400 move_cursor_after_animation_
= false;
402 aura::client::CursorClient
* cursor_client
=
403 aura::client::GetCursorClient(root_window_
);
405 cursor_client
->EnableMouseEvents();
408 is_on_animation_
= false;
411 void MagnificationControllerImpl::SwitchTargetRootWindow(
412 aura::RootWindow
* new_root_window
) {
413 if (new_root_window
== root_window_
)
416 float scale
= GetScale();
418 RedrawKeepingMousePosition(1.0f
, true);
419 root_window_
= new_root_window
;
420 RedrawKeepingMousePosition(scale
, true);
423 ////////////////////////////////////////////////////////////////////////////////
424 // MagnificationControllerImpl: MagnificationController implementation
426 void MagnificationControllerImpl::SetScale(float scale
, bool animate
) {
430 ValidateScale(&scale
);
431 ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale
);
432 RedrawKeepingMousePosition(scale
, animate
);
435 void MagnificationControllerImpl::MoveWindow(int x
, int y
, bool animate
) {
439 Redraw(gfx::Point(x
, y
), scale_
, animate
);
442 void MagnificationControllerImpl::MoveWindow(const gfx::Point
& point
,
447 Redraw(point
, scale_
, animate
);
450 void MagnificationControllerImpl::EnsureRectIsVisible(
451 const gfx::Rect
& target_rect
,
456 EnsureRectIsVisibleWithScale(target_rect
, scale_
, animate
);
459 void MagnificationControllerImpl::EnsurePointIsVisible(
460 const gfx::Point
& point
,
465 EnsurePointIsVisibleWithScale(point
, scale_
, animate
);
468 void MagnificationControllerImpl::SetEnabled(bool enabled
) {
471 ash::Shell::GetInstance()->delegate()->GetSavedScreenMagnifierScale();
473 scale
= kInitialMagnifiedScale
;
474 ValidateScale(&scale
);
476 // Do nothing, if already enabled with same scale.
477 if (is_enabled_
&& scale
== scale_
)
480 RedrawKeepingMousePosition(scale
, true);
481 ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale
);
482 is_enabled_
= enabled
;
484 // Do nothing, if already disabled.
488 RedrawKeepingMousePosition(kNonMagnifiedScale
, true);
489 is_enabled_
= enabled
;
493 bool MagnificationControllerImpl::IsEnabled() const {
497 ////////////////////////////////////////////////////////////////////////////////
498 // MagnificationControllerImpl: aura::EventFilter implementation
500 void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent
* event
) {
501 if (IsMagnified() && event
->type() == ui::ET_MOUSE_MOVED
) {
502 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
503 aura::RootWindow
* current_root
= target
->GetRootWindow();
504 gfx::Rect root_bounds
= current_root
->bounds();
506 if (root_bounds
.Contains(event
->root_location())) {
507 if (current_root
!= root_window_
)
508 SwitchTargetRootWindow(current_root
);
510 OnMouseMove(event
->root_location());
515 void MagnificationControllerImpl::OnScrollEvent(
516 ui::ScrollEvent
* event
) {
517 if (event
->IsAltDown() && event
->IsControlDown()) {
518 if (event
->type() == ui::ET_SCROLL_FLING_START
||
519 event
->type() == ui::ET_SCROLL_FLING_CANCEL
) {
520 event
->StopPropagation();
524 if (event
->type() == ui::ET_SCROLL
) {
525 ui::ScrollEvent
* scroll_event
= static_cast<ui::ScrollEvent
*>(event
);
526 float scale
= GetScale();
527 scale
+= scroll_event
->y_offset() * kScrollScaleChangeFactor
;
528 SetScale(scale
, true);
529 event
->StopPropagation();
535 ////////////////////////////////////////////////////////////////////////////////
536 // MagnificationController:
539 MagnificationController
* MagnificationController::CreateInstance() {
540 return new MagnificationControllerImpl();