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.
4
5#include <cmath>
6#include <stdarg.h>
7#include <stdio.h>
8
9#include "ppapi/c/ppb_console.h"
10#include "ppapi/c/ppb_input_event.h"
11#include "ppapi/cpp/graphics_2d.h"
12#include "ppapi/cpp/image_data.h"
13#include "ppapi/cpp/input_event.h"
14#include "ppapi/cpp/instance.h"
15#include "ppapi/cpp/logging.h"
16#include "ppapi/cpp/module.h"
17#include "ppapi/cpp/mouse_lock.h"
18#include "ppapi/cpp/private/flash_fullscreen.h"
19#include "ppapi/cpp/rect.h"
20#include "ppapi/cpp/var.h"
21#include "ppapi/utility/completion_callback_factory.h"
22
23class MyInstance : public pp::Instance, public pp::MouseLock {
24 public:
25  explicit MyInstance(PP_Instance instance)
26      : pp::Instance(instance),
27        pp::MouseLock(this),
28        width_(0),
29        height_(0),
30        mouse_locked_(false),
31        pending_paint_(false),
32        waiting_for_flush_completion_(false),
33        callback_factory_(this),
34        console_(NULL),
35        flash_fullscreen_(this) {
36  }
37  virtual ~MyInstance() {}
38
39  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
40    console_ = reinterpret_cast<const PPB_Console*>(
41        pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE));
42    if (!console_)
43      return false;
44
45    RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE |
46                       PP_INPUTEVENT_CLASS_KEYBOARD);
47    return true;
48  }
49
50  virtual bool HandleInputEvent(const pp::InputEvent& event) {
51    switch (event.GetType()) {
52      case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
53        pp::MouseInputEvent mouse_event(event);
54        if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
55            !mouse_locked_) {
56          LockMouse(callback_factory_.NewCallback(&MyInstance::DidLockMouse));
57        }
58        return true;
59      }
60      case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
61        pp::MouseInputEvent mouse_event(event);
62        mouse_movement_ = mouse_event.GetMovement();
63        static unsigned int i = 0;
64        Log(PP_LOGLEVEL_LOG, "[%d] movementX: %d; movementY: %d\n", i++,
65            mouse_movement_.x(), mouse_movement_.y());
66        Paint();
67        return true;
68      }
69      case PP_INPUTEVENT_TYPE_KEYDOWN: {
70        pp::KeyboardInputEvent key_event(event);
71        if (key_event.GetKeyCode() == 13) {
72          // Lock the mouse when the Enter key is pressed.
73          if (mouse_locked_)
74            UnlockMouse();
75          else
76            LockMouse(callback_factory_.NewCallback(&MyInstance::DidLockMouse));
77          return true;
78        } else if (key_event.GetKeyCode() == 70) {
79          // Enter Flash fullscreen mode when the 'f' key is pressed.
80          if (!flash_fullscreen_.IsFullscreen())
81            flash_fullscreen_.SetFullscreen(true);
82          return true;
83        }
84        return false;
85      }
86      default:
87        return false;
88    }
89  }
90
91  virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
92    if (position.size().width() == width_ &&
93        position.size().height() == height_)
94      return;  // We don't care about the position, only the size.
95
96    width_ = position.size().width();
97    height_ = position.size().height();
98
99    device_context_ = pp::Graphics2D(this, pp::Size(width_, height_), false);
100    if (!BindGraphics(device_context_))
101      return;
102
103    Paint();
104  }
105
106  virtual void MouseLockLost() {
107    if (mouse_locked_) {
108      mouse_locked_ = false;
109      Paint();
110    } else {
111      PP_NOTREACHED();
112    }
113  }
114
115 private:
116  void DidLockMouse(int32_t result) {
117    mouse_locked_ = result == PP_OK;
118    mouse_movement_.set_x(0);
119    mouse_movement_.set_y(0);
120    Paint();
121  }
122
123  void DidFlush(int32_t result) {
124    waiting_for_flush_completion_ = false;
125    if (pending_paint_) {
126      pending_paint_ = false;
127      Paint();
128    }
129  }
130
131  void Paint() {
132    if (waiting_for_flush_completion_) {
133      pending_paint_ = true;
134      return;
135    }
136
137    pp::ImageData image = PaintImage(width_, height_);
138    if (!image.is_null()) {
139      device_context_.ReplaceContents(&image);
140      waiting_for_flush_completion_ = true;
141      device_context_.Flush(
142          callback_factory_.NewCallback(&MyInstance::DidFlush));
143    }
144  }
145
146  pp::ImageData PaintImage(int width, int height) {
147    pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
148                        pp::Size(width, height), false);
149    if (image.is_null())
150      return image;
151
152    const static int kCenteralSpotRadius = 5;
153    const static uint32_t kBackgroundColor = 0xfff0f0f0;
154    const static uint32_t kLockedForegroundColor = 0xfff08080;
155    const static uint32_t kUnlockedForegroundColor = 0xff80f080;
156
157    int center_x = width / 2;
158    int center_y = height / 2;
159    pp::Point vertex(mouse_movement_.x() + center_x,
160                     mouse_movement_.y() + center_y);
161    pp::Point anchor_1;
162    pp::Point anchor_2;
163    enum {
164      LEFT = 0,
165      RIGHT = 1,
166      UP = 2,
167      DOWN = 3
168    } direction = LEFT;
169    bool draw_needle = GetDistance(mouse_movement_.x(), mouse_movement_.y(),
170                                   0, 0) > kCenteralSpotRadius;
171    if (draw_needle) {
172      if (abs(mouse_movement_.x()) >= abs(mouse_movement_.y())) {
173         anchor_1.set_x(center_x);
174         anchor_1.set_y(center_y - kCenteralSpotRadius);
175         anchor_2.set_x(center_x);
176         anchor_2.set_y(center_y + kCenteralSpotRadius);
177         direction = (mouse_movement_.x() < 0) ? LEFT : RIGHT;
178         if (direction == LEFT)
179           anchor_1.swap(anchor_2);
180      } else {
181         anchor_1.set_x(center_x + kCenteralSpotRadius);
182         anchor_1.set_y(center_y);
183         anchor_2.set_x(center_x - kCenteralSpotRadius);
184         anchor_2.set_y(center_y);
185         direction = (mouse_movement_.y() < 0) ? UP : DOWN;
186         if (direction == UP)
187           anchor_1.swap(anchor_2);
188      }
189    }
190    uint32_t foreground_color = mouse_locked_ ? kLockedForegroundColor :
191                                                kUnlockedForegroundColor;
192    for (int y = 0; y < image.size().height(); ++y) {
193      for (int x = 0; x < image.size().width(); ++x) {
194        if (GetDistance(x, y, center_x, center_y) < kCenteralSpotRadius) {
195          *image.GetAddr32(pp::Point(x, y)) = foreground_color;
196          continue;
197        }
198        if (draw_needle) {
199          bool within_bound_1 =
200              ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) >
201              ((vertex.y() - anchor_1.y()) * (x - anchor_1.x()));
202          bool within_bound_2 =
203              ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) <
204              ((vertex.y() - anchor_2.y()) * (x - anchor_2.x()));
205          bool within_bound_3 =
206              (direction == UP && y < center_y) ||
207              (direction == DOWN && y > center_y) ||
208              (direction == LEFT && x < center_x) ||
209              (direction == RIGHT && x > center_x);
210
211          if (within_bound_1 && within_bound_2 && within_bound_3) {
212            *image.GetAddr32(pp::Point(x, y)) = foreground_color;
213            continue;
214          }
215        }
216        *image.GetAddr32(pp::Point(x, y)) = kBackgroundColor;
217      }
218    }
219
220    return image;
221  }
222
223  double GetDistance(int point_1_x, int point_1_y,
224                     int point_2_x, int point_2_y) {
225    return sqrt(pow(static_cast<double>(point_1_x - point_2_x), 2) +
226                pow(static_cast<double>(point_1_y - point_2_y), 2));
227  }
228
229  void Log(PP_LogLevel level, const char* format, ...) {
230    va_list args;
231    va_start(args, format);
232    char buf[512];
233    vsnprintf(buf, sizeof(buf) - 1, format, args);
234    buf[sizeof(buf) - 1] = '\0';
235    va_end(args);
236
237    pp::Var value(buf);
238    console_->Log(pp_instance(), level, value.pp_var());
239  }
240
241  int width_;
242  int height_;
243
244  bool mouse_locked_;
245  pp::Point mouse_movement_;
246
247  bool pending_paint_;
248  bool waiting_for_flush_completion_;
249
250  pp::CompletionCallbackFactory<MyInstance> callback_factory_;
251
252  const PPB_Console* console_;
253
254  pp::FlashFullscreen flash_fullscreen_;
255
256  pp::Graphics2D device_context_;
257};
258
259// This object is the global object representing this plugin library as long
260// as it is loaded.
261class MyModule : public pp::Module {
262 public:
263  MyModule() : pp::Module() {}
264  virtual ~MyModule() {}
265
266  // Override CreateInstance to create your customized Instance object.
267  virtual pp::Instance* CreateInstance(PP_Instance instance) {
268    return new MyInstance(instance);
269  }
270};
271
272namespace pp {
273
274// Factory function for your specialization of the Module object.
275Module* CreateModule() {
276  return new MyModule();
277}
278
279}  // namespace pp
280
281