Swap touch HUD when switching primary display
[chromium-blink-merge.git] / ash / touch / touch_observer_hud.cc
blobab87c66de8d611ce255316ab68825609d301cd06
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 "ui/aura/root_window.h"
19 #include "ui/aura/window.h"
20 #include "ui/base/events/event.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/display.h"
23 #include "ui/gfx/rect.h"
24 #include "ui/gfx/screen.h"
25 #include "ui/gfx/size.h"
26 #include "ui/gfx/transform.h"
27 #include "ui/views/background.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/layout/box_layout.h"
30 #include "ui/views/widget/widget.h"
32 #if defined(USE_X11)
33 #include <X11/extensions/XInput2.h>
34 #include <X11/Xlib.h>
36 #include "ui/base/x/valuators.h"
37 #endif
39 namespace ash {
40 namespace internal {
42 const int kPointRadius = 20;
43 const int kColors[] = {
44 static_cast<int>(SK_ColorYELLOW),
45 static_cast<int>(SK_ColorGREEN),
46 static_cast<int>(SK_ColorRED),
47 static_cast<int>(SK_ColorBLUE),
48 static_cast<int>(SK_ColorGRAY),
49 static_cast<int>(SK_ColorMAGENTA),
50 static_cast<int>(SK_ColorCYAN),
51 static_cast<int>(SK_ColorWHITE),
52 static_cast<int>(SK_ColorBLACK)
54 const int kAlpha = 0x60;
55 const int kMaxPaths = arraysize(kColors);
56 const int kReducedScale = 10;
58 const char* GetTouchEventLabel(ui::EventType type) {
59 switch (type) {
60 case ui::ET_UNKNOWN:
61 return " ";
62 case ui::ET_TOUCH_PRESSED:
63 return "P";
64 case ui::ET_TOUCH_MOVED:
65 return "M";
66 case ui::ET_TOUCH_RELEASED:
67 return "R";
68 case ui::ET_TOUCH_CANCELLED:
69 return "C";
70 default:
71 break;
73 return "?";
76 int GetTrackingId(const ui::TouchEvent& event) {
77 if (!event.HasNativeEvent())
78 return 0;
79 #if defined(USE_XI2_MT)
80 ui::ValuatorTracker* valuators = ui::ValuatorTracker::GetInstance();
81 float tracking_id;
82 if (valuators->ExtractValuator(*event.native_event(),
83 ui::ValuatorTracker::VAL_TRACKING_ID,
84 &tracking_id)) {
85 return static_cast<int>(tracking_id);
87 #endif
88 return 0;
91 int GetSourceDeviceId(const ui::TouchEvent& event) {
92 if (!event.HasNativeEvent())
93 return 0;
94 #if defined(USE_X11)
95 XEvent* xev = event.native_event();
96 return static_cast<XIDeviceEvent*>(xev->xcookie.data)->sourceid;
97 #endif
98 return 0;
101 // A TouchPointLog represents a single touch-event of a touch point.
102 struct TouchPointLog {
103 public:
104 explicit TouchPointLog(const ui::TouchEvent& touch)
105 : id(touch.touch_id()),
106 type(touch.type()),
107 location(touch.root_location()),
108 timestamp(touch.time_stamp().InMillisecondsF()),
109 radius_x(touch.radius_x()),
110 radius_y(touch.radius_y()),
111 pressure(touch.force()),
112 tracking_id(GetTrackingId(touch)),
113 source_device(GetSourceDeviceId(touch)) {
116 // Populates a dictionary value with all the information about the touch
117 // point.
118 scoped_ptr<DictionaryValue> GetAsDictionary() const {
119 scoped_ptr<DictionaryValue> value(new DictionaryValue());
121 value->SetInteger("id", id);
122 value->SetString("type", std::string(GetTouchEventLabel(type)));
123 value->SetString("location", location.ToString());
124 value->SetDouble("timestamp", timestamp);
125 value->SetDouble("radius_x", radius_x);
126 value->SetDouble("radius_y", radius_y);
127 value->SetDouble("pressure", pressure);
128 value->SetInteger("tracking_id", tracking_id);
129 value->SetInteger("source_device", source_device);
131 return value.Pass();
134 int id;
135 ui::EventType type;
136 gfx::Point location;
137 double timestamp;
138 float radius_x;
139 float radius_y;
140 float pressure;
141 int tracking_id;
142 int source_device;
145 // A TouchTrace keeps track of all the touch events of a single touch point
146 // (starting from a touch-press and ending at touch-release).
147 class TouchTrace {
148 public:
149 TouchTrace() {
152 void AddTouchPoint(const ui::TouchEvent& touch) {
153 log_.push_back(TouchPointLog(touch));
156 bool empty() const { return log_.empty(); }
158 // Returns a list containing data from all events for the touch point.
159 scoped_ptr<ListValue> GetAsList() const {
160 scoped_ptr<ListValue> list(new ListValue());
161 for (std::vector<TouchPointLog>::const_iterator i = log_.begin();
162 i != log_.end(); ++i) {
163 list->Append((*i).GetAsDictionary().release());
166 return list.Pass();
169 void Reset() {
170 log_.clear();
173 private:
174 std::vector<TouchPointLog> log_;
176 DISALLOW_COPY_AND_ASSIGN(TouchTrace);
179 class TouchHudCanvas : public views::View {
180 public:
181 explicit TouchHudCanvas(TouchObserverHUD* owner)
182 : owner_(owner),
183 path_index_(0),
184 color_index_(0),
185 scale_(1) {
186 SetPaintToLayer(true);
187 SetFillsBoundsOpaquely(false);
190 virtual ~TouchHudCanvas() {}
192 void SetScale(int scale) {
193 if (scale_ == scale)
194 return;
195 scale_ = scale;
196 gfx::Transform transform;
197 transform.Scale(1. / scale_, 1. / scale_);
198 layer()->SetTransform(transform);
201 int scale() const { return scale_; }
203 void Start(const ui::TouchEvent& touch) {
204 int id = touch.touch_id();
205 paths_[path_index_].reset();
206 traces_[path_index_].Reset();
207 colors_[path_index_] = SkColorSetA(kColors[color_index_], kAlpha);
208 color_index_ = (color_index_ + 1) % arraysize(kColors);
209 touch_id_to_path_[id] = path_index_;
210 path_index_ = (path_index_ + 1) % kMaxPaths;
211 AddPoint(touch);
212 SchedulePaint();
215 void AddPoint(const ui::TouchEvent& touch) {
216 int id = touch.touch_id();
217 const gfx::Point& point = touch.root_location();
218 int path_id = touch_id_to_path_[id];
219 SkScalar x = SkIntToScalar(point.x());
220 SkScalar y = SkIntToScalar(point.y());
221 SkPoint last;
222 if (!paths_[path_id].getLastPt(&last) || x != last.x() || y != last.y()) {
223 paths_[path_id].addCircle(x, y, SkIntToScalar(kPointRadius));
224 traces_[path_id].AddTouchPoint(touch);
226 SchedulePaint();
229 void Clear() {
230 path_index_ = 0;
231 color_index_ = 0;
232 for (size_t i = 0; i < arraysize(paths_); ++i) {
233 paths_[i].reset();
234 traces_[i].Reset();
237 SchedulePaint();
240 scoped_ptr<ListValue> GetAsList() const {
241 scoped_ptr<ListValue> list(new ListValue());
242 for (size_t i = 0; i < arraysize(traces_); ++i) {
243 if (!traces_[i].empty())
244 list->Append(traces_[i].GetAsList().release());
246 return list.Pass();
249 private:
250 // Overridden from views::View.
251 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
252 canvas->DrawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
253 canvas->DrawColor(SkColorSetARGB(0, 0, 0, 0));
255 SkPaint paint;
256 paint.setStyle(SkPaint::kFill_Style);
257 for (size_t i = 0; i < arraysize(paths_); ++i) {
258 if (paths_[i].countPoints() == 0)
259 continue;
260 paint.setColor(colors_[i]);
261 canvas->DrawPath(paths_[i], paint);
265 TouchObserverHUD* owner_;
266 SkPath paths_[kMaxPaths];
267 SkColor colors_[kMaxPaths];
268 TouchTrace traces_[kMaxPaths];
270 int path_index_;
271 int color_index_;
272 int scale_;
274 std::map<int, int> touch_id_to_path_;
276 DISALLOW_COPY_AND_ASSIGN(TouchHudCanvas);
279 TouchObserverHUD::TouchObserverHUD(aura::RootWindow* initial_root)
280 : display_id_(initial_root->GetProperty(kDisplayIdKey)),
281 root_window_(initial_root) {
282 const gfx::Display& display =
283 Shell::GetInstance()->display_manager()->GetDisplayForId(display_id_);
285 views::View* content = new views::View;
287 canvas_ = new TouchHudCanvas(this);
288 content->AddChildView(canvas_);
290 const gfx::Size& display_size = display.size();
291 canvas_->SetSize(display_size);
292 content->SetSize(display_size);
294 label_container_ = new views::View;
295 label_container_->SetLayoutManager(new views::BoxLayout(
296 views::BoxLayout::kVertical, 0, 0, 0));
298 for (int i = 0; i < kMaxTouchPoints; ++i) {
299 touch_status_[i] = ui::ET_UNKNOWN;
300 touch_labels_[i] = new views::Label;
301 touch_labels_[i]->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
302 touch_labels_[i]->SetShadowColors(SK_ColorWHITE,
303 SK_ColorWHITE);
304 touch_labels_[i]->SetShadowOffset(1, 1);
305 label_container_->AddChildView(touch_labels_[i]);
307 label_container_->SetX(0);
308 label_container_->SetY(display_size.height() / kReducedScale);
309 label_container_->SetSize(label_container_->GetPreferredSize());
310 label_container_->SetVisible(false);
311 content->AddChildView(label_container_);
313 widget_ = new views::Widget();
314 views::Widget::InitParams
315 params(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
316 params.transparent = true;
317 params.can_activate = false;
318 params.accept_events = false;
319 params.bounds = gfx::Rect(display_size);
320 params.parent = Shell::GetContainer(
321 root_window_,
322 internal::kShellWindowId_OverlayContainer);
323 widget_->Init(params);
324 widget_->SetContentsView(content);
325 widget_->StackAtTop();
326 widget_->Show();
328 widget_->AddObserver(this);
330 // Observe changes in display size and mode to update touch HUD.
331 Shell::GetScreen()->AddObserver(this);
332 #if defined(OS_CHROMEOS)
333 Shell::GetInstance()->output_configurator()->AddObserver(this);
334 #endif // defined(OS_CHROMEOS)
336 Shell::GetInstance()->display_controller()->AddObserver(this);
337 root_window_->AddPreTargetHandler(this);
340 TouchObserverHUD::~TouchObserverHUD() {
341 Shell::GetInstance()->display_controller()->RemoveObserver(this);
343 #if defined(OS_CHROMEOS)
344 Shell::GetInstance()->output_configurator()->RemoveObserver(this);
345 #endif // defined(OS_CHROMEOS)
346 Shell::GetScreen()->RemoveObserver(this);
348 widget_->RemoveObserver(this);
351 // static
352 scoped_ptr<DictionaryValue> TouchObserverHUD::GetAllAsDictionary() {
353 scoped_ptr<DictionaryValue> value(new DictionaryValue());
354 Shell::RootWindowList roots = Shell::GetInstance()->GetAllRootWindows();
355 for (Shell::RootWindowList::iterator iter = roots.begin();
356 iter != roots.end(); ++iter) {
357 internal::RootWindowController* controller = GetRootWindowController(*iter);
358 if (controller->touch_observer_hud()) {
359 int64 display_id = (*iter)->GetProperty(kDisplayIdKey);
360 scoped_ptr<ListValue> list =
361 controller->touch_observer_hud()->GetLogAsList();
362 if (!list->empty())
363 value->Set(base::Int64ToString(display_id), list.release());
366 return value.Pass();
369 void TouchObserverHUD::ChangeToNextMode() {
370 if (widget_->IsVisible()) {
371 if (canvas_->scale() == kReducedScale) {
372 widget_->Hide();
373 } else {
374 label_container_->SetVisible(true);
375 canvas_->SetScale(kReducedScale);
377 } else {
378 canvas_->SetScale(1);
379 label_container_->SetVisible(false);
380 widget_->Show();
384 void TouchObserverHUD::Clear() {
385 if (widget_->IsVisible())
386 canvas_->Clear();
389 scoped_ptr<ListValue> TouchObserverHUD::GetLogAsList() const {
390 return canvas_->GetAsList();
393 void TouchObserverHUD::UpdateTouchPointLabel(int index) {
394 std::string string = base::StringPrintf("%2d: %s %s (%.4f)",
395 index, GetTouchEventLabel(touch_status_[index]),
396 touch_positions_[index].ToString().c_str(),
397 touch_radius_[index]);
398 touch_labels_[index]->SetText(UTF8ToUTF16(string));
401 void TouchObserverHUD::OnTouchEvent(ui::TouchEvent* event) {
402 if (event->touch_id() >= kMaxTouchPoints)
403 return;
405 if (event->type() != ui::ET_TOUCH_CANCELLED)
406 touch_positions_[event->touch_id()] = event->root_location();
408 touch_radius_[event->touch_id()] = std::max(event->radius_x(),
409 event->radius_y());
411 if (event->type() == ui::ET_TOUCH_PRESSED)
412 canvas_->Start(*event);
413 else if (event->type() != ui::ET_TOUCH_CANCELLED)
414 canvas_->AddPoint(*event);
415 touch_status_[event->touch_id()] = event->type();
417 UpdateTouchPointLabel(event->touch_id());
418 label_container_->SetSize(label_container_->GetPreferredSize());
421 void TouchObserverHUD::OnWidgetDestroying(views::Widget* widget) {
422 DCHECK_EQ(widget, widget_);
423 delete this;
426 void TouchObserverHUD::OnDisplayBoundsChanged(const gfx::Display& display) {
427 if (display.id() != display_id_)
428 return;
429 const gfx::Size& size = display.size();
430 widget_->SetSize(size);
431 canvas_->SetSize(size);
432 label_container_->SetY(size.height() / kReducedScale);
435 void TouchObserverHUD::OnDisplayAdded(const gfx::Display& new_display) {}
437 void TouchObserverHUD::OnDisplayRemoved(const gfx::Display& old_display) {
438 if (old_display.id() != display_id_)
439 return;
440 widget_->CloseNow();
443 #if defined(OS_CHROMEOS)
444 void TouchObserverHUD::OnDisplayModeChanged() {
445 // Clear touch HUD for any change in display mode (single, dual extended, dual
446 // mirrored, ...).
447 Clear();
449 #endif // defined(OS_CHROMEOS)
451 void TouchObserverHUD::OnDisplayConfigurationChanging() {
452 if (!root_window_)
453 return;
455 root_window_->RemovePreTargetHandler(this);
457 RootWindowController* controller = GetRootWindowController(root_window_);
458 controller->set_touch_observer_hud(NULL);
460 views::Widget::ReparentNativeView(
461 widget_->GetNativeView(),
462 Shell::GetContainer(root_window_,
463 internal::kShellWindowId_UnparentedControlContainer));
465 root_window_ = NULL;
468 void TouchObserverHUD::OnDisplayConfigurationChanged() {
469 if (root_window_)
470 return;
472 root_window_ = Shell::GetInstance()->display_controller()->
473 GetRootWindowForDisplayId(display_id_);
475 views::Widget::ReparentNativeView(
476 widget_->GetNativeView(),
477 Shell::GetContainer(root_window_,
478 internal::kShellWindowId_OverlayContainer));
480 RootWindowController* controller = GetRootWindowController(root_window_);
481 controller->set_touch_observer_hud(this);
483 root_window_->AddPreTargetHandler(this);
486 } // namespace internal
487 } // namespace ash