blob: 99993cfadedd44bdcab5c950f297a48d9ee11958 [file] [log] [blame]
[email protected]77f7c132012-11-15 06:52:541// 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.
4
5#include "ash/magnifier/partial_magnification_controller.h"
6
7#include "ash/shell.h"
James Cooka35a1e22017-04-08 02:33:088#include "ash/system/palette/palette_utils.h"
sammiequon8de2aed72016-10-05 21:57:079#include "third_party/skia/include/core/SkDrawLooper.h"
[email protected]7a60cd3a2014-03-20 20:54:5710#include "ui/aura/window_event_dispatcher.h"
[email protected]7a60cd3a2014-03-20 20:54:5711#include "ui/aura/window_tree_host.h"
[email protected]77f7c132012-11-15 06:52:5412#include "ui/compositor/layer.h"
jdufault62b8b6e2016-08-24 20:34:5313#include "ui/compositor/paint_recorder.h"
14#include "ui/events/event.h"
15#include "ui/events/event_constants.h"
estadec6686c52017-01-05 21:31:1916#include "ui/gfx/shadow_value.h"
enneeb8c638a2017-01-31 23:29:5617#include "ui/gfx/skia_paint_util.h"
[email protected]77f7c132012-11-15 06:52:5418#include "ui/views/widget/widget.h"
jdufault62b8b6e2016-08-24 20:34:5319#include "ui/wm/core/coordinate_conversion.h"
[email protected]77f7c132012-11-15 06:52:5420
jdufault62b8b6e2016-08-24 20:34:5321namespace ash {
[email protected]77f7c132012-11-15 06:52:5422namespace {
23
jdufault62b8b6e2016-08-24 20:34:5324// Ratio of magnifier scale.
25const float kMagnificationScale = 2.f;
sammiequon8de2aed72016-10-05 21:57:0726// Radius of the magnifying glass in DIP. This does not include the thickness
27// of the magnifying glass shadow and border.
28const int kMagnifierRadius = 188;
jdufault62b8b6e2016-08-24 20:34:5329// Size of the border around the magnifying glass in DIP.
30const int kBorderSize = 10;
sammiequon90419212016-09-16 17:25:1631// Thickness of the outline around magnifying glass border in DIP.
sammiequon8de2aed72016-10-05 21:57:0732const int kBorderOutlineThickness = 1;
33// Thickness of the shadow around the magnifying glass in DIP.
34const int kShadowThickness = 24;
35// Offset of the shadow around the magnifying glass in DIP. One of the shadows
36// is lowered a bit, so we have to include |kShadowOffset| in our calculations
37// to compensate.
38const int kShadowOffset = 24;
sammiequon90419212016-09-16 17:25:1639// The color of the border and its outlines. The border has an outline on both
40// sides, producing a black/white/black ring.
sammiequon8de2aed72016-10-05 21:57:0741const SkColor kBorderColor = SkColorSetARGB(204, 255, 255, 255);
42const SkColor kBorderOutlineColor = SkColorSetARGB(51, 0, 0, 0);
43// The colors of the two shadow around the magnifiying glass.
44const SkColor kTopShadowColor = SkColorSetARGB(26, 0, 0, 0);
45const SkColor kBottomShadowColor = SkColorSetARGB(61, 0, 0, 0);
jdufault62b8b6e2016-08-24 20:34:5346// Inset on the zoom filter.
47const int kZoomInset = 0;
48// Vertical offset between the center of the magnifier and the tip of the
49// pointer. TODO(jdufault): The vertical offset should only apply to the window
50// location, not the magnified contents. See crbug.com/637617.
51const int kVerticalOffset = 0;
[email protected]77f7c132012-11-15 06:52:5452
53// Name of the magnifier window.
54const char kPartialMagniferWindowName[] = "PartialMagnifierWindow";
55
jdufault62b8b6e2016-08-24 20:34:5356gfx::Size GetWindowSize() {
sammiequon8de2aed72016-10-05 21:57:0757 // The diameter of the window is the diameter of the magnifier, border and
58 // shadow combined. We apply |kShadowOffset| on all sides even though the
59 // shadow is only thicker on the bottom so as to keep the circle centered in
60 // the view and keep calculations (border rendering and content masking)
61 // simpler.
62 int window_diameter =
63 (kMagnifierRadius + kBorderSize + kShadowThickness + kShadowOffset) * 2;
64 return gfx::Size(window_diameter, window_diameter);
jdufault62b8b6e2016-08-24 20:34:5365}
66
67gfx::Rect GetBounds(gfx::Point mouse) {
68 gfx::Size size = GetWindowSize();
69 gfx::Point origin(mouse.x() - (size.width() / 2),
70 mouse.y() - (size.height() / 2) - kVerticalOffset);
71 return gfx::Rect(origin, size);
72}
73
74aura::Window* GetCurrentRootWindow() {
75 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
76 for (aura::Window* root_window : root_windows) {
77 if (root_window->ContainsPointInRoot(
78 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()))
79 return root_window;
80 }
81 return nullptr;
82}
83
[email protected]77f7c132012-11-15 06:52:5484} // namespace
85
jdufault62b8b6e2016-08-24 20:34:5386// The content mask provides a clipping layer for the magnification window so we
87// can show a circular magnifier.
88class PartialMagnificationController::ContentMask : public ui::LayerDelegate {
89 public:
sammiequon8de2aed72016-10-05 21:57:0790 // If |is_border| is true, the circle will be a stroke. This is useful if we
91 // wish to clip a border.
92 ContentMask(bool is_border, gfx::Size mask_bounds)
93 : layer_(ui::LAYER_TEXTURED), is_border_(is_border) {
jdufault62b8b6e2016-08-24 20:34:5394 layer_.set_delegate(this);
95 layer_.SetFillsBoundsOpaquely(false);
96 layer_.SetBounds(gfx::Rect(mask_bounds));
97 }
[email protected]77f7c132012-11-15 06:52:5498
jdufault62b8b6e2016-08-24 20:34:5399 ~ContentMask() override { layer_.set_delegate(nullptr); }
100
101 ui::Layer* layer() { return &layer_; }
102
103 private:
sammiequon90419212016-09-16 17:25:16104 // ui::LayerDelegate:
jdufault62b8b6e2016-08-24 20:34:53105 void OnPaintLayer(const ui::PaintContext& context) override {
106 ui::PaintRecorder recorder(context, layer()->size());
107
enne00bd4b72017-02-08 01:38:14108 cc::PaintFlags flags;
109 flags.setAlpha(255);
110 flags.setAntiAlias(true);
sammiequon8de2aed72016-10-05 21:57:07111 // Stroke is used for clipping the border which consists of the rendered
112 // border |kBorderSize| and the magnifier shadow |kShadowThickness| and
113 // |kShadowOffset|.
enne00bd4b72017-02-08 01:38:14114 flags.setStrokeWidth(kBorderSize + kShadowThickness + kShadowOffset);
115 flags.setStyle(is_border_ ? cc::PaintFlags::kStroke_Style
enne34f6084c2017-02-02 22:39:08116 : cc::PaintFlags::kFill_Style);
jdufault62b8b6e2016-08-24 20:34:53117
sammiequon8de2aed72016-10-05 21:57:07118 // If we want to clip the magnifier zone use the magnifiers radius.
119 // Otherwise we want to clip the border, shadow and shadow offset so we
120 // start
121 // at the halfway point of the stroke width.
jdufault62b8b6e2016-08-24 20:34:53122 gfx::Rect rect(layer()->bounds().size());
sammiequon8de2aed72016-10-05 21:57:07123 int clipping_radius = kMagnifierRadius;
124 if (is_border_)
125 clipping_radius += (kShadowThickness + kShadowOffset + kBorderSize) / 2;
enne00bd4b72017-02-08 01:38:14126 recorder.canvas()->DrawCircle(rect.CenterPoint(), clipping_radius, flags);
jdufault62b8b6e2016-08-24 20:34:53127 }
128
129 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
130
131 void OnDeviceScaleFactorChanged(float device_scale_factor) override {
132 // Redrawing will take care of scale factor change.
133 }
134
jdufault62b8b6e2016-08-24 20:34:53135 ui::Layer layer_;
sammiequon8de2aed72016-10-05 21:57:07136 bool is_border_;
jdufault62b8b6e2016-08-24 20:34:53137 DISALLOW_COPY_AND_ASSIGN(ContentMask);
138};
139
sammiequon90419212016-09-16 17:25:16140// The border renderer draws the border as well as outline on both the outer and
sammiequon8de2aed72016-10-05 21:57:07141// inner radius to increase visibility. The border renderer also handles drawing
142// the shadow.
sammiequon90419212016-09-16 17:25:16143class PartialMagnificationController::BorderRenderer
144 : public ui::LayerDelegate {
145 public:
sammiequon8de2aed72016-10-05 21:57:07146 explicit BorderRenderer(const gfx::Rect& window_bounds)
147 : magnifier_window_bounds_(window_bounds) {
148 magnifier_shadows_.push_back(gfx::ShadowValue(
149 gfx::Vector2d(0, kShadowOffset), kShadowThickness, kBottomShadowColor));
150 magnifier_shadows_.push_back(gfx::ShadowValue(
151 gfx::Vector2d(0, 0), kShadowThickness, kTopShadowColor));
152 }
sammiequon90419212016-09-16 17:25:16153
154 ~BorderRenderer() override {}
155
156 private:
157 // ui::LayerDelegate:
158 void OnPaintLayer(const ui::PaintContext& context) override {
sammiequon8de2aed72016-10-05 21:57:07159 ui::PaintRecorder recorder(context, magnifier_window_bounds_.size());
sammiequon90419212016-09-16 17:25:16160
sammiequon8de2aed72016-10-05 21:57:07161 // Draw the shadow.
enne00bd4b72017-02-08 01:38:14162 cc::PaintFlags shadow_flags;
163 shadow_flags.setAntiAlias(true);
164 shadow_flags.setColor(SK_ColorTRANSPARENT);
estade9ef3c962017-03-09 05:16:59165 shadow_flags.setLooper(gfx::CreateShadowDrawLooper(magnifier_shadows_));
sammiequon8de2aed72016-10-05 21:57:07166 gfx::Rect shadow_bounds(magnifier_window_bounds_.size());
167 recorder.canvas()->DrawCircle(
168 shadow_bounds.CenterPoint(),
169 shadow_bounds.width() / 2 - kShadowThickness - kShadowOffset,
enne00bd4b72017-02-08 01:38:14170 shadow_flags);
sammiequon90419212016-09-16 17:25:16171
enne00bd4b72017-02-08 01:38:14172 cc::PaintFlags border_flags;
173 border_flags.setAntiAlias(true);
174 border_flags.setStyle(cc::PaintFlags::kStroke_Style);
sammiequon8de2aed72016-10-05 21:57:07175
176 // The radius of the magnifier and its border.
177 const int magnifier_radius = kMagnifierRadius + kBorderSize;
178
sammiequon90419212016-09-16 17:25:16179 // Draw the inner border.
enne00bd4b72017-02-08 01:38:14180 border_flags.setStrokeWidth(kBorderSize);
181 border_flags.setColor(kBorderColor);
sammiequon8de2aed72016-10-05 21:57:07182 recorder.canvas()->DrawCircle(magnifier_window_bounds_.CenterPoint(),
183 magnifier_radius - kBorderSize / 2,
enne00bd4b72017-02-08 01:38:14184 border_flags);
sammiequon90419212016-09-16 17:25:16185
186 // Draw border outer outline and then draw the border inner outline.
enne00bd4b72017-02-08 01:38:14187 border_flags.setStrokeWidth(kBorderOutlineThickness);
188 border_flags.setColor(kBorderOutlineColor);
sammiequon90419212016-09-16 17:25:16189 recorder.canvas()->DrawCircle(
sammiequon8de2aed72016-10-05 21:57:07190 magnifier_window_bounds_.CenterPoint(),
enne00bd4b72017-02-08 01:38:14191 magnifier_radius - kBorderOutlineThickness / 2, border_flags);
sammiequon90419212016-09-16 17:25:16192 recorder.canvas()->DrawCircle(
sammiequon8de2aed72016-10-05 21:57:07193 magnifier_window_bounds_.CenterPoint(),
194 magnifier_radius - kBorderSize + kBorderOutlineThickness / 2,
enne00bd4b72017-02-08 01:38:14195 border_flags);
sammiequon90419212016-09-16 17:25:16196 }
197
198 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
199
200 void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
201
sammiequon8de2aed72016-10-05 21:57:07202 gfx::Rect magnifier_window_bounds_;
203 std::vector<gfx::ShadowValue> magnifier_shadows_;
sammiequon90419212016-09-16 17:25:16204
205 DISALLOW_COPY_AND_ASSIGN(BorderRenderer);
206};
207
jdufault62b8b6e2016-08-24 20:34:53208PartialMagnificationController::PartialMagnificationController() {
skycb4be5b2017-04-06 17:52:45209 Shell::Get()->AddPreTargetHandler(this);
[email protected]77f7c132012-11-15 06:52:54210}
211
212PartialMagnificationController::~PartialMagnificationController() {
213 CloseMagnifierWindow();
214
skycb4be5b2017-04-06 17:52:45215 Shell::Get()->RemovePreTargetHandler(this);
[email protected]77f7c132012-11-15 06:52:54216}
217
jdufault62b8b6e2016-08-24 20:34:53218void PartialMagnificationController::SetEnabled(bool enabled) {
219 is_enabled_ = enabled;
220 SetActive(false);
221}
222
223void PartialMagnificationController::SwitchTargetRootWindowIfNeeded(
224 aura::Window* new_root_window) {
225 if (host_widget_ &&
226 new_root_window == host_widget_->GetNativeView()->GetRootWindow())
[email protected]77f7c132012-11-15 06:52:54227 return;
228
jdufault62b8b6e2016-08-24 20:34:53229 if (!new_root_window)
230 new_root_window = GetCurrentRootWindow();
[email protected]77f7c132012-11-15 06:52:54231
jdufault62b8b6e2016-08-24 20:34:53232 if (is_enabled_ && is_active_) {
[email protected]77f7c132012-11-15 06:52:54233 CloseMagnifierWindow();
jdufault62b8b6e2016-08-24 20:34:53234 CreateMagnifierWindow(new_root_window);
[email protected]77f7c132012-11-15 06:52:54235 }
236}
237
jdufault62b8b6e2016-08-24 20:34:53238void PartialMagnificationController::OnTouchEvent(ui::TouchEvent* event) {
239 OnLocatedEvent(event, event->pointer_details());
240}
[email protected]77f7c132012-11-15 06:52:54241
jamescookb8dcef522016-06-25 14:42:55242void PartialMagnificationController::OnWindowDestroying(aura::Window* window) {
[email protected]77f7c132012-11-15 06:52:54243 CloseMagnifierWindow();
244
[email protected]c9390bd2013-11-08 20:33:13245 aura::Window* new_root_window = GetCurrentRootWindow();
[email protected]77f7c132012-11-15 06:52:54246 if (new_root_window != window)
jdufault62b8b6e2016-08-24 20:34:53247 SwitchTargetRootWindowIfNeeded(new_root_window);
[email protected]77f7c132012-11-15 06:52:54248}
249
jamescookb8dcef522016-06-25 14:42:55250void PartialMagnificationController::OnWidgetDestroying(views::Widget* widget) {
jdufault62b8b6e2016-08-24 20:34:53251 DCHECK_EQ(widget, host_widget_);
[email protected]77f7c132012-11-15 06:52:54252 RemoveZoomWidgetObservers();
jdufault62b8b6e2016-08-24 20:34:53253 host_widget_ = nullptr;
[email protected]77f7c132012-11-15 06:52:54254}
255
jdufault62b8b6e2016-08-24 20:34:53256void PartialMagnificationController::SetActive(bool active) {
257 // Fail if we're trying to activate while disabled.
258 DCHECK(is_enabled_ || !active);
[email protected]77f7c132012-11-15 06:52:54259
jdufault62b8b6e2016-08-24 20:34:53260 is_active_ = active;
261 if (is_active_) {
262 CreateMagnifierWindow(GetCurrentRootWindow());
263 } else {
264 CloseMagnifierWindow();
[email protected]77f7c132012-11-15 06:52:54265 }
266}
267
jdufault62b8b6e2016-08-24 20:34:53268void PartialMagnificationController::OnLocatedEvent(
269 ui::LocatedEvent* event,
270 const ui::PointerDetails& pointer_details) {
271 if (!is_enabled_)
[email protected]77f7c132012-11-15 06:52:54272 return;
273
jdufault62b8b6e2016-08-24 20:34:53274 if (pointer_details.pointer_type != ui::EventPointerType::POINTER_TYPE_PEN)
275 return;
276
jdufaultd8d49912016-09-08 00:03:03277 // Compute the event location in screen space.
278 aura::Window* target = static_cast<aura::Window*>(event->target());
279 aura::Window* event_root = target->GetRootWindow();
280 gfx::Point screen_point = event->root_location();
281 wm::ConvertPointToScreen(event_root, &screen_point);
282
sammiequon203ae022016-09-18 17:23:42283 // If the stylus is pressed on the palette icon or widget, do not activate.
jdufault1df36f32016-12-05 20:47:02284 if (event->type() == ui::ET_TOUCH_PRESSED &&
jdufaultd08c50b2017-02-08 02:14:22285 !palette_utils::PaletteContainsPointInScreen(screen_point)) {
jdufault62b8b6e2016-08-24 20:34:53286 SetActive(true);
jdufaultd8d49912016-09-08 00:03:03287 }
jdufault62b8b6e2016-08-24 20:34:53288
jdufault1df36f32016-12-05 20:47:02289 if (event->type() == ui::ET_TOUCH_RELEASED)
jdufault62b8b6e2016-08-24 20:34:53290 SetActive(false);
291
292 if (!is_active_)
293 return;
294
295 // If the previous root window was detached host_widget_ will be null;
296 // reconstruct it. We also need to change the root window if the cursor has
297 // crossed display boundries.
298 SwitchTargetRootWindowIfNeeded(GetCurrentRootWindow());
299
300 // If that failed for any reason return.
301 if (!host_widget_) {
302 SetActive(false);
303 return;
304 }
305
jdufault62b8b6e2016-08-24 20:34:53306 // Remap point from where it was captured to the display it is actually on.
jdufaultd8d49912016-09-08 00:03:03307 gfx::Point point = event->root_location();
jdufault62b8b6e2016-08-24 20:34:53308 aura::Window::ConvertPointToTarget(
309 event_root, host_widget_->GetNativeView()->GetRootWindow(), &point);
jdufault62b8b6e2016-08-24 20:34:53310 host_widget_->SetBounds(GetBounds(point));
311
sammiequon203ae022016-09-18 17:23:42312 // If the stylus is over the palette icon or widget, do not consume the event.
jdufaultd08c50b2017-02-08 02:14:22313 if (!palette_utils::PaletteContainsPointInScreen(screen_point))
jdufaultd8d49912016-09-08 00:03:03314 event->StopPropagation();
jdufault62b8b6e2016-08-24 20:34:53315}
316
317void PartialMagnificationController::CreateMagnifierWindow(
318 aura::Window* root_window) {
319 if (host_widget_ || !root_window)
[email protected]77f7c132012-11-15 06:52:54320 return;
321
322 root_window->AddObserver(this);
323
[email protected]2374d1812014-03-04 03:42:27324 gfx::Point mouse(
325 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
[email protected]77f7c132012-11-15 06:52:54326
jdufault62b8b6e2016-08-24 20:34:53327 host_widget_ = new views::Widget;
[email protected]77f7c132012-11-15 06:52:54328 views::Widget::InitParams params(
329 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
[email protected]4f254232014-05-19 22:59:07330 params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
[email protected]77f7c132012-11-15 06:52:54331 params.accept_events = false;
jdufault62b8b6e2016-08-24 20:34:53332 params.bounds = GetBounds(mouse);
[email protected]28fc4732013-06-27 12:15:09333 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
[email protected]77f7c132012-11-15 06:52:54334 params.parent = root_window;
jdufault62b8b6e2016-08-24 20:34:53335 host_widget_->Init(params);
336 host_widget_->set_focus_on_creation(false);
337 host_widget_->Show();
[email protected]77f7c132012-11-15 06:52:54338
jdufault62b8b6e2016-08-24 20:34:53339 aura::Window* window = host_widget_->GetNativeView();
[email protected]77f7c132012-11-15 06:52:54340 window->SetName(kPartialMagniferWindowName);
341
jdufault62b8b6e2016-08-24 20:34:53342 ui::Layer* root_layer = host_widget_->GetNativeView()->layer();
[email protected]77f7c132012-11-15 06:52:54343
jdufault62b8b6e2016-08-24 20:34:53344 zoom_layer_.reset(new ui::Layer(ui::LayerType::LAYER_SOLID_COLOR));
345 zoom_layer_->SetBounds(gfx::Rect(GetWindowSize()));
346 zoom_layer_->SetBackgroundZoom(kMagnificationScale, kZoomInset);
347 root_layer->Add(zoom_layer_.get());
348
sammiequon90419212016-09-16 17:25:16349 border_layer_.reset(new ui::Layer(ui::LayerType::LAYER_TEXTURED));
jdufault62b8b6e2016-08-24 20:34:53350 border_layer_->SetBounds(gfx::Rect(GetWindowSize()));
sammiequon90419212016-09-16 17:25:16351 border_renderer_.reset(new BorderRenderer(gfx::Rect(GetWindowSize())));
352 border_layer_->set_delegate(border_renderer_.get());
sammiequon8de2aed72016-10-05 21:57:07353 border_layer_->SetFillsBoundsOpaquely(false);
jdufault62b8b6e2016-08-24 20:34:53354 root_layer->Add(border_layer_.get());
355
356 border_mask_.reset(new ContentMask(true, GetWindowSize()));
357 border_layer_->SetMaskLayer(border_mask_->layer());
358
359 zoom_mask_.reset(new ContentMask(false, GetWindowSize()));
360 zoom_layer_->SetMaskLayer(zoom_mask_->layer());
361
362 host_widget_->AddObserver(this);
[email protected]77f7c132012-11-15 06:52:54363}
364
365void PartialMagnificationController::CloseMagnifierWindow() {
jdufault62b8b6e2016-08-24 20:34:53366 if (host_widget_) {
[email protected]77f7c132012-11-15 06:52:54367 RemoveZoomWidgetObservers();
jdufault62b8b6e2016-08-24 20:34:53368 host_widget_->Close();
369 host_widget_ = nullptr;
[email protected]77f7c132012-11-15 06:52:54370 }
371}
372
373void PartialMagnificationController::RemoveZoomWidgetObservers() {
jdufault62b8b6e2016-08-24 20:34:53374 DCHECK(host_widget_);
375 host_widget_->RemoveObserver(this);
376 aura::Window* root_window = host_widget_->GetNativeView()->GetRootWindow();
[email protected]77f7c132012-11-15 06:52:54377 DCHECK(root_window);
378 root_window->RemoveObserver(this);
379}
380
[email protected]77f7c132012-11-15 06:52:54381} // namespace ash