1 // Copyright 2016 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/display/touch_calibrator_view.h"
6 
7 #include "ash/public/cpp/shell_window_ids.h"
8 #include "ash/resources/vector_icons/vector_icons.h"
9 #include "ash/shell.h"
10 #include "base/memory/ptr_util.h"
11 #include "ui/aura/window.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/compositor/scoped_layer_animation_settings.h"
14 #include "ui/gfx/animation/throb_animation.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/paint_vector_icon.h"
17 #include "ui/strings/grit/ui_strings.h"
18 #include "ui/views/background.h"
19 #include "ui/views/bubble/bubble_border.h"
20 #include "ui/views/controls/label.h"
21 #include "ui/views/widget/widget.h"
22 
23 namespace ash {
24 
25 namespace {
26 
27 constexpr char kWidgetName[] = "TouchCalibratorOverlay";
28 
29 constexpr int kAnimationFrameRate = 100;
30 constexpr auto kFadeDuration = base::TimeDelta::FromMilliseconds(150);
31 constexpr auto kPointMoveDuration = base::TimeDelta::FromMilliseconds(400);
32 constexpr auto kPointMoveDurationLong = base::TimeDelta::FromMilliseconds(500);
33 
34 const SkColor kExitLabelColor = SkColorSetARGB(255, 138, 138, 138);
35 constexpr int kExitLabelWidth = 300;
36 constexpr int kExitLabelHeight = 20;
37 
38 const SkColor kTapHereLabelColor = SK_ColorWHITE;
39 
40 constexpr int kHintBoxWidth = 298;
41 constexpr int kHintBoxHeight = 180;
42 constexpr int kHintBoxLabelTextSize = 5;
43 constexpr int kHintBoxSublabelTextSize = 3;
44 
45 constexpr int kThrobberCircleViewWidth = 64;
46 constexpr float kThrobberCircleRadiusFactor = 3.f / 8.f;
47 
48 constexpr auto kFinalMessageTransitionDuration =
49     base::TimeDelta::FromMilliseconds(200);
50 constexpr int kCompleteMessageViewWidth = 427;
51 constexpr int kCompleteMessageViewHeight = kThrobberCircleViewWidth;
52 constexpr int kCompleteMessageTextSize = 16;
53 
54 constexpr int kTouchPointViewOffset = 100;
55 
56 constexpr int kTapLabelHeight = 48;
57 constexpr int kTapLabelWidth = 80;
58 
59 const SkColor kHintLabelTextColor = SK_ColorBLACK;
60 const SkColor kHintSublabelTextColor = SkColorSetARGB(255, 161, 161, 161);
61 
62 const SkColor kInnerCircleColor = SK_ColorWHITE;
63 const SkColor kOuterCircleColor = SkColorSetA(kInnerCircleColor, 255 * 0.2);
64 
65 constexpr auto kCircleAnimationDuration =
66     base::TimeDelta::FromMilliseconds(900);
67 
68 constexpr int kHintRectBorderRadius = 4;
69 
70 constexpr float kBackgroundFinalOpacity = 0.75f;
71 
72 constexpr int kTouchTargetWidth = 64;
73 constexpr int kTouchTargetHeight = kTouchTargetWidth + kTouchTargetWidth / 2;
74 
75 constexpr float kTouchTargetVerticalOffsetFactor = 11.f / 24.f;
76 
77 const SkColor kTouchTargetInnerCircleColor = SkColorSetARGB(255, 66, 133, 244);
78 const SkColor kTouchTargetOuterCircleColor =
79     SkColorSetA(kTouchTargetInnerCircleColor, 255 * 0.2);
80 const SkColor kHandIconColor = SkColorSetARGB(255, 201, 201, 201);
81 constexpr float kHandIconHorizontalOffsetFactor = 7.f / 32.f;
82 
83 // Returns the initialization params for the widget that contains the touch
84 // calibrator view.
GetWidgetParams(aura::Window * root_window)85 views::Widget::InitParams GetWidgetParams(aura::Window* root_window) {
86   views::Widget::InitParams params;
87   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
88   params.name = kWidgetName;
89   params.z_order = ui::ZOrderLevel::kFloatingWindow;
90   params.accept_events = true;
91   params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
92   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
93   params.parent =
94       Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
95   return params;
96 }
97 
98 // Returns the size of bounding box required for |text| of given |font_list|.
GetSizeForString(const base::string16 & text,const gfx::FontList & font_list)99 gfx::Size GetSizeForString(const base::string16& text,
100                            const gfx::FontList& font_list) {
101   int height = 0, width = 0;
102   gfx::Canvas::SizeStringInt(text, font_list, &width, &height, 0, 0);
103   return gfx::Size(width, height);
104 }
105 
AnimateLayerToPosition(views::View * view,base::TimeDelta duration,gfx::Point end_position,float opacity=1.f)106 void AnimateLayerToPosition(views::View* view,
107                             base::TimeDelta duration,
108                             gfx::Point end_position,
109                             float opacity = 1.f) {
110   ui::ScopedLayerAnimationSettings slide_settings(view->layer()->GetAnimator());
111   slide_settings.SetPreemptionStrategy(
112       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
113   slide_settings.SetTransitionDuration(duration);
114   view->SetBoundsRect(gfx::Rect(end_position, view->size()));
115   view->layer()->SetOpacity(opacity);
116 }
117 
118 }  // namespace
119 
120 // Creates a throbbing animated view with two concentric circles. The radius of
121 // the inner circle is fixed while that of the outer circle oscillates between a
122 // min and max radius. The animation takes |animation_duration| milliseconds
123 // to complete. The center of these circles are at the center of the view
124 // element.
125 class CircularThrobberView : public views::View,
126                              public views::AnimationDelegateViews {
127  public:
128   CircularThrobberView(int width,
129                        const SkColor& inner_circle_color,
130                        const SkColor& outer_circle_color,
131                        base::TimeDelta animation_duration);
132   ~CircularThrobberView() override;
133 
134   // views::View:
135   void OnPaint(gfx::Canvas* canvas) override;
136 
137   // views::AnimationDelegateViews:
138   void AnimationProgressed(const gfx::Animation* animation) override;
139 
140  private:
141   // Radius of the inner circle.
142   const int inner_radius_;
143 
144   // Current radius of the outer circle.
145   int outer_radius_;
146 
147   // Minimum radius for outer animated circle.
148   const int smallest_radius_animated_circle_;
149 
150   // Maximum radius for outer animated circle.
151   const int largest_radius_animated_circle_;
152 
153   cc::PaintFlags inner_circle_flags_;
154   cc::PaintFlags outer_circle_flags_;
155 
156   std::unique_ptr<gfx::ThrobAnimation> animation_;
157 
158   // Center of the concentric circles.
159   const gfx::Point center_;
160 
161   DISALLOW_COPY_AND_ASSIGN(CircularThrobberView);
162 };
163 
CircularThrobberView(int width,const SkColor & inner_circle_color,const SkColor & outer_circle_color,base::TimeDelta animation_duration)164 CircularThrobberView::CircularThrobberView(int width,
165                                            const SkColor& inner_circle_color,
166                                            const SkColor& outer_circle_color,
167                                            base::TimeDelta animation_duration)
168     : views::AnimationDelegateViews(this),
169       inner_radius_(width / 4),
170       outer_radius_(inner_radius_),
171       smallest_radius_animated_circle_(width * kThrobberCircleRadiusFactor),
172       largest_radius_animated_circle_(width / 2),
173       center_(gfx::Point(width / 2, width / 2)) {
174   SetSize(gfx::Size(width, width));
175 
176   inner_circle_flags_.setColor(inner_circle_color);
177   inner_circle_flags_.setAntiAlias(true);
178   inner_circle_flags_.setStyle(cc::PaintFlags::kFill_Style);
179 
180   outer_circle_flags_.setColor(outer_circle_color);
181   outer_circle_flags_.setAntiAlias(true);
182   outer_circle_flags_.setStyle(cc::PaintFlags::kFill_Style);
183 
184   animation_.reset(new gfx::ThrobAnimation(this));
185   animation_->SetThrobDuration(animation_duration);
186   animation_->StartThrobbing(-1);
187 
188   SchedulePaint();
189 }
190 
191 CircularThrobberView::~CircularThrobberView() = default;
192 
OnPaint(gfx::Canvas * canvas)193 void CircularThrobberView::OnPaint(gfx::Canvas* canvas) {
194   canvas->DrawCircle(center_, outer_radius_, outer_circle_flags_);
195   canvas->DrawCircle(center_, inner_radius_, inner_circle_flags_);
196 }
197 
AnimationProgressed(const gfx::Animation * animation)198 void CircularThrobberView::AnimationProgressed(
199     const gfx::Animation* animation) {
200   if (animation != animation_.get())
201     return;
202   outer_radius_ = animation->CurrentValueBetween(
203       smallest_radius_animated_circle_, largest_radius_animated_circle_);
204   SchedulePaint();
205 }
206 
207 class TouchTargetThrobberView : public CircularThrobberView {
208  public:
209   TouchTargetThrobberView(const gfx::Rect& bounds,
210                           const SkColor& inner_circle_color,
211                           const SkColor& outer_circle_color,
212                           const SkColor& hand_icon_color,
213                           base::TimeDelta animation_duration);
214   ~TouchTargetThrobberView() override;
215 
216   // views::View:
217   void OnPaint(gfx::Canvas* canvas) override;
218 
219  private:
220   const int horizontal_offset_;
221 
222   const int icon_width_;
223 
224   gfx::ImageSkia hand_icon_;
225 
226   DISALLOW_COPY_AND_ASSIGN(TouchTargetThrobberView);
227 };
228 
TouchTargetThrobberView(const gfx::Rect & bounds,const SkColor & inner_circle_color,const SkColor & outer_circle_color,const SkColor & hand_icon_color,base::TimeDelta animation_duration)229 TouchTargetThrobberView::TouchTargetThrobberView(
230     const gfx::Rect& bounds,
231     const SkColor& inner_circle_color,
232     const SkColor& outer_circle_color,
233     const SkColor& hand_icon_color,
234     base::TimeDelta animation_duration)
235     : CircularThrobberView(bounds.width(),
236                            inner_circle_color,
237                            outer_circle_color,
238                            animation_duration),
239       horizontal_offset_(bounds.width() * kHandIconHorizontalOffsetFactor),
240       icon_width_(bounds.width() * 0.5f) {
241   SetBoundsRect(bounds);
242 
243   hand_icon_ = gfx::CreateVectorIcon(kTouchCalibrationHandIcon, kHandIconColor);
244 }
245 
246 TouchTargetThrobberView::~TouchTargetThrobberView() = default;
247 
OnPaint(gfx::Canvas * canvas)248 void TouchTargetThrobberView::OnPaint(gfx::Canvas* canvas) {
249   CircularThrobberView::OnPaint(canvas);
250   canvas->DrawImageInt(hand_icon_, horizontal_offset_, icon_width_);
251 }
252 
253 //   Circular      _________________________________
254 //   Throbber     |                                 |
255 //     View       |                                 |
256 //  ___________   |                                 |
257 // |           |  |                                 |
258 // |           |  |                                 |
259 // |     .     |  |            Hint Box             |
260 // |           |  |                                 |
261 // |___________|  |                                 |
262 //                |                                 |
263 //                |                                 |
264 //                |_________________________________|
265 //
266 // This view is set next to the throbber circle view such that their centers
267 // align. The hint box has a label text and a sublabel text to assist the
268 // user by informing them about the next step in the calibration process.
269 class HintBox : public views::View {
270  public:
271   HintBox(const gfx::Rect& bounds, int border_radius);
272   ~HintBox() override;
273 
274   // views::View:
275   void OnPaint(gfx::Canvas* canvas) override;
276 
277   void SetLabel(const base::string16& text, const SkColor& color);
278   void SetSubLabel(const base::string16& text, const SkColor& color);
279 
280  private:
281   void UpdateWidth(int updated_width);
282 
283   base::string16 label_text_;
284   base::string16 sublabel_text_;
285 
286   SkColor label_color_;
287   SkColor sublabel_color_;
288 
289   const int border_radius_;
290 
291   int base_border_;
292 
293   int arrow_width_;
294 
295   int horizontal_offset_;
296 
297   gfx::Rect rounded_rect_bounds_;
298 
299   gfx::FontList label_font_list_;
300   gfx::FontList sublabel_font_list_;
301 
302   gfx::Rect label_text_bounds_;
303   gfx::Rect sublabel_text_bounds_;
304 
305   cc::PaintFlags flags_;
306 
307   DISALLOW_COPY_AND_ASSIGN(HintBox);
308 };
309 
HintBox(const gfx::Rect & bounds,int border_radius)310 HintBox::HintBox(const gfx::Rect& bounds, int border_radius)
311     : border_radius_(border_radius) {
312   SetBorder(std::make_unique<views::BubbleBorder>(
313       base::i18n::IsRTL() ? views::BubbleBorder::RIGHT_CENTER
314                           : views::BubbleBorder::LEFT_CENTER,
315       views::BubbleBorder::NO_SHADOW_OPAQUE_BORDER, SK_ColorWHITE));
316 
317   arrow_width_ = (GetInsets().right() - GetInsets().left()) *
318                  (base::i18n::IsRTL() ? 1 : -1);
319 
320   // Border on all sides are the same except on the side of the arrow, in which
321   // case the width of the arrow is additional.
322   base_border_ = base::i18n::IsRTL() ? GetInsets().left() : GetInsets().right();
323 
324   SetBounds(bounds.x(), bounds.y() - base_border_,
325             bounds.width() + 2 * base_border_ + arrow_width_,
326             bounds.height() + 2 * base_border_);
327 
328   rounded_rect_bounds_ = GetContentsBounds();
329 
330   flags_.setColor(SK_ColorWHITE);
331   flags_.setStyle(cc::PaintFlags::kFill_Style);
332   flags_.setAntiAlias(true);
333 
334   horizontal_offset_ =
335       arrow_width_ + base_border_ + rounded_rect_bounds_.width() * 0.08f;
336   int top_offset = horizontal_offset_;
337   int line_gap = rounded_rect_bounds_.height() * 0.018f;
338   int label_height = rounded_rect_bounds_.height() * 0.11f;
339 
340   label_text_bounds_.SetRect(horizontal_offset_, top_offset, 0, label_height);
341 
342   top_offset += label_text_bounds_.height() + line_gap;
343 
344   sublabel_text_bounds_.SetRect(horizontal_offset_, top_offset, 0,
345                                 label_height);
346 }
347 
348 HintBox::~HintBox() = default;
349 
UpdateWidth(int updated_width)350 void HintBox::UpdateWidth(int updated_width) {
351   SetSize(gfx::Size(updated_width + 2 * base_border_ + arrow_width_, height()));
352   rounded_rect_bounds_ = GetContentsBounds();
353 }
354 
SetLabel(const base::string16 & text,const SkColor & color)355 void HintBox::SetLabel(const base::string16& text, const SkColor& color) {
356   label_text_ = text;
357   label_color_ = color;
358 
359   label_font_list_ =
360       ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
361           kHintBoxLabelTextSize, gfx::Font::FontStyle::NORMAL,
362           gfx::Font::Weight::NORMAL);
363 
364   // Adjust size of label bounds based on text and font.
365   gfx::Size size = GetSizeForString(label_text_, label_font_list_);
366   label_text_bounds_.set_size(
367       gfx::Size(size.width(), label_text_bounds_.height()));
368 
369   // Check if the width of hint box needs to be updated.
370   int minimum_expected_width =
371       size.width() + 2 * horizontal_offset_ - arrow_width_;
372   if (minimum_expected_width > rounded_rect_bounds_.width())
373     UpdateWidth(minimum_expected_width);
374 }
375 
SetSubLabel(const base::string16 & text,const SkColor & color)376 void HintBox::SetSubLabel(const base::string16& text, const SkColor& color) {
377   sublabel_text_ = text;
378   sublabel_color_ = color;
379 
380   sublabel_font_list_ =
381       ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
382           kHintBoxSublabelTextSize, gfx::Font::FontStyle::NORMAL,
383           gfx::Font::Weight::NORMAL);
384 
385   // Adjust size of sublabel label bounds based on text and font.
386   gfx::Size size = GetSizeForString(sublabel_text_, sublabel_font_list_);
387   sublabel_text_bounds_.set_size(
388       gfx::Size(size.width(), sublabel_text_bounds_.height()));
389 
390   // Check if the width of hint box needs to be updated.
391   int minimum_expected_width =
392       size.width() + 2 * horizontal_offset_ - arrow_width_;
393   if (minimum_expected_width > rounded_rect_bounds_.width())
394     UpdateWidth(minimum_expected_width);
395 }
396 
OnPaint(gfx::Canvas * canvas)397 void HintBox::OnPaint(gfx::Canvas* canvas) {
398   views::View::OnPaint(canvas);
399   canvas->DrawRoundRect(rounded_rect_bounds_, border_radius_, flags_);
400   canvas->DrawStringRectWithFlags(label_text_, label_font_list_, label_color_,
401                                   label_text_bounds_, gfx::Canvas::NO_ELLIPSIS);
402   canvas->DrawStringRectWithFlags(sublabel_text_, sublabel_font_list_,
403                                   sublabel_color_, sublabel_text_bounds_,
404                                   gfx::Canvas::NO_ELLIPSIS);
405 }
406 
407 class CompletionMessageView : public views::View {
408  public:
409   CompletionMessageView(const gfx::Rect& bounds, const base::string16& message);
410   ~CompletionMessageView() override;
411 
412   // views::View:
413   void OnPaint(gfx::Canvas* canvas) override;
414 
415  private:
416   const base::string16 message_;
417   gfx::FontList font_list_;
418 
419   gfx::Rect text_bounds_;
420 
421   gfx::ImageSkia check_icon_;
422 
423   cc::PaintFlags flags_;
424 
425   DISALLOW_COPY_AND_ASSIGN(CompletionMessageView);
426 };
427 
CompletionMessageView(const gfx::Rect & bounds,const base::string16 & message)428 CompletionMessageView::CompletionMessageView(const gfx::Rect& bounds,
429                                              const base::string16& message)
430     : message_(message) {
431   SetBoundsRect(bounds);
432 
433   int x_offset = height() * 5.f / 4.f;
434   text_bounds_.SetRect(x_offset, 0, width() - x_offset, height());
435 
436   font_list_ = ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
437       kCompleteMessageTextSize, gfx::Font::FontStyle::NORMAL,
438       gfx::Font::Weight::NORMAL);
439 
440   // crbug/676513 moves this file to src/ash which will require an ash icon
441   // file.
442   check_icon_ =
443       gfx::CreateVectorIcon(kTouchCalibrationCompleteCheckIcon, SK_ColorWHITE);
444 
445   flags_.setColor(SK_ColorWHITE);
446   flags_.setStyle(cc::PaintFlags::kFill_Style);
447   flags_.setAntiAlias(true);
448 }
449 
450 CompletionMessageView::~CompletionMessageView() = default;
451 
OnPaint(gfx::Canvas * canvas)452 void CompletionMessageView::OnPaint(gfx::Canvas* canvas) {
453   canvas->DrawImageInt(check_icon_, 0, 0);
454 
455   // TODO(malaykeshav): Work with elizabethchiu@ to get better UX for RTL.
456   canvas->DrawStringRectWithFlags(
457       message_, font_list_, flags_.getColor(), text_bounds_,
458       gfx::Canvas::TEXT_ALIGN_LEFT | gfx::Canvas::NO_SUBPIXEL_RENDERING);
459 }
460 
461 // static
Create(const display::Display & target_display,bool is_primary_view)462 views::UniqueWidgetPtr TouchCalibratorView::Create(
463     const display::Display& target_display,
464     bool is_primary_view) {
465   aura::Window* root = Shell::GetRootWindowForDisplayId(target_display.id());
466   views::UniqueWidgetPtr widget(
467       std::make_unique<views::Widget>(GetWidgetParams(root)));
468   widget->SetContentsView(base::WrapUnique(
469       new TouchCalibratorView(target_display, is_primary_view)));
470   widget->SetBounds(target_display.bounds());
471   widget->Show();
472   return widget;
473 }
474 
TouchCalibratorView(const display::Display & target_display,bool is_primary_view)475 TouchCalibratorView::TouchCalibratorView(const display::Display& target_display,
476                                          bool is_primary_view)
477     : views::AnimationDelegateViews(this),
478       display_(target_display),
479       is_primary_view_(is_primary_view),
480       animator_(std::make_unique<gfx::LinearAnimation>(kFadeDuration,
481                                                        kAnimationFrameRate,
482                                                        this)) {
483   InitViewContents();
484   AdvanceToNextState();
485 }
486 
~TouchCalibratorView()487 TouchCalibratorView::~TouchCalibratorView() {
488   state_ = UNKNOWN;
489   animator_->End();
490 }
491 
InitViewContents()492 void TouchCalibratorView::InitViewContents() {
493   // Initialize the background rect.
494   background_rect_ =
495       gfx::RectF(0, 0, display_.bounds().width(), display_.bounds().height());
496 
497   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
498   // Initialize exit label that informs the user how to exit the touch
499   // calibration setup.
500   exit_label_ = AddChildView(std::make_unique<views::Label>(
501       rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_EXIT_LABEL),
502       views::Label::CustomFont{rb.GetFontListWithDelta(
503           8, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL)}));
504   exit_label_->SetBounds((display_.bounds().width() - kExitLabelWidth) / 2,
505                          display_.bounds().height() * 3.f / 4, kExitLabelWidth,
506                          kExitLabelHeight);
507   exit_label_->SetAutoColorReadabilityEnabled(false);
508   exit_label_->SetEnabledColor(kExitLabelColor);
509   exit_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
510   exit_label_->SetSubpixelRenderingEnabled(false);
511   exit_label_->SetVisible(false);
512 
513   // If this is not the screen that is being calibrated, then this is all we
514   // need to display.
515   if (!is_primary_view_)
516     return;
517 
518   // Initialize the touch point view that contains the animated circle that the
519   // user needs to tap.
520   const int kTouchPointViewHeight = kThrobberCircleViewWidth + kTapLabelHeight;
521   const int kThrobberCircleViewHorizontalOffset =
522       (kTapLabelWidth - kThrobberCircleViewWidth) / 2;
523 
524   touch_point_view_ = AddChildView(std::make_unique<views::View>());
525   touch_point_view_->SetBounds(kTouchPointViewOffset, kTouchPointViewOffset,
526                                kTapLabelWidth, kTouchPointViewHeight);
527   touch_point_view_->SetVisible(false);
528   touch_point_view_->SetPaintToLayer();
529   touch_point_view_->layer()->SetFillsBoundsOpaquely(false);
530   touch_point_view_->layer()->GetAnimator()->AddObserver(this);
531   touch_point_view_->SetBackground(
532       views::CreateSolidBackground(SK_ColorTRANSPARENT));
533 
534   throbber_circle_ =
535       touch_point_view_->AddChildView(std::make_unique<CircularThrobberView>(
536           kThrobberCircleViewWidth, kInnerCircleColor, kOuterCircleColor,
537           kCircleAnimationDuration));
538   throbber_circle_->SetPosition(
539       gfx::Point(kThrobberCircleViewHorizontalOffset, 0));
540 
541   // Initialize the tap label.
542   tap_label_ = touch_point_view_->AddChildView(std::make_unique<views::Label>(
543       rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_TAP_HERE_LABEL),
544       views::Label::CustomFont{rb.GetFontListWithDelta(
545           6, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL)}));
546   tap_label_->SetBounds(0, kThrobberCircleViewWidth, kTapLabelWidth,
547                         kTapLabelHeight);
548   tap_label_->SetEnabledColor(kTapHereLabelColor);
549   tap_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
550   tap_label_->SetAutoColorReadabilityEnabled(false);
551   tap_label_->SetSubpixelRenderingEnabled(false);
552   tap_label_->SetVisible(false);
553 
554   // Initialize the Hint Box view.
555   base::string16 hint_label_text =
556       rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_HINT_LABEL_TEXT);
557   base::string16 hint_sublabel_text =
558       rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_HINT_SUBLABEL_TEXT);
559 
560   int tpv_width = touch_point_view_->width();
561 
562   gfx::Size size(kHintBoxWidth, kHintBoxHeight);
563 
564   gfx::Point position(touch_point_view_->x() + tpv_width * 1.2f,
565                       touch_point_view_->y() +
566                           (kThrobberCircleViewWidth / 2.f) -
567                           (size.height() / 2.f));
568 
569   hint_box_view_ = AddChildView(std::make_unique<HintBox>(
570       gfx::Rect(position, size), kHintRectBorderRadius));
571   hint_box_view_->SetVisible(false);
572   hint_box_view_->SetLabel(hint_label_text, kHintLabelTextColor);
573   hint_box_view_->SetSubLabel(hint_sublabel_text, kHintSublabelTextColor);
574 
575   // Initialize the animated hint box throbber view.
576   auto* target_view =
577       hint_box_view_->AddChildView(std::make_unique<TouchTargetThrobberView>(
578           gfx::Rect((hint_box_view_->width() - kTouchTargetWidth) / 2,
579                     hint_box_view_->height() * kTouchTargetVerticalOffsetFactor,
580                     kTouchTargetWidth, kTouchTargetHeight),
581           kTouchTargetInnerCircleColor, kTouchTargetOuterCircleColor,
582           kHandIconColor, kCircleAnimationDuration));
583   target_view->SetVisible(true);
584 
585   // Initialize the view that contains the calibration complete message which
586   // will be displayed at the end.
587   base::string16 finish_msg_text =
588       rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_FINISH_LABEL);
589 
590   gfx::Rect msg_view_bounds(
591       (display_.bounds().width() - kCompleteMessageViewWidth) / 2,
592       display_.bounds().height() / 3, kCompleteMessageViewWidth,
593       kCompleteMessageViewHeight);
594   completion_message_view_ =
595       AddChildView(std::make_unique<CompletionMessageView>(msg_view_bounds,
596                                                            finish_msg_text));
597   completion_message_view_->SetVisible(false);
598   completion_message_view_->SetPaintToLayer();
599   completion_message_view_->layer()->SetFillsBoundsOpaquely(false);
600   completion_message_view_->layer()->GetAnimator()->AddObserver(this);
601   completion_message_view_->SetBackground(
602       views::CreateSolidBackground(SK_ColorTRANSPARENT));
603 }
604 
OnPaint(gfx::Canvas * canvas)605 void TouchCalibratorView::OnPaint(gfx::Canvas* canvas) {
606   OnPaintBackground(canvas);
607 }
608 
OnPaintBackground(gfx::Canvas * canvas)609 void TouchCalibratorView::OnPaintBackground(gfx::Canvas* canvas) {
610   float opacity;
611 
612   // If current state is a fade in or fade out state then update opacity
613   // based on how far the animation has progressed.
614   if (animator_ && (state_ == TouchCalibratorView::BACKGROUND_FADING_OUT ||
615                     state_ == TouchCalibratorView::BACKGROUND_FADING_IN)) {
616     opacity = static_cast<float>(animator_->CurrentValueBetween(
617         start_opacity_value_, end_opacity_value_));
618   } else {
619     opacity = state_ == BACKGROUND_FADING_OUT ? 0.0f : kBackgroundFinalOpacity;
620   }
621 
622   flags_.setColor(SkColorSetA(SK_ColorBLACK,
623                               std::numeric_limits<uint8_t>::max() * opacity));
624   canvas->DrawRect(background_rect_, flags_);
625 }
626 
AnimationProgressed(const gfx::Animation * animation)627 void TouchCalibratorView::AnimationProgressed(const gfx::Animation* animation) {
628   if (!is_primary_view_) {
629     SchedulePaint();
630     return;
631   }
632 }
633 
AnimationCanceled(const gfx::Animation * animation)634 void TouchCalibratorView::AnimationCanceled(const gfx::Animation* animation) {
635   AnimationEnded(animation);
636 }
637 
AnimationEnded(const gfx::Animation * animation)638 void TouchCalibratorView::AnimationEnded(const gfx::Animation* animation) {
639   switch (state_) {
640     case BACKGROUND_FADING_IN:
641       exit_label_->SetVisible(true);
642       state_ = is_primary_view_ ? DISPLAY_POINT_1 : CALIBRATION_COMPLETE;
643       if (is_primary_view_) {
644         touch_point_view_->SetVisible(true);
645         hint_box_view_->SetVisible(true);
646       }
647       break;
648     case BACKGROUND_FADING_OUT:
649       exit_label_->SetVisible(false);
650       if (is_primary_view_)
651         completion_message_view_->SetVisible(false);
652       GetWidget()->Hide();
653       break;
654     default:
655       break;
656   }
657 }
658 
OnLayerAnimationStarted(ui::LayerAnimationSequence * sequence)659 void TouchCalibratorView::OnLayerAnimationStarted(
660     ui::LayerAnimationSequence* sequence) {}
661 
OnLayerAnimationEnded(ui::LayerAnimationSequence * sequence)662 void TouchCalibratorView::OnLayerAnimationEnded(
663     ui::LayerAnimationSequence* sequence) {
664   switch (state_) {
665     case ANIMATING_1_TO_2:
666       state_ = DISPLAY_POINT_2;
667       tap_label_->SetVisible(true);
668       break;
669     case ANIMATING_2_TO_3:
670       state_ = DISPLAY_POINT_3;
671       break;
672     case ANIMATING_3_TO_4:
673       state_ = DISPLAY_POINT_4;
674       break;
675     case ANIMATING_FINAL_MESSAGE:
676       state_ = CALIBRATION_COMPLETE;
677       break;
678     default:
679       break;
680   }
681 }
682 
OnLayerAnimationAborted(ui::LayerAnimationSequence * sequence)683 void TouchCalibratorView::OnLayerAnimationAborted(
684     ui::LayerAnimationSequence* sequence) {
685   OnLayerAnimationEnded(sequence);
686 }
687 
OnLayerAnimationScheduled(ui::LayerAnimationSequence * sequence)688 void TouchCalibratorView::OnLayerAnimationScheduled(
689     ui::LayerAnimationSequence* sequence) {}
690 
AdvanceToNextState()691 void TouchCalibratorView::AdvanceToNextState() {
692   // Stop any previous animations and skip them to the end.
693   SkipCurrentAnimation();
694 
695   switch (state_) {
696     case UNKNOWN:
697     case BACKGROUND_FADING_IN:
698       state_ = BACKGROUND_FADING_IN;
699       start_opacity_value_ = 0.0f;
700       end_opacity_value_ = kBackgroundFinalOpacity;
701 
702       flags_.setStyle(cc::PaintFlags::kFill_Style);
703       animator_->SetDuration(kFadeDuration);
704       animator_->Start();
705       return;
706     case DISPLAY_POINT_1:
707       state_ = ANIMATING_1_TO_2;
708 
709       // The touch point has to be animated from the top left corner of the
710       // screen to the top right corner.
711       AnimateLayerToPosition(
712           touch_point_view_, kPointMoveDuration,
713           gfx::Point(display_.bounds().width() - kTouchPointViewOffset -
714                          touch_point_view_->width(),
715                      touch_point_view_->y()));
716       hint_box_view_->SetVisible(false);
717       return;
718     case DISPLAY_POINT_2:
719       state_ = ANIMATING_2_TO_3;
720 
721       // The touch point has to be animated from the top right corner of the
722       // screen to the bottom left corner.
723       AnimateLayerToPosition(
724           touch_point_view_, kPointMoveDurationLong,
725           gfx::Point(kTouchPointViewOffset, display_.bounds().height() -
726                                                 kTouchPointViewOffset -
727                                                 touch_point_view_->height()));
728       return;
729     case DISPLAY_POINT_3:
730       state_ = ANIMATING_3_TO_4;
731 
732       // The touch point has to be animated from the bottom left corner of the
733       // screen to the bottom right corner.
734       AnimateLayerToPosition(
735           touch_point_view_, kPointMoveDuration,
736           gfx::Point(display_.bounds().width() - kTouchPointViewOffset -
737                          touch_point_view_->width(),
738                      touch_point_view_->y()));
739       return;
740     case DISPLAY_POINT_4:
741       state_ = ANIMATING_FINAL_MESSAGE;
742       completion_message_view_->layer()->SetOpacity(0.0f);
743       completion_message_view_->SetVisible(true);
744 
745       touch_point_view_->SetVisible(false);
746 
747       AnimateLayerToPosition(completion_message_view_,
748                              kFinalMessageTransitionDuration,
749                              gfx::Point(completion_message_view_->x(),
750                                         display_.bounds().height() / 2));
751       return;
752     case CALIBRATION_COMPLETE:
753       state_ = BACKGROUND_FADING_OUT;
754       if (is_primary_view_) {
755         // In case of primary view, we also need to fade out the calibration
756         // complete message view.
757         AnimateLayerToPosition(
758             completion_message_view_, kFadeDuration,
759             gfx::Point(completion_message_view_->x(),
760                        completion_message_view_->y() +
761                            2 * completion_message_view_->height()),
762             0.0f);
763       }
764 
765       start_opacity_value_ = kBackgroundFinalOpacity;
766       end_opacity_value_ = 0.0f;
767 
768       flags_.setStyle(cc::PaintFlags::kFill_Style);
769       animator_->SetDuration(kFadeDuration);
770       animator_->Start();
771       return;
772     default:
773       return;
774   }
775 }
776 
GetDisplayPointLocation(gfx::Point * location)777 bool TouchCalibratorView::GetDisplayPointLocation(gfx::Point* location) {
778   DCHECK(location);
779   if (!is_primary_view_)
780     return false;
781 
782   if (state_ != DISPLAY_POINT_1 && state_ != DISPLAY_POINT_2 &&
783       state_ != DISPLAY_POINT_3 && state_ != DISPLAY_POINT_4) {
784     return false;
785   }
786 
787   if (!touch_point_view_ || !throbber_circle_)
788     return false;
789   // TODO(malaykeshav): Can use views::ConvertPointToScreen()
790   location->SetPoint(touch_point_view_->x() + touch_point_view_->width() / 2.f,
791                      touch_point_view_->y() + touch_point_view_->width() / 2.f);
792   return true;
793 }
794 
SkipToFinalState()795 void TouchCalibratorView::SkipToFinalState() {
796   state_ = CALIBRATION_COMPLETE;
797 
798   exit_label_->SetVisible(false);
799 
800   if (is_primary_view_) {
801     touch_point_view_->SetVisible(false);
802     hint_box_view_->SetVisible(false);
803   }
804 
805   AdvanceToNextState();
806 }
807 
SkipCurrentAnimation()808 void TouchCalibratorView::SkipCurrentAnimation() {
809   if (animator_->is_animating())
810     animator_->End();
811   if (touch_point_view_ &&
812       touch_point_view_->layer()->GetAnimator()->is_animating()) {
813     touch_point_view_->layer()->GetAnimator()->StopAnimating();
814   }
815 }
816 
817 }  // namespace ash
818