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/aura_constants.h"
19 #include "ui/aura/client/cursor_client.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura/window_tree_host.h"
22 #include "ui/base/ime/input_method.h"
23 #include "ui/base/ime/input_method_observer.h"
24 #include "ui/base/ime/text_input_client.h"
25 #include "ui/compositor/dip_util.h"
26 #include "ui/compositor/layer.h"
27 #include "ui/compositor/layer_animation_observer.h"
28 #include "ui/compositor/scoped_layer_animation_settings.h"
29 #include "ui/events/event.h"
30 #include "ui/events/event_handler.h"
31 #include "ui/gfx/point3_f.h"
32 #include "ui/gfx/point_conversions.h"
33 #include "ui/gfx/point_f.h"
34 #include "ui/gfx/rect_conversions.h"
35 #include "ui/gfx/screen.h"
36 #include "ui/wm/core/compound_event_filter.h"
37 #include "ui/wm/core/coordinate_conversion.h"
41 const float kMaxMagnifiedScale
= 4.0f
;
42 const float kMaxMagnifiedScaleThreshold
= 4.0f
;
43 const float kMinMagnifiedScaleThreshold
= 1.1f
;
44 const float kNonMagnifiedScale
= 1.0f
;
46 const float kInitialMagnifiedScale
= 2.0f
;
47 const float kScrollScaleChangeFactor
= 0.05f
;
49 // Threadshold of panning. If the cursor moves to within pixels (in DIP) of
50 // |kPanningMergin| from the edge, the view-port moves.
51 const int kPanningMergin
= 100;
53 // Gives a little panning margin for following caret, so that we will move the
54 // view-port before the caret is completely out of sight.
55 const int kCaretPanningMargin
= 10;
57 void MoveCursorTo(aura::WindowTreeHost
* host
, const gfx::Point
& root_location
) {
58 gfx::Point3F
host_location_3f(root_location
);
59 host
->GetRootTransform().TransformPoint(&host_location_3f
);
60 host
->MoveCursorToHostLocation(
61 gfx::ToCeiledPoint(host_location_3f
.AsPointF()));
68 ////////////////////////////////////////////////////////////////////////////////
69 // MagnificationControllerImpl:
71 class MagnificationControllerImpl
: virtual public MagnificationController
,
72 public ui::EventHandler
,
73 public ui::ImplicitAnimationObserver
,
74 public aura::WindowObserver
,
75 public ui::InputMethodObserver
{
77 MagnificationControllerImpl();
78 ~MagnificationControllerImpl() override
;
80 // MagnificationController overrides:
81 void SetEnabled(bool enabled
) override
;
82 bool IsEnabled() const override
;
83 void SetScale(float scale
, bool animate
) override
;
84 float GetScale() const override
{ return scale_
; }
85 void MoveWindow(int x
, int y
, bool animate
) override
;
86 void MoveWindow(const gfx::Point
& point
, bool animate
) override
;
87 gfx::Point
GetWindowPosition() const override
{
88 return gfx::ToFlooredPoint(origin_
);
90 void SetScrollDirection(ScrollDirection direction
) override
;
93 gfx::Point
GetPointOfInterestForTesting() override
{
94 return point_of_interest_
;
98 // ui::ImplicitAnimationObserver overrides:
99 void OnImplicitAnimationsCompleted() override
;
101 // aura::WindowObserver overrides:
102 void OnWindowDestroying(aura::Window
* root_window
) override
;
103 void OnWindowBoundsChanged(aura::Window
* window
,
104 const gfx::Rect
& old_bounds
,
105 const gfx::Rect
& new_bounds
) override
;
107 // Redraws the magnification window with the given origin position and the
108 // given scale. Returns true if the window is changed; otherwise, false.
109 // These methods should be called internally just after the scale and/or
110 // the position are changed to redraw the window.
111 bool Redraw(const gfx::PointF
& position
, float scale
, bool animate
);
112 bool RedrawDIP(const gfx::PointF
& position
, float scale
, bool animate
);
114 // 1) If the screen is scrolling (i.e. animating) and should scroll further,
116 // 2) If the screen is scrolling (i.e. animating) and the direction is NONE,
117 // it stops the scrolling animation.
118 // 3) If the direction is set to value other than NONE, it starts the
119 // scrolling/ animation towards that direction.
120 void StartOrStopScrollIfNecessary();
122 // Redraw with the given zoom scale keeping the mouse cursor location. In
123 // other words, zoom (or unzoom) centering around the cursor.
124 void RedrawKeepingMousePosition(float scale
, bool animate
);
126 void OnMouseMove(const gfx::Point
& location
);
128 // Move the mouse cursot to the given point. Actual move will be done when
129 // the animation is completed. This should be called after animation is
131 void AfterAnimationMoveCursorTo(const gfx::Point
& location
);
133 // Switch Magnified RootWindow to |new_root_window|. This does following:
134 // - Unzoom the current root_window.
135 // - Zoom the given new root_window |new_root_window|.
136 // - Switch the target window from current window to |new_root_window|.
137 void SwitchTargetRootWindow(aura::Window
* new_root_window
,
138 bool redraw_original_root_window
);
140 // Returns if the magnification scale is 1.0 or not (larger then 1.0).
141 bool IsMagnified() const;
143 // Returns the rect of the magnification window.
144 gfx::RectF
GetWindowRectDIP(float scale
) const;
145 // Returns the size of the root window.
146 gfx::Size
GetHostSizeDIP() const;
148 // Correct the givin scale value if nessesary.
149 void ValidateScale(float* scale
);
151 // ui::EventHandler overrides:
152 void OnMouseEvent(ui::MouseEvent
* event
) override
;
153 void OnScrollEvent(ui::ScrollEvent
* event
) override
;
154 void OnTouchEvent(ui::TouchEvent
* event
) override
;
156 // Moves the view port when |point| is located within
157 // |x_panning_margin| and |y_pannin_margin| to the edge of the visible
158 // window region. The view port will be moved so that the |point| will be
159 // moved to the point where it has |x_target_margin| and |y_target_margin|
160 // to the edge of the visible region.
161 void MoveMagnifierWindow(const gfx::Point
& point
,
162 int x_panning_margin
,
163 int y_panning_margin
,
165 int y_target_margin
);
167 // ui::InputMethodObserver:
168 void OnTextInputTypeChanged(const ui::TextInputClient
* client
) override
{}
169 void OnFocus() override
{}
170 void OnBlur() override
{}
171 void OnTextInputStateChanged(const ui::TextInputClient
* client
) override
{}
172 void OnInputMethodDestroyed(const ui::InputMethod
* input_method
) override
{}
173 void OnShowImeIfNeeded() override
{}
174 void OnCaretBoundsChanged(const ui::TextInputClient
* client
) override
;
176 // Target root window. This must not be NULL.
177 aura::Window
* root_window_
;
179 // True if the magnified window is currently animating a change. Otherwise,
181 bool is_on_animation_
;
185 // True if the cursor needs to move the given position after the animation
186 // will be finished. When using this, set |position_after_animation_| as well.
187 bool move_cursor_after_animation_
;
188 // Stores the position of cursor to be moved after animation.
189 gfx::Point position_after_animation_
;
191 // Stores the last mouse cursor (or last touched) location. This value is
192 // used on zooming to keep this location visible.
193 gfx::Point point_of_interest_
;
195 // Current scale, origin (left-top) position of the magnification window.
199 ScrollDirection scroll_direction_
;
201 ui::InputMethod
* input_method_
; // Not owned.
203 DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl
);
206 ////////////////////////////////////////////////////////////////////////////////
207 // MagnificationControllerImpl:
209 MagnificationControllerImpl::MagnificationControllerImpl()
210 : root_window_(Shell::GetPrimaryRootWindow()),
211 is_on_animation_(false),
213 move_cursor_after_animation_(false),
214 scale_(kNonMagnifiedScale
),
215 scroll_direction_(SCROLL_NONE
),
216 input_method_(NULL
) {
217 Shell::GetInstance()->AddPreTargetHandler(this);
218 root_window_
->AddObserver(this);
219 point_of_interest_
= root_window_
->bounds().CenterPoint();
222 MagnificationControllerImpl::~MagnificationControllerImpl() {
224 input_method_
->RemoveObserver(this);
226 root_window_
->RemoveObserver(this);
228 Shell::GetInstance()->RemovePreTargetHandler(this);
231 void MagnificationControllerImpl::RedrawKeepingMousePosition(
232 float scale
, bool animate
) {
233 gfx::Point mouse_in_root
= point_of_interest_
;
235 // mouse_in_root is invalid value when the cursor is hidden.
236 if (!root_window_
->bounds().Contains(mouse_in_root
))
237 mouse_in_root
= root_window_
->bounds().CenterPoint();
239 const gfx::PointF origin
=
240 gfx::PointF(mouse_in_root
.x() -
241 (scale_
/ scale
) * (mouse_in_root
.x() - origin_
.x()),
243 (scale_
/ scale
) * (mouse_in_root
.y() - origin_
.y()));
244 bool changed
= RedrawDIP(origin
, scale
, animate
);
246 AfterAnimationMoveCursorTo(mouse_in_root
);
249 bool MagnificationControllerImpl::Redraw(const gfx::PointF
& position
,
252 const gfx::PointF position_in_dip
=
253 ui::ConvertPointToDIP(root_window_
->layer(), position
);
254 return RedrawDIP(position_in_dip
, scale
, animate
);
257 bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF
& position_in_dip
,
260 DCHECK(root_window_
);
262 float x
= position_in_dip
.x();
263 float y
= position_in_dip
.y();
265 ValidateScale(&scale
);
272 const gfx::Size host_size_in_dip
= GetHostSizeDIP();
273 const gfx::SizeF window_size_in_dip
= GetWindowRectDIP(scale
).size();
274 float max_x
= host_size_in_dip
.width() - window_size_in_dip
.width();
275 float max_y
= host_size_in_dip
.height() - window_size_in_dip
.height();
281 // Does nothing if both the origin and the scale are not changed.
282 if (origin_
.x() == x
&&
292 // Creates transform matrix.
293 gfx::Transform transform
;
294 // Flips the signs intentionally to convert them from the position of the
295 // magnification window.
296 transform
.Scale(scale_
, scale_
);
297 transform
.Translate(-origin_
.x(), -origin_
.y());
299 ui::ScopedLayerAnimationSettings
settings(
300 root_window_
->layer()->GetAnimator());
301 settings
.AddObserver(this);
302 settings
.SetPreemptionStrategy(
303 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
304 settings
.SetTweenType(gfx::Tween::EASE_OUT
);
305 settings
.SetTransitionDuration(
306 base::TimeDelta::FromMilliseconds(animate
? 100 : 0));
308 gfx::Display display
=
309 Shell::GetScreen()->GetDisplayNearestWindow(root_window_
);
310 scoped_ptr
<RootWindowTransformer
> transformer(
311 CreateRootWindowTransformerForDisplay(root_window_
, display
));
312 GetRootWindowController(root_window_
)->ash_host()->SetRootWindowTransformer(
316 is_on_animation_
= true;
321 void MagnificationControllerImpl::StartOrStopScrollIfNecessary() {
322 // This value controls the scrolling speed.
323 const int kMoveOffset
= 40;
324 if (is_on_animation_
) {
325 if (scroll_direction_
== SCROLL_NONE
)
326 root_window_
->layer()->GetAnimator()->StopAnimating();
330 gfx::PointF new_origin
= origin_
;
331 switch (scroll_direction_
) {
333 // No need to take action.
336 new_origin
.Offset(-kMoveOffset
, 0);
339 new_origin
.Offset(kMoveOffset
, 0);
342 new_origin
.Offset(0, -kMoveOffset
);
345 new_origin
.Offset(0, kMoveOffset
);
348 RedrawDIP(new_origin
, scale_
, true);
351 void MagnificationControllerImpl::OnMouseMove(const gfx::Point
& location
) {
352 DCHECK(root_window_
);
354 gfx::Point
mouse(location
);
355 int margin
= kPanningMergin
/ scale_
; // No need to consider DPI.
356 MoveMagnifierWindow(mouse
, margin
, margin
, margin
, margin
);
359 void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
360 const gfx::Point
& location
) {
361 DCHECK(root_window_
);
363 aura::client::CursorClient
* cursor_client
=
364 aura::client::GetCursorClient(root_window_
);
366 // When cursor is invisible, do not move or show the cursor after the
368 if (!cursor_client
->IsCursorVisible())
370 cursor_client
->DisableMouseEvents();
372 move_cursor_after_animation_
= true;
373 position_after_animation_
= location
;
376 gfx::Size
MagnificationControllerImpl::GetHostSizeDIP() const {
377 return root_window_
->bounds().size();
380 gfx::RectF
MagnificationControllerImpl::GetWindowRectDIP(float scale
) const {
381 const gfx::Size size_in_dip
= root_window_
->bounds().size();
382 const float width
= size_in_dip
.width() / scale
;
383 const float height
= size_in_dip
.height() / scale
;
385 return gfx::RectF(origin_
.x(), origin_
.y(), width
, height
);
388 bool MagnificationControllerImpl::IsMagnified() const {
389 return scale_
>= kMinMagnifiedScaleThreshold
;
392 void MagnificationControllerImpl::ValidateScale(float* scale
) {
393 // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than
394 // |kMinMagnifiedScaleThreshold|;
395 if (*scale
< kMinMagnifiedScaleThreshold
)
396 *scale
= kNonMagnifiedScale
;
398 // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
399 // |kMinMagnifiedScaleThreshold|;
400 if (*scale
> kMaxMagnifiedScaleThreshold
)
401 *scale
= kMaxMagnifiedScale
;
403 DCHECK(kNonMagnifiedScale
<= *scale
&& *scale
<= kMaxMagnifiedScale
);
406 void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
407 if (!is_on_animation_
)
410 if (move_cursor_after_animation_
) {
411 MoveCursorTo(root_window_
->GetHost(), position_after_animation_
);
412 move_cursor_after_animation_
= false;
414 aura::client::CursorClient
* cursor_client
=
415 aura::client::GetCursorClient(root_window_
);
417 cursor_client
->EnableMouseEvents();
420 is_on_animation_
= false;
422 StartOrStopScrollIfNecessary();
425 void MagnificationControllerImpl::OnWindowDestroying(
426 aura::Window
* root_window
) {
427 if (root_window
== root_window_
) {
428 // There must be at least one root window because this controller is
429 // destroyed before the root windows get destroyed.
432 aura::Window
* target_root_window
= Shell::GetTargetRootWindow();
433 CHECK(target_root_window
);
435 // The destroyed root window must not be target.
436 CHECK_NE(target_root_window
, root_window
);
437 // Don't redraw the old root window as it's being destroyed.
438 SwitchTargetRootWindow(target_root_window
, false);
439 point_of_interest_
= target_root_window
->bounds().CenterPoint();
443 void MagnificationControllerImpl::OnWindowBoundsChanged(
444 aura::Window
* window
,
445 const gfx::Rect
& old_bounds
,
446 const gfx::Rect
& new_bounds
) {
447 // TODO(yoshiki): implement here. crbug.com/230979
450 void MagnificationControllerImpl::SwitchTargetRootWindow(
451 aura::Window
* new_root_window
,
452 bool redraw_original_root_window
) {
453 DCHECK(new_root_window
);
455 if (new_root_window
== root_window_
)
458 // Stores the previous scale.
459 float scale
= GetScale();
461 // Unmagnify the previous root window.
462 root_window_
->RemoveObserver(this);
463 if (redraw_original_root_window
)
464 RedrawKeepingMousePosition(1.0f
, true);
466 root_window_
= new_root_window
;
467 RedrawKeepingMousePosition(scale
, true);
468 root_window_
->AddObserver(this);
471 ////////////////////////////////////////////////////////////////////////////////
472 // MagnificationControllerImpl: MagnificationController implementation
474 void MagnificationControllerImpl::SetScale(float scale
, bool animate
) {
478 ValidateScale(&scale
);
479 Shell::GetInstance()->accessibility_delegate()->
480 SaveScreenMagnifierScale(scale
);
481 RedrawKeepingMousePosition(scale
, animate
);
484 void MagnificationControllerImpl::MoveWindow(int x
, int y
, bool animate
) {
488 Redraw(gfx::Point(x
, y
), scale_
, animate
);
491 void MagnificationControllerImpl::MoveWindow(const gfx::Point
& point
,
496 Redraw(point
, scale_
, animate
);
499 void MagnificationControllerImpl::SetScrollDirection(
500 ScrollDirection direction
) {
501 scroll_direction_
= direction
;
502 StartOrStopScrollIfNecessary();
505 void MagnificationControllerImpl::SetEnabled(bool enabled
) {
506 Shell
* shell
= Shell::GetInstance();
508 if (!input_method_
) {
510 root_window_
->GetProperty(aura::client::kRootWindowInputMethodKey
);
512 input_method_
->AddObserver(this);
516 Shell::GetInstance()->accessibility_delegate()->
517 GetSavedScreenMagnifierScale();
519 scale
= kInitialMagnifiedScale
;
520 ValidateScale(&scale
);
522 // Do nothing, if already enabled with same scale.
523 if (is_enabled_
&& scale
== scale_
)
526 is_enabled_
= enabled
;
527 RedrawKeepingMousePosition(scale
, true);
528 shell
->accessibility_delegate()->SaveScreenMagnifierScale(scale
);
530 // Do nothing, if already disabled.
535 input_method_
->RemoveObserver(this);
536 input_method_
= NULL
;
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 void MagnificationControllerImpl::MoveMagnifierWindow(const gfx::Point
& point
,
601 int x_panning_margin
,
602 int y_panning_margin
,
604 int y_target_margin
) {
605 DCHECK(root_window_
);
608 bool start_zoom
= false;
610 const gfx::Rect window_rect
= gfx::ToEnclosingRect(GetWindowRectDIP(scale_
));
611 const int left
= window_rect
.x();
612 const int right
= window_rect
.right();
615 if (point
.x() < left
+ x_panning_margin
) {
617 x_diff
= point
.x() - (left
+ x_target_margin
);
619 } else if (right
- x_panning_margin
< point
.x()) {
621 x_diff
= point
.x() - (right
- x_target_margin
);
626 const int top
= window_rect
.y();
627 const int bottom
= window_rect
.bottom();
630 if (point
.y() < top
+ y_panning_margin
) {
632 y_diff
= point
.y() - (top
+ y_target_margin
);
634 } else if (bottom
- y_panning_margin
< point
.y()) {
636 y_diff
= point
.y() - (bottom
- y_target_margin
);
640 if (start_zoom
&& !is_on_animation_
) {
641 // No animation on panning.
642 bool animate
= false;
643 bool ret
= RedrawDIP(gfx::Point(x
, y
), scale_
, animate
);
646 // If the magnified region is moved, hides the mouse cursor and moves it.
647 if (x_diff
!= 0 || y_diff
!= 0)
648 MoveCursorTo(root_window_
->GetHost(), point
);
653 void MagnificationControllerImpl::OnCaretBoundsChanged(
654 const ui::TextInputClient
* client
) {
655 // caret bounds in screen coordinates.
656 const gfx::Rect caret_bounds
= client
->GetCaretBounds();
657 // Note: OnCaretBoundsChanged could be fired OnTextInputTypeChanged during
658 // which the caret position is not set a meaning position, and we do not
659 // need to adjust the view port position based on the bogus caret position.
660 // This is only a transition period, the caret position will be fixed upon
661 // focusing right after.
662 if (caret_bounds
.width() == 0 && caret_bounds
.height() == 0)
665 gfx::Point caret_origin
= caret_bounds
.origin();
666 // caret_origin in |root_window_| coordinates.
667 wm::ConvertPointFromScreen(root_window_
, &caret_origin
);
669 // Visible window_rect in |root_window_| coordinates.
670 const gfx::Rect visible_window_rect
=
671 gfx::ToEnclosingRect(GetWindowRectDIP(scale_
));
673 const int panning_margin
= kCaretPanningMargin
/ scale_
;
674 MoveMagnifierWindow(caret_origin
,
677 visible_window_rect
.width() / 2,
678 visible_window_rect
.height() / 2);
681 ////////////////////////////////////////////////////////////////////////////////
682 // MagnificationController:
685 MagnificationController
* MagnificationController::CreateInstance() {
686 return new MagnificationControllerImpl();