Browse Source

Add variant of newCursor which takes an array, for different DPI scales.

Resolves #1708.
Sasha Szpakowski 7 months ago
parent
commit
7076688a65

+ 1 - 1
src/modules/mouse/Mouse.h

@@ -43,7 +43,7 @@ public:
 	// Implements Module.
 	virtual ModuleType getModuleType() const { return M_MOUSE; }
 
-	virtual Cursor *newCursor(love::image::ImageData *data, int hotx, int hoty) = 0;
+	virtual Cursor *newCursor(const std::vector<image::ImageData *> &data, int hotx, int hoty) = 0;
 	virtual Cursor *getSystemCursor(Cursor::SystemCursor cursortype) = 0;
 
 	virtual void setCursor(Cursor *cursor) = 0;

+ 37 - 11
src/modules/mouse/sdl/Cursor.cpp

@@ -29,23 +29,49 @@ namespace mouse
 namespace sdl
 {
 
-Cursor::Cursor(image::ImageData *data, int hotx, int hoty)
+Cursor::Cursor(const std::vector<image::ImageData *> &imageData, int hotx, int hoty)
 	: cursor(nullptr)
 	, type(CURSORTYPE_IMAGE)
 	, systemType(CURSOR_MAX_ENUM)
 {
-	int w = data->getWidth();
-	int h = data->getHeight();
-	int pitch = w * 4;
+	if (imageData.empty())
+		throw love::Exception("At least one ImageData must be provided for a custom cursor.");
 
-	SDL_Surface *surface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_ABGR8888, data->getData(), pitch);
-	if (!surface)
-		throw love::Exception("Cannot create cursor: out of memory.");
+	std::vector<SDL_Surface *> surfaces;
 
-	cursor = SDL_CreateColorCursor(surface, hotx, hoty);
-	SDL_DestroySurface(surface);
+	for (image::ImageData *data : imageData)
+	{
+		int w = data->getWidth();
+		int h = data->getHeight();
+		int pitch = w * 4;
 
-	if (!cursor)
+		if (getLinearPixelFormat(data->getFormat()) != PIXELFORMAT_RGBA8_UNORM)
+		{
+			for (SDL_Surface *surface : surfaces)
+				SDL_DestroySurface(surface);
+			throw love::Exception("Cannot create cursor: ImageData pixel format must be rgba8.");
+		}
+
+		surfaces.push_back(SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_ABGR8888, data->getData(), pitch));
+
+		if (surfaces.back() == nullptr)
+		{
+			for (SDL_Surface *surface : surfaces)
+				SDL_DestroySurface(surface);
+			throw love::Exception("Cannot create cursor: out of memory.");
+		}
+	}
+
+	// Add alternate representations for the OS to use in different DPI scales.
+	for (size_t i = 1; i < surfaces.size(); i++)
+		SDL_AddSurfaceAlternateImage(surfaces[0], surfaces[i]);
+
+	cursor = SDL_CreateColorCursor(surfaces[0], hotx, hoty);
+
+	for (SDL_Surface *surface : surfaces)
+		SDL_DestroySurface(surface);
+
+	if (cursor == nullptr)
 		throw love::Exception("Cannot create cursor: %s", SDL_GetError());
 }
 
@@ -61,7 +87,7 @@ Cursor::Cursor(mouse::Cursor::SystemCursor cursortype)
 	else
 		throw love::Exception("Cannot create system cursor: invalid type.");
 
-	if (!cursor)
+	if (cursor == nullptr)
 		throw love::Exception("Cannot create system cursor: %s", SDL_GetError());
 }
 

+ 3 - 1
src/modules/mouse/sdl/Cursor.h

@@ -28,6 +28,8 @@
 // SDL
 #include <SDL3/SDL_mouse.h>
 
+#include <vector>
+
 namespace love
 {
 namespace mouse
@@ -39,7 +41,7 @@ class Cursor : public love::mouse::Cursor
 {
 public:
 
-	Cursor(image::ImageData *imageData, int hotx, int hoty);
+	Cursor(const std::vector<image::ImageData *> &imageData, int hotx, int hoty);
 	Cursor(SystemCursor cursortype);
 	~Cursor();
 

+ 1 - 1
src/modules/mouse/sdl/Mouse.cpp

@@ -84,7 +84,7 @@ Mouse::~Mouse()
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 }
 
-love::mouse::Cursor *Mouse::newCursor(love::image::ImageData *data, int hotx, int hoty)
+love::mouse::Cursor *Mouse::newCursor(const std::vector<image::ImageData *> &data, int hotx, int hoty)
 {
 	return new Cursor(data, hotx, hoty);
 }

+ 1 - 1
src/modules/mouse/sdl/Mouse.h

@@ -42,7 +42,7 @@ public:
 	Mouse();
 	virtual ~Mouse();
 
-	love::mouse::Cursor *newCursor(love::image::ImageData *data, int hotx, int hoty) override;
+	love::mouse::Cursor *newCursor(const std::vector<image::ImageData *> &data, int hotx, int hoty) override;
 	love::mouse::Cursor *getSystemCursor(Cursor::SystemCursor cursortype) override;
 
 	void setCursor(love::mouse::Cursor *cursor) override;

+ 44 - 4
src/modules/mouse/wrap_Mouse.cpp

@@ -36,15 +36,55 @@ namespace mouse
 int w_newCursor(lua_State *L)
 {
 	Cursor *cursor = nullptr;
+	std::vector<love::image::ImageData *> data;
 
-	if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
-		luax_convobj(L, 1, "image", "newImageData");
+	if (lua_istable(L, 1))
+	{
+		// Do some type checking first, because memory will leak if we hit an error in the loop after this.
+		for (size_t i = 1; i <= luax_objlen(L, 1); i++)
+		{
+			lua_rawgeti(L, 1, i);
+
+			if (!luax_istype(L, -1, love::image::ImageData::type)
+				&& !(lua_isstring(L, -1) || luax_istype(L, -1, love::filesystem::File::type) || luax_istype(L, -1, love::filesystem::FileData::type)))
+			{
+				luax_checktype<love::image::ImageData>(L, -1);
+			}
+
+			lua_pop(L, 1);
+		}
+
+		for (size_t i = 1; i <= luax_objlen(L, 1); i++)
+		{
+			lua_rawgeti(L, 1, i);
+
+			if (lua_isstring(L, -1) || luax_istype(L, -1, love::filesystem::File::type) || luax_istype(L, -1, love::filesystem::FileData::type))
+				luax_convobj(L, -1, "image", "newImageData");
+
+			data.push_back(luax_checktype<love::image::ImageData>(L, -1));
+
+			// If a GC step happens within the loop, previous ImageData objects created within the loop may be released.
+			data.back()->retain();
+
+			lua_pop(L, 1);
+		}
+	}
+	else
+	{
+		if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
+			luax_convobj(L, 1, "image", "newImageData");
+
+		data.push_back(luax_checktype<love::image::ImageData>(L, 1));
+		data.back()->retain();
+	}
 
-	love::image::ImageData *data = luax_checktype<love::image::ImageData>(L, 1);
 	int hotx = (int) luaL_optinteger(L, 2, 0);
 	int hoty = (int) luaL_optinteger(L, 3, 0);
 
-	luax_catchexcept(L, [&](){ cursor = instance()->newCursor(data, hotx, hoty); });
+	luax_catchexcept(L,
+		[&](){ cursor = instance()->newCursor(data, hotx, hoty); },
+		[&](bool /*shoulderror*/) { for (auto d : data) d->release(); }
+	);
 
 	luax_pushtype(L, cursor);
 	cursor->release();