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