blob: 13967a22d7f114e534347b173fe4b030eee91c5b [file]
// Copyright 2021 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/wm/core/cursor_loader.h"
#include <map>
#include <optional>
#include <vector>
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/cursor_factory.h"
#include "ui/base/cursor/cursor_size.h"
#include "ui/base/cursor/mojom/cursor_type.mojom.h"
#include "ui/base/cursor/platform_cursor.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/wm/core/cursor_util.h"
namespace wm {
namespace {
using ::ui::mojom::CursorType;
constexpr base::TimeDelta kAnimatedCursorFrameDelay = base::Milliseconds(25);
// Converts DIPs (Device-independent pixels) to pixels.
int ConvertDipToPixel(int dip, float scale) {
return dip * scale;
}
} // namespace
CursorLoader::CursorLoader(bool use_platform_cursors)
: use_platform_cursors_(use_platform_cursors),
factory_(ui::CursorFactory::GetInstance()) {
factory_->AddObserver(this);
}
CursorLoader::~CursorLoader() {
factory_->RemoveObserver(this);
}
void CursorLoader::OnThemeLoaded() {
UnloadCursors();
}
bool CursorLoader::SetDisplay(const display::Display& display) {
const display::Display::Rotation rotation = display.panel_rotation();
const float scale = display.device_scale_factor();
if (rotation_ == rotation && scale_ == scale) {
return false;
}
rotation_ = rotation;
scale_ = scale;
resource_scale_ = ui::GetScaleForResourceScaleFactor(
ui::GetSupportedResourceScaleFactor(scale_));
UnloadCursors();
return true;
}
void CursorLoader::SetSize(ui::CursorSize size) {
if (size_ == size)
return;
size_ = size;
UnloadCursors();
}
void CursorLoader::SetLargeCursorSizeInDip(int large_cursor_size_in_dip) {
if (large_cursor_size_in_dip_ == large_cursor_size_in_dip) {
return;
}
large_cursor_size_in_dip_ = large_cursor_size_in_dip;
UnloadCursors();
}
void CursorLoader::SetColor(SkColor color) {
if (color_ == color) {
return;
}
color_ = color;
// Reset cursor lottie animation cache when new color needs to be applied.
wm::ClearCursorAnimationCache();
UnloadCursors();
}
void CursorLoader::SetPlatformCursor(ui::Cursor* cursor) {
DCHECK(cursor);
// The platform cursor was already set via WebCursor::GetNativeCursor.
if (cursor->type() == CursorType::kCustom) {
return;
}
cursor->SetPlatformCursor(CursorFromType(cursor->type()));
}
std::optional<ui::CursorData> CursorLoader::GetCursorData(
const ui::Cursor& cursor) const {
CursorType type = cursor.type();
if (type == CursorType::kNone)
return ui::CursorData();
if (type == CursorType::kCustom) {
auto cursor_data =
ui::CursorData({cursor.custom_bitmap()}, cursor.custom_hotspot(),
cursor.image_scale_factor());
ApplyColorAndLargeSize(cursor_data);
return cursor_data;
}
if (use_platform_cursors_) {
auto cursor_data = factory_->GetCursorData(type);
if (cursor_data) {
// TODO(crbug.com/40175364): consider either passing `scale_` to
// `CursorFactory::GetCursorData`, or relying on having called
// `CursorFactory::SetDeviceScaleFactor`, instead of setting it here.
cursor_data->scale_factor = scale_;
ApplyColorAndLargeSize(cursor_data.value());
return cursor_data;
}
}
const int large_cursor_size_in_px =
ConvertDipToPixel(large_cursor_size_in_dip_, scale_);
// TODO(crbug.com/40175364): use the actual `rotation_` if that makes
// sense for the current use cases of `GetCursorData` (e.g. Chrome Remote
// Desktop, WebRTC and VideoRecordingWatcher).
return wm::GetCursorData(type, resource_scale_,
size_ == ui::CursorSize::kLarge
? std::make_optional(large_cursor_size_in_px)
: std::nullopt,
display::Display::ROTATE_0, color_);
}
void CursorLoader::UnloadCursors() {
image_cursors_.clear();
}
void CursorLoader::ApplyColorAndLargeSize(
ui::CursorData& data_in_and_out) const {
if (color_ != ui::kDefaultCursorColor) {
std::for_each(data_in_and_out.bitmaps.begin(),
data_in_and_out.bitmaps.end(), [&](SkBitmap& bitmap) {
bitmap = wm::GetColorAdjustedBitmap(bitmap, color_);
});
}
if (size_ == ui::CursorSize::kLarge) {
const int large_cursor_size_in_px =
ConvertDipToPixel(large_cursor_size_in_dip_, scale_);
const gfx::Size cursor_size_in_px =
gfx::SkISizeToSize(data_in_and_out.bitmaps[0].dimensions());
if (large_cursor_size_in_px != cursor_size_in_px.height()) {
const float rescale = static_cast<float>(large_cursor_size_in_px) /
static_cast<float>(cursor_size_in_px.height());
std::for_each(data_in_and_out.bitmaps.begin(),
data_in_and_out.bitmaps.end(), [&](SkBitmap& bitmap) {
wm::ScaleAndRotateCursorBitmapAndHotpoint(
rescale, display::Display::ROTATE_0, &bitmap,
&data_in_and_out.hotspot);
});
data_in_and_out.scale_factor *= rescale;
}
}
}
scoped_refptr<ui::PlatformCursor> CursorLoader::CursorFromType(
CursorType type) {
// An image cursor is loaded for this type.
if (auto it = image_cursors_.find(type); it != image_cursors_.end())
return it->second;
// Check if there's a default platform cursor available.
// For the none cursor, we also need to use the platform factory to take
// into account the different ways of creating an invisible cursor.
scoped_refptr<ui::PlatformCursor> cursor;
if (use_platform_cursors_ || type == CursorType::kNone) {
cursor = factory_->GetDefaultCursor(type, scale_);
if (cursor)
return cursor;
// The cursor may fail to load if the cursor theme has just been reset.
// We will be notified when the theme is loaded, but at this time we have to
// fall back to the assets.
LOG(WARNING) << "Failed to load a platform cursor of type " << type;
}
// Loads the default Aura cursor bitmap for the cursor type. Falls back on
// pointer cursor if this fails.
cursor = LoadCursorFromAsset(type);
if (!cursor && type != CursorType::kPointer) {
cursor = CursorFromType(CursorType::kPointer);
image_cursors_[type] = cursor;
}
DCHECK(cursor) << "Failed to load a bitmap for the pointer cursor.";
return cursor;
}
scoped_refptr<ui::PlatformCursor> CursorLoader::LoadCursorFromAsset(
CursorType type) {
std::optional<ui::CursorData> cursor_data = wm::GetCursorData(
type, resource_scale_,
size_ == ui::CursorSize::kLarge ? std::make_optional(ConvertDipToPixel(
large_cursor_size_in_dip_, scale_))
: std::nullopt,
rotation_, color_);
if (!cursor_data) {
return nullptr;
}
if (cursor_data->bitmaps.size() == 1) {
image_cursors_[type] = factory_->CreateImageCursor(
type, cursor_data->bitmaps[0], cursor_data->hotspot,
cursor_data->scale_factor);
} else {
image_cursors_[type] = factory_->CreateAnimatedCursor(
type, cursor_data->bitmaps, cursor_data->hotspot,
cursor_data->scale_factor, kAnimatedCursorFrameDelay);
}
return image_cursors_[type];
}
} // namespace wm