From ecd08a2b0fef4bf2b87ecc0237514ab43a28ff76 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 29 Dec 2024 23:30:32 -0400 Subject: [PATCH] Add love.window.showFileDialog(type, callback [, settings]). Resolves #990. The type parameter is an enum: "openfile", "openfolder", or "savefile". The callback parameter takes 3 args: files (array of full platform-dependent paths to selected files, empty if the dialog is canceled by the user), filtername (nil if none is set, see below), errorstring (nil if no error). The optional settings parameter of showFileDialog has the following fields: { title = string (optional title), acceptlabel = string (optional custom name for the Accept button), cancellabel = string (optional custom name for the Cancel button), defaultname = string (optional default folder/file name for the file text box), filters = table (filter names as keys and filter pattern strings as values). } --- src/common/Reference.h | 2 + src/modules/event/sdl/Event.cpp | 48 +++++----- src/modules/event/sdl/Event.h | 7 +- src/modules/window/Window.cpp | 133 +++++++++------------------ src/modules/window/Window.h | 63 +++++++------ src/modules/window/sdl/Window.cpp | 140 ++++++++++++++++++++++++++++- src/modules/window/sdl/Window.h | 6 ++ src/modules/window/wrap_Window.cpp | 106 ++++++++++++++++++++++ 8 files changed, 361 insertions(+), 144 deletions(-) diff --git a/src/common/Reference.h b/src/common/Reference.h index 28d218b90..c0f941a07 100644 --- a/src/common/Reference.h +++ b/src/common/Reference.h @@ -69,6 +69,8 @@ public: **/ void push(lua_State *L); + lua_State *getPinnedL() const { return pinnedL; } + private: // A pinned coroutine (probably the main thread) belonging to the Lua state diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 8c40eb927..1c8de041d 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -37,6 +37,7 @@ #include #include "joystick/sdl/Joystick.h" +#include "window/sdl/Window.h" namespace love { @@ -47,25 +48,22 @@ namespace sdl // SDL reports mouse coordinates in the window coordinate system in OS X, but // we want them in pixel coordinates (may be different with high-DPI enabled.) -static void windowToDPICoords(double *x, double *y) +static void windowToDPICoords(love::window::Window *window, double *x, double *y) { - auto window = Module::getInstance(Module::M_WINDOW); if (window) window->windowToDPICoords(x, y); } -static void clampToWindow(double *x, double *y) +static void clampToWindow(love::window::Window *window, double *x, double *y) { - auto window = Module::getInstance(Module::M_WINDOW); if (window) window->clampPositionInWindow(x, y); } -static void normalizedToDPICoords(double *x, double *y) +static void normalizedToDPICoords(love::window::Window *window, double *x, double *y) { double w = 1.0, h = 1.0; - auto window = Module::getInstance(Module::M_WINDOW); if (window) { w = window->getWidth(); @@ -192,6 +190,7 @@ Message *Event::convert(const SDL_Event &e) love::filesystem::Filesystem *filesystem = nullptr; love::sensor::Sensor *sensorInstance = nullptr; + love::window::Window *win = Module::getInstance(Module::M_WINDOW); love::keyboard::Keyboard::Key key = love::keyboard::Keyboard::KEY_UNKNOWN; love::keyboard::Keyboard::Scancode scancode = love::keyboard::Keyboard::SCANCODE_UNKNOWN; @@ -202,6 +201,15 @@ Message *Event::convert(const SDL_Event &e) love::touch::sdl::Touch *touchmodule = nullptr; love::touch::Touch::TouchInfo touchinfo = {}; + if (win) + { + // Dubious cast, but it's not like having an SDL event backend + // with a non-SDL window backend will be a thing. + auto sdlwin = dynamic_cast(win); + if (sdlwin != nullptr) + sdlwin->handleSDLEvent(e); + } + switch (e.type) { case SDL_EVENT_KEY_DOWN: @@ -262,9 +270,9 @@ Message *Event::convert(const SDL_Event &e) // able to handle out-of-bounds coordinates. SDL has a hint to turn off // auto capture, but it doesn't report the mouse's position at the edge of // the window if the mouse moves fast enough when it's off. - clampToWindow(&x, &y); - windowToDPICoords(&x, &y); - windowToDPICoords(&xrel, &yrel); + clampToWindow(win, &x, &y); + windowToDPICoords(win, &x, &y); + windowToDPICoords(win, &xrel, &yrel); vargs.emplace_back(x); vargs.emplace_back(y); @@ -292,8 +300,8 @@ Message *Event::convert(const SDL_Event &e) double px = (double) e.button.x; double py = (double) e.button.y; - clampToWindow(&px, &py); - windowToDPICoords(&px, &py); + clampToWindow(win, &px, &py); + windowToDPICoords(win, &px, &py); vargs.emplace_back(px); vargs.emplace_back(py); @@ -329,8 +337,8 @@ Message *Event::convert(const SDL_Event &e) // SDL's coords are normalized to [0, 1], but we want screen coords for direct touches. if (touchinfo.deviceType == love::touch::Touch::DEVICE_TOUCHSCREEN) { - normalizedToDPICoords(&touchinfo.x, &touchinfo.y); - normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy); + normalizedToDPICoords(win, &touchinfo.x, &touchinfo.y); + normalizedToDPICoords(win, &touchinfo.dx, &touchinfo.dy); } // We need to update the love.touch.sdl internal state from here. @@ -387,7 +395,7 @@ Message *Event::convert(const SDL_Event &e) case SDL_EVENT_WINDOW_RESTORED: case SDL_EVENT_WINDOW_EXPOSED: case SDL_EVENT_WINDOW_OCCLUDED: - msg = convertWindowEvent(e); + msg = convertWindowEvent(e, win); break; case SDL_EVENT_DISPLAY_ORIENTATION: { @@ -440,7 +448,7 @@ Message *Event::convert(const SDL_Event &e) { double x = e.drop.x; double y = e.drop.y; - windowToDPICoords(&x, &y); + windowToDPICoords(win, &x, &y); vargs.emplace_back(x); vargs.emplace_back(y); msg = new Message("dropcompleted", vargs); @@ -450,7 +458,7 @@ Message *Event::convert(const SDL_Event &e) { double x = e.drop.x; double y = e.drop.y; - windowToDPICoords(&x, &y); + windowToDPICoords(win, &x, &y); vargs.emplace_back(x); vargs.emplace_back(y); msg = new Message("dropmoved", vargs); @@ -466,7 +474,7 @@ Message *Event::convert(const SDL_Event &e) double x = e.drop.x; double y = e.drop.y; - windowToDPICoords(&x, &y); + windowToDPICoords(win, &x, &y); if (filesystem->isRealDirectory(filepath)) { @@ -681,14 +689,13 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const return msg; } -Message *Event::convertWindowEvent(const SDL_Event &e) +Message *Event::convertWindowEvent(const SDL_Event &e, love::window::Window *win) { Message *msg = nullptr; std::vector vargs; vargs.reserve(4); - window::Window *win = nullptr; graphics::Graphics *gfx = nullptr; auto event = e.type; @@ -735,7 +742,6 @@ Message *Event::convertWindowEvent(const SDL_Event &e) double height = e.window.data2; gfx = Module::getInstance(Module::M_GRAPHICS); - win = Module::getInstance(Module::M_WINDOW); if (win) win->onSizeChanged(e.window.data1, e.window.data2); @@ -750,7 +756,7 @@ Message *Event::convertWindowEvent(const SDL_Event &e) { width = win->getWidth(); height = win->getHeight(); - windowToDPICoords(&width, &height); + windowToDPICoords(win, &width, &height); } vargs.emplace_back(width); diff --git a/src/modules/event/sdl/Event.h b/src/modules/event/sdl/Event.h index 429059ef3..e6a28de01 100644 --- a/src/modules/event/sdl/Event.h +++ b/src/modules/event/sdl/Event.h @@ -28,6 +28,11 @@ // SDL #include +namespace love::window +{ +class Window; +} + namespace love { namespace event @@ -63,7 +68,7 @@ private: Message *convert(const SDL_Event &e); Message *convertJoystickEvent(const SDL_Event &e) const; - Message *convertWindowEvent(const SDL_Event &e); + Message *convertWindowEvent(const SDL_Event &e, love::window::Window *win); }; // Event diff --git a/src/modules/window/Window.cpp b/src/modules/window/Window.cpp index 6b6b282da..15f949cc1 100644 --- a/src/modules/window/Window.cpp +++ b/src/modules/window/Window.cpp @@ -56,112 +56,61 @@ void Window::swapBuffers() { } -bool Window::getConstant(const char *in, FullscreenType &out) +STRINGMAP_CLASS_BEGIN(Window, Window::Setting, Window::SETTING_MAX_ENUM, setting) { - return fullscreenTypes.find(in, out); + {"fullscreen", Window::SETTING_FULLSCREEN}, + {"fullscreentype", Window::SETTING_FULLSCREEN_TYPE}, + {"vsync", Window::SETTING_VSYNC}, + {"msaa", Window::SETTING_MSAA}, + {"stencil", Window::SETTING_STENCIL}, + {"depth", Window::SETTING_DEPTH}, + {"resizable", Window::SETTING_RESIZABLE}, + {"minwidth", Window::SETTING_MIN_WIDTH}, + {"minheight", Window::SETTING_MIN_HEIGHT}, + {"borderless", Window::SETTING_BORDERLESS}, + {"centered", Window::SETTING_CENTERED}, + {"displayindex", Window::SETTING_DISPLAYINDEX}, + {"display", Window::SETTING_DISPLAY}, + {"highdpi", Window::SETTING_HIGHDPI}, + {"usedpiscale", Window::SETTING_USE_DPISCALE}, + {"refreshrate", Window::SETTING_REFRESHRATE}, + {"x", Window::SETTING_X}, + {"y", Window::SETTING_Y}, } +STRINGMAP_CLASS_END(Window, Window::Setting, Window::SETTING_MAX_ENUM, setting) -bool Window::getConstant(FullscreenType in, const char *&out) +STRINGMAP_CLASS_BEGIN(Window, Window::FullscreenType, Window::FULLSCREEN_MAX_ENUM, fullscreenType) { - return fullscreenTypes.find(in, out); + {"exclusive", Window::FULLSCREEN_EXCLUSIVE}, + {"desktop", Window::FULLSCREEN_DESKTOP}, } +STRINGMAP_CLASS_END(Window, Window::FullscreenType, Window::FULLSCREEN_MAX_ENUM, fullscreenType) -std::vector Window::getConstants(FullscreenType) +STRINGMAP_CLASS_BEGIN(Window, Window::MessageBoxType, Window::MESSAGEBOX_MAX_ENUM, messageBoxType) { - return fullscreenTypes.getNames(); + {"error", Window::MESSAGEBOX_ERROR}, + {"warning", Window::MESSAGEBOX_WARNING}, + {"info", Window::MESSAGEBOX_INFO}, } +STRINGMAP_CLASS_END(Window, Window::MessageBoxType, Window::MESSAGEBOX_MAX_ENUM, messageBoxType) -bool Window::getConstant(const char *in, Setting &out) +STRINGMAP_CLASS_BEGIN(Window, Window::FileDialogType, Window::FILEDIALOG_MAX_ENUM, fileDialogType) { - return settings.find(in, out); + { "openfile", Window::FILEDIALOG_OPENFILE }, + { "openfolder", Window::FILEDIALOG_OPENFOLDER }, + { "savefile", Window::FILEDIALOG_SAVEFILE }, } +STRINGMAP_CLASS_END(Window, Window::FileDialogType, Window::FILEDIALOG_MAX_ENUM, fileDialogType) -bool Window::getConstant(Setting in, const char *&out) +STRINGMAP_CLASS_BEGIN(Window, Window::DisplayOrientation, Window::ORIENTATION_MAX_ENUM, orientation) { - return settings.find(in, out); + {"unknown", Window::ORIENTATION_UNKNOWN}, + {"landscape", Window::ORIENTATION_LANDSCAPE}, + {"landscapeflipped", Window::ORIENTATION_LANDSCAPE_FLIPPED}, + {"portrait", Window::ORIENTATION_PORTRAIT}, + {"portraitflipped", Window::ORIENTATION_PORTRAIT_FLIPPED}, } - -bool Window::getConstant(const char *in, MessageBoxType &out) -{ - return messageBoxTypes.find(in, out); -} - -bool Window::getConstant(MessageBoxType in, const char *&out) -{ - return messageBoxTypes.find(in, out); -} - -std::vector Window::getConstants(MessageBoxType) -{ - return messageBoxTypes.getNames(); -} - -bool Window::getConstant(const char *in, DisplayOrientation &out) -{ - return orientations.find(in, out); -} - -bool Window::getConstant(DisplayOrientation in, const char *&out) -{ - return orientations.find(in, out); -} - -std::vector Window::getConstants(DisplayOrientation) -{ - return orientations.getNames(); -} - -StringMap::Entry Window::settingEntries[] = -{ - {"fullscreen", SETTING_FULLSCREEN}, - {"fullscreentype", SETTING_FULLSCREEN_TYPE}, - {"vsync", SETTING_VSYNC}, - {"msaa", SETTING_MSAA}, - {"stencil", SETTING_STENCIL}, - {"depth", SETTING_DEPTH}, - {"resizable", SETTING_RESIZABLE}, - {"minwidth", SETTING_MIN_WIDTH}, - {"minheight", SETTING_MIN_HEIGHT}, - {"borderless", SETTING_BORDERLESS}, - {"centered", SETTING_CENTERED}, - {"displayindex", SETTING_DISPLAYINDEX}, - {"display", SETTING_DISPLAY}, - {"highdpi", SETTING_HIGHDPI}, - {"usedpiscale", SETTING_USE_DPISCALE}, - {"refreshrate", SETTING_REFRESHRATE}, - {"x", SETTING_X}, - {"y", SETTING_Y}, -}; - -StringMap Window::settings(Window::settingEntries, sizeof(Window::settingEntries)); - -StringMap::Entry Window::fullscreenTypeEntries[] = -{ - {"exclusive", FULLSCREEN_EXCLUSIVE}, - {"desktop", FULLSCREEN_DESKTOP}, -}; - -StringMap Window::fullscreenTypes(Window::fullscreenTypeEntries, sizeof(Window::fullscreenTypeEntries)); - -StringMap::Entry Window::messageBoxTypeEntries[] = -{ - {"error", MESSAGEBOX_ERROR}, - {"warning", MESSAGEBOX_WARNING}, - {"info", MESSAGEBOX_INFO}, -}; - -StringMap Window::messageBoxTypes(Window::messageBoxTypeEntries, sizeof(Window::messageBoxTypeEntries)); - -StringMap::Entry Window::orientationEntries[] = -{ - {"unknown", ORIENTATION_UNKNOWN}, - {"landscape", ORIENTATION_LANDSCAPE}, - {"landscapeflipped", ORIENTATION_LANDSCAPE_FLIPPED}, - {"portrait", ORIENTATION_PORTRAIT}, - {"portraitflipped", ORIENTATION_PORTRAIT_FLIPPED}, -}; - -StringMap Window::orientations(Window::orientationEntries, sizeof(Window::orientationEntries)); +STRINGMAP_CLASS_END(Window, Window::DisplayOrientation, Window::ORIENTATION_MAX_ENUM, orientation) } // window } // love diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index 4c4f159a7..5586645dd 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -55,6 +55,8 @@ class Window : public Module { public: + typedef void (*FileDialogCallback)(void *context, const std::vector &files, const char *filtername, const char *err); + // Different window settings. enum Setting { @@ -94,6 +96,14 @@ public: MESSAGEBOX_MAX_ENUM }; + enum FileDialogType + { + FILEDIALOG_OPENFILE, + FILEDIALOG_OPENFOLDER, + FILEDIALOG_SAVEFILE, + FILEDIALOG_MAX_ENUM + }; + enum DisplayOrientation { ORIENTATION_UNKNOWN, @@ -129,6 +139,24 @@ public: bool attachToWindow; }; + struct FileDialogFilter + { + std::string name; + std::string pattern; + }; + + struct FileDialogData + { + FileDialogType type; + std::string title; + std::string acceptLabel; + std::string cancelLabel; + std::string defaultName; + std::vector filters; + bool multiSelect; + bool attachToWindow; + }; + virtual ~Window(); virtual void setGraphics(graphics::Graphics *graphics) = 0; @@ -220,41 +248,20 @@ public: virtual bool showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow) = 0; virtual int showMessageBox(const MessageBoxData &data) = 0; + virtual void showFileDialog(const FileDialogData &data, FileDialogCallback callback, void *context) = 0; + virtual void requestAttention(bool continuous) = 0; - static bool getConstant(const char *in, Setting &out); - static bool getConstant(Setting in, const char *&out); - - static bool getConstant(const char *in, FullscreenType &out); - static bool getConstant(FullscreenType in, const char *&out); - static std::vector getConstants(FullscreenType); - - static bool getConstant(const char *in, MessageBoxType &out); - static bool getConstant(MessageBoxType in, const char *&out); - static std::vector getConstants(MessageBoxType); - - static bool getConstant(const char *in, DisplayOrientation &out); - static bool getConstant(DisplayOrientation in, const char *&out); - static std::vector getConstants(DisplayOrientation); + STRINGMAP_CLASS_DECLARE(Setting); + STRINGMAP_CLASS_DECLARE(FullscreenType); + STRINGMAP_CLASS_DECLARE(MessageBoxType); + STRINGMAP_CLASS_DECLARE(FileDialogType); + STRINGMAP_CLASS_DECLARE(DisplayOrientation); protected: Window(const char *name); -private: - - static StringMap::Entry settingEntries[]; - static StringMap settings; - - static StringMap::Entry fullscreenTypeEntries[]; - static StringMap fullscreenTypes; - - static StringMap::Entry messageBoxTypeEntries[]; - static StringMap messageBoxTypes; - - static StringMap::Entry orientationEntries[]; - static StringMap orientations; - }; // Window struct WindowSettings diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 70527110c..3c252cdd5 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -26,6 +26,7 @@ # include "graphics/vulkan/Vulkan.h" #endif #include "Window.h" +#include "filesystem/Filesystem.h" #ifdef LOVE_ANDROID #include "common/android.h" @@ -86,7 +87,7 @@ Window::Window() , displayedWindowError(false) , contextAttribs() { - if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) + if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError()); // Make sure the screensaver doesn't activate by default. @@ -97,13 +98,15 @@ Window::Window() // on some setups. More investigation is needed before enabling it. canUseDwmFlush = SDL_GetHintBoolean("LOVE_GRAPHICS_VSYNC_DWM", false); #endif + + dialogEventId = SDL_RegisterEvents(1); } Window::~Window() { close(false); graphics.set(nullptr); - SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS); } void Window::setGraphics(graphics::Graphics *graphics) @@ -1490,6 +1493,139 @@ int Window::showMessageBox(const MessageBoxData &data) return pressedbutton; } +// As of a SDL3 prerelease, a lot of SDL file dialog parameters need to persist +// until the callback completes, so we store them here. +// This is also used to retrieve some useful info to pass to love's own callback, +// and to send that along to SDL events (see below). +class FileDialogState : public love::Object +{ +public: + + void *context; + Window::FileDialogCallback callback; + Uint32 dialogEventId; + Window::FileDialogData data; + std::vector sdlFilters; + SDL_PropertiesID props; + + Optional err; + std::vector files; + int filterIndex; +}; + +static void SDLCALL fileDialogCallbackSDL(void *userdata, const char *const *filelist, int filter) +{ + auto state = (FileDialogState *) userdata; + if (state == nullptr) + return; + + auto fs = Module::getInstance(Module::M_FILESYSTEM); + + if (filelist != nullptr) + { + // SDL's file list only lasts until the end of the callback, so we copy it. + for (int i = 0; filelist[i] != nullptr; i++) + { + std::string file(filelist[i]); + if (fs != nullptr) + file = fs->canonicalizeRealPath(file); + state->files.push_back(file); + } + } + else + { + state->err.set(SDL_GetError()); + } + + state->filterIndex = filter; + + SDL_DestroyProperties(state->props); + + // The SDL dialog callback isn't guaranteed to be called on the main thread, + // whereas SDL event polling will happen there. This is needed because Lua states + // aren't thread safe. + SDL_Event event = {}; + event.type = state->dialogEventId; + event.user.data1 = state; + + SDL_PushEvent(&event); +} + +void Window::handleSDLEvent(const SDL_Event &event) +{ + if (event.type == dialogEventId) + { + // Releases itself when it goes out of scope. + StrongRef state((FileDialogState *) event.user.data1, Acquire::NORETAIN); + + const char *filtername = state->filterIndex >= 0 + ? state->data.filters[state->filterIndex].name.c_str() + : nullptr; + + state->callback(state->context, state->files, filtername, state->err.hasValue ? state->err.value.c_str() : nullptr); + } +} + +void Window::showFileDialog(const FileDialogData &data, FileDialogCallback callback, void *context) +{ + SDL_FileDialogType sdltype = SDL_FILEDIALOG_OPENFILE; + switch (data.type) + { + case FILEDIALOG_OPENFILE: + default: + sdltype = SDL_FILEDIALOG_OPENFILE; + break; + case FILEDIALOG_OPENFOLDER: + sdltype = SDL_FILEDIALOG_OPENFOLDER; + break; + case FILEDIALOG_SAVEFILE: + sdltype = SDL_FILEDIALOG_SAVEFILE; + break; + } + + auto state = new FileDialogState(); + state->callback = callback; + state->context = context; + state->dialogEventId = dialogEventId; + state->data = data; + + for (const auto &filter : state->data.filters) + { + SDL_DialogFileFilter f = {}; + f.name = filter.name.c_str(); + f.pattern = filter.pattern.c_str(); + state->sdlFilters.push_back(f); + } + + // We destroy this in the dialog callback, since it needs to persist until then (until that's fixed in SDL code). + state->props = SDL_CreateProperties(); + + if (!data.title.empty()) + SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_TITLE_STRING, state->data.title.c_str()); + + if (!data.acceptLabel.empty()) + SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, state->data.acceptLabel.c_str()); + + if (!data.cancelLabel.empty()) + SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, state->data.cancelLabel.c_str()); + + if (!data.defaultName.empty()) + SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, state->data.defaultName.c_str()); + + if (data.attachToWindow) + SDL_SetPointerProperty(state->props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window); + + if (!state->sdlFilters.empty()) + { + SDL_SetPointerProperty(state->props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, state->sdlFilters.data()); + SDL_SetNumberProperty(state->props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, state->sdlFilters.size()); + } + + SDL_SetBooleanProperty(state->props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, data.multiSelect); + + SDL_ShowFileDialogWithProperties(sdltype, fileDialogCallbackSDL, state, state->props); +} + void Window::requestAttention(bool continuous) { #if defined(LOVE_WINDOWS) && !defined(LOVE_WINDOWS_UWP) diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index adb804667..4eb1f4cfb 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -129,8 +129,12 @@ public: bool showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow) override; int showMessageBox(const MessageBoxData &data) override; + void showFileDialog(const FileDialogData &data, FileDialogCallback callback, void *context) override; + void requestAttention(bool continuous) override; + void handleSDLEvent(const SDL_Event &event); + private: struct ContextAttribs @@ -185,6 +189,8 @@ private: StrongRef graphics; + Uint32 dialogEventId; + }; // Window } // sdl diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index a1777689a..5558d4e6a 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/src/modules/window/wrap_Window.cpp @@ -20,6 +20,7 @@ #include "wrap_Window.h" #include "sdl/Window.h" +#include "common/Reference.h" namespace love { @@ -647,6 +648,110 @@ int w_showMessageBox(lua_State *L) return 1; } +static void fileDialogCallback(void *context, const std::vector &files, const char *filtername, const char *errstr) +{ + auto r = (Reference *)context; + lua_State *L = r->getPinnedL(); + + r->push(L); + + lua_createtable(L, (int)files.size(), 0); + for (size_t i = 0; i < files.size(); i++) + { + lua_pushstring(L, files[i].c_str()); + lua_rawseti(L, -2, (int)i + 1); + } + + if (filtername != nullptr) + lua_pushstring(L, filtername); + else + lua_pushnil(L); + + if (errstr != nullptr) + lua_pushstring(L, errstr); + else + lua_pushnil(L); + + int err = lua_pcall(L, 3, 0, 0); + + delete r; + + // Unfortunately, this eats the stack trace, too bad. + if (err != 0) + throw love::Exception("Error in file dialog callback: %s", luax_tostring(L, -1)); +} + +int w_showFileDialog(lua_State *L) +{ + Window::FileDialogData data = {}; + + const char *typestr = luaL_checkstring(L, 1); + if (!Window::getConstant(typestr, data.type)) + return luax_enumerror(L, "file dialog type", Window::getConstants(data.type), typestr); + + luaL_checktype(L, 2, LUA_TFUNCTION); + + if (!lua_isnoneornil(L, 3)) + { + luaL_checktype(L, 3, LUA_TTABLE); + + lua_getfield(L, 3, "title"); + if (!lua_isnoneornil(L, -1)) + data.title = luax_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 3, "acceptlabel"); + if (!lua_isnoneornil(L, -1)) + data.acceptLabel = luax_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 3, "cancellabel"); + if (!lua_isnoneornil(L, -1)) + data.cancelLabel = luax_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 3, "defaultname"); + if (!lua_isnoneornil(L, -1)) + data.defaultName = luax_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 3, "filters"); + if (!lua_isnoneornil(L, -1)) + { + luaL_checktype(L, -1, LUA_TTABLE); + + lua_pushnil(L); + while (lua_next(L, -2)) + { + Window::FileDialogFilter filter = {}; + filter.name = luax_checkstring(L, -2); + filter.pattern = luax_checkstring(L, -1); + data.filters.push_back(filter); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + lua_getfield(L, 3, "multiselect"); + if (!lua_isnoneornil(L, -1)) + data.multiSelect = luax_checkboolean(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 3, "attachtowindow"); + if (!lua_isnoneornil(L, -1)) + data.attachToWindow = luax_checkboolean(L, -1); + lua_pop(L, 1); + } + + // Save the callback function as a Reference. + lua_pushvalue(L, 2); + Reference *r = new Reference(L); + lua_pop(L, 1); + + instance()->showFileDialog(data, fileDialogCallback, r); + return 0; +} + int w_requestAttention(lua_State *L) { bool continuous = luax_optboolean(L, 1, false); @@ -701,6 +806,7 @@ static const luaL_Reg functions[] = { "isMaximized", w_isMaximized }, { "isMinimized", w_isMinimized }, { "showMessageBox", w_showMessageBox }, + { "showFileDialog", w_showFileDialog }, { "requestAttention", w_requestAttention }, { "getPointer", w_getPointer }, { 0, 0 }