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