root/native_client_sdk/src/examples/api/mouse_lock/mouse_lock.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Init
  2. HandleInputEvent
  3. DidChangeView
  4. MouseLockLost
  5. DidLockMouse
  6. DidFlush
  7. Paint
  8. PaintImage
  9. ClearToBackground
  10. DrawCenterSpot
  11. DrawNeedle
  12. Log
  13. CreateInstance
  14. CreateModule

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <cmath>
#include <cstdlib>

#include <algorithm>

#include "mouse_lock.h"

#ifdef WIN32
#undef min
#undef max
#undef PostMessage
#endif

// Indicate the direction of the mouse location relative to the center of the
// view.  These values are used to determine which 2D quadrant the needle lies
// in.
typedef enum {
  kLeft = 0,
  kRight = 1,
  kUp = 2,
  kDown = 3
} MouseDirection;

namespace {
const int kCentralSpotRadius = 5;
const uint32_t kReturnKeyCode = 13;
const uint32_t kBackgroundColor = 0xff606060;
const uint32_t kForegroundColor = 0xfff08080;
}  // namespace

MouseLockInstance::~MouseLockInstance() {
  free(background_scanline_);
  background_scanline_ = NULL;
}

bool MouseLockInstance::Init(uint32_t argc,
                             const char* argn[],
                             const char* argv[]) {
  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD);
  return true;
}

bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) {
  switch (event.GetType()) {
    case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
      if (mouse_locked_) {
        UnlockMouse();
      } else {
        LockMouse(
            callback_factory_.NewCallback(&MouseLockInstance::DidLockMouse));
      }
      return true;
    }

    case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
      pp::MouseInputEvent mouse_event(event);
      mouse_movement_ = mouse_event.GetMovement();
      Paint();
      return true;
    }

    case PP_INPUTEVENT_TYPE_KEYDOWN: {
      pp::KeyboardInputEvent key_event(event);

      // Switch in and out of fullscreen when 'Enter' is hit
      if (key_event.GetKeyCode() == kReturnKeyCode) {
        // Ignore switch if in transition
        if (!is_context_bound_)
          return true;

        if (fullscreen_.IsFullscreen()) {
          if (!fullscreen_.SetFullscreen(false)) {
            Log("Could not leave fullscreen mode\n");
          } else {
            is_context_bound_ = false;
          }
        } else {
          if (!fullscreen_.SetFullscreen(true)) {
            Log("Could not enter fullscreen mode\n");
          } else {
            is_context_bound_ = false;
          }
        }
      }
      return true;
    }

    case PP_INPUTEVENT_TYPE_MOUSEUP:
    case PP_INPUTEVENT_TYPE_MOUSEENTER:
    case PP_INPUTEVENT_TYPE_MOUSELEAVE:
    case PP_INPUTEVENT_TYPE_WHEEL:
    case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
    case PP_INPUTEVENT_TYPE_KEYUP:
    case PP_INPUTEVENT_TYPE_CHAR:
    case PP_INPUTEVENT_TYPE_CONTEXTMENU:
    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
    case PP_INPUTEVENT_TYPE_IME_TEXT:
    case PP_INPUTEVENT_TYPE_UNDEFINED:
    case PP_INPUTEVENT_TYPE_TOUCHSTART:
    case PP_INPUTEVENT_TYPE_TOUCHMOVE:
    case PP_INPUTEVENT_TYPE_TOUCHEND:
    case PP_INPUTEVENT_TYPE_TOUCHCANCEL:
    default:
      return false;
  }
}

