Added projection mode to touch HUD
[chromium-blink-merge.git] / ash / touch / touch_observer_hud.cc
blob47fccffebdf08bb71cb32cce44930c52b519a564
1 // Copyright (c) 2012 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.
5 #include "ash/touch/touch_observer_hud.h"
7 #include "ash/display/display_controller.h"
8 #include "ash/display/display_manager.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/shell_window_ids.h"
11 #include "ash/wm/property_util.h"
12 #include "base/json/json_string_value_serializer.h"
13 #include "base/stringprintf.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/utf_string_conversions.h"
16 #include "third_party/skia/include/core/SkPath.h"
17 #include "third_party/skia/include/core/SkXfermode.h"
18 #include "third_party/skia/include/effects/SkGradientShader.h"
19 #include "ui/aura/root_window.h"
20 #include "ui/aura/window.h"
21 #include "ui/base/animation/animation_delegate.h"
22 #include "ui/base/animation/linear_animation.h"
23 #include "ui/base/events/event.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/display.h"
26 #include "ui/gfx/rect.h"
27 #include "ui/gfx/screen.h"
28 #include "ui/gfx/size.h"
29 #include "ui/gfx/transform.h"
30 #include "ui/views/background.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/layout/box_layout.h"
33 #include "ui/views/widget/widget.h"
35 #if defined(USE_X11)
36 #include <X11/extensions/XInput2.h>
37 #include <X11/Xlib.h>
39 #include "ui/base/x/valuators.h"
40 #endif
42 namespace ash {
43 namespace internal {
45 const int kPointRadius = 20;
46 const SkColor kColors[] = {
47 SK_ColorYELLOW,
48 SK_ColorGREEN,
49 SK_ColorRED,
50 SK_ColorBLUE,
51 SK_ColorGRAY,
52 SK_ColorMAGENTA,
53 SK_ColorCYAN,
54 SK_ColorWHITE,
55 SK_ColorBLACK,
56 SkColorSetRGB(0xFF, 0x8C, 0x00),
57 SkColorSetRGB(0x8B, 0x45, 0x13),
58 SkColorSetRGB(0xFF, 0xDE, 0xAD),
60 const int kAlpha = 0x60;
61 const SkColor kProjectionFillColor = SkColorSetRGB(0xF5, 0xF5, 0xDC);
62 const SkColor kProjectionStrokeColor = SK_ColorGRAY;
63 const int kProjectionAlpha = 0xB0;
64 const int kMaxPaths = arraysize(kColors);
65 const int kReducedScale = 10;
66 const int kFadeoutDurationInMs = 250;
67 const int kFadeoutFrameRate = 60;
69 const char* GetTouchEventLabel(ui::EventType type) {
70 switch (type) {
71 case ui::ET_UNKNOWN:
72 return " ";
73 case ui::ET_TOUCH_PRESSED:
74 return "P";
75 case ui::ET_TOUCH_MOVED:
76 return "M";
77 case ui::ET_TOUCH_RELEASED:
78 return "R";
79 case ui::ET_TOUCH_CANCELLED:
80 return "C";
81 default:
82 break;
84 return "?";
87 int GetTrackingId(const ui::TouchEvent& event) {
88 if (!event.HasNativeEvent())
89 return 0;
90 #if defined(USE_XI2_MT)
91 ui::ValuatorTracker* valuators = ui::ValuatorTracker::GetInstance();
92 float tracking_id;
93 if (valuators->ExtractValuator(*event.native_event(),
94 ui::ValuatorTracker::VAL_TRACKING_ID,
95 &tracking_id)) {
96 return static_cast<int>(tracking_id);
98 #endif
99 return 0;
102 int GetSourceDeviceId(const ui::TouchEvent& event) {
103 if (!event.HasNativeEvent())
104 return 0;
105 #if defined(USE_X11)
106 XEvent* xev = event.native_event();
107 return static_cast<XIDeviceEvent*>(xev->xcookie.data)->sourceid;
108 #endif
109 return 0;
112 // A TouchPointLog represents a single touch-event of a touch point.
113 struct TouchPointLog {
114 public:
115 explicit TouchPointLog(const ui::TouchEvent& touch)
116 : id(touch.touch_id()),
117 type(touch.type()),
118 location(touch.root_location()),
119 timestamp(touch.time_stamp().InMillisecondsF()),
120 radius_x(touch.radius_x()),
121 radius_y(touch.radius_y()),
122 pressure(touch.force()),
123 tracking_id(GetTrackingId(touch)),
124 source_device(GetSourceDeviceId(touch)) {
127 // Populates a dictionary value with all the information about the touch
128 // point.
129 scoped_ptr<DictionaryValue> GetAsDictionary() const {
130 scoped_ptr<DictionaryValue> value(new DictionaryValue());
132 value->SetInteger("id", id);
133 value->SetString("type", std::string(GetTouchEventLabel(type)));
134 value->SetString("location", location.ToString());
135 value->SetDouble("timestamp", timestamp);
136 value->SetDouble("radius_x", radius_x);
137 value->SetDouble("radius_y", radius_y);
138 value->SetDouble("pressure", pressure);
139 value->SetInteger("tracking_id", tracking_id);
140 value->SetInteger("source_device", source_device);
142 return value.Pass();
145 int id;
146 ui::EventType type;
147 gfx::Point location;
148 double timestamp;
149 float radius_x;
150 float radius_y;
151 float pressure;
152 int tracking_id;
153 int source_device;
156 // A TouchTrace keeps track of all the touch events of a single touch point
157 // (starting from a touch-press and ending at touch-release).
158 class TouchTrace {
159 public:
160 typedef std::vector<TouchPointLog>::iterator iterator;
161 typedef std::vector<TouchPointLog>::const_iterator const_iterator;
162 typedef std::vector<TouchPointLog>::reverse_iterator reverse_iterator;
163 typedef std::vector<TouchPointLog>::const_reverse_iterator
164 const_reverse_iterator;
166 TouchTrace() {
169 void AddTouchPoint(const ui::TouchEvent& touch) {
170 log_.push_back(TouchPointLog(touch));
173 const std::vector<TouchPointLog>& log() const { return log_; }
175 bool active() const {
176 return !log_.empty() && log_.back().type != ui::ET_TOUCH_RELEASED &&
177 log_.back().type != ui::ET_TOUCH_CANCELLED;
180 // Returns a list containing data from all events for the touch point.
181 scoped_ptr<ListValue> GetAsList() const {
182 scoped_ptr<ListValue> list(new ListValue());
183 for (const_iterator i = log_.begin(); i != log_.end(); ++i)
184 list->Append((*i).GetAsDictionary().release());
185 return list.Pass();
188 void Reset() {
189 log_.clear();
192 private:
193 std::vector<TouchPointLog> log_;
195 DISALLOW_COPY_AND_ASSIGN(TouchTrace);
198 // A TouchLog keeps track of all touch events of all touch points.
199 class TouchLog {
200 public:
201 TouchLog() : next_trace_index_(0) {
204 void AddTouchPoint(const ui::TouchEvent& touch) {
205 if (touch.type() == ui::ET_TOUCH_PRESSED)
206 StartTrace(touch);
207 AddToTrace(touch);
210 void Reset() {
211 next_trace_index_ = 0;
212 for (int i = 0; i < kMaxPaths; ++i)
213 traces_[i].Reset();
216 scoped_ptr<ListValue> GetAsList() const {
217 scoped_ptr<ListValue> list(new ListValue());
218 for (int i = 0; i < kMaxPaths; ++i) {
219 if (!traces_[i].log().empty())
220 list->Append(traces_[i].GetAsList().release());
222 return list.Pass();
225 int GetTraceIndex(int touch_id) const {
226 return touch_id_to_trace_index_.at(touch_id);
229 const TouchTrace* traces() const {
230 return traces_;
233 private:
234 void StartTrace(const ui::TouchEvent& touch) {
235 // Find the first inactive spot; otherwise, overwrite the one
236 // |next_trace_index_| is pointing to.
237 int old_trace_index = next_trace_index_;
238 do {
239 if (!traces_[next_trace_index_].active())
240 break;
241 next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
242 } while (next_trace_index_ != old_trace_index);
243 int touch_id = touch.touch_id();
244 traces_[next_trace_index_].Reset();
245 touch_id_to_trace_index_[touch_id] = next_trace_index_;
246 next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
249 void AddToTrace(const ui::TouchEvent& touch) {
250 int touch_id = touch.touch_id();
251 int trace_index = touch_id_to_trace_index_[touch_id];
252 traces_[trace_index].AddTouchPoint(touch);
255 TouchTrace traces_[kMaxPaths];
256 int next_trace_index_;
258 std::map<int, int> touch_id_to_trace_index_;
260 DISALLOW_COPY_AND_ASSIGN(TouchLog);
263 class TouchHudCanvas : public views::View, public ui::AnimationDelegate {
264 public:
265 TouchHudCanvas(TouchObserverHUD* owner, const TouchLog& touch_log)
266 : owner_(owner),
267 touch_log_(touch_log),
268 scale_(1) {
269 SetPaintToLayer(true);
270 SetFillsBoundsOpaquely(false);
272 paint_.setStyle(SkPaint::kFill_Style);
274 projection_stroke_paint_.setStyle(SkPaint::kStroke_Style);
275 projection_stroke_paint_.setColor(kProjectionStrokeColor);
277 projection_gradient_colors_[0] = kProjectionFillColor;
278 projection_gradient_colors_[1] = kProjectionStrokeColor;
280 projection_gradient_pos_[0] = SkFloatToScalar(0.9f);
281 projection_gradient_pos_[1] = SkFloatToScalar(1.0f);
284 virtual ~TouchHudCanvas() {}
286 void SetScale(int scale) {
287 if (scale_ == scale)
288 return;
289 scale_ = scale;
290 gfx::Transform transform;
291 transform.Scale(1. / scale_, 1. / scale_);
292 layer()->SetTransform(transform);
295 int scale() const { return scale_; }
297 void TouchPointAdded(int touch_id) {
298 int trace_index = touch_log_.GetTraceIndex(touch_id);
299 const TouchTrace& trace = touch_log_.traces()[trace_index];
300 const TouchPointLog& point = trace.log().back();
301 if (point.type == ui::ET_TOUCH_PRESSED)
302 StartedTrace(trace_index);
303 if (point.type != ui::ET_TOUCH_CANCELLED)
304 AddedPointToTrace(trace_index);
305 if (owner_->mode() == TouchObserverHUD::PROJECTION && !trace.active())
306 StartAnimation(trace_index);
309 void StopAnimations() {
310 for (int i = 0; i < kMaxPaths; ++i)
311 fadeouts_[i].reset(NULL);
314 void Clear() {
315 for (int i = 0; i < kMaxPaths; ++i)
316 paths_[i].reset();
317 if (owner_->mode() == TouchObserverHUD::PROJECTION)
318 StopAnimations();
320 SchedulePaint();
323 private:
324 void StartedTrace(int trace_index) {
325 paths_[trace_index].reset();
326 colors_[trace_index] = SkColorSetA(kColors[trace_index], kAlpha);
329 void AddedPointToTrace(int trace_index) {
330 const TouchTrace& trace = touch_log_.traces()[trace_index];
331 const TouchPointLog& point = trace.log().back();
332 const gfx::Point& location = point.location;
333 SkScalar x = SkIntToScalar(location.x());
334 SkScalar y = SkIntToScalar(location.y());
335 SkPoint last;
336 if (!paths_[trace_index].getLastPt(&last) || x != last.x() ||
337 y != last.y()) {
338 paths_[trace_index].addCircle(x, y, SkIntToScalar(kPointRadius));
339 SchedulePaint();
343 void StartAnimation(int path_index) {
344 DCHECK(!fadeouts_[path_index].get());
345 fadeouts_[path_index].reset(new ui::LinearAnimation(kFadeoutDurationInMs,
346 kFadeoutFrameRate,
347 this));
348 fadeouts_[path_index]->Start();
351 // Overridden from views::View.
352 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
353 if (owner_->mode() == TouchObserverHUD::PROJECTION) {
354 for (int i = 0; i < kMaxPaths; ++i) {
355 const TouchTrace& trace = touch_log_.traces()[i];
356 if (!trace.active() && !fadeouts_[i].get())
357 continue;
358 TouchTrace::const_reverse_iterator point = trace.log().rbegin();
359 while (point != trace.log().rend() &&
360 point->type == ui::ET_TOUCH_CANCELLED)
361 point++;
362 DCHECK(point != trace.log().rend());
363 int alpha = kProjectionAlpha;
364 if (fadeouts_[i].get())
365 alpha = static_cast<int>(fadeouts_[i]->CurrentValueBetween(alpha, 0));
366 projection_fill_paint_.setAlpha(alpha);
367 projection_stroke_paint_.setAlpha(alpha);
368 SkShader* shader = SkGradientShader::CreateRadial(
369 SkPoint::Make(SkIntToScalar(point->location.x()),
370 SkIntToScalar(point->location.y())),
371 SkIntToScalar(kPointRadius),
372 projection_gradient_colors_,
373 projection_gradient_pos_,
374 arraysize(projection_gradient_colors_),
375 SkShader::kMirror_TileMode,
376 NULL);
377 projection_fill_paint_.setShader(shader);
378 shader->unref();
379 canvas->DrawCircle(point->location, SkIntToScalar(kPointRadius),
380 projection_fill_paint_);
381 canvas->DrawCircle(point->location, SkIntToScalar(kPointRadius),
382 projection_stroke_paint_);
384 } else {
385 for (int i = 0; i < kMaxPaths; ++i) {
386 if (paths_[i].countPoints() == 0)
387 continue;
388 paint_.setColor(colors_[i]);
389 canvas->DrawPath(paths_[i], paint_);
394 // Overridden from ui::AnimationDelegate.
395 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE {
396 for (int i = 0; i < kMaxPaths; ++i)
397 if (fadeouts_[i].get() == animation) {
398 fadeouts_[i].reset(NULL);
399 break;
403 // Overridden from ui::AnimationDelegate.
404 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE {
405 SchedulePaint();
408 // Overridden from ui::AnimationDelegate.
409 virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE {
410 AnimationEnded(animation);
413 const TouchObserverHUD* const owner_;
414 const TouchLog& touch_log_;
416 SkPaint paint_;
417 SkPaint projection_fill_paint_;
418 SkPaint projection_stroke_paint_;
419 SkColor projection_gradient_colors_[2];
420 SkScalar projection_gradient_pos_[2];
422 SkPath paths_[kMaxPaths];
423 scoped_ptr<ui::Animation> fadeouts_[kMaxPaths];
424 SkColor colors_[kMaxPaths];
426 int scale_;
428 DISALLOW_COPY_AND_ASSIGN(TouchHudCanvas);
431 TouchObserverHUD::TouchObserverHUD(aura::RootWindow* initial_root)
432 : display_id_(initial_root->GetProperty(kDisplayIdKey)),
433 root_window_(initial_root),
434 mode_(FULLSCREEN),
435 touch_log_(new TouchLog()) {
436 const gfx::Display& display =
437 Shell::GetInstance()->display_manager()->GetDisplayForId(display_id_);
439 views::View* content = new views::View;
441 canvas_ = new TouchHudCanvas(this, *touch_log_);
442 content->AddChildView(canvas_);
444 const gfx::Size& display_size = display.size();
445 canvas_->SetSize(display_size);
446 content->SetSize(display_size);
448 label_container_ = new views::View;
449 label_container_->SetLayoutManager(new views::BoxLayout(
450 views::BoxLayout::kVertical, 0, 0, 0));
452 for (int i = 0; i < kMaxTouchPoints; ++i) {
453 touch_labels_[i] = new views::Label;
454 touch_labels_[i]->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
455 touch_labels_[i]->SetShadowColors(SK_ColorWHITE,
456 SK_ColorWHITE);
457 touch_labels_[i]->SetShadowOffset(1, 1);
458 label_container_->AddChildView(touch_labels_[i]);
460 label_container_->SetX(0);
461 label_container_->SetY(display_size.height() / kReducedScale);
462 label_container_->SetSize(label_container_->GetPreferredSize());
463 label_container_->SetVisible(false);
464 content->AddChildView(label_container_);
466 widget_ = new views::Widget();
467 views::Widget::InitParams
468 params(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
469 params.transparent = true;
470 params.can_activate = false;
471 params.accept_events = false;
472 params.bounds = gfx::Rect(display_size);
473 params.parent = Shell::GetContainer(
474 root_window_,
475 internal::kShellWindowId_OverlayContainer);
476 widget_->Init(params);
477 widget_->SetContentsView(content);
478 widget_->StackAtTop();
479 widget_->Show();
481 widget_->AddObserver(this);
483 // Observe changes in display size and mode to update touch HUD.
484 Shell::GetScreen()->AddObserver(this);
485 #if defined(OS_CHROMEOS)
486 Shell::GetInstance()->output_configurator()->AddObserver(this);
487 #endif // defined(OS_CHROMEOS)
489 Shell::GetInstance()->display_controller()->AddObserver(this);
490 root_window_->AddPreTargetHandler(this);
493 TouchObserverHUD::~TouchObserverHUD() {
494 Shell::GetInstance()->display_controller()->RemoveObserver(this);
496 #if defined(OS_CHROMEOS)
497 Shell::GetInstance()->output_configurator()->RemoveObserver(this);
498 #endif // defined(OS_CHROMEOS)
499 Shell::GetScreen()->RemoveObserver(this);
501 widget_->RemoveObserver(this);
504 // static
505 scoped_ptr<DictionaryValue> TouchObserverHUD::GetAllAsDictionary() {
506 scoped_ptr<DictionaryValue> value(new DictionaryValue());
507 Shell::RootWindowList roots = Shell::GetInstance()->GetAllRootWindows();
508 for (Shell::RootWindowList::iterator iter = roots.begin();
509 iter != roots.end(); ++iter) {
510 internal::RootWindowController* controller = GetRootWindowController(*iter);
511 if (controller->touch_observer_hud()) {
512 int64 display_id = (*iter)->GetProperty(kDisplayIdKey);
513 scoped_ptr<ListValue> list =
514 controller->touch_observer_hud()->GetLogAsList();
515 if (!list->empty())
516 value->Set(base::Int64ToString(display_id), list.release());
519 return value.Pass();
522 void TouchObserverHUD::ChangeToNextMode() {
523 switch (mode_) {
524 case FULLSCREEN:
525 SetMode(REDUCED_SCALE);
526 break;
527 case REDUCED_SCALE:
528 SetMode(PROJECTION);
529 break;
530 case PROJECTION:
531 SetMode(INVISIBLE);
532 break;
533 case INVISIBLE:
534 SetMode(FULLSCREEN);
535 break;
539 void TouchObserverHUD::Clear() {
540 if (widget_->IsVisible())
541 canvas_->Clear();
542 for (int i = 0; i < kMaxTouchPoints; ++i)
543 touch_labels_[i]->SetText(string16());
544 label_container_->SetSize(label_container_->GetPreferredSize());
547 scoped_ptr<ListValue> TouchObserverHUD::GetLogAsList() const {
548 return touch_log_->GetAsList();
551 void TouchObserverHUD::SetMode(Mode mode) {
552 if (mode_ == mode)
553 return;
554 mode_ = mode;
555 canvas_->StopAnimations();
556 switch (mode) {
557 case FULLSCREEN:
558 case PROJECTION:
559 label_container_->SetVisible(false);
560 canvas_->SetScale(1);
561 canvas_->SchedulePaint();
562 widget_->Show();
563 break;
564 case REDUCED_SCALE:
565 label_container_->SetVisible(true);
566 canvas_->SetScale(kReducedScale);
567 canvas_->SchedulePaint();
568 widget_->Show();
569 break;
570 case INVISIBLE:
571 widget_->Hide();
572 break;
576 void TouchObserverHUD::UpdateTouchPointLabel(int index) {
577 int trace_index = touch_log_->GetTraceIndex(index);
578 const TouchTrace& trace = touch_log_->traces()[trace_index];
579 TouchTrace::const_reverse_iterator point = trace.log().rbegin();
580 ui::EventType touch_status = point->type;
581 float touch_radius = std::max(point->radius_x, point->radius_y);
582 while (point != trace.log().rend() && point->type == ui::ET_TOUCH_CANCELLED)
583 point++;
584 DCHECK(point != trace.log().rend());
585 gfx::Point touch_position = point->location;
587 std::string string = base::StringPrintf("%2d: %s %s (%.4f)",
588 index,
589 GetTouchEventLabel(touch_status),
590 touch_position.ToString().c_str(),
591 touch_radius);
592 touch_labels_[index]->SetText(UTF8ToUTF16(string));
595 void TouchObserverHUD::OnTouchEvent(ui::TouchEvent* event) {
596 if (event->touch_id() >= kMaxTouchPoints)
597 return;
599 touch_log_->AddTouchPoint(*event);
600 canvas_->TouchPointAdded(event->touch_id());
602 UpdateTouchPointLabel(event->touch_id());
603 label_container_->SetSize(label_container_->GetPreferredSize());
606 void TouchObserverHUD::OnWidgetDestroying(views::Widget* widget) {
607 DCHECK_EQ(widget, widget_);
608 delete this;
611 void TouchObserverHUD::OnDisplayBoundsChanged(const gfx::Display& display) {
612 if (display.id() != display_id_)
613 return;
614 const gfx::Size& size = display.size();
615 widget_->SetSize(size);
616 canvas_->SetSize(size);
617 label_container_->SetY(size.height() / kReducedScale);
620 void TouchObserverHUD::OnDisplayAdded(const gfx::Display& new_display) {}
622 void TouchObserverHUD::OnDisplayRemoved(const gfx::Display& old_display) {
623 if (old_display.id() != display_id_)
624 return;
625 widget_->CloseNow();
628 #if defined(OS_CHROMEOS)
629 void TouchObserverHUD::OnDisplayModeChanged() {
630 // Clear touch HUD for any change in display mode (single, dual extended, dual
631 // mirrored, ...).
632 Clear();
634 #endif // defined(OS_CHROMEOS)
636 void TouchObserverHUD::OnDisplayConfigurationChanging() {
637 if (!root_window_)
638 return;
640 root_window_->RemovePreTargetHandler(this);
642 RootWindowController* controller = GetRootWindowController(root_window_);
643 controller->set_touch_observer_hud(NULL);
645 views::Widget::ReparentNativeView(
646 widget_->GetNativeView(),
647 Shell::GetContainer(root_window_,
648 internal::kShellWindowId_UnparentedControlContainer));
650 root_window_ = NULL;
653 void TouchObserverHUD::OnDisplayConfigurationChanged() {
654 if (root_window_)
655 return;
657 root_window_ = Shell::GetInstance()->display_controller()->
658 GetRootWindowForDisplayId(display_id_);
660 views::Widget::ReparentNativeView(
661 widget_->GetNativeView(),
662 Shell::GetContainer(root_window_,
663 internal::kShellWindowId_OverlayContainer));
665 RootWindowController* controller = GetRootWindowController(root_window_);
666 controller->set_touch_observer_hud(this);
668 root_window_->AddPreTargetHandler(this);
671 } // namespace internal
672 } // namespace ash