chromium/ash/fast_ink/fast_ink_pointer_controller.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 "ash/fast_ink/fast_ink_pointer_controller.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/window.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/types/event_type.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace ash {
namespace {

// The amount of time used to estimate time from VSYNC event to when
// visible light can be noticed by the user.
const int kPresentationDelayMs = 18;

}  // namespace

FastInkPointerController::FastInkPointerController()
    : presentation_delay_(base::Milliseconds(kPresentationDelayMs)) {
  auto* local_state = Shell::Get()->local_state();
  // |local_state| could be null in tests.
  if (!local_state)
    return;

  pref_change_registrar_local_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_local_->Init(local_state);
  pref_change_registrar_local_->Add(
      prefs::kHasSeenStylus,
      base::BindRepeating(&FastInkPointerController::OnHasSeenStylusPrefChanged,
                          base::Unretained(this)));

  OnHasSeenStylusPrefChanged();
}

FastInkPointerController::~FastInkPointerController() {}

void FastInkPointerController::SetEnabled(bool enabled) {
  enabled_ = enabled;
  // Not calling DestroyPointerView when disabling, leaving the control over
  // the pointer view lifetime to the specific controller implementation.
  // For instance, a controller might prefer to keep the pointer view around
  // while it is being animated away.
}

void FastInkPointerController::AddExcludedWindow(aura::Window* window) {
  DCHECK(window);
  excluded_windows_.Add(window);
}

bool FastInkPointerController::CanStartNewGesture(ui::LocatedEvent* event) {
  if (IsPointerInExcludedWindows(event))
    return false;

  // 1) The stylus/finger is pressed.
  // 2) The stylus/finger is moving, but the pointer session has not started yet
  // (most likely because the preceding press event was consumed by another
  // handler).
  bool can_start_on_touch_event =
      event->type() == ui::EventType::kTouchPressed ||
      (event->type() == ui::EventType::kTouchMoved && !GetPointerView());
  if (can_start_on_touch_event)
    return true;

  // 1) The mouse is pressed.
  // 2) The mouse is moving, but the pointer session has not started yet
  // (most likely because the preceding press event was consumed by another
  // handler).
  bool can_start_on_mouse_event =
      event->type() == ui::EventType::kMousePressed ||
      (event->type() == ui::EventType::kMouseMoved && !GetPointerView());
  if (can_start_on_mouse_event)
    return true;

  return false;
}

bool FastInkPointerController::ShouldProcessEvent(ui::LocatedEvent* event) {
  return event->type() == ui::EventType::kTouchReleased ||
         event->type() == ui::EventType::kTouchMoved ||
         event->type() == ui::EventType::kTouchPressed ||
         event->type() == ui::EventType::kTouchCancelled ||
         event->type() == ui::EventType::kMousePressed ||
         event->type() == ui::EventType::kMouseReleased ||
         event->type() == ui::EventType::kMouseMoved;
}

bool FastInkPointerController::IsEnabledForMouseEvent() const {
  return !has_seen_stylus_;
}

bool FastInkPointerController::IsPointerInExcludedWindows(
    ui::LocatedEvent* event) {
  gfx::Point screen_location = event->location();
  aura::Window* event_target = static_cast<aura::Window*>(event->target());
  wm::ConvertPointToScreen(event_target, &screen_location);

  for (const aura::Window* excluded_window : excluded_windows_.windows()) {
    if (excluded_window->GetBoundsInScreen().Contains(screen_location)) {
      return true;
    }
  }

  return false;
}

bool FastInkPointerController::MaybeCreatePointerView(
    ui::LocatedEvent* event,
    bool can_start_new_gesture) {
  aura::Window* root_window =
      static_cast<aura::Window*>(event->target())->GetRootWindow();
  if (can_start_new_gesture) {
    DestroyPointerView();
    CreatePointerView(presentation_delay_, root_window);
  } else {
    views::View* pointer_view = GetPointerView();
    if (!pointer_view)
      return false;
    views::Widget* widget = pointer_view->GetWidget();
    if (widget->IsClosed() ||
        widget->GetNativeWindow()->GetRootWindow() != root_window) {
      // The pointer widget is no longer valid, end the current pointer session.
      DestroyPointerView();
      return false;
    }
  }

  return true;
}

void FastInkPointerController::OnTouchEvent(ui::TouchEvent* event) {
  const int touch_id = event->pointer_details().id;
  // Keep track of touch point count.
  if (event->type() == ui::EventType::kTouchPressed) {
    touch_ids_.insert(touch_id);
  }
  if (event->type() == ui::EventType::kTouchReleased ||
      event->type() == ui::EventType::kTouchCancelled) {
    auto iter = touch_ids_.find(touch_id);

    // Can happen if this object is constructed while fingers were down.
    if (iter == touch_ids_.end())
      return;

    touch_ids_.erase(touch_id);
  }

  if (!enabled_)
    return;

  // Disable on touch events if the device has stylus.
  if (has_seen_stylus_ &&
      event->pointer_details().pointer_type != ui::EventPointerType::kPen) {
    return;
  }

  // Disable for multiple fingers touch.
  if (touch_ids_.size() > 1) {
    DestroyPointerView();
    return;
  }

  if (!ShouldProcessEvent(event))
    return;

  // Update pointer view and stop event propagation if pointer view is
  // available.
  if (MaybeCreatePointerView(event, CanStartNewGesture(event))) {
    UpdatePointerView(event);
    event->StopPropagation();
  }
}

void FastInkPointerController::OnMouseEvent(ui::MouseEvent* event) {
  if (!enabled_ || !IsEnabledForMouseEvent() || !ShouldProcessEvent(event))
    return;

  // Update pointer view and stop event propagation if pointer view is
  // available.
  if (MaybeCreatePointerView(event, CanStartNewGesture(event))) {
    UpdatePointerView(event);
    event->StopPropagation();
  }
}

void FastInkPointerController::OnHasSeenStylusPrefChanged() {
  auto* local_state = pref_change_registrar_local_->prefs();
  has_seen_stylus_ =
      local_state && local_state->GetBoolean(prefs::kHasSeenStylus);
}

}  // namespace ash