blob: 0075be20bb286ce2d2245759507264c02a75ab15 [file] [log] [blame]
// Copyright 2018 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 "content/browser/media/capture/mouse_cursor_overlay_controller.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/cursor_loader.h"
#include "ui/base/cursor/cursor_type.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#include "ui/wm/public/activation_client.h"
namespace content {
namespace {
ui::Cursor CreateDefaultPointerCursor() {
ui::Cursor cursor(ui::CursorType::kPointer);
std::unique_ptr<ui::CursorLoader> loader(ui::CursorLoader::Create());
loader->SetPlatformCursor(&cursor);
return cursor;
}
} // namespace
class MouseCursorOverlayController::Observer : public ui::EventHandler,
public aura::WindowObserver {
public:
explicit Observer(MouseCursorOverlayController* controller,
aura::Window* window)
: controller_(controller), window_(window) {
DCHECK(controller_);
DCHECK(window_);
controller_->OnMouseHasGoneIdle();
window_->AddObserver(this);
window_->AddPreTargetHandler(this);
}
~Observer() final {
if (window_) {
OnWindowDestroying(window_);
}
}
void StopTracking() {
if (window_) {
window_->RemovePreTargetHandler(this);
controller_->OnMouseHasGoneIdle();
}
}
static aura::Window* GetTargetWindow(
const std::unique_ptr<Observer>& observer) {
if (observer) {
return observer->window_;
}
return nullptr;
}
private:
bool IsWindowActive() const {
if (window_) {
if (auto* root_window = window_->GetRootWindow()) {
if (window_ == root_window) {
return true;
}
if (auto* client = wm::GetActivationClient(root_window)) {
if (auto* active_window = client->GetActiveWindow()) {
return active_window->Contains(window_);
}
}
}
}
return false;
}
gfx::PointF AsLocationInWindow(const ui::Event& event) const {
gfx::PointF location = event.AsLocatedEvent()->location_f();
if (event.target() != window_) {
aura::Window::ConvertPointToTarget(
static_cast<aura::Window*>(event.target()), window_, &location);
}
return location;
}
// ui::EventHandler overrides.
void OnEvent(ui::Event* event) final {
switch (event->type()) {
case ui::ET_MOUSE_DRAGGED:
case ui::ET_MOUSE_MOVED:
case ui::ET_MOUSE_ENTERED:
case ui::ET_MOUSE_EXITED:
case ui::ET_TOUCH_MOVED:
if (IsWindowActive()) {
controller_->OnMouseMoved(AsLocationInWindow(*event));
}
break;
case ui::ET_MOUSE_PRESSED:
case ui::ET_MOUSE_RELEASED:
case ui::ET_MOUSEWHEEL:
case ui::ET_TOUCH_PRESSED:
case ui::ET_TOUCH_RELEASED: {
controller_->OnMouseClicked(AsLocationInWindow(*event));
break;
}
default:
return;
}
}
// aura::WindowObserver overrides.
void OnWindowDestroying(aura::Window* window) final {
DCHECK_EQ(window_, window);
StopTracking();
window_->RemoveObserver(this);
window_ = nullptr;
}
MouseCursorOverlayController* const controller_;
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(Observer);
};
MouseCursorOverlayController::MouseCursorOverlayController()
: mouse_move_behavior_atomic_(kNotMoving), weak_factory_(this) {
// MouseCursorOverlayController can be constructed on any thread, but
// thereafter must be used according to class-level comments.
DETACH_FROM_SEQUENCE(ui_sequence_checker_);
}
MouseCursorOverlayController::~MouseCursorOverlayController() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
observer_.reset();
Stop();
}
void MouseCursorOverlayController::SetTargetView(aura::Window* window) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
observer_.reset();
if (window) {
observer_ = std::make_unique<Observer>(this, window);
}
}
gfx::NativeCursor MouseCursorOverlayController::GetCurrentCursorOrDefault()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
if (auto* window = Observer::GetTargetWindow(observer_)) {
if (auto* host = window->GetHost()) {
const gfx::NativeCursor cursor = host->last_cursor();
if (cursor != ui::CursorType::kNull) {
return cursor;
}
}
}
return CreateDefaultPointerCursor();
}
gfx::RectF MouseCursorOverlayController::ComputeRelativeBoundsForOverlay(
const gfx::NativeCursor& cursor,
const gfx::PointF& location) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
if (auto* window = Observer::GetTargetWindow(observer_)) {
const gfx::Size window_size = window->bounds().size();
if (!window_size.IsEmpty()) {
if (auto* root_window = window->GetRootWindow()) {
// Compute the cursor size in terms of DIP coordinates.
const SkBitmap& bitmap = cursor.GetBitmap();
const float scale_factor = cursor.device_scale_factor();
const gfx::SizeF size =
scale_factor > 0.0f
? gfx::ScaleSize(gfx::SizeF(bitmap.width(), bitmap.height()),
1.0f / scale_factor)
: gfx::SizeF(bitmap.width(), bitmap.height());
// Compute the hotspot in terms of DIP coordinates.
const gfx::PointF hotspot =
scale_factor > 0.0f
? gfx::ScalePoint(gfx::PointF(cursor.GetHotspot()),
1.0f / scale_factor)
: gfx::PointF(cursor.GetHotspot());
// Finally, put it all together: Scale the absolute bounds of the
// overlay by the window size to produce relative coordinates.
return gfx::ScaleRect(
gfx::RectF(location - hotspot.OffsetFromOrigin(), size),
1.0f / window_size.width(), 1.0f / window_size.height());
}
}
}
return gfx::RectF();
}
void MouseCursorOverlayController::DisconnectFromToolkitForTesting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
observer_->StopTracking();
// The default cursor is ui::CursorType::kNone. Make it kPointer so the tests
// have a non-empty cursor bitmap to work with.
auto* const window = Observer::GetTargetWindow(observer_);
CHECK(window);
auto* const host = window->GetHost();
CHECK(host);
host->SetCursor(CreateDefaultPointerCursor());
}
// static
SkBitmap MouseCursorOverlayController::GetCursorImage(
const gfx::NativeCursor& cursor) {
return cursor.GetBitmap();
}
} // namespace content