chromium/ash/touch/touch_hud_renderer.cc

// Copyright 2013 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/touch/touch_hud_renderer.h"

#include <memory>

#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_owner.h"
#include "ui/events/event.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"

namespace ash {

constexpr int kPointRadius = 20;
constexpr SkColor4f kProjectionFillColor{0.96f, 0.96f, 0.86f, 1.0f};
constexpr SkColor4f kProjectionStrokeColor = SkColors::kGray;
constexpr float kProjectionAlpha = 0xB0 / 255.0f;
constexpr base::TimeDelta kFadeoutDuration = base::Milliseconds(250);
constexpr int kFadeoutFrameRate = 60;

// TouchPointView draws a single touch point.
class TouchPointView : public views::View,
                       public views::AnimationDelegateViews,
                       public views::WidgetObserver {
  METADATA_HEADER(TouchPointView, views::View)

 public:
  explicit TouchPointView(views::Widget* parent_widget)
      : views::AnimationDelegateViews(this) {
    SetPaintToLayer();
    layer()->SetFillsBoundsOpaquely(false);

    SetSize(gfx::Size(2 * kPointRadius + 2, 2 * kPointRadius + 2));

    widget_observation_.Observe(parent_widget);
  }

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

  ~TouchPointView() override = default;

  // Begins fadeout animation. After this is called, |this| owns itself and is
  // responsible for deleting itself when the animation ends or the host widget
  // is destroyed.
  void FadeOut(std::unique_ptr<TouchPointView> self) {
    DCHECK_EQ(this, self.get());
    owned_self_reference_ = std::move(self);
    fadeout_ = std::make_unique<gfx::LinearAnimation>(kFadeoutDuration,
                                                      kFadeoutFrameRate, this);
    fadeout_->Start();
  }

  void UpdateLocation(const ui::LocatedEvent& touch) {
    SetPosition(
        gfx::Point(parent()->GetMirroredXInView(touch.root_location().x()) -
                       kPointRadius - 1,
                   touch.root_location().y() - kPointRadius - 1));
  }

 private:
  // views::View:
  void OnPaint(gfx::Canvas* canvas) override {
    const float alpha =
        fadeout_ ? fadeout_->CurrentValueBetween(kProjectionAlpha, 0.0f)
                 : kProjectionAlpha;

    cc::PaintFlags fill_flags;
    fill_flags.setAlphaf(alpha);

    constexpr SkColor4f gradient_colors[2] = {kProjectionFillColor,
                                              kProjectionStrokeColor};
    constexpr SkScalar gradient_pos[2] = {SkFloatToScalar(0.9f),
                                          SkFloatToScalar(1.0f)};
    constexpr gfx::Point center(kPointRadius + 1, kPointRadius + 1);

    fill_flags.setShader(cc::PaintShader::MakeRadialGradient(
        gfx::PointToSkPoint(center), SkIntToScalar(kPointRadius),
        gradient_colors, gradient_pos, std::size(gradient_colors),
        SkTileMode::kMirror));
    canvas->DrawCircle(center, SkIntToScalar(kPointRadius), fill_flags);

    cc::PaintFlags stroke_flags;
    stroke_flags.setStyle(cc::PaintFlags::kStroke_Style);
    stroke_flags.setColor(kProjectionStrokeColor);
    stroke_flags.setAlphaf(alpha);
    canvas->DrawCircle(center, SkIntToScalar(kPointRadius), stroke_flags);
  }

  // views::AnimationDelegateViews:
  void AnimationEnded(const gfx::Animation* animation) override {
    DCHECK_EQ(fadeout_.get(), animation);
    owned_self_reference_.reset();
  }

  void AnimationProgressed(const gfx::Animation* animation) override {
    DCHECK_EQ(fadeout_.get(), animation);
    SchedulePaint();
  }

  void AnimationCanceled(const gfx::Animation* animation) override {
    AnimationEnded(animation);
  }

  // views::WidgetObserver:
  void OnWidgetDestroying(views::Widget* widget) override {
    owned_self_reference_.reset();
  }

  std::unique_ptr<gfx::Animation> fadeout_;

  // When non-null, |owned_self_reference_| refers to |this|, and |this| owns
  // itself. This should be non-null when fading out, and null otherwise.
  std::unique_ptr<TouchPointView> owned_self_reference_;

  base::ScopedObservation<views::Widget, views::WidgetObserver>
      widget_observation_{this};
};

BEGIN_METADATA(TouchPointView)
END_METADATA

TouchHudRenderer::TouchHudRenderer(views::Widget* parent_widget)
    : parent_widget_(parent_widget) {
  parent_widget_->AddObserver(this);
}

TouchHudRenderer::~TouchHudRenderer() {
  if (parent_widget_)
    parent_widget_->RemoveObserver(this);
  CHECK(!IsInObserverList());
}

void TouchHudRenderer::Clear() {
  points_.clear();
}

void TouchHudRenderer::HandleTouchEvent(const ui::TouchEvent& event) {
  int id = event.pointer_details().id;
  auto iter = points_.find(id);
  if (event.type() == ui::EventType::kTouchPressed) {
    if (iter != points_.end()) {
      TouchPointView* view = iter->second;
      view->parent()->RemoveChildViewT(view);
      points_.erase(iter);
    }

    TouchPointView* point = parent_widget_->GetContentsView()->AddChildView(
        std::make_unique<TouchPointView>(parent_widget_));
    point->UpdateLocation(event);
    auto result = points_.insert(std::make_pair(id, point));
    DCHECK(result.second);
    return;
  }

  if (iter == points_.end())
    return;

  if (event.type() == ui::EventType::kTouchReleased ||
      event.type() == ui::EventType::kTouchCancelled) {
    TouchPointView* view = iter->second;
    view->FadeOut(view->parent()->RemoveChildViewT(view));
    points_.erase(iter);
  } else {
    iter->second->UpdateLocation(event);
  }
}

void TouchHudRenderer::OnWidgetDestroying(views::Widget* widget) {
  DCHECK_EQ(widget, parent_widget_);
  parent_widget_->RemoveObserver(this);
  parent_widget_ = nullptr;
  Clear();
}

}  // namespace ash