| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/magnifier/magnification_controller.h" |
| |
| #include "ash/accessibility_delegate.h" |
| #include "ash/display/root_window_transformers.h" |
| #include "ash/shell.h" |
| #include "ash/system/tray/system_tray_delegate.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "ui/aura/client/cursor_client.h" |
| #include "ui/aura/root_window.h" |
| #include "ui/aura/root_window_transformer.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_property.h" |
| #include "ui/compositor/dip_util.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_handler.h" |
| #include "ui/gfx/point3_f.h" |
| #include "ui/gfx/point_conversions.h" |
| #include "ui/gfx/point_f.h" |
| #include "ui/gfx/rect_conversions.h" |
| #include "ui/gfx/screen.h" |
| #include "ui/views/corewm/compound_event_filter.h" |
| |
| namespace { |
| |
| const float kMaxMagnifiedScale = 4.0f; |
| const float kMaxMagnifiedScaleThreshold = 4.0f; |
| const float kMinMagnifiedScaleThreshold = 1.1f; |
| const float kNonMagnifiedScale = 1.0f; |
| |
| const float kInitialMagnifiedScale = 2.0f; |
| const float kScrollScaleChangeFactor = 0.05f; |
| |
| // Threadshold of panning. If the cursor moves to within pixels (in DIP) of |
| // |kPanningMergin| from the edge, the view-port moves. |
| const int kPanningMergin = 100; |
| |
| void MoveCursorTo(aura::RootWindow* root_window, |
| const gfx::Point& root_location) { |
| gfx::Point3F host_location_3f(root_location); |
| root_window->GetRootTransform().TransformPoint(&host_location_3f); |
| root_window->MoveCursorToHostLocation( |
| gfx::ToCeiledPoint(host_location_3f.AsPointF())); |
| } |
| |
| } // namespace |
| |
| namespace ash { |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MagnificationControllerImpl: |
| |
| class MagnificationControllerImpl : virtual public MagnificationController, |
| public ui::EventHandler, |
| public ui::ImplicitAnimationObserver, |
| public aura::WindowObserver { |
| public: |
| MagnificationControllerImpl(); |
| virtual ~MagnificationControllerImpl(); |
| |
| // MagnificationController overrides: |
| virtual void SetEnabled(bool enabled) OVERRIDE; |
| virtual bool IsEnabled() const OVERRIDE; |
| virtual void SetScale(float scale, bool animate) OVERRIDE; |
| virtual float GetScale() const OVERRIDE { return scale_; } |
| virtual void MoveWindow(int x, int y, bool animate) OVERRIDE; |
| virtual void MoveWindow(const gfx::Point& point, bool animate) OVERRIDE; |
| virtual gfx::Point GetWindowPosition() const OVERRIDE { |
| return gfx::ToFlooredPoint(origin_); |
| } |
| virtual void EnsureRectIsVisible(const gfx::Rect& rect, |
| bool animate) OVERRIDE; |
| virtual void EnsurePointIsVisible(const gfx::Point& point, |
| bool animate) OVERRIDE; |
| // For test |
| virtual gfx::Point GetPointOfInterestForTesting() OVERRIDE { |
| return point_of_interest_; |
| } |
| |
| private: |
| // ui::ImplicitAnimationObserver overrides: |
| virtual void OnImplicitAnimationsCompleted() OVERRIDE; |
| |
| // aura::WindowObserver overrides: |
| virtual void OnWindowDestroying(aura::Window* root_window) OVERRIDE; |
| virtual void OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds) OVERRIDE; |
| |
| // Redraws the magnification window with the given origin position and the |
| // given scale. Returns true if the window is changed; otherwise, false. |
| // These methods should be called internally just after the scale and/or |
| // the position are changed to redraw the window. |
| bool Redraw(const gfx::PointF& position, float scale, bool animate); |
| bool RedrawDIP(const gfx::PointF& position, float scale, bool animate); |
| |
| // Redraw with the given zoom scale keeping the mouse cursor location. In |
| // other words, zoom (or unzoom) centering around the cursor. |
| void RedrawKeepingMousePosition(float scale, bool animate); |
| |
| // Ensures that the given point, rect or last mouse location is inside |
| // magnification window. If not, the controller moves the window to contain |
| // the given point/rect. |
| void EnsureRectIsVisibleWithScale(const gfx::Rect& target_rect, |
| float scale, |
| bool animate); |
| void EnsureRectIsVisibleDIP(const gfx::Rect& target_rect_in_dip, |
| float scale, |
| bool animate); |
| void EnsurePointIsVisibleWithScale(const gfx::Point& point, |
| float scale, |
| bool animate); |
| void OnMouseMove(const gfx::Point& location); |
| |
| // Move the mouse cursot to the given point. Actual move will be done when |
| // the animation is completed. This should be called after animation is |
| // started. |
| void AfterAnimationMoveCursorTo(const gfx::Point& location); |
| |
| // Switch Magnified RootWindow to |new_root_window|. This does following: |
| // - Unzoom the current root_window. |
| // - Zoom the given new root_window |new_root_window|. |
| // - Switch the target window from current window to |new_root_window|. |
| void SwitchTargetRootWindow(aura::Window* new_root_window, |
| bool redraw_original_root_window); |
| |
| // Returns if the magnification scale is 1.0 or not (larger then 1.0). |
| bool IsMagnified() const; |
| |
| // Returns the rect of the magnification window. |
| gfx::RectF GetWindowRectDIP(float scale) const; |
| // Returns the size of the root window. |
| gfx::Size GetHostSizeDIP() const; |
| |
| // Correct the givin scale value if nessesary. |
| void ValidateScale(float* scale); |
| |
| // ui::EventHandler overrides: |
| virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; |
| virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; |
| virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; |
| |
| // Target root window. This must not be NULL. |
| aura::Window* root_window_; |
| |
| // True if the magnified window is currently animating a change. Otherwise, |
| // false. |
| bool is_on_animation_; |
| |
| bool is_enabled_; |
| |
| // True if the cursor needs to move the given position after the animation |
| // will be finished. When using this, set |position_after_animation_| as well. |
| bool move_cursor_after_animation_; |
| // Stores the position of cursor to be moved after animation. |
| gfx::Point position_after_animation_; |
| |
| // Stores the last mouse cursor (or last touched) location. This value is |
| // used on zooming to keep this location visible. |
| gfx::Point point_of_interest_; |
| |
| // Current scale, origin (left-top) position of the magnification window. |
| float scale_; |
| gfx::PointF origin_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MagnificationControllerImpl: |
| |
| MagnificationControllerImpl::MagnificationControllerImpl() |
| : root_window_(ash::Shell::GetPrimaryRootWindow()), |
| is_on_animation_(false), |
| is_enabled_(false), |
| move_cursor_after_animation_(false), |
| scale_(kNonMagnifiedScale) { |
| Shell::GetInstance()->AddPreTargetHandler(this); |
| root_window_->AddObserver(this); |
| point_of_interest_ = root_window_->bounds().CenterPoint(); |
| } |
| |
| MagnificationControllerImpl::~MagnificationControllerImpl() { |
| root_window_->RemoveObserver(this); |
| |
| Shell::GetInstance()->RemovePreTargetHandler(this); |
| } |
| |
| void MagnificationControllerImpl::RedrawKeepingMousePosition( |
| float scale, bool animate) { |
| gfx::Point mouse_in_root = point_of_interest_; |
| |
| // mouse_in_root is invalid value when the cursor is hidden. |
| if (!root_window_->bounds().Contains(mouse_in_root)) |
| mouse_in_root = root_window_->bounds().CenterPoint(); |
| |
| const gfx::PointF origin = |
| gfx::PointF(mouse_in_root.x() - |
| (scale_ / scale) * (mouse_in_root.x() - origin_.x()), |
| mouse_in_root.y() - |
| (scale_ / scale) * (mouse_in_root.y() - origin_.y())); |
| bool changed = RedrawDIP(origin, scale, animate); |
| if (changed) |
| AfterAnimationMoveCursorTo(mouse_in_root); |
| } |
| |
| bool MagnificationControllerImpl::Redraw(const gfx::PointF& position, |
| float scale, |
| bool animate) { |
| const gfx::PointF position_in_dip = |
| ui::ConvertPointToDIP(root_window_->layer(), position); |
| return RedrawDIP(position_in_dip, scale, animate); |
| } |
| |
| bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF& position_in_dip, |
| float scale, |
| bool animate) { |
| DCHECK(root_window_); |
| |
| float x = position_in_dip.x(); |
| float y = position_in_dip.y(); |
| |
| ValidateScale(&scale); |
| |
| if (x < 0) |
| x = 0; |
| if (y < 0) |
| y = 0; |
| |
| const gfx::Size host_size_in_dip = GetHostSizeDIP(); |
| const gfx::SizeF window_size_in_dip = GetWindowRectDIP(scale).size(); |
| float max_x = host_size_in_dip.width() - window_size_in_dip.width(); |
| float max_y = host_size_in_dip.height() - window_size_in_dip.height(); |
| if (x > max_x) |
| x = max_x; |
| if (y > max_y) |
| y = max_y; |
| |
| // Does nothing if both the origin and the scale are not changed. |
| if (origin_.x() == x && |
| origin_.y() == y && |
| scale == scale_) { |
| return false; |
| } |
| |
| origin_.set_x(x); |
| origin_.set_y(y); |
| scale_ = scale; |
| |
| // Creates transform matrix. |
| gfx::Transform transform; |
| // Flips the signs intentionally to convert them from the position of the |
| // magnification window. |
| transform.Scale(scale_, scale_); |
| transform.Translate(-origin_.x(), -origin_.y()); |
| |
| ui::ScopedLayerAnimationSettings settings( |
| root_window_->layer()->GetAnimator()); |
| settings.AddObserver(this); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| settings.SetTweenType(gfx::Tween::EASE_OUT); |
| settings.SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(animate ? 100 : 0)); |
| |
| gfx::Display display = |
| Shell::GetScreen()->GetDisplayNearestWindow(root_window_); |
| scoped_ptr<aura::RootWindowTransformer> transformer( |
| internal::CreateRootWindowTransformerForDisplay(root_window_, display)); |
| root_window_->GetDispatcher()->SetRootWindowTransformer(transformer.Pass()); |
| |
| if (animate) |
| is_on_animation_ = true; |
| |
| return true; |
| } |
| |
| void MagnificationControllerImpl::EnsureRectIsVisibleWithScale( |
| const gfx::Rect& target_rect, |
| float scale, |
| bool animate) { |
| const gfx::Rect target_rect_in_dip = |
| ui::ConvertRectToDIP(root_window_->layer(), target_rect); |
| EnsureRectIsVisibleDIP(target_rect_in_dip, scale, animate); |
| } |
| |
| void MagnificationControllerImpl::EnsureRectIsVisibleDIP( |
| const gfx::Rect& target_rect, |
| float scale, |
| bool animate) { |
| ValidateScale(&scale); |
| |
| const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale)); |
| if (scale == scale_ && window_rect.Contains(target_rect)) |
| return; |
| |
| // TODO(yoshiki): Un-zoom and change the scale if the magnification window |
| // can't contain the whole given rect. |
| |
| gfx::Rect rect = window_rect; |
| if (target_rect.width() > rect.width()) |
| rect.set_x(target_rect.CenterPoint().x() - rect.x() / 2); |
| else if (target_rect.right() < rect.x()) |
| rect.set_x(target_rect.right()); |
| else if (rect.right() < target_rect.x()) |
| rect.set_x(target_rect.x() - rect.width()); |
| |
| if (rect.height() > window_rect.height()) |
| rect.set_y(target_rect.CenterPoint().y() - rect.y() / 2); |
| else if (target_rect.bottom() < rect.y()) |
| rect.set_y(target_rect.bottom()); |
| else if (rect.bottom() < target_rect.y()) |
| rect.set_y(target_rect.y() - rect.height()); |
| |
| RedrawDIP(rect.origin(), scale, animate); |
| } |
| |
| void MagnificationControllerImpl::EnsurePointIsVisibleWithScale( |
| const gfx::Point& point, |
| float scale, |
| bool animate) { |
| EnsureRectIsVisibleWithScale(gfx::Rect(point, gfx::Size(0, 0)), |
| scale, |
| animate); |
| } |
| |
| void MagnificationControllerImpl::OnMouseMove(const gfx::Point& location) { |
| DCHECK(root_window_); |
| |
| gfx::Point mouse(location); |
| |
| int x = origin_.x(); |
| int y = origin_.y(); |
| bool start_zoom = false; |
| |
| const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale_)); |
| const int left = window_rect.x(); |
| const int right = window_rect.right(); |
| int margin = kPanningMergin / scale_; // No need to consider DPI. |
| |
| int x_diff = 0; |
| |
| if (mouse.x() < left + margin) { |
| // Panning left. |
| x_diff = mouse.x() - (left + margin); |
| start_zoom = true; |
| } else if (right - margin < mouse.x()) { |
| // Panning right. |
| x_diff = mouse.x() - (right - margin); |
| start_zoom = true; |
| } |
| x = left + x_diff; |
| |
| const int top = window_rect.y(); |
| const int bottom = window_rect.bottom(); |
| |
| int y_diff = 0; |
| if (mouse.y() < top + margin) { |
| // Panning up. |
| y_diff = mouse.y() - (top + margin); |
| start_zoom = true; |
| } else if (bottom - margin < mouse.y()) { |
| // Panning down. |
| y_diff = mouse.y() - (bottom - margin); |
| start_zoom = true; |
| } |
| y = top + y_diff; |
| |
| if (start_zoom && !is_on_animation_) { |
| // No animation on panning. |
| bool animate = false; |
| bool ret = RedrawDIP(gfx::Point(x, y), scale_, animate); |
| |
| if (ret) { |
| // If the magnified region is moved, hides the mouse cursor and moves it. |
| if (x_diff != 0 || y_diff != 0) |
| MoveCursorTo(root_window_->GetDispatcher(), mouse); |
| } |
| } |
| } |
| |
| void MagnificationControllerImpl::AfterAnimationMoveCursorTo( |
| const gfx::Point& location) { |
| DCHECK(root_window_); |
| |
| aura::client::CursorClient* cursor_client = |
| aura::client::GetCursorClient(root_window_); |
| if (cursor_client) { |
| // When cursor is invisible, do not move or show the cursor after the |
| // animation. |
| if (!cursor_client->IsCursorVisible()) |
| return; |
| cursor_client->DisableMouseEvents(); |
| } |
| move_cursor_after_animation_ = true; |
| position_after_animation_ = location; |
| } |
| |
| gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const { |
| return root_window_->bounds().size(); |
| } |
| |
| gfx::RectF MagnificationControllerImpl::GetWindowRectDIP(float scale) const { |
| const gfx::Size size_in_dip = root_window_->bounds().size(); |
| const float width = size_in_dip.width() / scale; |
| const float height = size_in_dip.height() / scale; |
| |
| return gfx::RectF(origin_.x(), origin_.y(), width, height); |
| } |
| |
| bool MagnificationControllerImpl::IsMagnified() const { |
| return scale_ >= kMinMagnifiedScaleThreshold; |
| } |
| |
| void MagnificationControllerImpl::ValidateScale(float* scale) { |
| // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than |
| // |kMinMagnifiedScaleThreshold|; |
| if (*scale < kMinMagnifiedScaleThreshold) |
| *scale = kNonMagnifiedScale; |
| |
| // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than |
| // |kMinMagnifiedScaleThreshold|; |
| if (*scale > kMaxMagnifiedScaleThreshold) |
| *scale = kMaxMagnifiedScale; |
| |
| DCHECK(kNonMagnifiedScale <= *scale && *scale <= kMaxMagnifiedScale); |
| } |
| |
| void MagnificationControllerImpl::OnImplicitAnimationsCompleted() { |
| if (!is_on_animation_) |
| return; |
| |
| if (move_cursor_after_animation_) { |
| MoveCursorTo(root_window_->GetDispatcher(), position_after_animation_); |
| move_cursor_after_animation_ = false; |
| |
| aura::client::CursorClient* cursor_client = |
| aura::client::GetCursorClient(root_window_); |
| if (cursor_client) |
| cursor_client->EnableMouseEvents(); |
| } |
| |
| is_on_animation_ = false; |
| } |
| |
| void MagnificationControllerImpl::OnWindowDestroying( |
| aura::Window* root_window) { |
| if (root_window == root_window_) { |
| // There must be at least one root window because this controller is |
| // destroyed before the root windows get destroyed. |
| DCHECK(root_window); |
| |
| aura::Window* target_root_window = Shell::GetTargetRootWindow(); |
| CHECK(target_root_window); |
| |
| // The destroyed root window must not be target. |
| CHECK_NE(target_root_window, root_window); |
| // Don't redraw the old root window as it's being destroyed. |
| SwitchTargetRootWindow(target_root_window, false); |
| point_of_interest_ = target_root_window->bounds().CenterPoint(); |
| } |
| } |
| |
| void MagnificationControllerImpl::OnWindowBoundsChanged( |
| aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds) { |
| // TODO(yoshiki): implement here. crbug.com/230979 |
| } |
| |
| void MagnificationControllerImpl::SwitchTargetRootWindow( |
| aura::Window* new_root_window, |
| bool redraw_original_root_window) { |
| DCHECK(new_root_window); |
| |
| if (new_root_window == root_window_) |
| return; |
| |
| // Stores the previous scale. |
| float scale = GetScale(); |
| |
| // Unmagnify the previous root window. |
| root_window_->RemoveObserver(this); |
| if (redraw_original_root_window) |
| RedrawKeepingMousePosition(1.0f, true); |
| |
| root_window_ = new_root_window; |
| RedrawKeepingMousePosition(scale, true); |
| root_window_->AddObserver(this); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MagnificationControllerImpl: MagnificationController implementation |
| |
| void MagnificationControllerImpl::SetScale(float scale, bool animate) { |
| if (!is_enabled_) |
| return; |
| |
| ValidateScale(&scale); |
| ash::Shell::GetInstance()->accessibility_delegate()-> |
| SaveScreenMagnifierScale(scale); |
| RedrawKeepingMousePosition(scale, animate); |
| } |
| |
| void MagnificationControllerImpl::MoveWindow(int x, int y, bool animate) { |
| if (!is_enabled_) |
| return; |
| |
| Redraw(gfx::Point(x, y), scale_, animate); |
| } |
| |
| void MagnificationControllerImpl::MoveWindow(const gfx::Point& point, |
| bool animate) { |
| if (!is_enabled_) |
| return; |
| |
| Redraw(point, scale_, animate); |
| } |
| |
| void MagnificationControllerImpl::EnsureRectIsVisible( |
| const gfx::Rect& target_rect, |
| bool animate) { |
| if (!is_enabled_) |
| return; |
| |
| EnsureRectIsVisibleWithScale(target_rect, scale_, animate); |
| } |
| |
| void MagnificationControllerImpl::EnsurePointIsVisible( |
| const gfx::Point& point, |
| bool animate) { |
| if (!is_enabled_) |
| return; |
| |
| EnsurePointIsVisibleWithScale(point, scale_, animate); |
| } |
| |
| void MagnificationControllerImpl::SetEnabled(bool enabled) { |
| if (enabled) { |
| float scale = |
| ash::Shell::GetInstance()->accessibility_delegate()-> |
| GetSavedScreenMagnifierScale(); |
| if (scale <= 0.0f) |
| scale = kInitialMagnifiedScale; |
| ValidateScale(&scale); |
| |
| // Do nothing, if already enabled with same scale. |
| if (is_enabled_ && scale == scale_) |
| return; |
| |
| is_enabled_ = enabled; |
| RedrawKeepingMousePosition(scale, true); |
| ash::Shell::GetInstance()->accessibility_delegate()-> |
| SaveScreenMagnifierScale(scale); |
| } else { |
| // Do nothing, if already disabled. |
| if (!is_enabled_) |
| return; |
| |
| RedrawKeepingMousePosition(kNonMagnifiedScale, true); |
| is_enabled_ = enabled; |
| } |
| } |
| |
| bool MagnificationControllerImpl::IsEnabled() const { |
| return is_enabled_; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MagnificationControllerImpl: aura::EventFilter implementation |
| |
| void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent* event) { |
| aura::Window* target = static_cast<aura::Window*>(event->target()); |
| aura::Window* current_root = target->GetRootWindow(); |
| gfx::Rect root_bounds = current_root->bounds(); |
| |
| if (root_bounds.Contains(event->root_location())) { |
| // This must be before |SwitchTargetRootWindow()|. |
| point_of_interest_ = event->root_location(); |
| |
| if (current_root != root_window_) { |
| DCHECK(current_root); |
| SwitchTargetRootWindow(current_root, true); |
| } |
| |
| if (IsMagnified() && event->type() == ui::ET_MOUSE_MOVED) |
| OnMouseMove(event->root_location()); |
| } |
| } |
| |
| void MagnificationControllerImpl::OnScrollEvent(ui::ScrollEvent* event) { |
| if (event->IsAltDown() && event->IsControlDown()) { |
| if (event->type() == ui::ET_SCROLL_FLING_START || |
| event->type() == ui::ET_SCROLL_FLING_CANCEL) { |
| event->StopPropagation(); |
| return; |
| } |
| |
| if (event->type() == ui::ET_SCROLL) { |
| ui::ScrollEvent* scroll_event = static_cast<ui::ScrollEvent*>(event); |
| float scale = GetScale(); |
| scale += scroll_event->y_offset() * kScrollScaleChangeFactor; |
| SetScale(scale, true); |
| event->StopPropagation(); |
| return; |
| } |
| } |
| } |
| |
| void MagnificationControllerImpl::OnTouchEvent(ui::TouchEvent* event) { |
| aura::Window* target = static_cast<aura::Window*>(event->target()); |
| aura::Window* current_root = target->GetRootWindow(); |
| if (current_root == root_window_) { |
| gfx::Rect root_bounds = current_root->bounds(); |
| if (root_bounds.Contains(event->root_location())) |
| point_of_interest_ = event->root_location(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // MagnificationController: |
| |
| // static |
| MagnificationController* MagnificationController::CreateInstance() { |
| return new MagnificationControllerImpl(); |
| } |
| |
| } // namespace ash |