Window.cpp 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538
  1. /**
  2. * Copyright (c) 2006-2024 LOVE Development Team
  3. *
  4. * This software is provided 'as-is', without any express or implied
  5. * warranty. In no event will the authors be held liable for any damages
  6. * arising from the use of this software.
  7. *
  8. * Permission is granted to anyone to use this software for any purpose,
  9. * including commercial applications, and to alter it and redistribute it
  10. * freely, subject to the following restrictions:
  11. *
  12. * 1. The origin of this software must not be misrepresented; you must not
  13. * claim that you wrote the original software. If you use this software
  14. * in a product, an acknowledgment in the product documentation would be
  15. * appreciated but is not required.
  16. * 2. Altered source versions must be plainly marked as such, and must not be
  17. * misrepresented as being the original software.
  18. * 3. This notice may not be removed or altered from any source distribution.
  19. **/
  20. // LOVE
  21. #include "common/config.h"
  22. #include "graphics/Graphics.h"
  23. #ifdef LOVE_GRAPHICS_VULKAN
  24. # include "graphics/vulkan/Graphics.h"
  25. # include "graphics/vulkan/Vulkan.h"
  26. #endif
  27. #include "Window.h"
  28. #ifdef LOVE_ANDROID
  29. #include "common/android.h"
  30. #endif
  31. #ifdef LOVE_IOS
  32. #include "common/ios.h"
  33. #endif
  34. // C++
  35. #include <iostream>
  36. #include <vector>
  37. #include <algorithm>
  38. // C
  39. #include <cstdio>
  40. #ifdef LOVE_GRAPHICS_VULKAN
  41. #include <SDL3/SDL_vulkan.h>
  42. #endif
  43. #if defined(LOVE_WINDOWS)
  44. #define WIN32_LEAN_AND_MEAN
  45. #include <windows.h>
  46. #include <dwmapi.h>
  47. #include <VersionHelpers.h>
  48. #elif defined(LOVE_MACOS)
  49. #include "common/macos.h"
  50. #endif
  51. #ifndef APIENTRY
  52. #define APIENTRY
  53. #endif
  54. #ifndef SDL_HINT_WINDOWS_DPI_SCALING
  55. #define SDL_HINT_WINDOWS_DPI_SCALING "SDL_WINDOWS_DPI_SCALING"
  56. #endif
  57. namespace love
  58. {
  59. namespace window
  60. {
  61. // See src/modules/window/Window.cpp.
  62. void setHighDPIAllowedImplementation(bool enable)
  63. {
  64. #if defined(LOVE_WINDOWS)
  65. // Windows uses a different API than SDL_WINDOW_ALLOW_HIGHDPI.
  66. // This must be set before the video subsystem is initialized.
  67. // FIXME: How does this work in SDL3?
  68. SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, enable ? "1" : "0");
  69. #else
  70. LOVE_UNUSED(enable);
  71. #endif
  72. }
  73. namespace sdl
  74. {
  75. Window::Window()
  76. : love::window::Window("love.window.sdl")
  77. , open(false)
  78. , mouseGrabbed(false)
  79. , window(nullptr)
  80. , glcontext(nullptr)
  81. #ifdef LOVE_GRAPHICS_METAL
  82. , metalView(nullptr)
  83. #endif
  84. , displayedWindowError(false)
  85. , contextAttribs()
  86. {
  87. if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
  88. throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError());
  89. // Make sure the screensaver doesn't activate by default.
  90. setDisplaySleepEnabled(false);
  91. #ifdef LOVE_WINDOWS
  92. // Turned off by default, because it (ironically) causes stuttering issues
  93. // on some setups. More investigation is needed before enabling it.
  94. canUseDwmFlush = SDL_GetHintBoolean("LOVE_GRAPHICS_VSYNC_DWM", false);
  95. #endif
  96. }
  97. Window::~Window()
  98. {
  99. close(false);
  100. graphics.set(nullptr);
  101. SDL_QuitSubSystem(SDL_INIT_VIDEO);
  102. }
  103. void Window::setGraphics(graphics::Graphics *graphics)
  104. {
  105. this->graphics.set(graphics);
  106. }
  107. void Window::setGLFramebufferAttributes(bool sRGB)
  108. {
  109. // Set GL window / framebuffer attributes.
  110. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  111. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  112. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  113. SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
  114. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  115. SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
  116. // Always use 24/8 depth/stencil.
  117. // Changing this after initial window creation would need the context to be
  118. // destroyed and recreated, which we really don't want.
  119. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  120. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
  121. // Backbuffer MSAA is handled by the love.graphics implementation.
  122. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
  123. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
  124. SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
  125. #if defined(LOVE_WINDOWS)
  126. // Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
  127. // older Intel drivers like to use it as a fallback when requesting some
  128. // unsupported framebuffer attribute values, rather than properly failing.
  129. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  130. #endif
  131. }
  132. void Window::setGLContextAttributes(const ContextAttribs &attribs)
  133. {
  134. int profilemask = 0;
  135. int contextflags = 0;
  136. if (attribs.gles)
  137. profilemask = SDL_GL_CONTEXT_PROFILE_ES;
  138. else if (attribs.versionMajor * 10 + attribs.versionMinor >= 32)
  139. profilemask |= SDL_GL_CONTEXT_PROFILE_CORE;
  140. else if (attribs.debug)
  141. profilemask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
  142. if (attribs.debug)
  143. contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;
  144. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, attribs.versionMajor);
  145. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor);
  146. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
  147. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags);
  148. }
  149. bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversion)
  150. {
  151. typedef unsigned char GLubyte;
  152. typedef unsigned int GLenum;
  153. typedef const GLubyte *(APIENTRY *glGetStringPtr)(GLenum name);
  154. const GLenum GL_VENDOR_ENUM = 0x1F00;
  155. const GLenum GL_RENDERER_ENUM = 0x1F01;
  156. const GLenum GL_VERSION_ENUM = 0x1F02;
  157. // We don't have OpenGL headers or an automatic OpenGL function loader in
  158. // this module, so we have to get the glGetString function pointer ourselves.
  159. glGetStringPtr glGetStringFunc = (glGetStringPtr) SDL_GL_GetProcAddress("glGetString");
  160. if (!glGetStringFunc)
  161. return false;
  162. const char *glversion = (const char *) glGetStringFunc(GL_VERSION_ENUM);
  163. if (!glversion)
  164. return false;
  165. outversion = glversion;
  166. const char *glrenderer = (const char *) glGetStringFunc(GL_RENDERER_ENUM);
  167. if (glrenderer)
  168. outversion += " - " + std::string(glrenderer);
  169. const char *glvendor = (const char *) glGetStringFunc(GL_VENDOR_ENUM);
  170. if (glvendor)
  171. outversion += " (" + std::string(glvendor) + ")";
  172. int glmajor = 0;
  173. int glminor = 0;
  174. // glGetString(GL_VERSION) returns a string with the format "major.minor",
  175. // or "OpenGL ES major.minor" in GLES contexts.
  176. const char *format = "%d.%d";
  177. if (attribs.gles)
  178. format = "OpenGL ES %d.%d";
  179. if (sscanf(glversion, format, &glmajor, &glminor) != 2)
  180. return false;
  181. if (glmajor < attribs.versionMajor
  182. || (glmajor == attribs.versionMajor && glminor < attribs.versionMinor))
  183. return false;
  184. return true;
  185. }
  186. std::vector<Window::ContextAttribs> Window::getContextAttribsList() const
  187. {
  188. // If we already have a set of context attributes that we know work, just
  189. // return that. love.graphics doesn't really support switching GL versions
  190. // after the first initialization.
  191. if (contextAttribs.versionMajor > 0)
  192. return std::vector<ContextAttribs>{contextAttribs};
  193. bool preferGLES = false;
  194. #ifdef LOVE_GRAPHICS_USE_OPENGLES
  195. preferGLES = true;
  196. #endif
  197. const char *curdriver = SDL_GetCurrentVideoDriver();
  198. const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt", "emscripten"};
  199. // We always want to try OpenGL ES first on certain video backends.
  200. for (const char *glesdriver : glesdrivers)
  201. {
  202. if (curdriver && strstr(curdriver, glesdriver) == curdriver)
  203. {
  204. preferGLES = true;
  205. break;
  206. }
  207. }
  208. const char *gleshint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
  209. if (gleshint != nullptr)
  210. preferGLES = (gleshint != nullptr && gleshint[0] != '0');
  211. // Do we want a debug context?
  212. bool debug = love::graphics::isDebugEnabled();
  213. const char *preferGL3hint = SDL_GetHint("LOVE_GRAPHICS_USE_GL3");
  214. bool preferGL3 = (preferGL3hint != nullptr && preferGL3hint[0] != '0');
  215. std::vector<ContextAttribs> glcontexts =
  216. {
  217. {4, 3, false, debug},
  218. {3, 3, false, debug},
  219. };
  220. std::vector<ContextAttribs> glescontexts =
  221. {
  222. {3, 2, true, debug},
  223. {3, 0, true, debug},
  224. };
  225. if (preferGL3)
  226. {
  227. std::swap(glcontexts[0], glcontexts[1]);
  228. std::swap(glescontexts[0], glescontexts[1]);
  229. }
  230. std::vector<ContextAttribs> attribslist;
  231. if (preferGLES)
  232. {
  233. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  234. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  235. }
  236. else
  237. {
  238. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  239. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  240. }
  241. return attribslist;
  242. }
  243. bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, graphics::Renderer renderer)
  244. {
  245. bool needsglcontext = (windowflags & SDL_WINDOW_OPENGL) != 0;
  246. #ifdef LOVE_GRAPHICS_METAL
  247. bool needsmetalview = (windowflags & SDL_WINDOW_METAL) != 0;
  248. #endif
  249. std::string windowerror;
  250. std::string contexterror;
  251. std::string glversion;
  252. // Unfortunately some OpenGL context settings are part of the internal
  253. // window state in the Windows and Linux SDL backends, so we have to
  254. // recreate the window when we want to change those settings...
  255. // Also, apparently some Intel drivers on Windows give back a Microsoft
  256. // OpenGL 1.1 software renderer context when high MSAA values are requested!
  257. const auto create = [&](const ContextAttribs *attribs) -> bool
  258. {
  259. if (glcontext)
  260. {
  261. SDL_GL_DestroyContext(glcontext);
  262. glcontext = nullptr;
  263. }
  264. #ifdef LOVE_GRAPHICS_METAL
  265. if (metalView)
  266. {
  267. SDL_Metal_DestroyView(metalView);
  268. metalView = nullptr;
  269. }
  270. #endif
  271. if (window)
  272. {
  273. SDL_DestroyWindow(window);
  274. SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
  275. window = nullptr;
  276. }
  277. window = SDL_CreateWindow(title.c_str(), w, h, windowflags);
  278. if (!window)
  279. {
  280. windowerror = std::string(SDL_GetError());
  281. return false;
  282. }
  283. SDL_SetWindowPosition(window, x, y);
  284. if (attribs != nullptr && renderer == love::graphics::Renderer::RENDERER_OPENGL)
  285. {
  286. #ifdef LOVE_MACOS
  287. love::macos::setWindowSRGBColorSpace(window);
  288. #endif
  289. glcontext = SDL_GL_CreateContext(window);
  290. if (!glcontext)
  291. contexterror = std::string(SDL_GetError());
  292. // Make sure the context's version is at least what we requested.
  293. if (glcontext && !checkGLVersion(*attribs, glversion))
  294. {
  295. SDL_GL_DestroyContext(glcontext);
  296. glcontext = nullptr;
  297. }
  298. if (!glcontext)
  299. {
  300. SDL_DestroyWindow(window);
  301. window = nullptr;
  302. return false;
  303. }
  304. }
  305. return true;
  306. };
  307. if (renderer == graphics::RENDERER_OPENGL)
  308. {
  309. std::vector<ContextAttribs> attribslist = getContextAttribsList();
  310. // Try each context profile in order.
  311. for (ContextAttribs attribs : attribslist)
  312. {
  313. bool curSRGB = love::graphics::isGammaCorrect();
  314. setGLFramebufferAttributes(curSRGB);
  315. setGLContextAttributes(attribs);
  316. windowerror.clear();
  317. contexterror.clear();
  318. create(&attribs);
  319. if (!window && curSRGB)
  320. {
  321. // The sRGB setting could have caused the failure.
  322. setGLFramebufferAttributes(false);
  323. if (create(&attribs))
  324. curSRGB = false;
  325. }
  326. if (window && glcontext)
  327. {
  328. // Store the successful context attributes so we can re-use them in
  329. // subsequent calls to createWindowAndContext.
  330. contextAttribs = attribs;
  331. love::graphics::setGammaCorrect(curSRGB);
  332. break;
  333. }
  334. }
  335. }
  336. #ifdef LOVE_GRAPHICS_METAL
  337. else if (renderer == graphics::RENDERER_METAL)
  338. {
  339. if (create(nullptr) && window != nullptr)
  340. metalView = SDL_Metal_CreateView(window);
  341. if (metalView == nullptr && window != nullptr)
  342. {
  343. contexterror = SDL_GetError();
  344. SDL_DestroyWindow(window);
  345. window = nullptr;
  346. }
  347. }
  348. #endif
  349. else
  350. {
  351. create(nullptr);
  352. }
  353. bool failed = window == nullptr;
  354. failed |= (needsglcontext && !glcontext);
  355. #ifdef LOVE_GRAPHICS_METAL
  356. failed |= (needsmetalview && !metalView);
  357. #endif
  358. if (failed)
  359. {
  360. std::string title = "Unable to create renderer";
  361. std::string message = "This program requires a graphics card and video drivers which support OpenGL 3.3 or OpenGL ES 3.0.";
  362. if (!glversion.empty())
  363. message += "\n\nDetected OpenGL version:\n" + glversion;
  364. else if (!contexterror.empty())
  365. message += "\n\nRenderer context creation error: " + contexterror;
  366. else if (!windowerror.empty())
  367. message += "\n\nSDL window creation error: " + windowerror;
  368. std::cerr << title << std::endl << message << std::endl;
  369. // Display a message box with the error, but only once.
  370. if (!displayedWindowError)
  371. {
  372. showMessageBox(title, message, MESSAGEBOX_ERROR, false);
  373. displayedWindowError = true;
  374. }
  375. close();
  376. return false;
  377. }
  378. open = true;
  379. return true;
  380. }
  381. struct SDLDisplayIDs
  382. {
  383. SDLDisplayIDs()
  384. {
  385. ids = SDL_GetDisplays(&count);
  386. }
  387. ~SDLDisplayIDs()
  388. {
  389. if (ids)
  390. SDL_free(ids);
  391. }
  392. int count = 0;
  393. SDL_DisplayID *ids = nullptr;
  394. };
  395. static SDL_DisplayID GetSDLDisplayIDForIndex(int displayindex)
  396. {
  397. SDLDisplayIDs displayids;
  398. if (displayindex < 0 || displayindex >= displayids.count)
  399. return (SDL_DisplayID) 0;
  400. return displayids.ids[displayindex];
  401. }
  402. bool Window::setWindow(int width, int height, WindowSettings *settings)
  403. {
  404. if (!graphics.get())
  405. graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
  406. if (graphics.get() && graphics->isRenderTargetActive())
  407. throw love::Exception("love.window.setMode cannot be called while a render target is active in love.graphics.");
  408. auto renderer = graphics != nullptr ? graphics->getRenderer() : graphics::RENDERER_NONE;
  409. if (isOpen())
  410. updateSettings(this->settings, false);
  411. WindowSettings f;
  412. if (settings)
  413. f = *settings;
  414. f.minwidth = std::max(f.minwidth, 1);
  415. f.minheight = std::max(f.minheight, 1);
  416. SDLDisplayIDs displays;
  417. int displaycount = displays.count;
  418. f.displayindex = std::min(std::max(f.displayindex, 0), displaycount - 1);
  419. // Use the desktop resolution if a width or height of 0 is specified.
  420. if (width == 0 || height == 0)
  421. {
  422. const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(displays.ids[f.displayindex]);
  423. width = mode->w;
  424. height = mode->h;
  425. }
  426. // On Android, disable fullscreen first on window creation so it's
  427. // possible to change the orientation by specifying portait width and
  428. // height, otherwise SDL will pick the current orientation dimensions when
  429. // fullscreen flag is set. Don't worry, we'll set it back later when user
  430. // also requested fullscreen after the window is created.
  431. // See https://github.com/love2d/love-android/issues/196
  432. #ifdef LOVE_ANDROID
  433. bool fullscreen = f.fullscreen;
  434. f.fullscreen = false;
  435. f.fstype = FULLSCREEN_DESKTOP;
  436. #endif
  437. int x = f.x;
  438. int y = f.y;
  439. if (f.useposition)
  440. {
  441. // The position needs to be in the global coordinate space.
  442. SDL_Rect displaybounds = {};
  443. SDL_GetDisplayBounds(f.displayindex, &displaybounds);
  444. x += displaybounds.x;
  445. y += displaybounds.y;
  446. }
  447. else
  448. {
  449. if (f.centered)
  450. x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.displayindex);
  451. else
  452. x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.displayindex);
  453. }
  454. Uint32 sdlflags = 0;
  455. SDL_DisplayMode fsmode = {};
  456. if (f.fullscreen)
  457. {
  458. sdlflags |= SDL_WINDOW_FULLSCREEN;
  459. if (f.fstype == FULLSCREEN_EXCLUSIVE)
  460. {
  461. SDL_DisplayID display = displays.ids[f.displayindex];
  462. if (!SDL_GetClosestFullscreenDisplayMode(display, width, height, 0, isHighDPIAllowed(), &fsmode))
  463. {
  464. // GetClosestDisplayMode will fail if we request a size larger
  465. // than the largest available display mode, so we'll try to use
  466. // the largest (first) mode in that case.
  467. int modecount = 0;
  468. SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &modecount);
  469. if (modecount > 0)
  470. fsmode = *modes[0];
  471. SDL_free(modes);
  472. if (fsmode.w == 0 || fsmode.h == 0)
  473. return false;
  474. }
  475. }
  476. }
  477. bool needsetmode = false;
  478. if (renderer != windowRenderer && isOpen())
  479. close();
  480. if (isOpen())
  481. {
  482. if (fsmode.w > 0 && fsmode.h > 0)
  483. SDL_SetWindowFullscreenMode(window, &fsmode);
  484. else
  485. SDL_SetWindowFullscreenMode(window, nullptr);
  486. if (SDL_SetWindowFullscreen(window, (sdlflags & SDL_WINDOW_FULLSCREEN) != 0) && renderer == graphics::RENDERER_OPENGL)
  487. SDL_GL_MakeCurrent(window, glcontext);
  488. // TODO: should we make this conditional, to avoid love.resize events when the size doesn't change?
  489. SDL_SetWindowSize(window, width, height);
  490. if (this->settings.resizable != f.resizable)
  491. SDL_SetWindowResizable(window, f.resizable);
  492. if (this->settings.borderless != f.borderless)
  493. SDL_SetWindowBordered(window, !f.borderless);
  494. }
  495. else
  496. {
  497. if (renderer == graphics::RENDERER_OPENGL)
  498. sdlflags |= SDL_WINDOW_OPENGL;
  499. #ifdef LOVE_GRAPHICS_METAL
  500. if (renderer == graphics::RENDERER_METAL)
  501. sdlflags |= SDL_WINDOW_METAL;
  502. #endif
  503. if (renderer == graphics::RENDERER_VULKAN)
  504. sdlflags |= SDL_WINDOW_VULKAN;
  505. if (f.resizable)
  506. sdlflags |= SDL_WINDOW_RESIZABLE;
  507. if (f.borderless)
  508. sdlflags |= SDL_WINDOW_BORDERLESS;
  509. // Note: this flag is ignored on Windows.
  510. if (isHighDPIAllowed())
  511. sdlflags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
  512. Uint32 createflags = sdlflags & (~SDL_WINDOW_FULLSCREEN);
  513. if (!createWindowAndContext(x, y, width, height, createflags, renderer))
  514. return false;
  515. if (f.fullscreen)
  516. {
  517. if (fsmode.w > 0 && fsmode.h > 0)
  518. SDL_SetWindowFullscreenMode(window, &fsmode);
  519. else
  520. SDL_SetWindowFullscreenMode(window, nullptr);
  521. SDL_SetWindowFullscreen(window, true);
  522. }
  523. needsetmode = true;
  524. }
  525. // Make sure the window keeps any previously set icon.
  526. setIcon(icon.get());
  527. // Make sure the mouse keeps its previous grab setting.
  528. setMouseGrab(mouseGrabbed);
  529. // Enforce minimum window dimensions.
  530. SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);
  531. if (this->settings.displayindex != f.displayindex || f.useposition || f.centered)
  532. SDL_SetWindowPosition(window, x, y);
  533. SDL_RaiseWindow(window);
  534. setVSync(f.vsync);
  535. updateSettings(f, false);
  536. windowRenderer = renderer;
  537. if (graphics.get())
  538. {
  539. double scaledw, scaledh;
  540. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  541. if (needsetmode)
  542. {
  543. void *context = nullptr;
  544. if (renderer == graphics::RENDERER_OPENGL)
  545. context = (void *) glcontext;
  546. #ifdef LOVE_GRAPHICS_METAL
  547. if (renderer == graphics::RENDERER_METAL && metalView)
  548. context = (void *) SDL_Metal_GetLayer(metalView);
  549. #endif
  550. // TODO: try/catch
  551. graphics->setMode(context, (int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil, f.depth, f.msaa);
  552. }
  553. else
  554. {
  555. graphics->backbufferChanged((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil, f.depth, f.msaa);
  556. }
  557. this->settings.msaa = graphics->getBackbufferMSAA();
  558. }
  559. // Set fullscreen when user requested it before.
  560. // See above for explanation.
  561. #ifdef LOVE_ANDROID
  562. setFullscreen(fullscreen);
  563. love::android::setImmersive(fullscreen);
  564. #endif
  565. SDL_SyncWindow(window);
  566. return true;
  567. }
  568. bool Window::onSizeChanged(int width, int height)
  569. {
  570. if (!window)
  571. return false;
  572. SDL_GetWindowSize(window, &windowWidth, &windowHeight);
  573. if (!SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight))
  574. {
  575. pixelWidth = width;
  576. pixelHeight = height;
  577. }
  578. if (graphics.get())
  579. {
  580. double scaledw, scaledh;
  581. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  582. graphics->backbufferChanged((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  583. }
  584. return true;
  585. }
  586. void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
  587. {
  588. SDL_SyncWindow(window);
  589. Uint32 wflags = SDL_GetWindowFlags(window);
  590. // Set the new display mode as the current display mode.
  591. SDL_GetWindowSize(window, &windowWidth, &windowHeight);
  592. pixelWidth = windowWidth;
  593. pixelHeight = windowHeight;
  594. SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight);
  595. if (((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) && SDL_GetWindowFullscreenMode(window) == nullptr)
  596. {
  597. settings.fullscreen = true;
  598. settings.fstype = FULLSCREEN_DESKTOP;
  599. }
  600. else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
  601. {
  602. settings.fullscreen = true;
  603. settings.fstype = FULLSCREEN_EXCLUSIVE;
  604. }
  605. else
  606. {
  607. settings.fullscreen = false;
  608. settings.fstype = newsettings.fstype;
  609. }
  610. #ifdef LOVE_ANDROID
  611. settings.fullscreen = love::android::getImmersive();
  612. #endif
  613. // SDL_GetWindowMinimumSize gives back 0,0 sometimes...
  614. settings.minwidth = newsettings.minwidth;
  615. settings.minheight = newsettings.minheight;
  616. settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
  617. settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
  618. settings.centered = newsettings.centered;
  619. getPosition(settings.x, settings.y, settings.displayindex);
  620. setHighDPIAllowed((wflags & SDL_WINDOW_HIGH_PIXEL_DENSITY) != 0);
  621. settings.usedpiscale = newsettings.usedpiscale;
  622. // Only minimize on focus loss if the window is in exclusive-fullscreen mode
  623. if (settings.fullscreen && settings.fstype == FULLSCREEN_EXCLUSIVE)
  624. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
  625. else
  626. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
  627. settings.vsync = getVSync();
  628. settings.stencil = newsettings.stencil;
  629. settings.depth = newsettings.depth;
  630. SDLDisplayIDs displayids;
  631. const SDL_DisplayMode *dmode = SDL_GetCurrentDisplayMode(displayids.ids[settings.displayindex]);
  632. // May be 0 if the refresh rate can't be determined.
  633. settings.refreshrate = dmode->refresh_rate;
  634. // Update the viewport size now instead of waiting for event polling.
  635. if (updateGraphicsViewport && graphics.get())
  636. {
  637. double scaledw, scaledh;
  638. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  639. graphics->backbufferChanged((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  640. }
  641. }
  642. void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
  643. {
  644. // The window might have been modified (moved, resized, etc.) by the user.
  645. if (window)
  646. updateSettings(settings, true);
  647. width = windowWidth;
  648. height = windowHeight;
  649. newsettings = settings;
  650. }
  651. void Window::close()
  652. {
  653. close(true);
  654. }
  655. void Window::close(bool allowExceptions)
  656. {
  657. if (graphics.get())
  658. {
  659. if (allowExceptions && graphics->isRenderTargetActive())
  660. throw love::Exception("love.window.close cannot be called while a render target is active in love.graphics.");
  661. graphics->unSetMode();
  662. }
  663. if (glcontext)
  664. {
  665. SDL_GL_DestroyContext(glcontext);
  666. glcontext = nullptr;
  667. }
  668. #ifdef LOVE_GRAPHICS_METAL
  669. if (metalView)
  670. {
  671. SDL_Metal_DestroyView(metalView);
  672. metalView = nullptr;
  673. }
  674. #endif
  675. if (window)
  676. {
  677. SDL_DestroyWindow(window);
  678. window = nullptr;
  679. // The old window may have generated pending events which are no longer
  680. // relevant. Destroy them all!
  681. SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
  682. }
  683. open = false;
  684. }
  685. bool Window::setFullscreen(bool fullscreen, FullscreenType fstype)
  686. {
  687. if (!window)
  688. return false;
  689. if (graphics.get() && graphics->isRenderTargetActive())
  690. throw love::Exception("love.window.setFullscreen cannot be called while a render target is active in love.graphics.");
  691. WindowSettings newsettings = settings;
  692. newsettings.fullscreen = fullscreen;
  693. newsettings.fstype = fstype;
  694. bool sdlflags = fullscreen;
  695. if (fullscreen)
  696. {
  697. if (fstype == FULLSCREEN_DESKTOP)
  698. SDL_SetWindowFullscreenMode(window, nullptr);
  699. else
  700. {
  701. SDL_DisplayID displayid = SDL_GetDisplayForWindow(window);
  702. SDL_DisplayMode mode = {};
  703. if (SDL_GetClosestFullscreenDisplayMode(displayid, windowWidth, windowHeight, 0, isHighDPIAllowed(), &mode))
  704. SDL_SetWindowFullscreenMode(window, &mode);
  705. }
  706. }
  707. #ifdef LOVE_ANDROID
  708. love::android::setImmersive(fullscreen);
  709. #endif
  710. if (SDL_SetWindowFullscreen(window, sdlflags))
  711. {
  712. if (glcontext)
  713. SDL_GL_MakeCurrent(window, glcontext);
  714. updateSettings(newsettings, true);
  715. return true;
  716. }
  717. return false;
  718. }
  719. bool Window::setFullscreen(bool fullscreen)
  720. {
  721. return setFullscreen(fullscreen, settings.fstype);
  722. }
  723. int Window::getDisplayCount() const
  724. {
  725. SDLDisplayIDs displayids;
  726. return displayids.count;
  727. }
  728. const char *Window::getDisplayName(int displayindex) const
  729. {
  730. const char *name = SDL_GetDisplayName(GetSDLDisplayIDForIndex(displayindex));
  731. if (name == nullptr)
  732. throw love::Exception("Invalid display index: %d", displayindex + 1);
  733. return name;
  734. }
  735. Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
  736. {
  737. switch (SDL_GetCurrentDisplayOrientation(GetSDLDisplayIDForIndex(displayindex)))
  738. {
  739. case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
  740. case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
  741. case SDL_ORIENTATION_LANDSCAPE_FLIPPED: return ORIENTATION_LANDSCAPE_FLIPPED;
  742. case SDL_ORIENTATION_PORTRAIT: return ORIENTATION_PORTRAIT;
  743. case SDL_ORIENTATION_PORTRAIT_FLIPPED: return ORIENTATION_PORTRAIT_FLIPPED;
  744. }
  745. return ORIENTATION_UNKNOWN;
  746. }
  747. std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) const
  748. {
  749. std::vector<WindowSize> sizes;
  750. int count = 0;
  751. SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(GetSDLDisplayIDForIndex(displayindex), &count);
  752. for (int i = 0; i < count; i++)
  753. {
  754. // TODO: other mode properties?
  755. WindowSize w = {modes[i]->w, modes[i]->h};
  756. // SDL2's display mode list has multiple entries for modes of the same
  757. // size with different bits per pixel, so we need to filter those out.
  758. if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
  759. sizes.push_back(w);
  760. }
  761. SDL_free(modes);
  762. return sizes;
  763. }
  764. void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
  765. {
  766. const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(GetSDLDisplayIDForIndex(displayindex));
  767. if (mode != nullptr)
  768. {
  769. // TODO: other properties?
  770. width = mode->w;
  771. height = mode->h;
  772. }
  773. else
  774. {
  775. width = 0;
  776. height = 0;
  777. }
  778. }
  779. void Window::setPosition(int x, int y, int displayindex)
  780. {
  781. if (!window)
  782. return;
  783. displayindex = std::min(std::max(displayindex, 0), getDisplayCount() - 1);
  784. SDL_Rect displaybounds = {};
  785. SDL_GetDisplayBounds(displayindex, &displaybounds);
  786. // The position needs to be in the global coordinate space.
  787. x += displaybounds.x;
  788. y += displaybounds.y;
  789. SDL_SetWindowPosition(window, x, y);
  790. SDL_SyncWindow(window);
  791. settings.useposition = true;
  792. }
  793. void Window::getPosition(int &x, int &y, int &displayindex)
  794. {
  795. if (!window)
  796. {
  797. x = y = 0;
  798. displayindex = 0;
  799. return;
  800. }
  801. SDL_DisplayID displayid = SDL_GetDisplayForWindow(window);
  802. SDLDisplayIDs displayids;
  803. displayindex = 0;
  804. for (int i = 0; i < displayids.count; i++)
  805. {
  806. if (displayids.ids[i] == displayid)
  807. {
  808. displayindex = i;
  809. break;
  810. }
  811. }
  812. SDL_GetWindowPosition(window, &x, &y);
  813. // In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
  814. // other case we need to convert the position from global coordinates to the
  815. // monitor's coordinate space.
  816. if (x != 0 || y != 0)
  817. {
  818. SDL_Rect displaybounds = {};
  819. SDL_GetDisplayBounds(displayid, &displaybounds);
  820. x -= displaybounds.x;
  821. y -= displaybounds.y;
  822. }
  823. }
  824. Rect Window::getSafeArea() const
  825. {
  826. #if defined(LOVE_IOS)
  827. if (window != nullptr)
  828. return love::ios::getSafeArea(window);
  829. #elif defined(LOVE_ANDROID)
  830. if (window != nullptr)
  831. {
  832. int top, left, bottom, right;
  833. if (love::android::getSafeArea(top, left, bottom, right))
  834. {
  835. // DisplayCutout API returns safe area in pixels
  836. // and is affected by display orientation.
  837. double safeLeft, safeTop, safeWidth, safeHeight;
  838. fromPixels(left, top, safeLeft, safeTop);
  839. fromPixels(pixelWidth - left - right, pixelHeight - top - bottom, safeWidth, safeHeight);
  840. return {(int) safeLeft, (int) safeTop, (int) safeWidth, (int) safeHeight};
  841. }
  842. }
  843. #endif
  844. double dw, dh;
  845. fromPixels(pixelWidth, pixelHeight, dw, dh);
  846. return {0, 0, (int) dw, (int) dh};
  847. }
  848. bool Window::isOpen() const
  849. {
  850. return open;
  851. }
  852. void Window::setWindowTitle(const std::string &title)
  853. {
  854. this->title = title;
  855. if (window)
  856. SDL_SetWindowTitle(window, title.c_str());
  857. }
  858. const std::string &Window::getWindowTitle() const
  859. {
  860. return title;
  861. }
  862. bool Window::setIcon(love::image::ImageData *imgd)
  863. {
  864. if (!imgd)
  865. return false;
  866. if (imgd->getFormat() != PIXELFORMAT_RGBA8_UNORM)
  867. throw love::Exception("setIcon only accepts 32-bit RGBA images.");
  868. icon.set(imgd);
  869. if (!window)
  870. return false;
  871. int w = imgd->getWidth();
  872. int h = imgd->getHeight();
  873. int bytesperpixel = (int) getPixelFormatBlockSize(imgd->getFormat());
  874. int pitch = w * bytesperpixel;
  875. SDL_Surface *sdlicon = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA8888, imgd->getData(), pitch);
  876. if (!sdlicon)
  877. return false;
  878. SDL_SetWindowIcon(window, sdlicon);
  879. SDL_DestroySurface(sdlicon);
  880. return true;
  881. }
  882. love::image::ImageData *Window::getIcon()
  883. {
  884. return icon.get();
  885. }
  886. void Window::setVSync(int vsync)
  887. {
  888. if (glcontext != nullptr)
  889. {
  890. SDL_GL_SetSwapInterval(vsync);
  891. // Check if adaptive vsync was requested but not supported, and fall
  892. // back to regular vsync if so.
  893. if (vsync == -1)
  894. {
  895. int actualvsync = 0;
  896. SDL_GL_GetSwapInterval(&actualvsync);
  897. if (actualvsync != -1)
  898. SDL_GL_SetSwapInterval(1);
  899. }
  900. }
  901. #ifdef LOVE_GRAPHICS_VULKAN
  902. if (windowRenderer == love::graphics::RENDERER_VULKAN)
  903. {
  904. auto vgfx = dynamic_cast<love::graphics::vulkan::Graphics*>(graphics.get());
  905. vgfx->setVsync(vsync);
  906. }
  907. #endif
  908. #if defined(LOVE_GRAPHICS_METAL) && defined(LOVE_MACOS)
  909. if (metalView != nullptr)
  910. {
  911. void *metallayer = SDL_Metal_GetLayer(metalView);
  912. love::macos::setMetalLayerVSync(metallayer, vsync != 0);
  913. }
  914. #endif
  915. }
  916. int Window::getVSync() const
  917. {
  918. if (glcontext != nullptr)
  919. {
  920. int interval = 0;
  921. SDL_GL_GetSwapInterval(&interval);
  922. return interval;
  923. }
  924. #if defined(LOVE_GRAPHICS_METAL)
  925. if (metalView != nullptr)
  926. {
  927. #ifdef LOVE_MACOS
  928. void *metallayer = SDL_Metal_GetLayer(metalView);
  929. return love::macos::getMetalLayerVSync(metallayer) ? 1 : 0;
  930. #else
  931. return 1;
  932. #endif
  933. }
  934. #endif
  935. #ifdef LOVE_GRAPHICS_VULKAN
  936. if (windowRenderer == love::graphics::RENDERER_VULKAN)
  937. {
  938. auto vgfx = dynamic_cast<love::graphics::vulkan::Graphics*>(graphics.get());
  939. return vgfx->getVsync();
  940. }
  941. #endif
  942. return 0;
  943. }
  944. void Window::setDisplaySleepEnabled(bool enable)
  945. {
  946. if (enable)
  947. SDL_EnableScreenSaver();
  948. else
  949. SDL_DisableScreenSaver();
  950. }
  951. bool Window::isDisplaySleepEnabled() const
  952. {
  953. return SDL_ScreenSaverEnabled();
  954. }
  955. void Window::minimize()
  956. {
  957. if (window != nullptr)
  958. {
  959. SDL_MinimizeWindow(window);
  960. updateSettings(settings, true);
  961. }
  962. }
  963. void Window::maximize()
  964. {
  965. if (window != nullptr)
  966. {
  967. SDL_MaximizeWindow(window);
  968. updateSettings(settings, true);
  969. }
  970. }
  971. void Window::restore()
  972. {
  973. if (window != nullptr)
  974. {
  975. SDL_RestoreWindow(window);
  976. updateSettings(settings, true);
  977. }
  978. }
  979. void Window::focus()
  980. {
  981. if (window != nullptr)
  982. {
  983. SDL_RaiseWindow(window);
  984. updateSettings(settings, true);
  985. }
  986. }
  987. bool Window::isMaximized() const
  988. {
  989. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED);
  990. }
  991. bool Window::isMinimized() const
  992. {
  993. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
  994. }
  995. void Window::swapBuffers()
  996. {
  997. if (glcontext)
  998. {
  999. #ifdef LOVE_WINDOWS
  1000. bool useDwmFlush = false;
  1001. int swapInterval = getVSync();
  1002. // https://github.com/love2d/love/issues/1628
  1003. // VSync can interact badly with Windows desktop composition (DWM) in windowed mode. DwmFlush can be used instead
  1004. // of vsync, but it's much less flexible so we're very conservative here with where it's used:
  1005. // - It won't work with exclusive or desktop fullscreen.
  1006. // - DWM refreshes don't always match the refresh rate of the monitor the window is in (or the requested swap
  1007. // interval), so we only use it when they do match.
  1008. // - The user may force GL vsync, and DwmFlush shouldn't be used together with GL vsync.
  1009. if (canUseDwmFlush && !settings.fullscreen && swapInterval == 1)
  1010. {
  1011. // Desktop composition is always enabled in Windows 8+. But DwmIsCompositionEnabled won't always return true...
  1012. // (see DwmIsCompositionEnabled docs).
  1013. BOOL compositionEnabled = IsWindows8OrGreater();
  1014. if (compositionEnabled || (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled)) && compositionEnabled))
  1015. {
  1016. DWM_TIMING_INFO info = {};
  1017. info.cbSize = sizeof(DWM_TIMING_INFO);
  1018. double dwmRefreshRate = 0;
  1019. if (SUCCEEDED(DwmGetCompositionTimingInfo(nullptr, &info)))
  1020. dwmRefreshRate = (double)info.rateRefresh.uiNumerator / (double)info.rateRefresh.uiDenominator;
  1021. SDL_DisplayMode dmode = {};
  1022. SDL_DisplayID display = SDL_GetDisplayForWindow(window);
  1023. const SDL_DisplayMode* modePtr = SDL_GetCurrentDisplayMode(display);
  1024. if (modePtr)
  1025. dmode = *modePtr;
  1026. if (dmode.refresh_rate > 0 && dwmRefreshRate > 0 && (fabs(dmode.refresh_rate - dwmRefreshRate) < 2))
  1027. {
  1028. SDL_GL_SetSwapInterval(0);
  1029. int interval = 0;
  1030. if (SDL_GL_GetSwapInterval(&interval) == 0 && interval == 0)
  1031. useDwmFlush = true;
  1032. else
  1033. SDL_GL_SetSwapInterval(swapInterval);
  1034. }
  1035. }
  1036. }
  1037. #endif
  1038. SDL_GL_SwapWindow(window);
  1039. #ifdef LOVE_WINDOWS
  1040. if (useDwmFlush)
  1041. {
  1042. DwmFlush();
  1043. SDL_GL_SetSwapInterval(swapInterval);
  1044. }
  1045. #endif
  1046. }
  1047. }
  1048. bool Window::hasFocus() const
  1049. {
  1050. return (window && SDL_GetKeyboardFocus() == window);
  1051. }
  1052. bool Window::hasMouseFocus() const
  1053. {
  1054. return (window && SDL_GetMouseFocus() == window);
  1055. }
  1056. bool Window::isVisible() const
  1057. {
  1058. return window && (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
  1059. }
  1060. bool Window::isOccluded() const
  1061. {
  1062. return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_OCCLUDED) != 0;
  1063. }
  1064. void Window::setMouseGrab(bool grab)
  1065. {
  1066. mouseGrabbed = grab;
  1067. if (window)
  1068. SDL_SetWindowMouseGrab(window, grab);
  1069. }
  1070. bool Window::isMouseGrabbed() const
  1071. {
  1072. if (window)
  1073. return SDL_GetWindowMouseGrab(window);
  1074. else
  1075. return mouseGrabbed;
  1076. }
  1077. int Window::getWidth() const
  1078. {
  1079. return windowWidth;
  1080. }
  1081. int Window::getHeight() const
  1082. {
  1083. return windowHeight;
  1084. }
  1085. int Window::getPixelWidth() const
  1086. {
  1087. return pixelWidth;
  1088. }
  1089. int Window::getPixelHeight() const
  1090. {
  1091. return pixelHeight;
  1092. }
  1093. void Window::clampPositionInWindow(double *wx, double *wy) const
  1094. {
  1095. if (wx != nullptr)
  1096. *wx = std::min(std::max(0.0, *wx), (double) getWidth() - 1);
  1097. if (wy != nullptr)
  1098. *wy = std::min(std::max(0.0, *wy), (double) getHeight() - 1);
  1099. }
  1100. void Window::windowToPixelCoords(double *x, double *y) const
  1101. {
  1102. if (x != nullptr)
  1103. *x = (*x) * ((double) pixelWidth / (double) windowWidth);
  1104. if (y != nullptr)
  1105. *y = (*y) * ((double) pixelHeight / (double) windowHeight);
  1106. }
  1107. void Window::pixelToWindowCoords(double *x, double *y) const
  1108. {
  1109. if (x != nullptr)
  1110. *x = (*x) * ((double) windowWidth / (double) pixelWidth);
  1111. if (y != nullptr)
  1112. *y = (*y) * ((double) windowHeight / (double) pixelHeight);
  1113. }
  1114. void Window::windowToDPICoords(double *x, double *y) const
  1115. {
  1116. double px = x != nullptr ? *x : 0.0;
  1117. double py = y != nullptr ? *y : 0.0;
  1118. windowToPixelCoords(&px, &py);
  1119. double dpix = 0.0;
  1120. double dpiy = 0.0;
  1121. fromPixels(px, py, dpix, dpiy);
  1122. if (x != nullptr)
  1123. *x = dpix;
  1124. if (y != nullptr)
  1125. *y = dpiy;
  1126. }
  1127. void Window::DPIToWindowCoords(double *x, double *y) const
  1128. {
  1129. double dpix = x != nullptr ? *x : 0.0;
  1130. double dpiy = y != nullptr ? *y : 0.0;
  1131. double px = 0.0;
  1132. double py = 0.0;
  1133. toPixels(dpix, dpiy, px, py);
  1134. pixelToWindowCoords(&px, &py);
  1135. if (x != nullptr)
  1136. *x = px;
  1137. if (y != nullptr)
  1138. *y = py;
  1139. }
  1140. double Window::getDPIScale() const
  1141. {
  1142. return settings.usedpiscale ? getNativeDPIScale() : 1.0;
  1143. }
  1144. double Window::getNativeDPIScale() const
  1145. {
  1146. #ifdef LOVE_ANDROID
  1147. return love::android::getScreenScale();
  1148. #else
  1149. return (double) pixelHeight / (double) windowHeight;
  1150. #endif
  1151. }
  1152. double Window::toPixels(double x) const
  1153. {
  1154. return x * getDPIScale();
  1155. }
  1156. void Window::toPixels(double wx, double wy, double &px, double &py) const
  1157. {
  1158. double scale = getDPIScale();
  1159. px = wx * scale;
  1160. py = wy * scale;
  1161. }
  1162. double Window::fromPixels(double x) const
  1163. {
  1164. return x / getDPIScale();
  1165. }
  1166. void Window::fromPixels(double px, double py, double &wx, double &wy) const
  1167. {
  1168. double scale = getDPIScale();
  1169. wx = px / scale;
  1170. wy = py / scale;
  1171. }
  1172. void *Window::getHandle() const
  1173. {
  1174. return window;
  1175. }
  1176. SDL_MessageBoxFlags Window::convertMessageBoxType(MessageBoxType type) const
  1177. {
  1178. switch (type)
  1179. {
  1180. case MESSAGEBOX_ERROR:
  1181. return SDL_MESSAGEBOX_ERROR;
  1182. case MESSAGEBOX_WARNING:
  1183. return SDL_MESSAGEBOX_WARNING;
  1184. case MESSAGEBOX_INFO:
  1185. default:
  1186. return SDL_MESSAGEBOX_INFORMATION;
  1187. }
  1188. }
  1189. bool Window::showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow)
  1190. {
  1191. SDL_MessageBoxFlags flags = convertMessageBoxType(type);
  1192. SDL_Window *sdlwindow = attachtowindow ? window : nullptr;
  1193. return SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), sdlwindow);
  1194. }
  1195. int Window::showMessageBox(const MessageBoxData &data)
  1196. {
  1197. SDL_MessageBoxData sdldata = {};
  1198. sdldata.flags = convertMessageBoxType(data.type);
  1199. sdldata.title = data.title.c_str();
  1200. sdldata.message = data.message.c_str();
  1201. sdldata.window = data.attachToWindow ? window : nullptr;
  1202. sdldata.numbuttons = (int) data.buttons.size();
  1203. std::vector<SDL_MessageBoxButtonData> sdlbuttons;
  1204. for (int i = 0; i < (int) data.buttons.size(); i++)
  1205. {
  1206. SDL_MessageBoxButtonData sdlbutton = {};
  1207. sdlbutton.buttonID = i;
  1208. sdlbutton.text = data.buttons[i].c_str();
  1209. if (i == data.enterButtonIndex)
  1210. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
  1211. if (i == data.escapeButtonIndex)
  1212. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
  1213. sdlbuttons.push_back(sdlbutton);
  1214. }
  1215. sdldata.buttons = &sdlbuttons[0];
  1216. int pressedbutton = -2;
  1217. SDL_ShowMessageBox(&sdldata, &pressedbutton);
  1218. return pressedbutton;
  1219. }
  1220. void Window::requestAttention(bool continuous)
  1221. {
  1222. #if defined(LOVE_WINDOWS) && !defined(LOVE_WINDOWS_UWP)
  1223. if (hasFocus())
  1224. return;
  1225. FLASHWINFO flashinfo = { sizeof(FLASHWINFO) };
  1226. flashinfo.hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
  1227. flashinfo.uCount = 1;
  1228. flashinfo.dwFlags = FLASHW_ALL;
  1229. if (continuous)
  1230. {
  1231. flashinfo.uCount = 0;
  1232. flashinfo.dwFlags |= FLASHW_TIMERNOFG;
  1233. }
  1234. FlashWindowEx(&flashinfo);
  1235. #elif defined(LOVE_MACOS)
  1236. love::macos::requestAttention(continuous);
  1237. #else
  1238. LOVE_UNUSED(continuous);
  1239. #endif
  1240. // TODO: Linux?
  1241. }
  1242. } // sdl
  1243. } // window
  1244. } // love