From 7076688a654020516ac5747affb654785c3ed0c3 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Wed, 25 Dec 2024 17:45:39 -0400 Subject: [PATCH] Add variant of newCursor which takes an array, for different DPI scales. Resolves #1708. --- src/modules/mouse/Mouse.h | 2 +- src/modules/mouse/sdl/Cursor.cpp | 48 ++++++++++++++++++++++++-------- src/modules/mouse/sdl/Cursor.h | 4 ++- src/modules/mouse/sdl/Mouse.cpp | 2 +- src/modules/mouse/sdl/Mouse.h | 2 +- src/modules/mouse/wrap_Mouse.cpp | 48 +++++++++++++++++++++++++++++--- 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/src/modules/mouse/Mouse.h b/src/modules/mouse/Mouse.h index 4ad70dc56..eb051e5d2 100644 --- a/src/modules/mouse/Mouse.h +++ b/src/modules/mouse/Mouse.h @@ -43,7 +43,7 @@ class Mouse : public Module // 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 &data, int hotx, int hoty) = 0; virtual Cursor *getSystemCursor(Cursor::SystemCursor cursortype) = 0; virtual void setCursor(Cursor *cursor) = 0; diff --git a/src/modules/mouse/sdl/Cursor.cpp b/src/modules/mouse/sdl/Cursor.cpp index 9b80d02b3..7f985dd9b 100644 --- a/src/modules/mouse/sdl/Cursor.cpp +++ b/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 &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 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()); } diff --git a/src/modules/mouse/sdl/Cursor.h b/src/modules/mouse/sdl/Cursor.h index ee0defc27..baa8fda8c 100644 --- a/src/modules/mouse/sdl/Cursor.h +++ b/src/modules/mouse/sdl/Cursor.h @@ -28,6 +28,8 @@ // SDL #include +#include + 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 &imageData, int hotx, int hoty); Cursor(SystemCursor cursortype); ~Cursor(); diff --git a/src/modules/mouse/sdl/Mouse.cpp b/src/modules/mouse/sdl/Mouse.cpp index 4f51a23d6..e5638ea15 100644 --- a/src/modules/mouse/sdl/Mouse.cpp +++ b/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 &data, int hotx, int hoty) { return new Cursor(data, hotx, hoty); } diff --git a/src/modules/mouse/sdl/Mouse.h b/src/modules/mouse/sdl/Mouse.h index a74dd334d..fab4aef9b 100644 --- a/src/modules/mouse/sdl/Mouse.h +++ b/src/modules/mouse/sdl/Mouse.h @@ -42,7 +42,7 @@ class Mouse : public love::mouse::Mouse Mouse(); virtual ~Mouse(); - love::mouse::Cursor *newCursor(love::image::ImageData *data, int hotx, int hoty) override; + love::mouse::Cursor *newCursor(const std::vector &data, int hotx, int hoty) override; love::mouse::Cursor *getSystemCursor(Cursor::SystemCursor cursortype) override; void setCursor(love::mouse::Cursor *cursor) override; diff --git a/src/modules/mouse/wrap_Mouse.cpp b/src/modules/mouse/wrap_Mouse.cpp index f54a69803..d0f575363 100644 --- a/src/modules/mouse/wrap_Mouse.cpp +++ b/src/modules/mouse/wrap_Mouse.cpp @@ -36,15 +36,55 @@ namespace mouse int w_newCursor(lua_State *L) { Cursor *cursor = nullptr; + std::vector 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(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(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(L, 1)); + data.back()->retain(); + } - love::image::ImageData *data = luax_checktype(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();