| // Copyright 2020 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/magnifier_glass.h" |
| |
| #include "ash/shell.h" |
| #include "base/check_op.h" |
| #include "third_party/skia/include/core/SkDrawLooper.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/paint_recorder.h" |
| #include "ui/gfx/shadow_value.h" |
| #include "ui/gfx/skia_paint_util.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Inset on the zoom filter. |
| constexpr 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. |
| constexpr int kVerticalOffset = 0; |
| |
| // Name of the magnifier window. |
| constexpr char kMagniferGlassWindowName[] = "MagnifierGlassWindow"; |
| |
| int GetShadowOffset(const MagnifierGlass::Params& params) { |
| return std::max(params.bottom_shadow.y(), params.top_shadow.y()); |
| } |
| |
| int GetShadowThickness(const MagnifierGlass::Params& params) { |
| return std::max(params.bottom_shadow.blur(), params.top_shadow.blur()); |
| } |
| |
| gfx::Size GetWindowSize(const MagnifierGlass::Params& params) { |
| // The diameter of the window is the diameter of the magnifier, border and |
| // shadow combined. We apply the larger shadow offset on all sides, despite |
| // the shadow offsets potentially being unequal, so as to keep the circle |
| // centered in the view and keep calculations (border rendering and content |
| // masking) simpler. |
| int window_diameter = (params.radius + params.border_size + |
| GetShadowThickness(params) + GetShadowOffset(params)) * |
| 2; |
| return gfx::Size(window_diameter, window_diameter); |
| } |
| |
| gfx::Rect GetBounds(const MagnifierGlass::Params& params, |
| const gfx::Point& point) { |
| gfx::Size size = GetWindowSize(params); |
| gfx::Point origin(point.x() - (size.width() / 2), |
| point.y() - (size.height() / 2) - kVerticalOffset); |
| return gfx::Rect(origin, size); |
| } |
| |
| } // namespace |
| |
| // The border renderer draws the border as well as outline on both the outer and |
| // inner radius to increase visibility. The border renderer also handles drawing |
| // the shadow. |
| class MagnifierGlass::BorderRenderer : public ui::LayerDelegate { |
| public: |
| BorderRenderer(const gfx::Rect& window_bounds, |
| const MagnifierGlass::Params& params) |
| : magnifier_window_bounds_(window_bounds), params_(params) { |
| magnifier_shadows_.push_back(params_.bottom_shadow); |
| magnifier_shadows_.push_back(params_.top_shadow); |
| } |
| |
| ~BorderRenderer() override = default; |
| |
| private: |
| // ui::LayerDelegate: |
| void OnPaintLayer(const ui::PaintContext& context) override { |
| ui::PaintRecorder recorder(context, magnifier_window_bounds_.size()); |
| |
| // Draw the shadow. |
| cc::PaintFlags shadow_flags; |
| shadow_flags.setAntiAlias(true); |
| shadow_flags.setColor(SK_ColorTRANSPARENT); |
| shadow_flags.setLooper(gfx::CreateShadowDrawLooper(magnifier_shadows_)); |
| gfx::Rect shadow_bounds(magnifier_window_bounds_.size()); |
| recorder.canvas()->DrawCircle(shadow_bounds.CenterPoint(), |
| shadow_bounds.width() / 2 - |
| GetShadowThickness(params_) - |
| GetShadowOffset(params_), |
| shadow_flags); |
| |
| // The radius of the magnifier and its border. |
| const int magnifier_radius = params_.radius + params_.border_size; |
| |
| // Clear the shadow for the magnified area. |
| cc::PaintFlags mask_flags; |
| mask_flags.setAntiAlias(true); |
| mask_flags.setBlendMode(SkBlendMode::kClear); |
| mask_flags.setStyle(cc::PaintFlags::kFill_Style); |
| recorder.canvas()->DrawCircle( |
| magnifier_window_bounds_.CenterPoint(), |
| magnifier_radius - params_.border_outline_thickness / 2, mask_flags); |
| |
| cc::PaintFlags border_flags; |
| border_flags.setAntiAlias(true); |
| border_flags.setStyle(cc::PaintFlags::kStroke_Style); |
| |
| // Draw the inner border. |
| border_flags.setStrokeWidth(params_.border_size); |
| border_flags.setColor(params_.border_color); |
| recorder.canvas()->DrawCircle(magnifier_window_bounds_.CenterPoint(), |
| magnifier_radius - params_.border_size / 2, |
| border_flags); |
| |
| // Draw border outer outline and then draw the border inner outline. |
| border_flags.setStrokeWidth(params_.border_outline_thickness); |
| border_flags.setColor(params_.border_outline_color); |
| recorder.canvas()->DrawCircle( |
| magnifier_window_bounds_.CenterPoint(), |
| magnifier_radius - params_.border_outline_thickness / 2, border_flags); |
| recorder.canvas()->DrawCircle(magnifier_window_bounds_.CenterPoint(), |
| magnifier_radius - params_.border_size + |
| params_.border_outline_thickness / 2, |
| border_flags); |
| } |
| |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| |
| const gfx::Rect magnifier_window_bounds_; |
| const Params params_; |
| std::vector<gfx::ShadowValue> magnifier_shadows_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BorderRenderer); |
| }; |
| |
| MagnifierGlass::MagnifierGlass(Params params) : params_(std::move(params)) {} |
| |
| MagnifierGlass::~MagnifierGlass() { |
| CloseMagnifierWindow(); |
| CHECK(!views::WidgetObserver::IsInObserverList()); |
| } |
| |
| void MagnifierGlass::ShowFor(aura::Window* root_window, |
| const gfx::Point& location_in_root) { |
| if (!host_widget_) { |
| CreateMagnifierWindow(root_window, location_in_root); |
| return; |
| } |
| |
| if (root_window != host_widget_->GetNativeView()->GetRootWindow()) { |
| CloseMagnifierWindow(); |
| CreateMagnifierWindow(root_window, location_in_root); |
| return; |
| } |
| |
| host_widget_->SetBounds(GetBounds(params_, location_in_root)); |
| } |
| |
| void MagnifierGlass::Close() { |
| CloseMagnifierWindow(); |
| } |
| |
| void MagnifierGlass::OnWindowDestroying(aura::Window* window) { |
| CloseMagnifierWindow(); |
| } |
| |
| void MagnifierGlass::OnWidgetDestroying(views::Widget* widget) { |
| DCHECK_EQ(widget, host_widget_); |
| RemoveZoomWidgetObservers(); |
| host_widget_ = nullptr; |
| } |
| |
| void MagnifierGlass::CreateMagnifierWindow(aura::Window* root_window, |
| const gfx::Point& location_in_root) { |
| if (host_widget_ || !root_window) |
| return; |
| |
| root_window->AddObserver(this); |
| |
| 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(params_, location_in_root); |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| params.name = kMagniferGlassWindowName; |
| params.parent = root_window; |
| host_widget_->Init(std::move(params)); |
| host_widget_->set_focus_on_creation(false); |
| host_widget_->Show(); |
| |
| ui::Layer* root_layer = host_widget_->GetNativeView()->layer(); |
| |
| const gfx::Size window_size = GetWindowSize(params_); |
| const gfx::Rect window_bounds = gfx::Rect(window_size); |
| |
| zoom_layer_ = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR); |
| zoom_layer_->SetBounds(window_bounds); |
| zoom_layer_->SetBackgroundZoom(params_.scale, kZoomInset); |
| zoom_layer_->SetFillsBoundsOpaquely(false); |
| root_layer->Add(zoom_layer_.get()); |
| |
| // Create a rounded rect clip, so that only we see a circle of the zoomed |
| // content. This circle radius should match that of the drawn border. |
| const gfx::RoundedCornersF kRoundedCorners{params_.radius}; |
| zoom_layer_->SetRoundedCornerRadius(kRoundedCorners); |
| gfx::Rect clip_rect = window_bounds; |
| clip_rect.ClampToCenteredSize( |
| gfx::Size(params_.radius * 2, params_.radius * 2)); |
| zoom_layer_->SetClipRect(clip_rect); |
| |
| border_layer_ = std::make_unique<ui::Layer>(); |
| border_layer_->SetBounds(window_bounds); |
| border_renderer_ = std::make_unique<BorderRenderer>(window_bounds, params_); |
| border_layer_->set_delegate(border_renderer_.get()); |
| border_layer_->SetFillsBoundsOpaquely(false); |
| root_layer->Add(border_layer_.get()); |
| |
| host_widget_->AddObserver(this); |
| } |
| |
| void MagnifierGlass::CloseMagnifierWindow() { |
| if (host_widget_) { |
| RemoveZoomWidgetObservers(); |
| host_widget_->Close(); |
| host_widget_ = nullptr; |
| } |
| } |
| |
| void MagnifierGlass::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 |