1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218

ash / fast_ink / laser / laser_pointer_controller.cc [blame]

// Copyright 2016 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/laser/laser_pointer_controller.h"

#include <memory>

#include "ash/fast_ink/laser/laser_pointer_view.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/system/palette/palette_utils.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.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 {

// A point gets removed from the collection if it is older than
// |kPointLifeDurationMs|.
const int kPointLifeDurationMs = 200;

// When no move events are being received we add a new point every
// |kAddStationaryPointsDelayMs| so that points older than
// |kPointLifeDurationMs| can get removed.
// Note: Using a delay less than the screen refresh interval will not
// provide a visual benefit but instead just waste time performing
// unnecessary updates. 16ms is the refresh interval on most devices.
// TODO(reveman): Use real VSYNC interval instead of a hard-coded delay.
const int kAddStationaryPointsDelayMs = 16;

}  // namespace

// A class to hide and lock mouse cursor while it is alive.
class LaserPointerController::ScopedLockedHiddenCursor {
 public:
  ScopedLockedHiddenCursor() : cursor_manager_(Shell::Get()->cursor_manager()) {
    DCHECK(cursor_manager_);
    // Hide and lock the cursor.
    cursor_manager_->HideCursor();
    cursor_manager_->LockCursor();
  }
  ~ScopedLockedHiddenCursor() {
    // Unlock the cursor.
    cursor_manager_->UnlockCursor();
  }

 private:
  const raw_ptr<wm::CursorManager> cursor_manager_;
};

LaserPointerController::LaserPointerController() {
  Shell::Get()->AddPreTargetHandler(this);
}

LaserPointerController::~LaserPointerController() {
  Shell::Get()->RemovePreTargetHandler(this);
}

void LaserPointerController::AddObserver(LaserPointerObserver* observer) {
  observers_.AddObserver(observer);
}

void LaserPointerController::RemoveObserver(LaserPointerObserver* observer) {
  observers_.RemoveObserver(observer);
}

void LaserPointerController::OnWindowBoundsChanged(
    aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds,
    ui::PropertyChangeReason reason) {
  DCHECK_EQ(window, root_window_observation_.GetSource());

  // If the display is rotated or the device scale factor is changed during a
  // gesture, the last point segment will be in a different position on the
  // display. Since the root window bounds are synced with the display, the
  // controller will be notified if the display's rotation or device scale
  // factor changes.
  // Resetting the state of the buffer associated with the
  // `laser_pointer_view_widget_` is non-trivial, so the view is destroyed
  // instead to start with a fresh state. Since it is very rare to have an
  // ongoing gesture with display changes, we can safely assume that there is no
  // performance penalty.
  DestroyPointerView();
}

void LaserPointerController::OnWindowDestroyed(aura::Window* window) {
  DCHECK_EQ(window, root_window_observation_.GetSource());
  root_window_observation_.Reset();
}

void LaserPointerController::SetEnabled(bool enabled) {
  if (enabled == is_enabled())
    return;

  FastInkPointerController::SetEnabled(enabled);
  if (!enabled) {
    DestroyPointerView();
    // Unlock mouse cursor when disabling.
    scoped_locked_hidden_cursor_.reset();
  }
  NotifyStateChanged(enabled);
}

views::View* LaserPointerController::GetPointerView() const {
  return laser_pointer_view_widget_
             ? laser_pointer_view_widget_->GetContentsView()
             : nullptr;
}

void LaserPointerController::CreatePointerView(
    base::TimeDelta presentation_delay,
    aura::Window* root_window) {
  laser_pointer_view_widget_ = LaserPointerView::Create(
      base::Milliseconds(kPointLifeDurationMs), presentation_delay,
      base::Milliseconds(kAddStationaryPointsDelayMs),
      Shell::GetContainer(root_window, kShellWindowId_OverlayContainer));

  DCHECK(!root_window_observation_.IsObserving());
  root_window_observation_.Observe(root_window);
}

void LaserPointerController::UpdatePointerView(ui::TouchEvent* event) {
  LaserPointerView* laser_pointer_view = GetLaserPointerView();

  if (IsPointerInExcludedWindows(event)) {
    // Destroy the |LaserPointerView| since the pointer is in the bound of
    // excluded windows.
    DestroyPointerView();
    return;
  }

  if (event->type() != ui::EventType::kTouchCancelled) {
    // Unlock mouse cursor when switch to touch event.
    scoped_locked_hidden_cursor_.reset();

    laser_pointer_view->AddNewPoint(event->root_location_f(),
                                    event->time_stamp());
  }

  // Upon disabling the touch display, a canceled touch event is received. The
  // `laser_pointer_view` should be destroyed to avoid visual artifacts. See
  // b/303924797 for more information.
  if (event->type() == ui::EventType::kTouchReleased ||
      event->type() == ui::EventType::kTouchCancelled) {
    laser_pointer_view->FadeOut(base::BindOnce(
        &LaserPointerController::DestroyPointerView, base::Unretained(this)));
  }
}

void LaserPointerController::UpdatePointerView(ui::MouseEvent* event) {
  LaserPointerView* laser_pointer_view = GetLaserPointerView();
  if (event->type() == ui::EventType::kMouseMoved) {
    if (IsPointerInExcludedWindows(event)) {
      // Destroy the |LaserPointerView| and unlock the cursor since the cursor
      // is in the bound of excluded windows.
      DestroyPointerView();
      scoped_locked_hidden_cursor_.reset();
      return;
    }

    if (!scoped_locked_hidden_cursor_) {
      scoped_locked_hidden_cursor_ =
          std::make_unique<ScopedLockedHiddenCursor>();
    }
  }

  laser_pointer_view->AddNewPoint(event->root_location_f(),
                                  event->time_stamp());
  if (event->type() == ui::EventType::kMouseReleased) {
    laser_pointer_view->FadeOut(base::BindOnce(
        &LaserPointerController::DestroyPointerView, base::Unretained(this)));
  }
}

void LaserPointerController::DestroyPointerView() {
  root_window_observation_.Reset();
  laser_pointer_view_widget_.reset();
}

bool LaserPointerController::CanStartNewGesture(ui::LocatedEvent* event) {
  // Ignore events over the palette.
  // TODO(llin): Register palette as a excluded window instead.
  aura::Window* target = static_cast<aura::Window*>(event->target());
  gfx::Point screen_point = event->location();
  wm::ConvertPointToScreen(target, &screen_point);
  if (palette_utils::PaletteContainsPointInScreen(screen_point))
    return false;
  return FastInkPointerController::CanStartNewGesture(event);
}

bool LaserPointerController::ShouldProcessEvent(ui::LocatedEvent* event) {
  // Allow clicking when laser pointer is enabled.
  if (event->type() == ui::EventType::kMousePressed ||
      event->type() == ui::EventType::kMouseReleased) {
    return false;
  }

  return FastInkPointerController::ShouldProcessEvent(event);
}

void LaserPointerController::NotifyStateChanged(bool enabled) {
  for (LaserPointerObserver& observer : observers_)
    observer.OnLaserPointerStateChanged(enabled);
}

LaserPointerView* LaserPointerController::GetLaserPointerView() const {
  return static_cast<LaserPointerView*>(GetPointerView());
}

}  // namespace ash