1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/touch/touch_hud_renderer.h"
6 
7 #include "base/time/time.h"
8 #include "third_party/skia/include/effects/SkGradientShader.h"
9 #include "ui/compositor/layer.h"
10 #include "ui/compositor/layer_owner.h"
11 #include "ui/events/event.h"
12 #include "ui/gfx/animation/linear_animation.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/geometry/size.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/views/animation/animation_delegate_views.h"
17 #include "ui/views/view.h"
18 #include "ui/views/widget/widget.h"
19 #include "ui/views/widget/widget_observer.h"
20 
21 namespace ash {
22 
23 constexpr int kPointRadius = 20;
24 constexpr SkColor kProjectionFillColor = SkColorSetRGB(0xF5, 0xF5, 0xDC);
25 constexpr SkColor kProjectionStrokeColor = SK_ColorGRAY;
26 constexpr int kProjectionAlpha = 0xB0;
27 constexpr base::TimeDelta kFadeoutDuration =
28     base::TimeDelta::FromMilliseconds(250);
29 constexpr int kFadeoutFrameRate = 60;
30 
31 // TouchPointView draws a single touch point.
32 class TouchPointView : public views::View,
33                        public views::AnimationDelegateViews,
34                        public views::WidgetObserver {
35  public:
TouchPointView(views::Widget * parent_widget)36   explicit TouchPointView(views::Widget* parent_widget)
37       : views::AnimationDelegateViews(this) {
38     SetPaintToLayer();
39     layer()->SetFillsBoundsOpaquely(false);
40 
41     SetSize(gfx::Size(2 * kPointRadius + 2, 2 * kPointRadius + 2));
42 
43     widget_observer_.Add(parent_widget);
44   }
45 
46   ~TouchPointView() override = default;
47 
48   // Begins fadeout animation. After this is called, |this| owns itself and is
49   // responsible for deleting itself when the animation ends or the host widget
50   // is destroyed.
FadeOut(std::unique_ptr<TouchPointView> self)51   void FadeOut(std::unique_ptr<TouchPointView> self) {
52     DCHECK_EQ(this, self.get());
53     owned_self_reference_ = std::move(self);
54     fadeout_.reset(
55         new gfx::LinearAnimation(kFadeoutDuration, kFadeoutFrameRate, this));
56     fadeout_->Start();
57   }
58 
UpdateLocation(const ui::LocatedEvent & touch)59   void UpdateLocation(const ui::LocatedEvent& touch) {
60     SetPosition(
61         gfx::Point(parent()->GetMirroredXInView(touch.root_location().x()) -
62                        kPointRadius - 1,
63                    touch.root_location().y() - kPointRadius - 1));
64   }
65 
66  private:
67   // views::View:
OnPaint(gfx::Canvas * canvas)68   void OnPaint(gfx::Canvas* canvas) override {
69     int alpha = kProjectionAlpha;
70     if (fadeout_)
71       alpha = static_cast<int>(fadeout_->CurrentValueBetween(alpha, 0));
72 
73     cc::PaintFlags fill_flags;
74     fill_flags.setAlpha(alpha);
75 
76     constexpr SkColor gradient_colors[2] = {kProjectionFillColor,
77                                             kProjectionStrokeColor};
78     constexpr SkScalar gradient_pos[2] = {SkFloatToScalar(0.9f),
79                                           SkFloatToScalar(1.0f)};
80     constexpr gfx::Point center(kPointRadius + 1, kPointRadius + 1);
81 
82     fill_flags.setShader(cc::PaintShader::MakeRadialGradient(
83         gfx::PointToSkPoint(center), SkIntToScalar(kPointRadius),
84         gradient_colors, gradient_pos, base::size(gradient_colors),
85         SkTileMode::kMirror));
86     canvas->DrawCircle(center, SkIntToScalar(kPointRadius), fill_flags);
87 
88     cc::PaintFlags stroke_flags;
89     stroke_flags.setStyle(cc::PaintFlags::kStroke_Style);
90     stroke_flags.setColor(kProjectionStrokeColor);
91     stroke_flags.setAlpha(alpha);
92     canvas->DrawCircle(center, SkIntToScalar(kPointRadius), stroke_flags);
93   }
94 
95   // views::AnimationDelegateViews:
AnimationEnded(const gfx::Animation * animation)96   void AnimationEnded(const gfx::Animation* animation) override {
97     DCHECK_EQ(fadeout_.get(), animation);
98     owned_self_reference_.reset();
99   }
100 
AnimationProgressed(const gfx::Animation * animation)101   void AnimationProgressed(const gfx::Animation* animation) override {
102     DCHECK_EQ(fadeout_.get(), animation);
103     SchedulePaint();
104   }
105 
AnimationCanceled(const gfx::Animation * animation)106   void AnimationCanceled(const gfx::Animation* animation) override {
107     AnimationEnded(animation);
108   }
109 
110   // views::WidgetObserver:
OnWidgetDestroying(views::Widget * widget)111   void OnWidgetDestroying(views::Widget* widget) override {
112     owned_self_reference_.reset();
113   }
114 
115   std::unique_ptr<gfx::Animation> fadeout_;
116 
117   // When non-null, |owned_self_reference_| refers to |this|, and |this| owns
118   // itself. This should be non-null when fading out, and null otherwise.
119   std::unique_ptr<TouchPointView> owned_self_reference_;
120 
121   ScopedObserver<views::Widget, views::WidgetObserver> widget_observer_{this};
122 
123   DISALLOW_COPY_AND_ASSIGN(TouchPointView);
124 };
125 
TouchHudRenderer(views::Widget * parent_widget)126 TouchHudRenderer::TouchHudRenderer(views::Widget* parent_widget)
127     : parent_widget_(parent_widget) {
128   parent_widget_->AddObserver(this);
129 }
130 
~TouchHudRenderer()131 TouchHudRenderer::~TouchHudRenderer() {
132   if (parent_widget_)
133     parent_widget_->RemoveObserver(this);
134   CHECK(!IsInObserverList());
135 }
136 
Clear()137 void TouchHudRenderer::Clear() {
138   points_.clear();
139 }
140 
HandleTouchEvent(const ui::TouchEvent & event)141 void TouchHudRenderer::HandleTouchEvent(const ui::TouchEvent& event) {
142   int id = event.pointer_details().id;
143   auto iter = points_.find(id);
144   if (event.type() == ui::ET_TOUCH_PRESSED) {
145     if (iter != points_.end()) {
146       TouchPointView* view = iter->second;
147       view->parent()->RemoveChildViewT(view);
148       points_.erase(iter);
149     }
150 
151     TouchPointView* point = parent_widget_->GetContentsView()->AddChildView(
152         std::make_unique<TouchPointView>(parent_widget_));
153     point->UpdateLocation(event);
154     auto result = points_.insert(std::make_pair(id, point));
155     DCHECK(result.second);
156     return;
157   }
158 
159   if (iter == points_.end())
160     return;
161 
162   if (event.type() == ui::ET_TOUCH_RELEASED ||
163       event.type() == ui::ET_TOUCH_CANCELLED) {
164     TouchPointView* view = iter->second;
165     view->FadeOut(view->parent()->RemoveChildViewT(view));
166     points_.erase(iter);
167   } else {
168     iter->second->UpdateLocation(event);
169   }
170 }
171 
OnWidgetDestroying(views::Widget * widget)172 void TouchHudRenderer::OnWidgetDestroying(views::Widget* widget) {
173   DCHECK_EQ(widget, parent_widget_);
174   parent_widget_->RemoveObserver(this);
175   parent_widget_ = nullptr;
176   Clear();
177 }
178 
179 }  // namespace ash
180