chromium/ash/accessibility/autoclick/autoclick_ring_handler.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/accessibility/autoclick/autoclick_ring_handler.h"

#include "base/memory/raw_ptr.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/view.h"

namespace ash {
namespace {

// The following is half width to avoid division by 2.
const int kAutoclickRingArcWidth = 2;

const int kAutoclickRingAngleStartValue = -90;
// The sweep angle is a bit greater than 360 to make sure the circle
// completes at the end of the animation.
const int kAutoclickRingAngleEndValue = 360;

// Constants for colors.
const SkColor kAutoclickRingArcColor = SkColorSetARGB(255, 255, 255, 255);
const SkColor kAutoclickRingCircleColor = SkColorSetARGB(128, 0, 0, 0);
const SkColor kAutoclickRingUnderArcColor = SkColorSetARGB(100, 128, 134, 139);

// Paints the full Autoclick ring.
void PaintAutoclickRing(gfx::Canvas* canvas,
                        const gfx::Point& center,
                        int radius,
                        int end_angle) {
  cc::PaintFlags flags;
  flags.setAntiAlias(true);

  // Draw the point being selected.
  flags.setStyle(cc::PaintFlags::kFill_Style);
  flags.setColor(kAutoclickRingArcColor);
  canvas->DrawCircle(center, kAutoclickRingArcWidth / 2, flags);

  // Draw the outline of the point being selected.
  flags.setStyle(cc::PaintFlags::kStroke_Style);
  flags.setStrokeWidth(kAutoclickRingArcWidth);
  flags.setColor(kAutoclickRingCircleColor);
  canvas->DrawCircle(center, kAutoclickRingArcWidth * 3 / 2, flags);

  // Draw the outline of the arc.
  flags.setColor(kAutoclickRingCircleColor);
  canvas->DrawCircle(center, radius + kAutoclickRingArcWidth, flags);
  canvas->DrawCircle(center, radius - kAutoclickRingArcWidth, flags);

  // Draw the background of the arc.
  flags.setColor(kAutoclickRingUnderArcColor);
  canvas->DrawCircle(center, radius, flags);

  // Draw the arc.
  SkPath arc_path;
  arc_path.addArc(SkRect::MakeXYWH(center.x() - radius, center.y() - radius,
                                   2 * radius, 2 * radius),
                  kAutoclickRingAngleStartValue,
                  end_angle - kAutoclickRingAngleStartValue);
  flags.setStrokeWidth(kAutoclickRingArcWidth);
  flags.setColor(kAutoclickRingArcColor);

  canvas->DrawPath(arc_path, flags);
}

}  // namespace

// View of the AutoclickRingHandler. Draws the actual contents and updates as
// the animation proceeds. It also maintains the views::Widget that the
// animation is shown in.
class AutoclickRingHandler::AutoclickRingView : public views::View {
 public:
  AutoclickRingView(views::Widget* ring_widget, int radius)
      : views::View(), widget_(ring_widget), radius_(radius) {}

  AutoclickRingView(const AutoclickRingView&) = delete;
  AutoclickRingView& operator=(const AutoclickRingView&) = delete;

  ~AutoclickRingView() override = default;

  static AutoclickRingView* Create(const gfx::Point& event_location,
                                   views::Widget* ring_widget,
                                   int radius) {
    AutoclickRingView* ring_view = ring_widget->SetContentsView(
        std::make_unique<AutoclickRingView>(ring_widget, radius));
    ring_view->SetLocation(event_location);
    return ring_view;
  }

  void SetLocation(const gfx::Point& new_event_location) {
    gfx::Point point = new_event_location;
    widget_->SetBounds(
        gfx::Rect(point.x() - (radius_ + kAutoclickRingArcWidth * 2),
                  point.y() - (radius_ + kAutoclickRingArcWidth * 2),
                  GetPreferredSize().width(), GetPreferredSize().height()));
    widget_->Show();
    widget_->GetNativeView()->layer()->SetOpacity(1.0);
  }

