chromium/ui/views/win/pen_event_processor.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/win/pen_event_processor.h"

#include "base/check.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"

namespace views {
namespace {

int GetFlagsFromPointerMessage(UINT message, const POINTER_INFO& pointer_info) {
  int flags = ui::EF_NONE;
  if (pointer_info.pointerFlags & POINTER_FLAG_FIRSTBUTTON)
    flags |= ui::EF_LEFT_MOUSE_BUTTON;

  if (pointer_info.pointerFlags & POINTER_FLAG_SECONDBUTTON)
    flags |= ui::EF_RIGHT_MOUSE_BUTTON;

  return flags;
}

}  // namespace

PenEventProcessor::PenEventProcessor(ui::SequentialIDGenerator* id_generator,
                                     bool direct_manipulation_enabled)
    : id_generator_(id_generator),
      direct_manipulation_enabled_(direct_manipulation_enabled) {}

PenEventProcessor::~PenEventProcessor() = default;

std::unique_ptr<ui::Event> PenEventProcessor::GenerateEvent(
    UINT message,
    UINT32 pointer_id,
    const POINTER_PEN_INFO& pointer_pen_info,
    const gfx::Point& point) {
  auto mapped_pointer_id =
      static_cast<ui::PointerId>(id_generator_->GetGeneratedID(pointer_id));

  // We are now creating a fake mouse event with pointer type of pen from
  // the WM_POINTER message and then setting up an associated pointer
  // details in the MouseEvent which contains the pen's information.
  ui::EventPointerType input_type = ui::EventPointerType::kPen;
  // For the pointerup event, the penFlags is not set to PEN_FLAG_ERASER, so we
  // have to check if previously the pointer type is an eraser.
  if (pointer_pen_info.penFlags & PEN_FLAG_ERASER) {
    input_type = ui::EventPointerType::kEraser;
    DCHECK(!eraser_pointer_id_ || *eraser_pointer_id_ == mapped_pointer_id);
    eraser_pointer_id_ = mapped_pointer_id;
  } else if (eraser_pointer_id_ && *eraser_pointer_id_ == mapped_pointer_id &&
             (message == WM_POINTERUP || message == WM_NCPOINTERUP)) {
    input_type = ui::EventPointerType::kEraser;
    eraser_pointer_id_.reset();
  }

  // convert pressure into a float [0, 1]. The range of the pressure is
  // [0, 1024] as specified on MSDN.
  float pressure = static_cast<float>(pointer_pen_info.pressure) / 1024;
  int rotation_angle = static_cast<int>(pointer_pen_info.rotation) % 180;
  if (rotation_angle < 0)
    rotation_angle += 180;
  int tilt_x = pointer_pen_info.tiltX;
  int tilt_y = pointer_pen_info.tiltY;

  ui::PointerDetails pointer_details(
      input_type, mapped_pointer_id, /* radius_x */ 0.0f, /* radius_y */ 0.0f,
      pressure, rotation_angle, tilt_x, tilt_y, /* tangential_pressure */ 0.0f);

  int32_t device_id = pen_id_handler_.TryGetPenUniqueId(pointer_id)
                          .value_or(ui::ED_UNKNOWN_DEVICE);

  // If the flag is disabled, we send mouse events for all pen inputs.
  if (!direct_manipulation_enabled_) {
    return GenerateMouseEvent(message, pointer_id, pointer_pen_info.pointerInfo,
                              point, pointer_details, device_id);
  }
  bool is_pointer_event =
      message == WM_POINTERENTER || message == WM_POINTERLEAVE;

  // Send MouseEvents when the pen is hovering or any buttons (other than the
  // tip) are depressed when the stylus makes contact with the digitizer. Ensure
  // we read |send_touch_for_pen_| before we process the event as we want to
  // ensure a TouchRelease is sent appropriately at the end when the stylus is
  // no longer in contact with the digitizer.
  bool send_touch = send_touch_for_pen_.count(pointer_id) == 0
                        ? false
                        : send_touch_for_pen_[pointer_id];
  if (pointer_pen_info.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) {
    if (!pen_in_contact_[pointer_id]) {
      send_touch = send_touch_for_pen_[pointer_id] =
          (pointer_pen_info.pointerInfo.pointerFlags &
           (POINTER_FLAG_SECONDBUTTON | POINTER_FLAG_THIRDBUTTON |
            POINTER_FLAG_FOURTHBUTTON | POINTER_FLAG_FIFTHBUTTON)) == 0;
    }
    pen_in_contact_[pointer_id] = true;
  } else {
    pen_in_contact_.erase(pointer_id);
    send_touch_for_pen_.erase(pointer_id);
  }

  if (is_pointer_event || !send_touch) {
    return GenerateMouseEvent(message, pointer_id, pointer_pen_info.pointerInfo,
                              point, pointer_details, device_id);
  }

  return GenerateTouchEvent(message, pointer_id, pointer_pen_info.pointerInfo,
                            point, pointer_details, device_id);
}

std::unique_ptr<ui::Event> PenEventProcessor::GenerateMouseEvent(
    UINT message,
    UINT32 pointer_id,
    const POINTER_INFO& pointer_info,
    const gfx::Point& point,
    const ui::PointerDetails& pointer_details,
    int32_t device_id) {
  ui::EventType event_type = ui::EventType::kMouseMoved;
  int flag = GetFlagsFromPointerMessage(message, pointer_info);
  int changed_flag = ui::EF_NONE;
  int click_count = 0;
  switch (message) {
    case WM_POINTERDOWN:
    case WM_NCPOINTERDOWN:
      event_type = ui::EventType::kMousePressed;
      if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_DOWN)
        changed_flag = ui::EF_LEFT_MOUSE_BUTTON;
      else
        changed_flag = ui::EF_RIGHT_MOUSE_BUTTON;
      click_count = 1;
      sent_mouse_down_[pointer_id] = true;
      break;
    case WM_POINTERUP:
    case WM_NCPOINTERUP:
      event_type = ui::EventType::kMouseReleased;
      if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) {
        flag |= ui::EF_LEFT_MOUSE_BUTTON;
        changed_flag = ui::EF_LEFT_MOUSE_BUTTON;
      } else {
        flag |= ui::EF_RIGHT_MOUSE_BUTTON;
        changed_flag = ui::EF_RIGHT_MOUSE_BUTTON;
      }
      id_generator_->ReleaseNumber(pointer_id);
      click_count = 1;
      if (sent_mouse_down_.count(pointer_id) == 0 ||
          !sent_mouse_down_[pointer_id])
        return nullptr;
      sent_mouse_down_[pointer_id] = false;
      break;
    case WM_POINTERUPDATE:
    case WM_NCPOINTERUPDATE:
      event_type = ui::EventType::kMouseDragged;
      if (flag == ui::EF_NONE)
        event_type = ui::EventType::kMouseMoved;
      break;
    case WM_POINTERENTER:
      event_type = ui::EventType::kMouseEntered;
      break;
    case WM_POINTERLEAVE:
      event_type = ui::EventType::kMouseExited;
      id_generator_->ReleaseNumber(pointer_id);
      break;
    default:
      NOTREACHED();
  }
  std::unique_ptr<ui::Event> event = std::make_unique<ui::MouseEvent>(
      event_type, point, point, ui::EventTimeForNow(),
      flag | ui::GetModifiersFromKeyState(), changed_flag, pointer_details);
  event->AsMouseEvent()->SetClickCount(click_count);
  event->set_source_device_id(device_id);
  return event;
}

