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 }