blob: 5b51928eb434836f45a8fe10a500f85ce0b54272 [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/partial_magnification_controller.h"
#include "ash/shell.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#if defined(OS_CHROMEOS)
#include "ash/common/system/chromeos/palette/palette_utils.h"
#endif
namespace ash {
namespace {
// Ratio of magnifier scale.
const float kMagnificationScale = 2.f;
// Radius of the magnifying glass in DIP.
const int kMagnifierRadius = 200;
// Size of the border around the magnifying glass in DIP.
const int kBorderSize = 10;
// Inset on the zoom filter.
const int kZoomInset = 0;
// Vertical offset between the center of the magnifier and the tip of the
// pointer. TODO(jdufault): The vertical offset should only apply to the window
// location, not the magnified contents. See crbug.com/637617.
const int kVerticalOffset = 0;
// Name of the magnifier window.
const char kPartialMagniferWindowName[] = "PartialMagnifierWindow";
gfx::Size GetWindowSize() {
return gfx::Size(kMagnifierRadius * 2, kMagnifierRadius * 2);
}
gfx::Rect GetBounds(gfx::Point mouse) {
gfx::Size size = GetWindowSize();
gfx::Point origin(mouse.x() - (size.width() / 2),
mouse.y() - (size.height() / 2) - kVerticalOffset);
return gfx::Rect(origin, size);
}
aura::Window* GetCurrentRootWindow() {
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window* root_window : root_windows) {
if (root_window->ContainsPointInRoot(
root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()))
return root_window;
}
return nullptr;
}
// Returns true if the event should be processed normally, ie, the stylus is
// over the palette icon or widget.
bool ShouldSkipEventFiltering(const gfx::Point& point) {
#if defined(OS_CHROMEOS)
return PaletteContainsPointInScreen(point);
#else
return false;
#endif
}
} // namespace
// The content mask provides a clipping layer for the magnification window so we
// can show a circular magnifier.
class PartialMagnificationController::ContentMask : public ui::LayerDelegate {
public:
// If |stroke| is true, the circle will be a stroke. This is useful if we wish
// to clip a border.
ContentMask(bool stroke, gfx::Size mask_bounds)
: layer_(ui::LAYER_TEXTURED), stroke_(stroke) {
layer_.set_delegate(this);
layer_.SetFillsBoundsOpaquely(false);
layer_.SetBounds(gfx::Rect(mask_bounds));
}
~ContentMask() override { layer_.set_delegate(nullptr); }
ui::Layer* layer() { return &layer_; }
private:
// Overridden from LayerDelegate.
void OnPaintLayer(const ui::PaintContext& context) override {
ui::PaintRecorder recorder(context, layer()->size());
SkPaint paint;
paint.setAlpha(255);
paint.setAntiAlias(true);
paint.setStrokeWidth(kBorderSize);
paint.setStyle(stroke_ ? SkPaint::kStroke_Style : SkPaint::kFill_Style);
gfx::Rect rect(layer()->bounds().size());
recorder.canvas()->DrawCircle(rect.CenterPoint(),
rect.width() / 2 - kBorderSize / 2, paint);
}
void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
void OnDeviceScaleFactorChanged(float device_scale_factor) override {
// Redrawing will take care of scale factor change.
}
base::Closure PrepareForLayerBoundsChange() override {
return base::Closure();
}
ui::Layer layer_;
bool stroke_;
DISALLOW_COPY_AND_ASSIGN(ContentMask);
};
PartialMagnificationController::PartialMagnificationController() {
Shell::GetInstance()->AddPreTargetHandler(this);
}
PartialMagnificationController::~PartialMagnificationController() {
CloseMagnifierWindow();
Shell::GetInstance()->RemovePreTargetHandler(this);
}
void PartialMagnificationController::SetEnabled(bool enabled) {
is_enabled_ = enabled;
SetActive(false);
}
void PartialMagnificationController::SwitchTargetRootWindowIfNeeded(
aura::Window* new_root_window) {
if (host_widget_ &&
new_root_window == host_widget_->GetNativeView()->GetRootWindow())
return;
if (!new_root_window)
new_root_window = GetCurrentRootWindow();
if (is_enabled_ && is_active_) {
CloseMagnifierWindow();
CreateMagnifierWindow(new_root_window);
}
}
void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) {
OnLocatedEvent(event, event->pointer_details());
}
void PartialMagnificationController::OnTouchEvent(ui::TouchEvent* event) {
OnLocatedEvent(event, event->pointer_details());
}
void PartialMagnificationController::OnWindowDestroying(aura::Window* window) {
CloseMagnifierWindow();
aura::Window* new_root_window = GetCurrentRootWindow();
if (new_root_window != window)
SwitchTargetRootWindowIfNeeded(new_root_window);
}
void PartialMagnificationController::OnWidgetDestroying(views::Widget* widget) {
DCHECK_EQ(widget, host_widget_);
RemoveZoomWidgetObservers();
host_widget_ = nullptr;
}
void PartialMagnificationController::SetActive(bool active) {
// Fail if we're trying to activate while disabled.
DCHECK(is_enabled_ || !active);
is_active_ = active;
if (is_active_) {
CreateMagnifierWindow(GetCurrentRootWindow());
} else {
CloseMagnifierWindow();
}
}
void PartialMagnificationController::OnLocatedEvent(
ui::LocatedEvent* event,
const ui::PointerDetails& pointer_details) {
if (!is_enabled_)
return;
if (pointer_details.pointer_type != ui::EventPointerType::POINTER_TYPE_PEN)
return;
// Compute the event location in screen space.
aura::Window* target = static_cast<aura::Window*>(event->target());
aura::Window* event_root = target->GetRootWindow();
gfx::Point screen_point = event->root_location();
wm::ConvertPointToScreen(event_root, &screen_point);
if (event->type() == ui::ET_MOUSE_PRESSED &&
!ShouldSkipEventFiltering(screen_point)) {
SetActive(true);
}
if (event->type() == ui::ET_MOUSE_RELEASED)
SetActive(false);
if (!is_active_)
return;
// If the previous root window was detached host_widget_ will be null;
// reconstruct it. We also need to change the root window if the cursor has
// crossed display boundries.
SwitchTargetRootWindowIfNeeded(GetCurrentRootWindow());
// If that failed for any reason return.
if (!host_widget_) {
SetActive(false);
return;
}
// Remap point from where it was captured to the display it is actually on.
gfx::Point point = event->root_location();
aura::Window::ConvertPointToTarget(
event_root, host_widget_->GetNativeView()->GetRootWindow(), &point);
host_widget_->SetBounds(GetBounds(point));
if (!ShouldSkipEventFiltering(screen_point))
event->StopPropagation();
}
void PartialMagnificationController::CreateMagnifierWindow(
aura::Window* root_window) {
if (host_widget_ || !root_window)
return;
root_window->AddObserver(this);
gfx::Point mouse(
root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
host_widget_ = new views::Widget;
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
params.accept_events = false;
params.bounds = GetBounds(mouse);
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.parent = root_window;
host_widget_->Init(params);
host_widget_->set_focus_on_creation(false);
host_widget_->Show();
aura::Window* window = host_widget_->GetNativeView();
window->SetName(kPartialMagniferWindowName);
ui::Layer* root_layer = host_widget_->GetNativeView()->layer();
zoom_layer_.reset(new ui::Layer(ui::LayerType::LAYER_SOLID_COLOR));
zoom_layer_->SetBounds(gfx::Rect(GetWindowSize()));
zoom_layer_->SetBackgroundZoom(kMagnificationScale, kZoomInset);
root_layer->Add(zoom_layer_.get());
border_layer_.reset(new ui::Layer(ui::LayerType::LAYER_SOLID_COLOR));
border_layer_->SetBounds(gfx::Rect(GetWindowSize()));
border_layer_->SetColor(SK_ColorWHITE);
root_layer->Add(border_layer_.get());
border_mask_.reset(new ContentMask(true, GetWindowSize()));
border_layer_->SetMaskLayer(border_mask_->layer());
zoom_mask_.reset(new ContentMask(false, GetWindowSize()));
zoom_layer_->SetMaskLayer(zoom_mask_->layer());
host_widget_->AddObserver(this);
}
void PartialMagnificationController::CloseMagnifierWindow() {
if (host_widget_) {
RemoveZoomWidgetObservers();
host_widget_->Close();
host_widget_ = nullptr;
}
}
void PartialMagnificationController::RemoveZoomWidgetObservers() {
DCHECK(host_widget_);
host_widget_->RemoveObserver(this);
aura::Window* root_window = host_widget_->GetNativeView()->GetRootWindow();
DCHECK(root_window);
root_window->RemoveObserver(this);
}
} // namespace ash