Chromium Code Reviews| Index: ash/magnifier/partial_magnification_controller.cc |
| diff --git a/ash/magnifier/partial_magnification_controller.cc b/ash/magnifier/partial_magnification_controller.cc |
| index 3e609791502fcff24e672f31babdee393ec15da9..7cb68e2918456192495be4576c66cc8b74447a38 100644 |
| --- a/ash/magnifier/partial_magnification_controller.cc |
| +++ b/ash/magnifier/partial_magnification_controller.cc |
| @@ -4,42 +4,158 @@ |
| #include "ash/magnifier/partial_magnification_controller.h" |
| -#include "ash/common/shell_window_ids.h" |
| #include "ash/shell.h" |
| -#include "ui/aura/window.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| -#include "ui/aura/window_property.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/compositor/layer.h" |
| -#include "ui/views/layout/fill_layout.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/views/widget/widget_delegate.h" |
| -#include "ui/wm/core/compound_event_filter.h" |
| +#include "ui/wm/core/coordinate_conversion.h" |
| +namespace ash { |
| namespace { |
| -const float kMinPartialMagnifiedScaleThreshold = 1.1f; |
| +// 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; |
| +// Thickness of the outline around magnifiying glass border. |
| +const int kBorderOutlineThickness = 2; |
| +const SkColor kBorderColor = SK_ColorWHITE; |
| +const SkColor kBorderOutlineColor = SK_ColorBLACK; |
| +// 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; |
| -// Number of pixels to make the border of the magnified area. |
| -const int kZoomInset = 16; |
| +// Name of the magnifier window. |
| +const char kPartialMagniferWindowName[] = "PartialMagnifierWindow"; |
| -// Width of the magnified area. |
| -const int kMagnifierWidth = 200; |
| +gfx::Size GetWindowSize() { |
| + return gfx::Size(kMagnifierRadius * 2, kMagnifierRadius * 2); |
| +} |
| -// Height of the magnified area. |
| -const int kMagnifierHeight = 200; |
| +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); |
| +} |
| -// Name of the magnifier window. |
| -const char kPartialMagniferWindowName[] = "PartialMagnifierWindow"; |
| +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; |
| +} |
| } // namespace |
| -namespace ash { |
| +// 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 {} |
| -PartialMagnificationController::PartialMagnificationController() |
| - : is_enabled_(false), |
| - scale_(kNonPartialMagnifiedScale), |
| - zoom_widget_(NULL) { |
| + 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); |
| +}; |
| + |
| +// The border render draws the border as well as outline on both the outer and |
| +// inner radius to increase visibility. |
| +class PartialMagnificationController::BorderRenderer |
| + : public ui::LayerDelegate { |
| + public: |
| + BorderRenderer(const gfx::Rect& bounds) { bounds_ = bounds; } |
| + |
| + ~BorderRenderer() override {} |
| + |
| + private: |
| + // Overridden from LayerDelegate. |
| + void OnPaintLayer(const ui::PaintContext& context) override { |
| + ui::PaintRecorder recorder(context, bounds_.size()); |
| + |
| + // Draw the border. |
| + SkPaint paint; |
| + paint.setAntiAlias(true); |
| + paint.setStrokeWidth(kBorderSize); |
| + paint.setStyle(SkPaint::kStroke_Style); |
| + paint.setColor(kBorderColor); |
| + recorder.canvas()->DrawCircle(bounds_.CenterPoint(), |
| + bounds_.width() / 2 - kBorderSize / 2, paint); |
| + |
| + // Draw the border outlines. |
| + paint.setStrokeWidth(kBorderOutlineThickness); |
| + paint.setColor(kBorderOutlineColor); |
| + recorder.canvas()->DrawCircle( |
|
jdufault
2016/08/24 21:15:39
Add comment saying which circle is the inner outli
sammiequon
2016/08/25 19:47:39
Done.
|
| + bounds_.CenterPoint(), |
| + bounds_.width() / 2 - kBorderOutlineThickness / 2, paint); |
| + recorder.canvas()->DrawCircle( |
| + bounds_.CenterPoint(), |
| + bounds_.width() / 2 - kBorderSize + kBorderOutlineThickness / 2, paint); |
| + } |
| + |
| + void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {} |
| + |
| + void OnDeviceScaleFactorChanged(float device_scale_factor) override {} |
| + |
| + base::Closure PrepareForLayerBoundsChange() override { |
| + return base::Closure(); |
| + } |
| + gfx::Rect bounds_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(BorderRenderer); |
| +}; |
| + |
| +PartialMagnificationController::PartialMagnificationController() { |
| Shell::GetInstance()->AddPreTargetHandler(this); |
| } |
| @@ -49,87 +165,105 @@ PartialMagnificationController::~PartialMagnificationController() { |
| Shell::GetInstance()->RemovePreTargetHandler(this); |
| } |
| -void PartialMagnificationController::SetScale(float scale) { |
| - if (!is_enabled_) |
| +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; |
| - scale_ = scale; |
| + if (!new_root_window) |
| + new_root_window = GetCurrentRootWindow(); |
| - if (IsPartialMagnified()) { |
| - CreateMagnifierWindow(); |
| - } else { |
| + if (is_enabled_ && is_active_) { |
| CloseMagnifierWindow(); |
| + CreateMagnifierWindow(new_root_window); |
| } |
| } |
| -void PartialMagnificationController::SetEnabled(bool enabled) { |
| - if (enabled) { |
| - is_enabled_ = enabled; |
| - SetScale(kDefaultPartialMagnifiedScale); |
| - } else { |
| - SetScale(kNonPartialMagnifiedScale); |
| - is_enabled_ = enabled; |
| - } |
| -} |
| - |
| -//////////////////////////////////////////////////////////////////////////////// |
| -// PartialMagnificationController: ui::EventHandler implementation |
| - |
| void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) { |
| - if (IsPartialMagnified() && event->type() == ui::ET_MOUSE_MOVED) { |
| - aura::Window* target = static_cast<aura::Window*>(event->target()); |
| - aura::Window* current_root = target->GetRootWindow(); |
| - // TODO(zork): Handle the case where the event is captured on a different |
| - // display, such as when a menu is opened. |
| - gfx::Rect root_bounds = current_root->bounds(); |
| - |
| - if (root_bounds.Contains(event->root_location())) { |
| - SwitchTargetRootWindow(current_root); |
| - |
| - OnMouseMove(event->root_location()); |
| - } |
| - } |
| + OnLocatedEvent(event, event->pointer_details()); |
| } |
| -//////////////////////////////////////////////////////////////////////////////// |
| -// PartialMagnificationController: aura::WindowObserver implementation |
| +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) |
| - SwitchTargetRootWindow(new_root_window); |
| + SwitchTargetRootWindowIfNeeded(new_root_window); |
| } |
| void PartialMagnificationController::OnWidgetDestroying(views::Widget* widget) { |
| - DCHECK_EQ(widget, zoom_widget_); |
| + DCHECK_EQ(widget, host_widget_); |
| RemoveZoomWidgetObservers(); |
| - zoom_widget_ = NULL; |
| + host_widget_ = nullptr; |
| } |
| -void PartialMagnificationController::OnMouseMove( |
| - const gfx::Point& location_in_root) { |
| - gfx::Point origin(location_in_root); |
| - |
| - origin.Offset(-kMagnifierWidth / 2, -kMagnifierHeight / 2); |
| +void PartialMagnificationController::SetActive(bool active) { |
| + // Fail if we're trying to activate while disabled. |
| + DCHECK(is_enabled_ || !active); |
| - if (zoom_widget_) { |
| - zoom_widget_->SetBounds( |
| - gfx::Rect(origin.x(), origin.y(), kMagnifierWidth, kMagnifierHeight)); |
| + is_active_ = active; |
| + if (is_active_) { |
| + CreateMagnifierWindow(GetCurrentRootWindow()); |
| + } else { |
| + CloseMagnifierWindow(); |
| } |
| } |
| -bool PartialMagnificationController::IsPartialMagnified() const { |
| - return scale_ >= kMinPartialMagnifiedScaleThreshold; |
| -} |
| +void PartialMagnificationController::OnLocatedEvent( |
| + ui::LocatedEvent* event, |
| + const ui::PointerDetails& pointer_details) { |
| + if (!is_enabled_) |
| + return; |
| -void PartialMagnificationController::CreateMagnifierWindow() { |
| - if (zoom_widget_) |
| + if (pointer_details.pointer_type != ui::EventPointerType::POINTER_TYPE_PEN) |
| return; |
| - aura::Window* root_window = GetCurrentRootWindow(); |
| - if (!root_window) |
| + if (event->type() == ui::ET_MOUSE_PRESSED) |
| + 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; |
| + } |
| + |
| + gfx::Point point = event->root_location(); |
| + |
| + // Remap point from where it was captured to the display it is actually on. |
| + aura::Window* target = static_cast<aura::Window*>(event->target()); |
| + aura::Window* event_root = target->GetRootWindow(); |
| + aura::Window::ConvertPointToTarget( |
| + event_root, host_widget_->GetNativeView()->GetRootWindow(), &point); |
| + |
| + host_widget_->SetBounds(GetBounds(point)); |
| + |
| + event->StopPropagation(); |
| +} |
| + |
| +void PartialMagnificationController::CreateMagnifierWindow( |
| + aura::Window* root_window) { |
| + if (host_widget_ || !root_window) |
| return; |
| root_window->AddObserver(this); |
| @@ -137,68 +271,56 @@ void PartialMagnificationController::CreateMagnifierWindow() { |
| gfx::Point mouse( |
| root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()); |
| - zoom_widget_ = new views::Widget; |
| + 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; |
| - zoom_widget_->Init(params); |
| - zoom_widget_->SetBounds(gfx::Rect(mouse.x() - kMagnifierWidth / 2, |
| - mouse.y() - kMagnifierHeight / 2, |
| - kMagnifierWidth, kMagnifierHeight)); |
| - zoom_widget_->set_focus_on_creation(false); |
| - zoom_widget_->Show(); |
| - |
| - aura::Window* window = zoom_widget_->GetNativeView(); |
| + host_widget_->Init(params); |
| + host_widget_->set_focus_on_creation(false); |
| + host_widget_->Show(); |
| + |
| + aura::Window* window = host_widget_->GetNativeView(); |
| window->SetName(kPartialMagniferWindowName); |
| - zoom_widget_->GetNativeView()->layer()->SetBounds( |
| - gfx::Rect(0, 0, kMagnifierWidth, kMagnifierHeight)); |
| - zoom_widget_->GetNativeView()->layer()->SetBackgroundZoom(scale_, kZoomInset); |
| + 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_TEXTURED)); |
| + border_layer_->SetBounds(gfx::Rect(GetWindowSize())); |
| + border_layer_->set_delegate(new BorderRenderer(gfx::Rect(GetWindowSize()))); |
| + root_layer->Add(border_layer_.get()); |
| - zoom_widget_->AddObserver(this); |
| + 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 (zoom_widget_) { |
| + if (host_widget_) { |
| RemoveZoomWidgetObservers(); |
| - zoom_widget_->Close(); |
| - zoom_widget_ = NULL; |
| + host_widget_->Close(); |
| + host_widget_ = nullptr; |
| } |
| } |
| void PartialMagnificationController::RemoveZoomWidgetObservers() { |
| - DCHECK(zoom_widget_); |
| - zoom_widget_->RemoveObserver(this); |
| - aura::Window* root_window = zoom_widget_->GetNativeView()->GetRootWindow(); |
| + DCHECK(host_widget_); |
| + host_widget_->RemoveObserver(this); |
| + aura::Window* root_window = host_widget_->GetNativeView()->GetRootWindow(); |
| DCHECK(root_window); |
| root_window->RemoveObserver(this); |
| } |
| -void PartialMagnificationController::SwitchTargetRootWindow( |
| - aura::Window* new_root_window) { |
| - if (zoom_widget_ && |
| - new_root_window == zoom_widget_->GetNativeView()->GetRootWindow()) |
| - return; |
| - |
| - CloseMagnifierWindow(); |
| - |
| - // Recreate the magnifier window by updating the scale factor. |
| - SetScale(GetScale()); |
| -} |
| - |
| -aura::Window* PartialMagnificationController::GetCurrentRootWindow() { |
| - aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| - for (aura::Window::Windows::const_iterator iter = root_windows.begin(); |
| - iter != root_windows.end(); ++iter) { |
| - aura::Window* root_window = *iter; |
| - if (root_window->ContainsPointInRoot( |
| - root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot())) |
| - return root_window; |
| - } |
| - return NULL; |
| -} |
| - |
| } // namespace ash |