  void UpdateWithGrowAnimation(gfx::Animation* animation) {
    // Update the portion of the circle filled so far and re-draw.
    current_angle_ = animation->CurrentValueBetween(
        kAutoclickRingAngleStartValue, kAutoclickRingAngleEndValue);
    SchedulePaint();
  }

  void SetSize(int radius) { radius_ = radius; }

 private:
  // Overridden from views::View.
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override {
    return gfx::Size(2 * (radius_ + kAutoclickRingArcWidth * 2),
                     2 * (radius_ + kAutoclickRingArcWidth * 2));
  }

  void OnPaint(gfx::Canvas* canvas) override {
    gfx::Point center(GetPreferredSize().width() / 2,
                      GetPreferredSize().height() / 2);
    canvas->Save();

    PaintAutoclickRing(canvas, center, radius_, current_angle_);

    canvas->Restore();
  }

  raw_ptr<views::Widget> widget_;
  int radius_;
  int current_angle_ = kAutoclickRingAngleStartValue;
};

////////////////////////////////////////////////////////////////////////////////

// AutoclickRingHandler, public
AutoclickRingHandler::AutoclickRingHandler() : gfx::LinearAnimation(nullptr) {}

AutoclickRingHandler::~AutoclickRingHandler() {
  StopAutoclickRing();
}

void AutoclickRingHandler::StartGesture(
    base::TimeDelta duration,
    const gfx::Point& center_point_in_screen,
    views::Widget* widget) {
  StopAutoclickRing();
  tap_down_location_ = center_point_in_screen;
  ring_widget_ = widget;
  current_animation_type_ = AnimationType::kGrowAnimation;
  animation_duration_ = duration;
  StartAnimation(animation_duration_);
}

void AutoclickRingHandler::StopGesture() {
  StopAutoclickRing();
}

void AutoclickRingHandler::SetGestureCenter(
    const gfx::Point& center_point_in_screen,
    views::Widget* widget) {
  tap_down_location_ = center_point_in_screen;
  ring_widget_ = widget;
}

void AutoclickRingHandler::SetSize(int radius) {
  radius_ = radius;
  if (view_)
    view_->SetSize(radius);
}
////////////////////////////////////////////////////////////////////////////////

// AutoclickRingHandler, private
void AutoclickRingHandler::StartAnimation(base::TimeDelta delay) {
  switch (current_animation_type_) {
    case AnimationType::kGrowAnimation: {
      DCHECK(!view_);
      view_ =
          AutoclickRingView::Create(tap_down_location_, ring_widget_, radius_);
      SetDuration(delay);
      Start();
      break;
    }
    case AnimationType::kNone:
      NOTREACHED();
  }
}

void AutoclickRingHandler::StopAutoclickRing() {
  // Since, Animation::Stop() calls AnimationStopped(), we need to reset the
  // |current_animation_type_| before Stop(), otherwise AnimationStopped() may
  // start the timer again.
  current_animation_type_ = AnimationType::kNone;
  Stop();
  if (view_) {
    ring_widget_->GetRootView()->RemoveChildViewT(view_.get());
    view_ = nullptr;
  }
}

void AutoclickRingHandler::AnimateToState(double state) {
  DCHECK(view_);
  switch (current_animation_type_) {
    case AnimationType::kGrowAnimation:
      view_->SetLocation(tap_down_location_);
      view_->UpdateWithGrowAnimation(this);
      break;
    case AnimationType::kNone:
      NOTREACHED();
  }
}

void AutoclickRingHandler::AnimationStopped() {
  switch (current_animation_type_) {
    case AnimationType::kGrowAnimation:
      current_animation_type_ = AnimationType::kNone;
      break;
    case AnimationType::kNone:
      // Fall through to reset the view.
      if (view_) {
        ring_widget_->GetRootView()->RemoveChildViewT(view_.get());
        view_ = nullptr;
      }
      break;
  }
}

}  // namespace ash