text: // 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/accessibility/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
  