blob: 602e95aa629fa88be6a4821cb0e94e79376e5d14 [file] [log] [blame]
// 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/shell.h"
#include "ui/aura/event.h"
#include "ui/aura/event_filter.h"
#include "ui/aura/root_window.h"
#include "ui/aura/shared/compound_event_filter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_property.h"
#include "ui/gfx/point3.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"
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;
} // namespace
namespace ash {
namespace internal {
////////////////////////////////////////////////////////////////////////////////
// MagnificationControllerImpl:
class MagnificationControllerImpl : virtual public MagnificationController,
public aura::EventFilter,
public ui::ImplicitAnimationObserver {
public:
MagnificationControllerImpl();
virtual ~MagnificationControllerImpl();
// MagnificationController overrides:
virtual void SetEnabled(bool enabled) 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 origin_; }
virtual void EnsureRectIsVisible(const gfx::Rect& rect,
bool animate) OVERRIDE;
virtual void EnsurePointIsVisible(const gfx::Point& point,
bool animate) OVERRIDE;
private:
// ui::ImplicitAnimationObserver overrides:
virtual void OnImplicitAnimationsCompleted() 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::Point& position, float scale, bool animate);
bool RedrawDIP(const gfx::Point& position, 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);
// 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::Rect 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);
// aura::EventFilter overrides:
virtual bool PreHandleKeyEvent(aura::Window* target,
aura::KeyEvent* event) OVERRIDE;
virtual bool PreHandleMouseEvent(aura::Window* target,
aura::MouseEvent* event) OVERRIDE;
virtual ui::TouchStatus PreHandleTouchEvent(aura::Window* target,
aura::TouchEvent* event) OVERRIDE;
virtual ui::GestureStatus PreHandleGestureEvent(
aura::Window* target,
aura::GestureEvent* event) OVERRIDE;
aura::RootWindow* root_window_;
// True if the magnified window is in motion of zooming or un-zooming effect.
// Otherwise, false.
bool is_on_zooming_;
bool is_enabled_;
// Current scale, origin (left-top) position of the magnification window.
float scale_;
gfx::Point origin_;
DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl);
};
////////////////////////////////////////////////////////////////////////////////
// MagnificationControllerImpl:
MagnificationControllerImpl::MagnificationControllerImpl()
: root_window_(ash::Shell::GetPrimaryRootWindow()),
is_on_zooming_(false),
is_enabled_(false),
scale_(kNonMagnifiedScale) {
Shell::GetInstance()->AddEnvEventFilter(this);
}
MagnificationControllerImpl::~MagnificationControllerImpl() {
Shell::GetInstance()->RemoveEnvEventFilter(this);
}
bool MagnificationControllerImpl::Redraw(const gfx::Point& position,
float scale,
bool animate) {
const gfx::Point position_in_dip =
ui::ConvertPointToDIP(root_window_->layer(), position);
return RedrawDIP(position_in_dip, scale, animate);
}
bool MagnificationControllerImpl::RedrawDIP(const gfx::Point& position_in_dip,
float scale,
bool animate) {
int x = position_in_dip.x();
int 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::Size window_size_in_dip = GetWindowRectDIP(scale).size();
int max_x = host_size_in_dip.width() - window_size_in_dip.width();
int 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;
// Ignores 1 px diffirence because it may be error on calculation.
if (std::abs(origin_.x() - x) <= 1 &&
std::abs(origin_.y() - y) <= 1 &&
scale == scale_)
return false;
origin_.set_x(x);
origin_.set_y(y);
scale_ = scale;
// Creates transform matrix.
ui::Transform transform;
// Flips the signs intentionally to convert them from the position of the
// magnification window.
transform.ConcatTranslate(-origin_.x(), -origin_.y());
transform.ConcatScale(scale_, scale_);
ui::ScopedLayerAnimationSettings settings(
root_window_->layer()->GetAnimator());
settings.AddObserver(this);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTweenType(ui::Tween::EASE_OUT);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(animate ? 100 : 0));
root_window_->layer()->SetTransform(transform);
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 = 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) {
gfx::Point mouse(location);
int x = origin_.x();
int y = origin_.y();
bool start_zoom = false;
const gfx::Rect window_rect = GetWindowRectDIP(scale_);
const int left = window_rect.x();
const int right = window_rect.right();
const int width_margin = static_cast<int>(0.1f * window_rect.width());
const int width_offset = static_cast<int>(0.5f * window_rect.width());
if (mouse.x() < left + width_margin) {
x -= width_offset;
start_zoom = true;
} else if (right - width_margin < mouse.x()) {
x += width_offset;
start_zoom = true;
}
const int top = window_rect.y();
const int bottom = window_rect.bottom();
// Uses same margin with x-axis's one.
const int height_margin = width_margin;
const int height_offset = static_cast<int>(0.5f * window_rect.height());
if (mouse.y() < top + height_margin) {
y -= height_offset;
start_zoom = true;
} else if (bottom - height_margin < mouse.y()) {
y += height_offset;
start_zoom = true;
}
if (start_zoom && !is_on_zooming_) {
bool ret = Redraw(gfx::Point(x, y), scale_, true);
if (ret) {
is_on_zooming_ = true;
int x_diff = origin_.x() - window_rect.x();
int y_diff = origin_.y() - window_rect.y();
// If the magnified region is moved, hides the mouse cursor and moves it.
if (x_diff != 0 || y_diff != 0) {
ash::Shell::GetInstance()->
env_filter()->set_update_cursor_visibility(false);
root_window_->ShowCursor(false);
mouse.set_x(mouse.x() - (origin_.x() - window_rect.x()));
mouse.set_y(mouse.y() - (origin_.y() - window_rect.y()));
root_window_->MoveCursorTo(mouse);
}
}
}
}
gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const {
return ui::ConvertSizeToDIP(root_window_->layer(),
root_window_->GetHostSize());
}
gfx::Rect MagnificationControllerImpl::GetWindowRectDIP(float scale) const {
const gfx::Size size_in_dip =
ui::ConvertSizeToDIP(root_window_->layer(),
root_window_->GetHostSize());
const int width = size_in_dip.width() / scale;
const int height = size_in_dip.height() / scale;
return gfx::Rect(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;
}
void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
root_window_->ShowCursor(true);
is_on_zooming_ = false;
}
////////////////////////////////////////////////////////////////////////////////
// MagnificationControllerImpl: MagnificationController implementation
void MagnificationControllerImpl::SetScale(float scale, bool animate) {
if (!is_enabled_)
return;
ValidateScale(&scale);
// Try not to change the point which the mouse cursor indicates to.
const gfx::Rect window_rect = GetWindowRectDIP(scale);
const gfx::Point mouse = root_window_->last_mouse_location();
const gfx::Point origin = gfx::Point(mouse.x() * (1.0f - 1.0f / scale),
mouse.y() * (1.0f - 1.0f / scale));
Redraw(origin, 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) {
is_enabled_ = enabled;
SetScale(kInitialMagnifiedScale, true);
} else {
SetScale(kNonMagnifiedScale, true);
is_enabled_ = enabled;
}
}
////////////////////////////////////////////////////////////////////////////////
// MagnificationControllerImpl: aura::EventFilter implementation
bool MagnificationControllerImpl::PreHandleKeyEvent(aura::Window* target,
aura::KeyEvent* event) {
return false;
}
bool MagnificationControllerImpl::PreHandleMouseEvent(aura::Window* target,
aura::MouseEvent* event) {
if (IsMagnified() && event->type() == ui::ET_MOUSE_MOVED)
OnMouseMove(event->root_location());
return false;
}
ui::TouchStatus MagnificationControllerImpl::PreHandleTouchEvent(
aura::Window* target,
aura::TouchEvent* event) {
return ui::TOUCH_STATUS_UNKNOWN;
}
ui::GestureStatus MagnificationControllerImpl::PreHandleGestureEvent(
aura::Window* target,
aura::GestureEvent* event) {
return ui::GESTURE_STATUS_UNKNOWN;
}
////////////////////////////////////////////////////////////////////////////////
// MagnificationController:
// static
MagnificationController* MagnificationController::CreateInstance() {
return new MagnificationControllerImpl();
}
} // namespace internal
} // namespace ash