void MouseLockInstance::DidChangeView(const pp::View& view) {
  // DidChangeView can get called for many reasons, so we only want to
  // rebuild the device context if we really need to.

  if ((size_ == view.GetRect().size()) &&
      (was_fullscreen_ == view.IsFullscreen()) && is_context_bound_) {
    Log("DidChangeView SKIP %d,%d FULL=%s CTX Bound=%s",
        view.GetRect().width(),
        view.GetRect().height(),
        view.IsFullscreen() ? "true" : "false",
        is_context_bound_ ? "true" : "false");
    return;
  }

  Log("DidChangeView DO %d,%d FULL=%s CTX Bound=%s",
      view.GetRect().width(),
      view.GetRect().height(),
      view.IsFullscreen() ? "true" : "false",
      is_context_bound_ ? "true" : "false");

  size_ = view.GetRect().size();
  device_context_ = pp::Graphics2D(this, size_, false);
  waiting_for_flush_completion_ = false;

  is_context_bound_ = BindGraphics(device_context_);
  if (!is_context_bound_) {
    Log("Could not bind to 2D context\n.");
    return;
  } else {
    Log("Bound to 2D context size %d,%d.\n", size_.width(), size_.height());
  }

  // Create a scanline for fill.
  delete[] background_scanline_;
  background_scanline_ = new uint32_t[size_.width()];
  uint32_t* bg_pixel = background_scanline_;
  for (int x = 0; x < size_.width(); ++x) {
    *bg_pixel++ = kBackgroundColor;
  }

  // Remember if we are fullscreen or not
  was_fullscreen_ = view.IsFullscreen();

  // Paint this context
  Paint();
}

void MouseLockInstance::MouseLockLost() {
  if (mouse_locked_) {
    Log("Mouselock unlocked.\n");
    mouse_locked_ = false;
    Paint();
  }
}

void MouseLockInstance::DidLockMouse(int32_t result) {
  mouse_locked_ = result == PP_OK;
  if (result != PP_OK) {
    Log("Mouselock failed with failed with error number %d.\n", result);
  }
  mouse_movement_.set_x(0);
  mouse_movement_.set_y(0);
  Paint();
}

void MouseLockInstance::DidFlush(int32_t result) {
  if (result != 0)
    Log("Flushed failed with error number %d.\n", result);
  waiting_for_flush_completion_ = false;
}

void MouseLockInstance::Paint() {
  // If we are already waiting to paint...
  if (waiting_for_flush_completion_) {
    return;
  }

  pp::ImageData image = PaintImage(size_);
  if (image.is_null()) {
    Log("Could not create image data\n");
    return;
  }

  device_context_.ReplaceContents(&image);
  waiting_for_flush_completion_ = true;
  device_context_.Flush(
      callback_factory_.NewCallback(&MouseLockInstance::DidFlush));
}

pp::ImageData MouseLockInstance::PaintImage(const pp::Size& size) {
  pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, false);
  if (image.is_null() || image.data() == NULL) {
    Log("Skipping image.\n");
    return image;
  }

  ClearToBackground(&image);

  DrawCenterSpot(&image, kForegroundColor);
  DrawNeedle(&image, kForegroundColor);
  return image;
}

void MouseLockInstance::ClearToBackground(pp::ImageData* image) {
  if (image == NULL) {
    Log("ClearToBackground with NULL image.");
    return;
  }
  if (background_scanline_ == NULL) {
    Log("ClearToBackground with no scanline.");
    return;
  }
  int image_height = image->size().height();
  int image_width = image->size().width();

  for (int y = 0; y < image_height; ++y) {
    uint32_t* scanline = image->GetAddr32(pp::Point(0, y));
    memcpy(scanline,
           background_scanline_,
           image_width * sizeof(*background_scanline_));
  }
}

void MouseLockInstance::DrawCenterSpot(pp::ImageData* image,
                                       uint32_t spot_color) {
  if (image == NULL) {
    Log("DrawCenterSpot with NULL image");
    return;
  }
  // Draw the center spot.  The ROI is bounded by the size of the spot, plus
  // one pixel.
  int center_x = image->size().width() / 2;
  int center_y = image->size().height() / 2;
  int region_of_interest_radius = kCentralSpotRadius + 1;

  pp::Point left_top(std::max(0, center_x - region_of_interest_radius),
                     std::max(0, center_y - region_of_interest_radius));
  pp::Point right_bottom(
      std::min(image->size().width(), center_x + region_of_interest_radius),
      std::min(image->size().height(), center_y + region_of_interest_radius));
  for (int y = left_top.y(); y < right_bottom.y(); ++y) {
    for (int x = left_top.x(); x < right_bottom.x(); ++x) {
      if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) {
        *image->GetAddr32(pp::Point(x, y)) = spot_color;
      }
    }
  }
}