std::unique_ptr<ui::Event> PenEventProcessor::GenerateTouchEvent(
    UINT message,
    UINT32 pointer_id,
    const POINTER_INFO& pointer_info,
    const gfx::Point& point,
    const ui::PointerDetails& pointer_details,
    int32_t device_id) {
  int flags = GetFlagsFromPointerMessage(message, pointer_info);

  ui::EventType event_type = ui::EventType::kTouchMoved;
  switch (message) {
    case WM_POINTERDOWN:
    case WM_NCPOINTERDOWN:
      event_type = ui::EventType::kTouchPressed;
      sent_touch_start_[pointer_id] = true;
      break;
    case WM_POINTERUP:
    case WM_NCPOINTERUP:
      event_type = ui::EventType::kTouchReleased;
      id_generator_->ReleaseNumber(pointer_id);
      if (sent_touch_start_.count(pointer_id) == 0 ||
          !sent_touch_start_[pointer_id])
        return nullptr;
      sent_touch_start_[pointer_id] = false;
      break;
    case WM_POINTERUPDATE:
    case WM_NCPOINTERUPDATE:
      event_type = ui::EventType::kTouchMoved;
      break;
    default:
      NOTREACHED();
  }

  const base::TimeTicks event_time = ui::EventTimeForNow();

  std::unique_ptr<ui::TouchEvent> event = std::make_unique<ui::TouchEvent>(
      event_type, point, event_time, pointer_details,
      flags | ui::GetModifiersFromKeyState());
  ui::ComputeEventLatencyOSFromPOINTER_INFO(event_type, pointer_info,
                                            event_time);
  event->set_hovering(event_type == ui::EventType::kTouchReleased);
  event->latency()->AddLatencyNumberWithTimestamp(
      ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time);
  event->set_source_device_id(device_id);
  return event;
}

}  // namespace views