chromium/ui/base/win/win_cursor_factory.cc

// 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/base/win/win_cursor_factory.h"

#include <windows.h>

#include <optional>
#include <string>
#include <utility>

#include "base/check_op.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/windows_types.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/cursor/platform_cursor.h"
#include "ui/base/resource/resource_bundle_win.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/icon_util.h"
#include "ui/resources/grit/ui_unscaled_resources.h"

namespace ui {

namespace {

const wchar_t* GetCursorId(mojom::CursorType type) {
  switch (type) {
    case mojom::CursorType::kNull:
    case mojom::CursorType::kPointer:
      return IDC_ARROW;
    case mojom::CursorType::kCross:
      return IDC_CROSS;
    case mojom::CursorType::kHand:
      return IDC_HAND;
    case mojom::CursorType::kIBeam:
      return IDC_IBEAM;
    case mojom::CursorType::kWait:
      return IDC_WAIT;
    case mojom::CursorType::kHelp:
      return IDC_HELP;
    case mojom::CursorType::kEastResize:
    case mojom::CursorType::kWestResize:
    case mojom::CursorType::kEastWestResize:
      return IDC_SIZEWE;
    case mojom::CursorType::kNorthResize:
    case mojom::CursorType::kSouthResize:
    case mojom::CursorType::kNorthSouthResize:
      return IDC_SIZENS;
    case mojom::CursorType::kNorthEastResize:
    case mojom::CursorType::kSouthWestResize:
    case mojom::CursorType::kNorthEastSouthWestResize:
      return IDC_SIZENESW;
    case mojom::CursorType::kNorthWestResize:
    case mojom::CursorType::kSouthEastResize:
    case mojom::CursorType::kNorthWestSouthEastResize:
      return IDC_SIZENWSE;
    case mojom::CursorType::kMove:
      return IDC_SIZEALL;
    case mojom::CursorType::kProgress:
      return IDC_APPSTARTING;
    case mojom::CursorType::kNoDrop:
    case mojom::CursorType::kNotAllowed:
    case mojom::CursorType::kEastWestNoResize:
    case mojom::CursorType::kNorthEastSouthWestNoResize:
    case mojom::CursorType::kNorthSouthNoResize:
    case mojom::CursorType::kNorthWestSouthEastNoResize:
      return IDC_NO;
    case mojom::CursorType::kColumnResize:
      return MAKEINTRESOURCE(IDC_COLRESIZE);
    case mojom::CursorType::kRowResize:
      return MAKEINTRESOURCE(IDC_ROWRESIZE);
    case mojom::CursorType::kMiddlePanning:
      return MAKEINTRESOURCE(IDC_PAN_MIDDLE);
    case mojom::CursorType::kMiddlePanningVertical:
      return MAKEINTRESOURCE(IDC_PAN_MIDDLE_VERTICAL);
    case mojom::CursorType::kMiddlePanningHorizontal:
      return MAKEINTRESOURCE(IDC_PAN_MIDDLE_HORIZONTAL);
    case mojom::CursorType::kEastPanning:
      return MAKEINTRESOURCE(IDC_PAN_EAST);
    case mojom::CursorType::kNorthPanning:
      return MAKEINTRESOURCE(IDC_PAN_NORTH);
    case mojom::CursorType::kNorthEastPanning:
      return MAKEINTRESOURCE(IDC_PAN_NORTH_EAST);
    case mojom::CursorType::kNorthWestPanning:
      return MAKEINTRESOURCE(IDC_PAN_NORTH_WEST);
    case mojom::CursorType::kSouthPanning:
      return MAKEINTRESOURCE(IDC_PAN_SOUTH);
    case mojom::CursorType::kSouthEastPanning:
      return MAKEINTRESOURCE(IDC_PAN_SOUTH_EAST);
    case mojom::CursorType::kSouthWestPanning:
      return MAKEINTRESOURCE(IDC_PAN_SOUTH_WEST);
    case mojom::CursorType::kWestPanning:
      return MAKEINTRESOURCE(IDC_PAN_WEST);
    case mojom::CursorType::kVerticalText:
      return MAKEINTRESOURCE(IDC_VERTICALTEXT);
    case mojom::CursorType::kCell:
      return MAKEINTRESOURCE(IDC_CELL);
    case mojom::CursorType::kZoomIn:
      return MAKEINTRESOURCE(IDC_ZOOMIN);
    case mojom::CursorType::kZoomOut:
      return MAKEINTRESOURCE(IDC_ZOOMOUT);
    case mojom::CursorType::kGrab:
      return MAKEINTRESOURCE(IDC_HAND_GRAB);
    case mojom::CursorType::kGrabbing:
      return MAKEINTRESOURCE(IDC_HAND_GRABBING);
    case mojom::CursorType::kCopy:
      return MAKEINTRESOURCE(IDC_COPYCUR);
    case mojom::CursorType::kAlias:
      return MAKEINTRESOURCE(IDC_ALIAS);
    case mojom::CursorType::kDndCopy:
    case mojom::CursorType::kDndLink:
    case mojom::CursorType::kDndMove:
    case mojom::CursorType::kDndNone:
    case mojom::CursorType::kContextMenu:
      NOTIMPLEMENTED();
      return IDC_ARROW;
    case mojom::CursorType::kNone:
    case mojom::CursorType::kCustom:
      NOTREACHED();
  }
  NOTREACHED();
}

}  // namespace

WinCursorFactory::WinCursorFactory() = default;

WinCursorFactory::~WinCursorFactory() = default;

scoped_refptr<PlatformCursor> WinCursorFactory::GetDefaultCursor(
    mojom::CursorType type) {
  if (!default_cursors_.count(type)) {
    // Using a dark 1x1 bit bmp for the kNone cursor may still cause DWM to do
    // composition work unnecessarily. Better to totally remove it from the
    // screen. crbug.com/1069698
    HCURSOR hcursor = nullptr;
    if (type != mojom::CursorType::kNone) {
      const wchar_t* id = GetCursorId(type);
      hcursor = LoadCursor(nullptr, id);
      // Try loading the cursor from the Chromium resources.
      if (!hcursor)
        hcursor = LoadCursorFromResourcesDataDLL(id);
      if (!hcursor)
        return nullptr;
    }
    default_cursors_[type] = base::MakeRefCounted<WinCursor>(hcursor);
  }

  return default_cursors_[type];
}

std::optional<CursorData> WinCursorFactory::GetCursorData(
    mojom::CursorType type) {
  DCHECK_NE(type, mojom::CursorType::kNone);
  DCHECK_NE(type, mojom::CursorType::kCustom);

  auto cursor = GetDefaultCursor(type);
  if (!cursor) {
    return std::nullopt;
  }

  HCURSOR hcursor = WinCursor::FromPlatformCursor(cursor)->hcursor();
  SkBitmap bitmap = IconUtil::CreateSkBitmapFromHICON(hcursor);
  if (bitmap.isNull()) {
    return std::nullopt;
  }

  return ui::CursorData({std::move(bitmap)},
                        IconUtil::GetHotSpotFromHICON(hcursor));
}

scoped_refptr<PlatformCursor> WinCursorFactory::CreateImageCursor(
    mojom::CursorType type,
    const SkBitmap& bitmap,
    const gfx::Point& hotspot,
    float scale) {
  return base::MakeRefCounted<WinCursor>(
      IconUtil::CreateCursorFromSkBitmap(bitmap, hotspot).release(),
      /*should_destroy=*/true);
}

}  // namespace ui