void MouseLockInstance::DrawNeedle(pp::ImageData* image,
                                   uint32_t needle_color) {
  if (image == NULL) {
    Log("DrawNeedle with NULL image");
    return;
  }
  if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <=
      kCentralSpotRadius) {
    return;
  }

  int abs_mouse_x = std::abs(mouse_movement_.x());
  int abs_mouse_y = std::abs(mouse_movement_.y());
  int center_x = image->size().width() / 2;
  int center_y = image->size().height() / 2;
  pp::Point vertex(mouse_movement_.x() + center_x,
                   mouse_movement_.y() + center_y);
  pp::Point anchor_1;
  pp::Point anchor_2;
  MouseDirection direction = kLeft;

  if (abs_mouse_x >= abs_mouse_y) {
    anchor_1.set_x(center_x);
    anchor_1.set_y(center_y - kCentralSpotRadius);
    anchor_2.set_x(center_x);
    anchor_2.set_y(center_y + kCentralSpotRadius);
    direction = (mouse_movement_.x() < 0) ? kLeft : kRight;
    if (direction == kLeft)
      anchor_1.swap(anchor_2);
  } else {
    anchor_1.set_x(center_x + kCentralSpotRadius);
    anchor_1.set_y(center_y);
    anchor_2.set_x(center_x - kCentralSpotRadius);
    anchor_2.set_y(center_y);
    direction = (mouse_movement_.y() < 0) ? kUp : kDown;
    if (direction == kUp)
      anchor_1.swap(anchor_2);
  }

  pp::Point left_top(std::max(0, center_x - abs_mouse_x),
                     std::max(0, center_y - abs_mouse_y));
  pp::Point right_bottom(
      std::min(image->size().width(), center_x + abs_mouse_x),
      std::min(image->size().height(), center_y + abs_mouse_y));
  for (int y = left_top.y(); y < right_bottom.y(); ++y) {
    for (int x = left_top.x(); x < right_bottom.x(); ++x) {
      bool within_bound_1 = ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) >
                            ((vertex.y() - anchor_1.y()) * (x - anchor_1.x()));
      bool within_bound_2 = ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) <
                            ((vertex.y() - anchor_2.y()) * (x - anchor_2.x()));
      bool within_bound_3 = (direction == kUp && y < center_y) ||
                            (direction == kDown && y > center_y) ||
                            (direction == kLeft && x < center_x) ||
                            (direction == kRight && x > center_x);

      if (within_bound_1 && within_bound_2 && within_bound_3) {
        *image->GetAddr32(pp::Point(x, y)) = needle_color;
      }
    }
  }
}

void MouseLockInstance::Log(const char* format, ...) {
  static PPB_Console* console =
      (PPB_Console*)pp::Module::Get()->GetBrowserInterface(
          PPB_CONSOLE_INTERFACE);

  if (NULL == console)
    return;
  va_list args;
  va_start(args, format);
  char buf[512];
  vsnprintf(buf, sizeof(buf) - 1, format, args);
  buf[sizeof(buf) - 1] = '\0';
  va_end(args);

  pp::Var value(buf);
  console->Log(pp_instance(), PP_LOGLEVEL_ERROR, value.pp_var());
}

// This object is the global object representing this plugin library as long
// as it is loaded.
class MouseLockModule : public pp::Module {
 public:
  MouseLockModule() : pp::Module() {}
  virtual ~MouseLockModule() {}

  // Override CreateInstance to create your customized Instance object.
  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new MouseLockInstance(instance);
  }
};

namespace pp {

// Factory function for your specialization of the Module object.
Module* CreateModule() { return new MouseLockModule(); }

}  // namespace pp

/* [<][>][^][v][top][bottom][index][help] */