1 // Copyright 2014 Wouter van Oortmerssen. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "lobster/stdafx.h"
16
17 #include "lobster/sdlincludes.h"
18 #include "lobster/sdlinterface.h"
19
20 #include "lobster/glinterface.h"
21
22 #define STB_IMAGE_WRITE_IMPLEMENTATION
23 #ifdef _WIN32
24 #pragma warning(push)
25 #pragma warning(disable: 4244)
26 #endif
27 #include "stb/stb_image_write.h"
28 #ifdef _WIN32
29 #pragma warning(pop)
30 #endif
31
32 SDL_Window *_sdl_window = nullptr;
33 SDL_GLContext _sdl_context = nullptr;
34
35
36 /*
37 // FIXME: document this, especially the ones containing spaces.
38
39 mouse1 mouse2 mouse3...
40 backspace tab clear return pause escape space delete
41 ! " # $ & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ [ \ ] ^ _ `
42 a b c d e f g h i j k l m n o p q r s t u v w x y z
43 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [.] [/] [*] [-] [+]
44 enter equals up down right left insert home end page up page down
45 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15
46 numlock caps lock scroll lock right shift left shift right ctrl left ctrl right alt left alt
47 right meta left meta left super right super alt gr compose help print screen sys req break
48 */
49
50
51 struct KeyState {
52 TimeBool8 button;
53
54 double lasttime[2];
55 int2 lastpos[2];
56
KeyStateKeyState57 KeyState() {
58 lasttime[0] = lasttime[1] = 0x80000000;
59 lastpos[0] = lastpos[1] = int2(-1, -1);
60 }
61
FrameResetKeyState62 void FrameReset() {
63 button.Advance();
64 }
65 };
66
67 map<string, KeyState, less<>> keymap;
68
69 int mousewheeldelta = 0;
70
71 int skipmousemotion = 3;
72
73 double frametime = 1.0f / 60.0f, lasttime = 0;
74 uint64_t timefreq = 0, timestart = 0;
75 int frames = 0;
76 vector<float> frametimelog;
77
78 int2 screensize = int2_0;
79 int2 inputscale = int2_1;
80
81 bool fullscreen = false;
82 bool cursor = true;
83 bool landscape = true;
84 bool minimized = false;
85 bool noninteractivetestmode = false;
86
87 const int MAXAXES = 8;
88 float joyaxes[MAXAXES] = { 0 };
89
90 struct Finger {
91 SDL_FingerID id;
92 int2 mousepos;
93 int2 mousedelta;
94 bool used;
95
FingerFinger96 Finger() : id(0), mousepos(-1), mousedelta(0), used(false) {};
97 };
98
99 const int MAXFINGERS = 10;
100 Finger fingers[MAXFINGERS];
101
102
updatebutton(string & name,bool on,int posfinger)103 void updatebutton(string &name, bool on, int posfinger) {
104 auto &ks = keymap[name];
105 ks.button.Set(on);
106 ks.lasttime[on] = lasttime;
107 ks.lastpos[on] = fingers[posfinger].mousepos;
108 }
109
updatemousebutton(int button,int finger,bool on)110 void updatemousebutton(int button, int finger, bool on) {
111 string name = "mouse";
112 name += '0' + (char)button;
113 if (finger) name += '0' + (char)finger;
114 updatebutton(name, on, finger);
115 }
116
clearfingers(bool delta)117 void clearfingers(bool delta) {
118 for (auto &f : fingers) (delta ? f.mousedelta : f.mousepos) = int2(0);
119 }
120
findfinger(SDL_FingerID id,bool remove)121 int findfinger(SDL_FingerID id, bool remove) {
122 for (auto &f : fingers) if (f.id == id && f.used) {
123 if (remove) {
124 // would be more correct to clear mouse position here, but that doesn't work with delayed touch..
125 // would have to delay it too
126 f.used = false;
127 }
128 return int(&f - fingers);
129 }
130 if (remove) return MAXFINGERS - 1; // FIXME: this is masking a bug...
131 assert(!remove);
132 for (auto &f : fingers) if (!f.used) {
133 f.id = id;
134 f.used = true;
135 return int(&f - fingers);
136 }
137 assert(0);
138 return 0;
139 }
140
GetFinger(int i,bool delta)141 const int2 &GetFinger(int i, bool delta) {
142 auto &f = fingers[max(min(i, MAXFINGERS - 1), 0)];
143 return delta ? f.mousedelta : f.mousepos;
144 }
145
GetJoyAxis(int i)146 float GetJoyAxis(int i) {
147 return joyaxes[max(min(i, MAXAXES - 1), 0)];
148 }
149
updatedragpos(SDL_TouchFingerEvent & e,Uint32 et)150 int updatedragpos(SDL_TouchFingerEvent &e, Uint32 et) {
151 int numfingers = SDL_GetNumTouchFingers(e.touchId);
152 //assert(numfingers && e.fingerId < numfingers);
153 for (int i = 0; i < numfingers; i++) {
154 auto finger = SDL_GetTouchFinger(e.touchId, i);
155 if (finger->id == e.fingerId) {
156 // this is a bit clumsy as SDL has a list of fingers and so do we, but they work a bit differently
157 int j = findfinger(e.fingerId, et == SDL_FINGERUP);
158 auto &f = fingers[j];
159 auto ep = float2(e.x, e.y);
160 auto ed = float2(e.dx, e.dy);
161 auto xy = ep * float2(screensize);
162
163 // FIXME: converting back to int coords even though touch theoretically may have higher res
164 f.mousepos = int2(xy * float2(inputscale));
165 f.mousedelta += int2(ed * float2(screensize));
166 return j;
167 }
168 }
169 //assert(0);
170 return 0;
171 }
172
173
SDLError(const char * msg)174 string SDLError(const char *msg) {
175 string s = string_view(msg) + ": " + SDL_GetError();
176 LOG_WARN(s);
177 SDLShutdown();
178 return s;
179 }
180
SDLHandleAppEvents(void *,SDL_Event * event)181 int SDLHandleAppEvents(void * /*userdata*/, SDL_Event *event) {
182 switch (event->type) {
183 case SDL_APP_TERMINATING:
184 /* Terminate the app.
185 Shut everything down before returning from this function.
186 */
187 return 0;
188 case SDL_APP_LOWMEMORY:
189 /* You will get this when your app is paused and iOS wants more memory.
190 Release as much memory as possible.
191 */
192 return 0;
193 case SDL_APP_WILLENTERBACKGROUND:
194 minimized = true;
195 /* Prepare your app to go into the background. Stop loops, etc.
196 This gets called when the user hits the home button, or gets a call.
197 */
198 return 0;
199 case SDL_APP_DIDENTERBACKGROUND:
200 /* This will get called if the user accepted whatever sent your app to the background.
201 If the user got a phone call and canceled it,
202 you'll instead get an SDL_APP_DIDENTERFOREGROUND event and restart your loops.
203 When you get this, you have 5 seconds to save all your state or the app will be terminated.
204 Your app is NOT active at this point.
205 */
206 return 0;
207 case SDL_APP_WILLENTERFOREGROUND:
208 /* This call happens when your app is coming back to the foreground.
209 Restore all your state here.
210 */
211 return 0;
212 case SDL_APP_DIDENTERFOREGROUND:
213 /* Restart your loops here.
214 Your app is interactive and getting CPU again.
215 */
216 minimized = false;
217 return 0;
218 default:
219 /* No special processing, add it to the event queue */
220 return 1;
221 }
222 }
223
GetScreenSize()224 const int2 &GetScreenSize() { return screensize; }
225
ScreenSizeChanged()226 void ScreenSizeChanged() {
227 int2 inputsize;
228 SDL_GetWindowSize(_sdl_window, &inputsize.x, &inputsize.y);
229 SDL_GL_GetDrawableSize(_sdl_window, &screensize.x, &screensize.y);
230 inputscale = screensize / inputsize;
231 }
232
233 #ifdef PLATFORM_ES3
234 int gl_major = 3, gl_minor = 0;
235 #else
236 int gl_major = 3, gl_minor = 2;
237 string glslversion = "150";
238 #endif
SDLRequireGLVersion(int major,int minor)239 void SDLRequireGLVersion(int major, int minor) {
240 #ifdef PLATFORM_WINNIX
241 gl_major = major;
242 gl_minor = minor;
243 glslversion = cat(major, minor, "0");
244 #endif
245 };
246
SDLInit(string_view title,const int2 & desired_screensize,bool isfullscreen,int vsync,int samples)247 string SDLInit(string_view title, const int2 &desired_screensize, bool isfullscreen, int vsync,
248 int samples) {
249 MakeDPIAware();
250 //SDL_SetMainReady();
251 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER /* | SDL_INIT_AUDIO*/) < 0) {
252 return SDLError("Unable to initialize SDL");
253 }
254
255 SDL_SetEventFilter(SDLHandleAppEvents, nullptr);
256
257 LOG_INFO("SDL initialized...");
258
259 SDL_LogSetAllPriority(SDL_LOG_PRIORITY_WARN);
260
261 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_major);
262 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_minor);
263 #ifdef PLATFORM_ES3
264 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
265 #else
266 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
267 #if defined(__APPLE__) || defined(_WIN32)
268 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, samples > 1);
269 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, samples);
270 #endif
271 #endif
272
273 //SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); // set this if we're in 2D mode for speed on mobile?
274 SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 1); // because we redraw the screen each frame
275
276 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
277
278 LOG_INFO("SDL about to figure out display mode...");
279
280 #ifdef PLATFORM_ES3
281 landscape = desired_screensize.x >= desired_screensize.y;
282 int modes = SDL_GetNumDisplayModes(0);
283 screensize = int2(1920, 1080);
284 for (int i = 0; i < modes; i++) {
285 SDL_DisplayMode mode;
286 SDL_GetDisplayMode(0, i, &mode);
287 LOG_INFO("mode: ", mode.w, " ", mode.h);
288 if (landscape ? mode.w > screensize.x : mode.h > screensize.y) {
289 screensize = int2(mode.w, mode.h);
290 }
291 }
292
293 LOG_INFO("chosen resolution: ", screensize.x, " ", screensize.y);
294 LOG_INFO("SDL about to create window...");
295
296 _sdl_window = SDL_CreateWindow(null_terminated(title),
297 0, 0,
298 screensize.x, screensize.y,
299 SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS);
300
301 LOG_INFO(_sdl_window ? "SDL window passed..." : "SDL window FAILED...");
302
303 if (landscape) SDL_SetHint("SDL_HINT_ORIENTATIONS", "LandscapeLeft LandscapeRight");
304 #else
305 int display = 0; // FIXME: we're not dealing with multiple displays.
306 float dpi = 0;
307 const float default_dpi =
308 #ifdef __APPLE__
309 72.0f;
310 #else
311 96.0f;
312 #endif
313 if (SDL_GetDisplayDPI(display, NULL, &dpi, NULL)) dpi = default_dpi;
314 LOG_INFO(cat("dpi: ", dpi));
315 screensize = desired_screensize * int(dpi) / int(default_dpi);
316 _sdl_window = SDL_CreateWindow(null_terminated(title),
317 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
318 screensize.x, screensize.y,
319 SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE |
320 SDL_WINDOW_ALLOW_HIGHDPI |
321 (isfullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
322 #endif
323 ScreenSizeChanged();
324 LOG_INFO("obtained resolution: ", screensize.x, " ", screensize.y);
325
326 if (!_sdl_window)
327 return SDLError("Unable to create window");
328
329 LOG_INFO("SDL window opened...");
330
331
332 _sdl_context = SDL_GL_CreateContext(_sdl_window);
333 LOG_INFO(_sdl_context ? "SDL context passed..." : "SDL context FAILED...");
334 if (!_sdl_context) return SDLError("Unable to create OpenGL context");
335
336 LOG_INFO("SDL OpenGL context created...");
337
338 #ifndef __IOS__
339 SDL_GL_SetSwapInterval(vsync);
340 #endif
341
342 SDL_JoystickEventState(SDL_ENABLE);
343 SDL_JoystickUpdate();
344 for(int i = 0; i < SDL_NumJoysticks(); i++) {
345 SDL_Joystick *joy = SDL_JoystickOpen(i);
346 if (joy) {
347 LOG_INFO("Detected joystick: ", SDL_JoystickName(joy), " (",
348 SDL_JoystickNumAxes(joy), " axes, ",
349 SDL_JoystickNumButtons(joy), " buttons, ",
350 SDL_JoystickNumBalls(joy), " balls, ",
351 SDL_JoystickNumHats(joy), " hats)");
352 };
353 };
354
355 timestart = SDL_GetPerformanceCounter();
356 timefreq = SDL_GetPerformanceFrequency();
357
358 lasttime = -0.02f; // ensure first frame doesn't get a crazy delta
359
360 OpenGLInit(samples);
361
362 return "";
363 }
364
GetSeconds()365 double GetSeconds() { return (double)(SDL_GetPerformanceCounter() - timestart) / (double)timefreq; }
366
SDLShutdown()367 void SDLShutdown() {
368 // FIXME: SDL gives ERROR: wglMakeCurrent(): The handle is invalid. upon SDL_GL_DeleteContext
369 if (_sdl_context) { /*SDL_GL_DeleteContext(_sdl_context);*/ _sdl_context = nullptr; }
370 if (_sdl_window) { SDL_DestroyWindow(_sdl_window); _sdl_window = nullptr; }
371
372 SDL_Quit();
373 }
374
375 // Used to update the time when SDL isn't running.
SDLUpdateTime(double delta)376 void SDLUpdateTime(double delta) {
377 frametime = delta;
378 lasttime += delta;
379 frames++;
380 frametimelog.push_back((float)delta);
381 if (frametimelog.size() > 64) frametimelog.erase(frametimelog.begin());
382 }
383
SDLGetFrameTimeLog()384 vector<float> &SDLGetFrameTimeLog() { return frametimelog; }
385
SDLFrame()386 bool SDLFrame() {
387 auto millis = GetSeconds();
388 SDLUpdateTime(millis - lasttime);
389
390 for (auto &it : keymap) it.second.FrameReset();
391
392 mousewheeldelta = 0;
393 clearfingers(true);
394
395 if (minimized) {
396 SDL_Delay(10); // save CPU/battery
397 } else {
398 #ifndef __EMSCRIPTEN__
399 SDL_GL_SwapWindow(_sdl_window);
400 #endif
401 }
402
403 //SDL_Delay(1000);
404
405 if (!cursor) clearfingers(false);
406
407 bool closebutton = false;
408
409 SDL_Event event;
410 while(SDL_PollEvent(&event)) {
411 extern pair<bool, bool> IMGUIEvent(SDL_Event *event);
412 auto nomousekeyb = IMGUIEvent(&event);
413 switch(event.type) {
414 case SDL_QUIT:
415 closebutton = true;
416 break;
417
418 case SDL_KEYDOWN:
419 case SDL_KEYUP: {
420 if (nomousekeyb.second) break;
421 const char *kn = SDL_GetKeyName(event.key.keysym.sym);
422 if (!*kn) break;
423 string name = kn;
424 std::transform(name.begin(), name.end(), name.begin(),
425 [](char c) { return (char)::tolower(c); });
426 updatebutton(name, event.key.state==SDL_PRESSED, 0);
427 if (event.type == SDL_KEYDOWN) {
428 // Built-in key-press functionality.
429 switch (event.key.keysym.sym) {
430 case SDLK_PRINTSCREEN:
431 ScreenShot("screenshot-" + GetDateTime() + ".jpg");
432 break;
433 }
434 }
435 break;
436 }
437
438 // This #ifdef is needed, because on e.g. OS X we'd otherwise get SDL_FINGERDOWN in addition to SDL_MOUSEBUTTONDOWN on laptop touch pads.
439 #ifdef PLATFORM_TOUCH
440
441 // FIXME: if we're in cursor==0 mode, only update delta, not position
442 case SDL_FINGERDOWN: {
443 if (nomousekeyb.first) break;
444 int i = updatedragpos(event.tfinger, event.type);
445 updatemousebutton(1, i, true);
446 break;
447 }
448 case SDL_FINGERUP: {
449 if (nomousekeyb.first) break;
450 int i = findfinger(event.tfinger.fingerId, true);
451 updatemousebutton(1, i, false);
452 break;
453 }
454
455 case SDL_FINGERMOTION: {
456 if (nomousekeyb.first) break;
457 updatedragpos(event.tfinger, event.type);
458 break;
459 }
460
461 #else
462
463 case SDL_MOUSEBUTTONDOWN:
464 case SDL_MOUSEBUTTONUP: {
465 if (nomousekeyb.first) break;
466 updatemousebutton(event.button.button, 0, event.button.state != 0);
467 if (cursor) {
468 fingers[0].mousepos = int2(event.button.x, event.button.y) * inputscale;
469 }
470 break;
471 }
472
473 case SDL_MOUSEMOTION:
474 if (nomousekeyb.first) break;
475 fingers[0].mousedelta += int2(event.motion.xrel, event.motion.yrel);
476 if (cursor) {
477 fingers[0].mousepos = int2(event.motion.x, event.motion.y) * inputscale;
478 } else {
479 //if (skipmousemotion) { skipmousemotion--; break; }
480 //if (event.motion.x == screensize.x / 2 && event.motion.y == screensize.y / 2) break;
481
482 //auto delta = int3(event.motion.xrel, event.motion.yrel);
483 //fingers[0].mousedelta += delta;
484
485 //auto delta = int3(event.motion.x, event.motion.y) - screensize / 2;
486 //fingers[0].mousepos -= delta;
487
488 //SDL_WarpMouseInWindow(_sdl_window, screensize.x / 2, screensize.y / 2);
489 }
490 break;
491
492 case SDL_MOUSEWHEEL: {
493 if (nomousekeyb.first) break;
494 if (event.wheel.which == SDL_TOUCH_MOUSEID) break; // Emulated scrollwheel on touch devices?
495 auto y = event.wheel.y;
496 #ifdef __EMSCRIPTEN__
497 y = y > 0 ? 1 : -1; // For some reason, it defaults to 10 / -10 ??
498 #endif
499 mousewheeldelta += event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -y : y;
500 break;
501 }
502
503 #endif
504
505 case SDL_JOYAXISMOTION: {
506 const int deadzone = 800; // FIXME
507 if (event.jaxis.axis < MAXAXES) {
508 joyaxes[event.jaxis.axis] = abs(event.jaxis.value) > deadzone ? event.jaxis.value / (float)0x8000 : 0;
509 };
510 break;
511 }
512
513 case SDL_JOYHATMOTION:
514 break;
515
516 case SDL_JOYBUTTONDOWN:
517 case SDL_JOYBUTTONUP: {
518 string name = "joy";
519 name += '0' + (char)event.jbutton.button;
520 updatebutton(name, event.jbutton.state == SDL_PRESSED, 0);
521 break;
522 }
523
524 case SDL_WINDOWEVENT:
525 switch (event.window.event) {
526 case SDL_WINDOWEVENT_RESIZED: {
527 ScreenSizeChanged();
528 // reload and bind shaders/textures here
529 break;
530 }
531 case SDL_WINDOWEVENT_LEAVE:
532 // never gets hit?
533 /*
534 for (int i = 1; i <= 5; i++)
535 updatemousebutton(i, false);
536 */
537 break;
538 }
539 break;
540
541 case SDL_WINDOWEVENT_MINIMIZED:
542 //minimized = true;
543 break;
544
545 case SDL_WINDOWEVENT_MAXIMIZED:
546 case SDL_WINDOWEVENT_RESTORED:
547 /*
548 #ifdef __IOS__
549 SDL_Delay(10); // IOS crashes in SDL_GL_SwapWindow if we start rendering straight away
550 #endif
551 minimized = false;
552 */
553 break;
554 }
555 }
556
557 // simulate mouse up events, since SDL won't send any if the mouse leaves the window while down
558 // doesn't work
559 /*
560 for (int i = 1; i <= 5; i++)
561 if (!(SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(i)))
562 updatemousebutton(i, false);
563 */
564
565 /*
566 if (SDL_GetMouseFocus() != _sdl_window) {
567 int A = 1;
568 }
569 */
570
571 return closebutton || (noninteractivetestmode && frames == 2 /* has rendered one full frame */);
572 }
573
SDLWindowMinMax(int dir)574 void SDLWindowMinMax(int dir) {
575 if (!_sdl_window) return;
576 if (dir < 0) SDL_MinimizeWindow(_sdl_window);
577 else if (dir > 0) SDL_MaximizeWindow(_sdl_window);
578 else SDL_RestoreWindow(_sdl_window);
579 }
580
SDLTime()581 double SDLTime() { return lasttime; }
SDLDeltaTime()582 double SDLDeltaTime() { return frametime; }
583
GetKS(string_view name)584 TimeBool8 GetKS(string_view name) {
585 auto ks = keymap.find(name);
586 if (ks == keymap.end()) return {};
587 #ifdef PLATFORM_TOUCH
588 // delayed results by one frame, that way they get 1 frame over finger hovering over target,
589 // which makes gl_hit work correctly
590 // FIXME: this causes more lag on mobile, instead, set a flag that this is the first frame we're touching,
591 // and make that into a special case inside gl_hit
592 return ks->second.button.Back();
593 #else
594 return ks->second.button;
595 #endif
596 }
597
GetKeyTime(string_view name,int on)598 double GetKeyTime(string_view name, int on) {
599 auto ks = keymap.find(name);
600 return ks == keymap.end() ? -3600 : ks->second.lasttime[on];
601 }
602
GetKeyPos(string_view name,int on)603 int2 GetKeyPos(string_view name, int on) {
604 auto ks = keymap.find(name);
605 return ks == keymap.end() ? int2(-1, -1) : ks->second.lastpos[on];
606 }
607
SDLTitle(string_view title)608 void SDLTitle(string_view title) { SDL_SetWindowTitle(_sdl_window, null_terminated(title)); }
609
SDLWheelDelta()610 int SDLWheelDelta() { return mousewheeldelta; }
SDLIsMinimized()611 bool SDLIsMinimized() { return minimized; }
612
SDLCursor(bool on)613 bool SDLCursor(bool on) {
614 if (on != cursor) {
615 cursor = !cursor;
616 if (cursor) {
617 if (fullscreen) SDL_SetWindowGrab(_sdl_window, SDL_FALSE);
618 SDL_ShowCursor(1);
619 SDL_SetRelativeMouseMode(SDL_FALSE);
620 } else {
621 if (fullscreen) SDL_SetWindowGrab(_sdl_window, SDL_TRUE);
622 SDL_ShowCursor(0);
623 SDL_SetRelativeMouseMode(SDL_TRUE);
624 clearfingers(false);
625 }
626 }
627 return cursor;
628 }
629
SDLGrab(bool on)630 bool SDLGrab(bool on) {
631 SDL_SetWindowGrab(_sdl_window, on ? SDL_TRUE : SDL_FALSE);
632 return SDL_GetWindowGrab(_sdl_window) == SDL_TRUE;
633 }
634
SDLMessageBox(string_view title,string_view msg)635 void SDLMessageBox(string_view title, string_view msg) {
636 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, null_terminated<0>(title), null_terminated<1>(msg), _sdl_window);
637 }
638
SDLLoadFile(string_view absfilename,string * dest,int64_t start,int64_t len)639 int64_t SDLLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len) {
640 LOG_INFO("SDLLoadFile: ", absfilename);
641 auto f = SDL_RWFromFile(null_terminated(absfilename), "rb");
642 if (!f) return -1;
643 auto filelen = SDL_RWseek(f, 0, RW_SEEK_END);
644 if (filelen < 0 || filelen == LLONG_MAX) {
645 // If SDL_RWseek fails it is supposed to return -1, but on Linux it returns LLONG_MAX instead.
646 SDL_RWclose(f);
647 return -1;
648 }
649 if (!len) { // Just the file length requested.
650 SDL_RWclose(f);
651 return filelen;
652 }
653 if (len < 0) len = filelen;
654 SDL_RWseek(f, start, RW_SEEK_SET);
655 dest->resize((size_t)len);
656 auto rlen = SDL_RWread(f, &(*dest)[0], 1, (size_t)len);
657 SDL_RWclose(f);
658 return len != (int64_t)rlen ? -1 : len;
659 }
660
ScreenShot(string_view filename)661 bool ScreenShot(string_view filename) {
662 auto pixels = ReadPixels(int2(0), screensize);
663 auto ok = stbi_write_png(null_terminated(filename), screensize.x, screensize.y, 3, pixels,
664 screensize.x * 3);
665 delete[] pixels;
666 return ok != 0;
667 }
668
SDLTestMode()669 void SDLTestMode() { noninteractivetestmode = true; }
670
SDLScreenDPI(int screen)671 int SDLScreenDPI(int screen) {
672 int screens = max(1, SDL_GetNumVideoDisplays());
673 float ddpi = 200; // Reasonable default just in case screen 0 gives an error.
674 #ifndef __EMSCRIPTEN__
675 SDL_GetDisplayDPI(screen, &ddpi, nullptr, nullptr);
676 #endif
677 return screen >= screens
678 ? 0 // Screen not present.
679 : (int)(ddpi + 0.5f);
680 }
681