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"
36 #include <X11/extensions/XInput2.h>
39 #include "ui/base/x/valuators.h"
45 const int kPointRadius
= 20;
46 const SkColor kColors
[] = {
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
) {
73 case ui::ET_TOUCH_PRESSED
:
75 case ui::ET_TOUCH_MOVED
:
77 case ui::ET_TOUCH_RELEASED
:
79 case ui::ET_TOUCH_CANCELLED
:
87 int GetTrackingId(const ui::TouchEvent
& event
) {
88 if (!event
.HasNativeEvent())
90 #if defined(USE_XI2_MT)
91 ui::ValuatorTracker
* valuators
= ui::ValuatorTracker::GetInstance();
93 if (valuators
->ExtractValuator(*event
.native_event(),
94 ui::ValuatorTracker::VAL_TRACKING_ID
,
96 return static_cast<int>(tracking_id
);
102 int GetSourceDeviceId(const ui::TouchEvent
& event
) {
103 if (!event
.HasNativeEvent())
106 XEvent
* xev
= event
.native_event();
107 return static_cast<XIDeviceEvent
*>(xev
->xcookie
.data
)->sourceid
;
112 // A TouchPointLog represents a single touch-event of a touch point.
113 struct TouchPointLog
{
115 explicit TouchPointLog(const ui::TouchEvent
& touch
)
116 : id(touch
.touch_id()),
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
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
);
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).
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
;
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());
193 std::vector
<TouchPointLog
> log_
;
195 DISALLOW_COPY_AND_ASSIGN(TouchTrace
);
198 // A TouchLog keeps track of all touch events of all touch points.
201 TouchLog() : next_trace_index_(0) {
204 void AddTouchPoint(const ui::TouchEvent
& touch
) {
205 if (touch
.type() == ui::ET_TOUCH_PRESSED
)
211 next_trace_index_
= 0;
212 for (int i
= 0; i
< kMaxPaths
; ++i
)
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());
225 int GetTraceIndex(int touch_id
) const {
226 return touch_id_to_trace_index_
.at(touch_id
);
229 const TouchTrace
* traces() const {
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_
;
239 if (!traces_
[next_trace_index_
].active())
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
{
265 TouchHudCanvas(TouchObserverHUD
* owner
, const TouchLog
& touch_log
)
267 touch_log_(touch_log
),
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
) {
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
);
315 for (int i
= 0; i
< kMaxPaths
; ++i
)
317 if (owner_
->mode() == TouchObserverHUD::PROJECTION
)
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());
336 if (!paths_
[trace_index
].getLastPt(&last
) || x
!= last
.x() ||
338 paths_
[trace_index
].addCircle(x
, y
, SkIntToScalar(kPointRadius
));
343 void StartAnimation(int path_index
) {
344 DCHECK(!fadeouts_
[path_index
].get());
345 fadeouts_
[path_index
].reset(new ui::LinearAnimation(kFadeoutDurationInMs
,
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())
358 TouchTrace::const_reverse_iterator point
= trace
.log().rbegin();
359 while (point
!= trace
.log().rend() &&
360 point
->type
== ui::ET_TOUCH_CANCELLED
)
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
,
377 projection_fill_paint_
.setShader(shader
);
379 canvas
->DrawCircle(point
->location
, SkIntToScalar(kPointRadius
),
380 projection_fill_paint_
);
381 canvas
->DrawCircle(point
->location
, SkIntToScalar(kPointRadius
),
382 projection_stroke_paint_
);
385 for (int i
= 0; i
< kMaxPaths
; ++i
) {
386 if (paths_
[i
].countPoints() == 0)
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
);
403 // Overridden from ui::AnimationDelegate.
404 virtual void AnimationProgressed(const ui::Animation
* animation
) OVERRIDE
{
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_
;
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
];
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
),
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
,
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(
475 internal::kShellWindowId_OverlayContainer
);
476 widget_
->Init(params
);
477 widget_
->SetContentsView(content
);
478 widget_
->StackAtTop();
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);
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();
516 value
->Set(base::Int64ToString(display_id
), list
.release());
522 void TouchObserverHUD::ChangeToNextMode() {
525 SetMode(REDUCED_SCALE
);
539 void TouchObserverHUD::Clear() {
540 if (widget_
->IsVisible())
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
) {
555 canvas_
->StopAnimations();
559 label_container_
->SetVisible(false);
560 canvas_
->SetScale(1);
561 canvas_
->SchedulePaint();
565 label_container_
->SetVisible(true);
566 canvas_
->SetScale(kReducedScale
);
567 canvas_
->SchedulePaint();
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
)
584 DCHECK(point
!= trace
.log().rend());
585 gfx::Point touch_position
= point
->location
;
587 std::string string
= base::StringPrintf("%2d: %s %s (%.4f)",
589 GetTouchEventLabel(touch_status
),
590 touch_position
.ToString().c_str(),
592 touch_labels_
[index
]->SetText(UTF8ToUTF16(string
));
595 void TouchObserverHUD::OnTouchEvent(ui::TouchEvent
* event
) {
596 if (event
->touch_id() >= kMaxTouchPoints
)
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_
);
611 void TouchObserverHUD::OnDisplayBoundsChanged(const gfx::Display
& display
) {
612 if (display
.id() != display_id_
)
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_
)
628 #if defined(OS_CHROMEOS)
629 void TouchObserverHUD::OnDisplayModeChanged() {
630 // Clear touch HUD for any change in display mode (single, dual extended, dual
634 #endif // defined(OS_CHROMEOS)
636 void TouchObserverHUD::OnDisplayConfigurationChanging() {
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
));
653 void TouchObserverHUD::OnDisplayConfigurationChanged() {
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