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