1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "CursorRepository.h"
11 
12 #include <cmath>
13 #include <openrct2/common.h>
14 #include <openrct2/config/Config.h>
15 #include <openrct2/core/Guard.hpp>
16 #include <openrct2/interface/Cursors.h>
17 #include <vector>
18 
19 using namespace OpenRCT2::Ui;
20 
~CursorRepository()21 CursorRepository::~CursorRepository()
22 {
23     _scaledCursors.clear();
24     _currentCursor = CursorID::Undefined;
25     _currentCursorScale = 1;
26 }
27 
LoadCursors()28 void CursorRepository::LoadCursors()
29 {
30     SetCursorScale(static_cast<uint8_t>(round(gConfigGeneral.window_scale)));
31     SetCurrentCursor(CursorID::Arrow);
32 }
33 
GetCurrentCursor()34 CursorID CursorRepository::GetCurrentCursor()
35 {
36     return _currentCursor;
37 }
38 
SetCurrentCursor(CursorID cursorId)39 void CursorRepository::SetCurrentCursor(CursorID cursorId)
40 {
41     if (_currentCursor != cursorId)
42     {
43         SDL_Cursor* cursor = _scaledCursors.at(_currentCursorScale).getScaledCursor(cursorId);
44         SDL_SetCursor(cursor);
45         _currentCursor = cursorId;
46     }
47 }
48 
getBit(const uint8_t data[],size_t x,size_t y,size_t width)49 static bool getBit(const uint8_t data[], size_t x, size_t y, size_t width) noexcept
50 {
51     const size_t position = y * width + x;
52     return (data[position / 8] & (1 << (7 - (x % 8)))) != 0;
53 }
54 
setBit(uint8_t data[],size_t x,size_t y,size_t width)55 static void setBit(uint8_t data[], size_t x, size_t y, size_t width) noexcept
56 {
57     size_t position = y * width + x;
58     data[position / 8] |= (1 << (7 - (position % 8)));
59 }
60 
drawRect(uint8_t data[],size_t x,size_t y,size_t width,size_t scale)61 static void drawRect(uint8_t data[], size_t x, size_t y, size_t width, size_t scale) noexcept
62 {
63     for (size_t outY = (y * scale); outY < ((1 + y) * scale); outY++)
64     {
65         for (size_t outX = (x * scale); outX < ((1 + x) * scale); outX++)
66         {
67             setBit(data, outX, outY, width * scale);
68         }
69     }
70 }
71 
scaleDataArray(const uint8_t data[],size_t width,size_t height,size_t scale)72 static std::vector<uint8_t> scaleDataArray(const uint8_t data[], size_t width, size_t height, size_t scale)
73 {
74     const size_t length = width * height;
75 
76     std::vector<uint8_t> res;
77     res.resize(length * scale * scale);
78 
79     for (size_t y = 0; y < height * 8; y++)
80     {
81         for (size_t x = 0; x < width; x++)
82         {
83             const bool value = getBit(data, x, y, width);
84             if (!value)
85                 continue;
86 
87             drawRect(res.data(), x, y, width, scale);
88         }
89     }
90 
91     return res;
92 }
93 
Create(const CursorData * cursorInfo,uint8_t scale)94 SDL_Cursor* CursorRepository::Create(const CursorData* cursorInfo, uint8_t scale)
95 {
96     const auto integer_scale = static_cast<int>(round(scale));
97 
98     auto data = scaleDataArray(cursorInfo->Data, CURSOR_BIT_WIDTH, CURSOR_HEIGHT, static_cast<size_t>(integer_scale));
99     auto mask = scaleDataArray(cursorInfo->Mask, CURSOR_BIT_WIDTH, CURSOR_HEIGHT, static_cast<size_t>(integer_scale));
100 
101     auto* cursor = SDL_CreateCursor(
102         data.data(), mask.data(), BASE_CURSOR_WIDTH * integer_scale, BASE_CURSOR_HEIGHT * integer_scale,
103         cursorInfo->HotSpot.X * integer_scale, cursorInfo->HotSpot.Y * integer_scale);
104 
105     return cursor;
106 }
107 
SetCursorScale(uint8_t cursorScale)108 void CursorRepository::SetCursorScale(uint8_t cursorScale)
109 {
110     if (cursorScale > 0.0)
111     {
112         _currentCursorScale = cursorScale;
113         GenerateScaledCursorSetHolder(_currentCursorScale);
114     }
115 }
116 
GenerateScaledCursorSetHolder(uint8_t scale)117 void CursorRepository::GenerateScaledCursorSetHolder(uint8_t scale)
118 {
119     if (_scaledCursors.find(scale) == _scaledCursors.end())
120     {
121         std::function<SDL_Cursor*(CursorID)> cursorGenerator = [this, scale](CursorID cursorId) {
122             switch (cursorId)
123             {
124                 // We can't scale the system cursors, but they should be appropriately scaled anyway
125                 case CursorID::Arrow:
126                     return SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
127                 case CursorID::HandPoint:
128                     return SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
129                 default:
130                     return this->Create(GetCursorData(cursorId), scale);
131             }
132         };
133         _scaledCursors.emplace(scale, cursorGenerator);
134     }
135 }
136