OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ash/magnifier/partial_magnification_controller.h" | 5 #include "ash/magnifier/partial_magnification_controller.h" |
6 | 6 |
7 #include "ash/common/shell_window_ids.h" | |
8 #include "ash/shell.h" | 7 #include "ash/shell.h" |
9 #include "ui/aura/window.h" | |
10 #include "ui/aura/window_event_dispatcher.h" | 8 #include "ui/aura/window_event_dispatcher.h" |
11 #include "ui/aura/window_property.h" | |
12 #include "ui/aura/window_tree_host.h" | 9 #include "ui/aura/window_tree_host.h" |
13 #include "ui/compositor/layer.h" | 10 #include "ui/compositor/layer.h" |
14 #include "ui/views/layout/fill_layout.h" | 11 #include "ui/compositor/paint_recorder.h" |
12 #include "ui/events/event.h" | |
13 #include "ui/events/event_constants.h" | |
15 #include "ui/views/widget/widget.h" | 14 #include "ui/views/widget/widget.h" |
16 #include "ui/views/widget/widget_delegate.h" | 15 #include "ui/wm/core/coordinate_conversion.h" |
17 #include "ui/wm/core/compound_event_filter.h" | |
18 | 16 |
17 namespace ash { | |
19 namespace { | 18 namespace { |
20 | 19 |
21 const float kMinPartialMagnifiedScaleThreshold = 1.1f; | 20 // Ratio of magnifier scale. |
22 | 21 const float kMagnificationScale = 2.f; |
23 // Number of pixels to make the border of the magnified area. | 22 // Radius of the magnifying glass in DIP. |
24 const int kZoomInset = 16; | 23 const int kMagnifierRadius = 200; |
25 | 24 // Size of the border around the magnifying glass in DIP. |
26 // Width of the magnified area. | 25 const int kBorderSize = 10; |
27 const int kMagnifierWidth = 200; | 26 // Thickness of the outline around magnifiying glass border. |
28 | 27 const int kBorderOutlineThickness = 2; |
29 // Height of the magnified area. | 28 const SkColor kBorderColor = SK_ColorWHITE; |
30 const int kMagnifierHeight = 200; | 29 const SkColor kBorderOutlineColor = SK_ColorBLACK; |
30 // Inset on the zoom filter. | |
31 const int kZoomInset = 0; | |
32 // Vertical offset between the center of the magnifier and the tip of the | |
33 // pointer. TODO(jdufault): The vertical offset should only apply to the window | |
34 // location, not the magnified contents. See crbug.com/637617. | |
35 const int kVerticalOffset = 0; | |
31 | 36 |
32 // Name of the magnifier window. | 37 // Name of the magnifier window. |
33 const char kPartialMagniferWindowName[] = "PartialMagnifierWindow"; | 38 const char kPartialMagniferWindowName[] = "PartialMagnifierWindow"; |
34 | 39 |
40 gfx::Size GetWindowSize() { | |
41 return gfx::Size(kMagnifierRadius * 2, kMagnifierRadius * 2); | |
42 } | |
43 | |
44 gfx::Rect GetBounds(gfx::Point mouse) { | |
45 gfx::Size size = GetWindowSize(); | |
46 gfx::Point origin(mouse.x() - (size.width() / 2), | |
47 mouse.y() - (size.height() / 2) - kVerticalOffset); | |
48 return gfx::Rect(origin, size); | |
49 } | |
50 | |
51 aura::Window* GetCurrentRootWindow() { | |
52 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); | |
53 for (aura::Window* root_window : root_windows) { | |
54 if (root_window->ContainsPointInRoot( | |
55 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot())) | |
56 return root_window; | |
57 } | |
58 return nullptr; | |
59 } | |
60 | |
35 } // namespace | 61 } // namespace |
36 | 62 |
37 namespace ash { | 63 // The content mask provides a clipping layer for the magnification window so we |
64 // can show a circular magnifier. | |
65 class PartialMagnificationController::ContentMask : public ui::LayerDelegate { | |
66 public: | |
67 // If |stroke| is true, the circle will be a stroke. This is useful if we wish | |
68 // to clip a border. | |
69 ContentMask(bool stroke, gfx::Size mask_bounds) | |
70 : layer_(ui::LAYER_TEXTURED), stroke_(stroke) { | |
71 layer_.set_delegate(this); | |
72 layer_.SetFillsBoundsOpaquely(false); | |
73 layer_.SetBounds(gfx::Rect(mask_bounds)); | |
74 } | |
38 | 75 |
39 PartialMagnificationController::PartialMagnificationController() | 76 ~ContentMask() override { layer_.set_delegate(nullptr); } |
40 : is_enabled_(false), | 77 |
41 scale_(kNonPartialMagnifiedScale), | 78 ui::Layer* layer() { return &layer_; } |
42 zoom_widget_(NULL) { | 79 |
80 private: | |
81 // Overridden from LayerDelegate. | |
82 void OnPaintLayer(const ui::PaintContext& context) override { | |
83 ui::PaintRecorder recorder(context, layer()->size()); | |
84 | |
85 SkPaint paint; | |
86 paint.setAlpha(255); | |
87 paint.setAntiAlias(true); | |
88 paint.setStrokeWidth(kBorderSize); | |
89 paint.setStyle(stroke_ ? SkPaint::kStroke_Style : SkPaint::kFill_Style); | |
90 | |
91 gfx::Rect rect(layer()->bounds().size()); | |
92 recorder.canvas()->DrawCircle(rect.CenterPoint(), | |
93 rect.width() / 2 - kBorderSize / 2, paint); | |
94 } | |
95 | |
96 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {} | |
97 | |
98 void OnDeviceScaleFactorChanged(float device_scale_factor) override { | |
99 // Redrawing will take care of scale factor change. | |
100 } | |
101 | |
102 base::Closure PrepareForLayerBoundsChange() override { | |
103 return base::Closure(); | |
104 } | |
105 | |
106 ui::Layer layer_; | |
107 bool stroke_; | |
108 | |
109 DISALLOW_COPY_AND_ASSIGN(ContentMask); | |
110 }; | |
111 | |
112 // The border render draws the border as well as outline on both the outer and | |
113 // inner radius to increase visibility. | |
114 class PartialMagnificationController::BorderRenderer | |
115 : public ui::LayerDelegate { | |
116 public: | |
117 BorderRenderer(const gfx::Rect& bounds) { bounds_ = bounds; } | |
118 | |
119 ~BorderRenderer() override {} | |
120 | |
121 private: | |
122 // Overridden from LayerDelegate. | |
123 void OnPaintLayer(const ui::PaintContext& context) override { | |
124 ui::PaintRecorder recorder(context, bounds_.size()); | |
125 | |
126 // Draw the border. | |
127 SkPaint paint; | |
128 paint.setAntiAlias(true); | |
129 paint.setStrokeWidth(kBorderSize); | |
130 paint.setStyle(SkPaint::kStroke_Style); | |
131 paint.setColor(kBorderColor); | |
132 recorder.canvas()->DrawCircle(bounds_.CenterPoint(), | |
133 bounds_.width() / 2 - kBorderSize / 2, paint); | |
134 | |
135 // Draw the border outlines. | |
136 paint.setStrokeWidth(kBorderOutlineThickness); | |
137 paint.setColor(kBorderOutlineColor); | |
138 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.
| |
139 bounds_.CenterPoint(), | |
140 bounds_.width() / 2 - kBorderOutlineThickness / 2, paint); | |
141 recorder.canvas()->DrawCircle( | |
142 bounds_.CenterPoint(), | |
143 bounds_.width() / 2 - kBorderSize + kBorderOutlineThickness / 2, paint); | |
144 } | |
145 | |
146 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {} | |
147 | |
148 void OnDeviceScaleFactorChanged(float device_scale_factor) override {} | |
149 | |
150 base::Closure PrepareForLayerBoundsChange() override { | |
151 return base::Closure(); | |
152 } | |
153 gfx::Rect bounds_; | |
154 | |
155 DISALLOW_COPY_AND_ASSIGN(BorderRenderer); | |
156 }; | |
157 | |
158 PartialMagnificationController::PartialMagnificationController() { | |
43 Shell::GetInstance()->AddPreTargetHandler(this); | 159 Shell::GetInstance()->AddPreTargetHandler(this); |
44 } | 160 } |
45 | 161 |
46 PartialMagnificationController::~PartialMagnificationController() { | 162 PartialMagnificationController::~PartialMagnificationController() { |
47 CloseMagnifierWindow(); | 163 CloseMagnifierWindow(); |
48 | 164 |
49 Shell::GetInstance()->RemovePreTargetHandler(this); | 165 Shell::GetInstance()->RemovePreTargetHandler(this); |
50 } | 166 } |
51 | 167 |
52 void PartialMagnificationController::SetScale(float scale) { | 168 void PartialMagnificationController::SetEnabled(bool enabled) { |
53 if (!is_enabled_) | 169 is_enabled_ = enabled; |
170 SetActive(false); | |
171 } | |
172 | |
173 void PartialMagnificationController::SwitchTargetRootWindowIfNeeded( | |
174 aura::Window* new_root_window) { | |
175 if (host_widget_ && | |
176 new_root_window == host_widget_->GetNativeView()->GetRootWindow()) | |
54 return; | 177 return; |
55 | 178 |
56 scale_ = scale; | 179 if (!new_root_window) |
180 new_root_window = GetCurrentRootWindow(); | |
57 | 181 |
58 if (IsPartialMagnified()) { | 182 if (is_enabled_ && is_active_) { |
59 CreateMagnifierWindow(); | |
60 } else { | |
61 CloseMagnifierWindow(); | 183 CloseMagnifierWindow(); |
184 CreateMagnifierWindow(new_root_window); | |
62 } | 185 } |
63 } | 186 } |
64 | 187 |
65 void PartialMagnificationController::SetEnabled(bool enabled) { | 188 void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) { |
66 if (enabled) { | 189 OnLocatedEvent(event, event->pointer_details()); |
67 is_enabled_ = enabled; | |
68 SetScale(kDefaultPartialMagnifiedScale); | |
69 } else { | |
70 SetScale(kNonPartialMagnifiedScale); | |
71 is_enabled_ = enabled; | |
72 } | |
73 } | 190 } |
74 | 191 |
75 //////////////////////////////////////////////////////////////////////////////// | 192 void PartialMagnificationController::OnTouchEvent(ui::TouchEvent* event) { |
76 // PartialMagnificationController: ui::EventHandler implementation | 193 OnLocatedEvent(event, event->pointer_details()); |
77 | |
78 void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) { | |
79 if (IsPartialMagnified() && event->type() == ui::ET_MOUSE_MOVED) { | |
80 aura::Window* target = static_cast<aura::Window*>(event->target()); | |
81 aura::Window* current_root = target->GetRootWindow(); | |
82 // TODO(zork): Handle the case where the event is captured on a different | |
83 // display, such as when a menu is opened. | |
84 gfx::Rect root_bounds = current_root->bounds(); | |
85 | |
86 if (root_bounds.Contains(event->root_location())) { | |
87 SwitchTargetRootWindow(current_root); | |
88 | |
89 OnMouseMove(event->root_location()); | |
90 } | |
91 } | |
92 } | 194 } |
93 | 195 |
94 //////////////////////////////////////////////////////////////////////////////// | |
95 // PartialMagnificationController: aura::WindowObserver implementation | |
96 | |
97 void PartialMagnificationController::OnWindowDestroying(aura::Window* window) { | 196 void PartialMagnificationController::OnWindowDestroying(aura::Window* window) { |
98 CloseMagnifierWindow(); | 197 CloseMagnifierWindow(); |
99 | 198 |
100 aura::Window* new_root_window = GetCurrentRootWindow(); | 199 aura::Window* new_root_window = GetCurrentRootWindow(); |
101 if (new_root_window != window) | 200 if (new_root_window != window) |
102 SwitchTargetRootWindow(new_root_window); | 201 SwitchTargetRootWindowIfNeeded(new_root_window); |
103 } | 202 } |
104 | 203 |
105 void PartialMagnificationController::OnWidgetDestroying(views::Widget* widget) { | 204 void PartialMagnificationController::OnWidgetDestroying(views::Widget* widget) { |
106 DCHECK_EQ(widget, zoom_widget_); | 205 DCHECK_EQ(widget, host_widget_); |
107 RemoveZoomWidgetObservers(); | 206 RemoveZoomWidgetObservers(); |
108 zoom_widget_ = NULL; | 207 host_widget_ = nullptr; |
109 } | 208 } |
110 | 209 |
111 void PartialMagnificationController::OnMouseMove( | 210 void PartialMagnificationController::SetActive(bool active) { |
112 const gfx::Point& location_in_root) { | 211 // Fail if we're trying to activate while disabled. |
113 gfx::Point origin(location_in_root); | 212 DCHECK(is_enabled_ || !active); |
114 | 213 |
115 origin.Offset(-kMagnifierWidth / 2, -kMagnifierHeight / 2); | 214 is_active_ = active; |
116 | 215 if (is_active_) { |
117 if (zoom_widget_) { | 216 CreateMagnifierWindow(GetCurrentRootWindow()); |
118 zoom_widget_->SetBounds( | 217 } else { |
119 gfx::Rect(origin.x(), origin.y(), kMagnifierWidth, kMagnifierHeight)); | 218 CloseMagnifierWindow(); |
120 } | 219 } |
121 } | 220 } |
122 | 221 |
123 bool PartialMagnificationController::IsPartialMagnified() const { | 222 void PartialMagnificationController::OnLocatedEvent( |
124 return scale_ >= kMinPartialMagnifiedScaleThreshold; | 223 ui::LocatedEvent* event, |
224 const ui::PointerDetails& pointer_details) { | |
225 if (!is_enabled_) | |
226 return; | |
227 | |
228 if (pointer_details.pointer_type != ui::EventPointerType::POINTER_TYPE_PEN) | |
229 return; | |
230 | |
231 if (event->type() == ui::ET_MOUSE_PRESSED) | |
232 SetActive(true); | |
233 | |
234 if (event->type() == ui::ET_MOUSE_RELEASED) | |
235 SetActive(false); | |
236 | |
237 if (!is_active_) | |
238 return; | |
239 | |
240 // If the previous root window was detached host_widget_ will be null; | |
241 // reconstruct it. We also need to change the root window if the cursor has | |
242 // crossed display boundries. | |
243 SwitchTargetRootWindowIfNeeded(GetCurrentRootWindow()); | |
244 | |
245 // If that failed for any reason return. | |
246 if (!host_widget_) { | |
247 SetActive(false); | |
248 return; | |
249 } | |
250 | |
251 gfx::Point point = event->root_location(); | |
252 | |
253 // Remap point from where it was captured to the display it is actually on. | |
254 aura::Window* target = static_cast<aura::Window*>(event->target()); | |
255 aura::Window* event_root = target->GetRootWindow(); | |
256 aura::Window::ConvertPointToTarget( | |
257 event_root, host_widget_->GetNativeView()->GetRootWindow(), &point); | |
258 | |
259 host_widget_->SetBounds(GetBounds(point)); | |
260 | |
261 event->StopPropagation(); | |
125 } | 262 } |
126 | 263 |
127 void PartialMagnificationController::CreateMagnifierWindow() { | 264 void PartialMagnificationController::CreateMagnifierWindow( |
128 if (zoom_widget_) | 265 aura::Window* root_window) { |
129 return; | 266 if (host_widget_ || !root_window) |
130 | |
131 aura::Window* root_window = GetCurrentRootWindow(); | |
132 if (!root_window) | |
133 return; | 267 return; |
134 | 268 |
135 root_window->AddObserver(this); | 269 root_window->AddObserver(this); |
136 | 270 |
137 gfx::Point mouse( | 271 gfx::Point mouse( |
138 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()); | 272 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot()); |
139 | 273 |
140 zoom_widget_ = new views::Widget; | 274 host_widget_ = new views::Widget; |
141 views::Widget::InitParams params( | 275 views::Widget::InitParams params( |
142 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); | 276 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
143 params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; | 277 params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; |
144 params.accept_events = false; | 278 params.accept_events = false; |
279 params.bounds = GetBounds(mouse); | |
145 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | 280 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
146 params.parent = root_window; | 281 params.parent = root_window; |
147 zoom_widget_->Init(params); | 282 host_widget_->Init(params); |
148 zoom_widget_->SetBounds(gfx::Rect(mouse.x() - kMagnifierWidth / 2, | 283 host_widget_->set_focus_on_creation(false); |
149 mouse.y() - kMagnifierHeight / 2, | 284 host_widget_->Show(); |
150 kMagnifierWidth, kMagnifierHeight)); | |
151 zoom_widget_->set_focus_on_creation(false); | |
152 zoom_widget_->Show(); | |
153 | 285 |
154 aura::Window* window = zoom_widget_->GetNativeView(); | 286 aura::Window* window = host_widget_->GetNativeView(); |
155 window->SetName(kPartialMagniferWindowName); | 287 window->SetName(kPartialMagniferWindowName); |
156 | 288 |
157 zoom_widget_->GetNativeView()->layer()->SetBounds( | 289 ui::Layer* root_layer = host_widget_->GetNativeView()->layer(); |
158 gfx::Rect(0, 0, kMagnifierWidth, kMagnifierHeight)); | |
159 zoom_widget_->GetNativeView()->layer()->SetBackgroundZoom(scale_, kZoomInset); | |
160 | 290 |
161 zoom_widget_->AddObserver(this); | 291 zoom_layer_.reset(new ui::Layer(ui::LayerType::LAYER_SOLID_COLOR)); |
292 zoom_layer_->SetBounds(gfx::Rect(GetWindowSize())); | |
293 zoom_layer_->SetBackgroundZoom(kMagnificationScale, kZoomInset); | |
294 root_layer->Add(zoom_layer_.get()); | |
295 | |
296 border_layer_.reset(new ui::Layer(ui::LayerType::LAYER_TEXTURED)); | |
297 border_layer_->SetBounds(gfx::Rect(GetWindowSize())); | |
298 border_layer_->set_delegate(new BorderRenderer(gfx::Rect(GetWindowSize()))); | |
299 root_layer->Add(border_layer_.get()); | |
300 | |
301 border_mask_.reset(new ContentMask(true, GetWindowSize())); | |
302 border_layer_->SetMaskLayer(border_mask_->layer()); | |
303 | |
304 zoom_mask_.reset(new ContentMask(false, GetWindowSize())); | |
305 zoom_layer_->SetMaskLayer(zoom_mask_->layer()); | |
306 | |
307 host_widget_->AddObserver(this); | |
162 } | 308 } |
163 | 309 |
164 void PartialMagnificationController::CloseMagnifierWindow() { | 310 void PartialMagnificationController::CloseMagnifierWindow() { |
165 if (zoom_widget_) { | 311 if (host_widget_) { |
166 RemoveZoomWidgetObservers(); | 312 RemoveZoomWidgetObservers(); |
167 zoom_widget_->Close(); | 313 host_widget_->Close(); |
168 zoom_widget_ = NULL; | 314 host_widget_ = nullptr; |
169 } | 315 } |
170 } | 316 } |
171 | 317 |
172 void PartialMagnificationController::RemoveZoomWidgetObservers() { | 318 void PartialMagnificationController::RemoveZoomWidgetObservers() { |
173 DCHECK(zoom_widget_); | 319 DCHECK(host_widget_); |
174 zoom_widget_->RemoveObserver(this); | 320 host_widget_->RemoveObserver(this); |
175 aura::Window* root_window = zoom_widget_->GetNativeView()->GetRootWindow(); | 321 aura::Window* root_window = host_widget_->GetNativeView()->GetRootWindow(); |
176 DCHECK(root_window); | 322 DCHECK(root_window); |
177 root_window->RemoveObserver(this); | 323 root_window->RemoveObserver(this); |
178 } | 324 } |
179 | 325 |
180 void PartialMagnificationController::SwitchTargetRootWindow( | |
181 aura::Window* new_root_window) { | |
182 if (zoom_widget_ && | |
183 new_root_window == zoom_widget_->GetNativeView()->GetRootWindow()) | |
184 return; | |
185 | |
186 CloseMagnifierWindow(); | |
187 | |
188 // Recreate the magnifier window by updating the scale factor. | |
189 SetScale(GetScale()); | |
190 } | |
191 | |
192 aura::Window* PartialMagnificationController::GetCurrentRootWindow() { | |
193 aura::Window::Windows root_windows = Shell::GetAllRootWindows(); | |
194 for (aura::Window::Windows::const_iterator iter = root_windows.begin(); | |
195 iter != root_windows.end(); ++iter) { | |
196 aura::Window* root_window = *iter; | |
197 if (root_window->ContainsPointInRoot( | |
198 root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot())) | |
199 return root_window; | |
200 } | |
201 return NULL; | |
202 } | |
203 | |
204 } // namespace ash | 326 } // namespace ash |
OLD | NEW |