1 /*
2     Copyright (c) 2009 Andrew Caudwell (acaudwell@gmail.com)
3     All rights reserved.
4 
5     Redistribution and use in source and binary forms, with or without
6     modification, are permitted provided that the following conditions
7     are met:
8     1. Redistributions of source code must retain the above copyright
9        notice, this list of conditions and the following disclaimer.
10     2. Redistributions in binary form must reproduce the above copyright
11        notice, this list of conditions and the following disclaimer in the
12        documentation and/or other materials provided with the distribution.
13     3. The name of the author may not be used to endorse or promote products
14        derived from this software without specific prior written permission.
15 
16     THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19     IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28 #include "sdlapp.h"
29 #include "display.h"
30 #include "logger.h"
31 #include "SDL_syswm.h"
32 
33 std::string gSDLAppResourceDir;
34 std::string gSDLAppConfDir;
35 
36 #ifdef _WIN32
37 std::string gSDLAppPathSeparator = "\\";
38 #else
39 std::string gSDLAppPathSeparator = "/";
40 #endif
41 
42 std::string gSDLAppTitle = "SDL App";
43 std::string gSDLAppExec  = "sdlapp";
44 
45 #ifdef _WIN32
46 
47 HWND SDLApp::console_window   = 0;
48 bool SDLApp::existing_console = false;
49 
showConsole(bool show)50 void SDLApp::showConsole(bool show) {
51     if(!SDLApp::console_window) return;
52 
53     ShowWindow( SDLApp::console_window, show);
54 }
55 
initConsole()56 void SDLApp::initConsole() {
57     if(SDLApp::console_window != 0) return;
58 
59     SDLApp::console_window = GetConsoleWindow();
60 
61     // check if this is a new console or not
62     CONSOLE_SCREEN_BUFFER_INFO buffer_info;
63     if(GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &buffer_info )) {
64         existing_console = !(buffer_info.dwCursorPosition.X==0 && buffer_info.dwCursorPosition.Y==0);
65     // assume TERM env implies this is an existing terminal
66     } else if(getenv("TERM") != 0) {
67         existing_console = true;
68     }
69 
70     // don't customize the console unless it was created for this program
71     if(existing_console) return;
72 
73     //disable the close button so the user cant crash the application
74     HMENU hm = GetSystemMenu(SDLApp::console_window, false);
75     DeleteMenu(hm, SC_CLOSE, MF_BYCOMMAND);
76 
77     //set title
78 
79     char consoleTitle[512];
80     int console_suffix = 0;
81     sprintf(consoleTitle, "%s Console", gSDLAppTitle.c_str());
82 
83     while(FindWindow(0, consoleTitle)) {
84         sprintf(consoleTitle, "%s Console %d", gSDLAppTitle.c_str(), ++console_suffix);
85     }
86 
87     SetConsoleTitle(consoleTitle);
88 }
89 
resizeConsole(int height)90 void SDLApp::resizeConsole(int height) {
91     if(SDLApp::console_window !=0 && !existing_console) {
92         RECT windowRect;
93         if(GetWindowRect(SDLApp::console_window, &windowRect)) {
94             float width = windowRect.right - windowRect.left;
95             MoveWindow(SDLApp::console_window,windowRect.left,windowRect.top,width,height,true);
96         }
97     }
98 }
99 
100 #endif
101 
SDLAppDirExists(std::string dir)102 bool SDLAppDirExists(std::string dir) {
103     struct stat st;
104     return !stat(dir.c_str(), &st) && S_ISDIR(st.st_mode);
105 }
106 
SDLAppAddSlash(std::string path)107 std::string SDLAppAddSlash(std::string path) {
108 
109     //append slash unless the path is empty
110     if(path.size() && path[path.size()-1] != gSDLAppPathSeparator[0]) {
111         path += gSDLAppPathSeparator;
112     }
113 
114     return path;
115 }
116 
117 //info message
SDLAppInfo(std::string msg)118 void SDLAppInfo(std::string msg) {
119 #ifdef _WIN32
120     SDLApp::showConsole(true);
121 #endif
122 
123     printf("%s\n", msg.c_str());
124 
125 #ifdef _WIN32
126     if(!SDLApp::existing_console) {
127         printf("\nPress Enter\n");
128         getchar();
129     }
130 #endif
131 
132     exit(0);
133 }
134 
135 //display error only
SDLAppQuit(std::string error)136 void SDLAppQuit(std::string error) {
137     SDL_Quit();
138 
139 #ifdef _WIN32
140     SDLApp::showConsole(true);
141 #endif
142 
143     fprintf(stderr, "%s: %s\n", gSDLAppExec.c_str(), error.c_str());
144     fprintf(stderr, "Try '%s --help' for more information.\n\n", gSDLAppExec.c_str());
145 
146 #ifdef _WIN32
147     if(!SDLApp::existing_console) {
148         fprintf(stderr, "Press Enter\n");
149         getchar();
150     }
151 #endif
152 
153     exit(1);
154 }
155 
getClipboardText(std::string & text)156 bool SDLApp::getClipboardText(std::string& text) {
157 
158 #if SDL_VERSION_ATLEAST(2,0,0)
159     char* clipboard_text = SDL_GetClipboardText();
160 
161     if(clipboard_text!=0) {
162         text = std::string(clipboard_text);
163     } else {
164         text.resize(0);
165     }
166 
167     return true;
168 #else
169     return false;
170 #endif
171 }
172 
setClipboardText(const std::string & text)173 void SDLApp::setClipboardText(const std::string& text) {
174 #if SDL_VERSION_ATLEAST(2,0,0)
175     SDL_SetClipboardText(text.c_str());
176 #endif
177 }
178 
SDLAppInit(std::string apptitle,std::string execname,std::string exepath)179 void SDLAppInit(std::string apptitle, std::string execname, std::string exepath) {
180     gSDLAppTitle = apptitle;
181     gSDLAppExec  = execname;
182 
183     std::string conf_dir     = "";
184     std::string resource_dir = "data/";
185     std::string fonts_dir    = "data/fonts/";
186     std::string shaders_dir  = "data/shaders/";
187 
188 #ifdef _WIN32
189 
190     char szAppPath[MAX_PATH];
191     GetModuleFileName(0, szAppPath, MAX_PATH);
192 
193     // Extract directory
194     std::string winexepath = std::string(szAppPath);
195 
196     int pos = winexepath.rfind("\\");
197 
198     std::string path = winexepath.substr(0, pos+1);
199     conf_dir     = path + std::string("\\");
200     resource_dir = path + std::string("\\data\\");
201     fonts_dir    = path + std::string("\\data\\fonts\\");
202     shaders_dir  = path + std::string("\\data\\shaders\\");
203 #else
204     //get working directory
205     char cwd_buff[1024];
206 
207     if(getcwd(cwd_buff, 1024) == cwd_buff) {
208         conf_dir     = std::string(cwd_buff) + std::string("/");
209         resource_dir = std::string(cwd_buff) + std::string("/") + resource_dir;
210         fonts_dir    = std::string(cwd_buff) + std::string("/") + fonts_dir;
211         shaders_dir  = std::string(cwd_buff) + std::string("/") + shaders_dir;
212     }
213 
214 #endif
215 
216 #ifdef SDLAPP_CONF_DIR
217     if (SDLAppDirExists(SDLAPP_CONF_DIR)) {
218         conf_dir = SDLAPP_CONF_DIR;
219     }
220 #endif
221 
222 #ifdef SDLAPP_RESOURCE_DIR
223     if (SDLAppDirExists(SDLAPP_RESOURCE_DIR)) {
224         resource_dir = SDLAPP_RESOURCE_DIR;
225         fonts_dir    = SDLAPP_RESOURCE_DIR + std::string("/fonts/");
226         shaders_dir  = SDLAPP_RESOURCE_DIR + std::string("/shaders/");
227     }
228     else if(!exepath.empty()) {
229         // make resource path relative to the directory of the executable
230         // if the resource directory doesn't exist
231         size_t pos = exepath.rfind("/");
232         if (pos != std::string::npos) {
233             std::string path = exepath.substr(0, pos+1);
234             resource_dir = path + std::string("data/");
235             fonts_dir    = path + std::string("data/fonts/");
236             shaders_dir  = path + std::string("data/shaders/");
237         }
238     }
239 #endif
240 
241 #ifdef SDLAPP_FONT_DIR
242     if (SDLAppDirExists(SDLAPP_FONT_DIR)) {
243         fonts_dir    = SDLAPP_FONT_DIR;
244     }
245 #endif
246 
247     resource_dir  = SDLAppAddSlash(resource_dir);
248     conf_dir      = SDLAppAddSlash(conf_dir);
249     fonts_dir     = SDLAppAddSlash(fonts_dir);
250     shaders_dir   = SDLAppAddSlash(shaders_dir);
251 
252     texturemanager.setDir(resource_dir);
253     fontmanager.setDir(fonts_dir);
254     shadermanager.setDir(shaders_dir);
255 
256     gSDLAppResourceDir = resource_dir;
257     gSDLAppConfDir     = conf_dir;
258     gSDLAppShaderDir   = shaders_dir;
259 
260     fontmanager.init();
261 }
262 
SDLAppParseArgs(int argc,char * argv[],int * xres,int * yres,bool * fullscreen,std::vector<std::string> * otherargs)263 void SDLAppParseArgs(int argc, char *argv[], int* xres, int* yres, bool* fullscreen, std::vector<std::string>* otherargs) {
264 
265     for (int i=1; i<argc; i++) {
266         debugLog("argv[%d] = %s", i, argv[i]);
267 
268         std::string args(argv[i]);
269 
270         if (args == "-f") {
271             *fullscreen = true;
272             continue;
273         }
274         else if (args == "-w") {
275             *fullscreen = false;
276             continue;
277         }
278 
279         //get video mode
280         if(args.size()>1 && args[0] == '-' && args.rfind("x") != std::string::npos) {
281 
282             std::string displayarg = args;
283 
284             while(displayarg.size()>1 && displayarg[0] == '-') {
285                 displayarg = displayarg.substr(1, displayarg.size()-1);
286             }
287 
288             size_t x = displayarg.rfind("x");
289 
290             if(x != std::string::npos) {
291                 std::string widthstr  = displayarg.substr(0, x);
292                 std::string heightstr = displayarg.substr(x+1);
293 
294                 int width = atoi(widthstr.c_str());
295                 int height = atoi(heightstr.c_str());
296 
297                 if(width>0 && height>0) {
298                     debugLog("w=%d, h=%d",width,height);
299 
300                     *xres = width;
301                     *yres = height;
302                     continue;
303                 }
304             }
305         }
306 
307         // non display argument
308         if(otherargs != 0) {
309             otherargs->push_back(args);
310         }
311     }
312 }
313 
SDLApp()314 SDLApp::SDLApp() {
315     fps=0;
316     return_code=0;
317     appFinished=false;
318     min_delta_msec = 8;
319 }
320 
updateFramerate()321 void SDLApp::updateFramerate() {
322     if(fps_updater>0) {
323         fps = (float)frame_count / (float)fps_updater * 1000.0f;
324     } else {
325         fps = 0;
326     }
327     fps_updater = 0;
328     frame_count = 0;
329 }
330 
isFinished()331 bool SDLApp::isFinished() {
332     return appFinished;
333 }
334 
returnCode()335 int SDLApp::returnCode() {
336     return return_code;
337 }
338 
stop(int return_code)339 void SDLApp::stop(int return_code) {
340     this->return_code = return_code;
341     appFinished=true;
342 }
343 
handleEvent(SDL_Event & event)344 bool SDLApp::handleEvent(SDL_Event& event) {
345 
346     switch(event.type) {
347         case SDL_QUIT:
348             quit();
349             break;
350 
351         case SDL_MOUSEMOTION:
352             mouseMove(&event.motion);
353             break;
354 
355 #if SDL_VERSION_ATLEAST(2,0,0)
356         case SDL_TEXTINPUT:
357             textInput(&event.text);
358              break;
359 
360         case SDL_TEXTEDITING:
361             textEdit(&event.edit);
362             break;
363 
364         case SDL_MOUSEWHEEL:
365             mouseWheel(&event.wheel);
366             break;
367 
368         case SDL_WINDOWEVENT:
369             if(event.window.event == SDL_WINDOWEVENT_RESIZED) {
370                 resize(event.window.data1, event.window.data2);
371             }
372             break;
373 #else
374         case SDL_VIDEORESIZE:
375             resize(event.resize.w, event.resize.h);
376             break;
377 #endif
378 
379         case SDL_MOUSEBUTTONDOWN:
380         case SDL_MOUSEBUTTONUP:
381             mouseClick(&event.button);
382             break;
383 
384         case SDL_KEYDOWN:
385         case SDL_KEYUP:
386             keyPress(&event.key);
387             break;
388 
389         default:
390             return false;
391     }
392 
393     return true;
394 }
395 
run()396 int SDLApp::run() {
397 
398     Uint32 msec=0, last_msec=0, buffer_msec=0, total_msec = 0;
399 
400     frame_count = 0;
401     fps_updater = 0;
402 
403     if(!appFinished) init();
404 
405     msec = SDL_GetTicks();
406     last_msec = msec;
407 
408 #if SDL_VERSION_ATLEAST(2,0,0)
409         //text input seems to be enabled by default, turn it off
410         SDL_StopTextInput();
411 #endif
412 
413     while(!appFinished) {
414         last_msec = msec;
415         msec      = SDL_GetTicks();
416 
417         Uint32 delta_msec = msec - last_msec;
418 
419         // cant have delta ticks be less than 8ms
420         buffer_msec += delta_msec;
421         if(buffer_msec < min_delta_msec) {
422             SDL_Delay(1);
423             continue;
424         }
425 
426         delta_msec = buffer_msec;
427         buffer_msec =0;
428 
429         //determine time elapsed since last time we were here
430         total_msec += delta_msec;
431 
432         float t  = total_msec / 1000.0f;
433         float dt = delta_msec / 1000.0f;
434 
435         fps_updater += delta_msec;
436 
437         //update framerate if a second has passed
438         if (fps_updater >= 1000) {
439             updateFramerate();
440         }
441 
442         //process new events
443         SDL_Event event;
444         while ( SDL_PollEvent(&event) ) {
445             handleEvent(event);
446         }
447 
448         update(t, dt);
449 
450         //update display
451         display.update();
452         frame_count++;
453     }
454 
455     return return_code;
456 }
457