Chromium Code Reviews| 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 |