1 //---------------------------------------------------------------------------
2 #include "stdafx.h"
3 
4 #include <cassert>
5 #include <ctime>
6 
7 #include "screen.h"
8 #include "sound.h"
9 #include "game.h"
10 #include "utils.h"
11 #include "gamestate.h"
12 #include "image.h"
13 #include "sector.h"
14 #include "panel.h"
15 #include "gui.h"
16 #include "player.h"
17 
18 //---------------------------------------------------------------------------
19 
Screen()20 Screen::Screen() : m_pos_x(0), m_pos_y(0), m_down_left(false), m_down_middle(false), m_down_right(false) {
21 #if SDL_MAJOR_VERSION == 1
22 	surface = NULL;
23 #else
24 	sdlWindow = NULL;
25 	sdlRenderer = NULL;
26 	width = 0;
27 	height = 0;
28 #endif
29 }
30 
~Screen()31 Screen::~Screen() {
32 }
33 
canOpenFullscreen(int width,int height)34 bool Screen::canOpenFullscreen(int width, int height) {
35 #if SDL_MAJOR_VERSION == 1
36 	if( SDL_VideoModeOK(width, height, 32, SDL_HWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN) != 0 )
37 		return true;
38 	return false;
39 #else
40 	return true; // assume always true?
41 #endif
42 }
43 
open(int screen_width,int screen_height,bool fullscreen)44 bool Screen::open(int screen_width, int screen_height, bool fullscreen) {
45 	LOG("Screen::open(%d, %d, %s)\n", screen_width, screen_height, fullscreen?"fullscreen":"windowed");
46 #if SDL_MAJOR_VERSION == 1
47 	if( fullscreen )
48 		surface = SDL_SetVideoMode(screen_width, screen_height, 32, SDL_HWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN);
49 	else
50 		surface = SDL_SetVideoMode(screen_width, screen_height, 32, SDL_HWSURFACE|SDL_HWPALETTE);
51 	if( surface == NULL ) {
52 		LOG("failed to open screen at this resolution\n");
53 		return false;
54 	}
55 #else
56 	if( SDL_CreateWindowAndRenderer(screen_width, screen_height, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_RESIZABLE, &sdlWindow, &sdlRenderer) != 0 ) {
57 		LOG("failed to open screen at this resolution\n");
58 		return false;
59 	}
60 	this->width = screen_width;
61 	this->height = screen_height;
62 	{
63 		SDL_BlendMode blendMode = SDL_BLENDMODE_NONE;
64 		SDL_GetRenderDrawBlendMode(sdlRenderer, &blendMode);
65 		LOG("render draw blend mode: %d\n", blendMode);
66 	}
67 	SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND); // needed for Screen::fillRectWithAlpha, as blending is off by default for drawing functions
68 #endif
69 	LOG("screen opened ok\n");
70 
71 	if( game_g->isMobileUI() ) {
72 		SDL_ShowCursor(false); // comment out to test with system cursor, for testing purposes when ran on non-touchscreen devices
73 	}
74 	else {
75 		//SDL_ShowCursor(false);
76 		// Set up a blank cursor
77 		// We use this hacky way due to a bug in SDL_ShowCursor(false), where when alt-tabbing out of full screen mode in Windows, the mouse pointer is
78 		// trapped in the top left corner. See
79 		unsigned char data[1] = "";
80 		unsigned char mask[1] = "";
81 		SDL_Cursor *cursor = SDL_CreateCursor(data, mask, 8, 1, 0, 0);
82 		SDL_Cursor *old_cursor = SDL_GetCursor();
83 		SDL_SetCursor(cursor);
84 		SDL_FreeCursor(old_cursor);
85 	}
86 
87 #if SDL_MAJOR_VERSION == 1
88 	Image::setGraphicsOutput(surface);
89 #else
90 	Image::setGraphicsOutput(sdlRenderer);
91 #endif
92 
93 	return true;
94 }
95 
96 #if SDL_MAJOR_VERSION == 1
97 #else
setLogicalSize(int width,int height,bool smooth)98 void Screen::setLogicalSize(int width, int height, bool smooth) {
99 	this->width = width;
100 	this->height = height;
101 	LOG("width, height: %d, %d\n", width, height);
102 	if( smooth ) {
103 		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");  // make the scaled rendering look smoother.
104 	}
105 	else {
106 		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
107 	}
108 	SDL_RenderSetLogicalSize(sdlRenderer, width, height);
109 }
110 #endif
111 
setTitle(const char * title)112 void Screen::setTitle(const char *title) {
113 #if SDL_MAJOR_VERSION == 1
114 	SDL_WM_SetCaption(title, "");
115 #else
116 	SDL_SetWindowTitle(sdlWindow, title);
117 #endif
118 }
119 
clear()120 void Screen::clear() {
121 #if SDL_MAJOR_VERSION == 1
122 	SDL_Rect rect;
123 	rect.x = 0;
124 	rect.y = 0;
125 	rect.w = getWidth();
126 	rect.h = getHeight();
127 	SDL_FillRect(surface, &rect, 0);
128 #else
129 	SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
130 	SDL_RenderClear(sdlRenderer);
131 #endif
132 }
133 
refresh()134 void Screen::refresh() {
135 #if SDL_MAJOR_VERSION == 1
136 	SDL_Flip(surface);
137 #else
138 	SDL_RenderPresent(sdlRenderer);
139 #endif
140 }
141 
getWidth() const142 int Screen::getWidth() const {
143 #if SDL_MAJOR_VERSION == 1
144 	return surface->w;
145 #else
146 	/*int w = 0, h = 0;
147 	SDL_RenderGetLogicalSize(sdlRenderer, &w, &h);
148 	return w;*/
149 	//LOG("width = %d\n", width);
150 	return this->width;
151 #endif
152 }
153 
getHeight() const154 int Screen::getHeight() const {
155 #if SDL_MAJOR_VERSION == 1
156 	return surface->h;
157 #else
158 	/*int w = 0, h = 0;
159 	SDL_RenderGetLogicalSize(sdlRenderer, &w, &h);
160 	return h;*/
161 	//LOG("height = %d\n", height);
162 	return this->height;
163 #endif
164 }
165 
fillRect(short x,short y,short w,short h,unsigned char r,unsigned char g,unsigned char b)166 void Screen::fillRect(short x, short y, short w, short h, unsigned char r, unsigned char g, unsigned char b) {
167 	SDL_Rect rect;
168 	rect.x = x;
169 	rect.y = y;
170 	rect.w = w;
171 	rect.h = h;
172 #if SDL_MAJOR_VERSION == 1
173 	Uint32 col = SDL_MapRGB(surface->format, r, g, b);
174 	SDL_FillRect(surface, &rect, col);
175 #else
176 	SDL_SetRenderDrawColor(sdlRenderer, r, g, b, 255);
177 	SDL_RenderFillRect(sdlRenderer, &rect);
178 #endif
179 }
180 
181 #if SDL_MAJOR_VERSION == 1
182 // not supported with SDL 1.2 (as SDL_FillRect can't do blending)!
183 #else
fillRectWithAlpha(short x,short y,short w,short h,unsigned char r,unsigned char g,unsigned char b,unsigned char alpha)184 void Screen::fillRectWithAlpha(short x, short y, short w, short h, unsigned char r, unsigned char g, unsigned char b, unsigned char alpha) {
185 	SDL_Rect rect;
186 	rect.x = x;
187 	rect.y = y;
188 	rect.w = w;
189 	rect.h = h;
190 	//LOG("fill rect %d %d %d %d\n", r, g, b, alpha);
191 	SDL_SetRenderDrawColor(sdlRenderer, r, g, b, alpha);
192 	SDL_RenderFillRect(sdlRenderer, &rect);
193 }
194 #endif
195 
196 #if SDL_MAJOR_VERSION == 1
197 // not supported with SDL 1.2
198 #else
drawLine(short x1,short y1,short x2,short y2,unsigned char r,unsigned char g,unsigned char b)199 void Screen::drawLine(short x1, short y1, short x2, short y2, unsigned char r, unsigned char g, unsigned char b) {
200 	SDL_SetRenderDrawColor(sdlRenderer, r, g, b, 255);
201 	SDL_RenderDrawLine(sdlRenderer, x1, y1, x2, y2);
202 }
203 #endif
204 
205 #if SDL_MAJOR_VERSION == 1
206 // not supported with SDL 1.2
207 #else
convertWindowToLogical(int * m_x,int * m_y) const208 void Screen::convertWindowToLogical(int *m_x, int *m_y) const {
209 	SDL_Rect rect;
210 	SDL_RenderGetViewport(sdlRenderer, &rect);
211 	float scale_x = 0.0f, scale_y = 0.0f;
212 	SDL_RenderGetScale(sdlRenderer, &scale_x, &scale_y);
213 	//LOG("viewport: %d x %d : %d, %d\n", rect.x, rect.y, rect.w, rect.h);
214 	//LOG("render scale: %f x %f\n", scale_x, scale_y);
215 	*m_x = (int)(*m_x / scale_x);
216 	*m_y = (int)(*m_y / scale_y);
217 	*m_x -= rect.x;
218 	*m_y -= rect.y;
219 }
220 
getWindowSize(int * window_width,int * window_height) const221 void Screen::getWindowSize(int *window_width, int *window_height) const {
222 	SDL_GetWindowSize(sdlWindow, window_width, window_height);
223 }
224 #endif
225 
226 
getMouseCoords(int * m_x,int * m_y) const227 void Screen::getMouseCoords(int *m_x, int *m_y) const {
228 	// SDL_GetMouseState doesn't play well with touch events
229 	/*SDL_GetMouseState(m_x, m_y);
230 	// need to convert from window space to logical space
231 #if SDL_MAJOR_VERSION == 1
232 #else
233 	this->convertWindowToLogical(m_x, m_y);
234 #endif*/
235 	*m_x = this->m_pos_x;
236 	*m_y = this->m_pos_y;
237 	//LOG("Screen::getMouseCoords: %d, %d\n", *m_x, *m_y);
238 }
239 
getMouseState(int * m_x,int * m_y,bool * m_left,bool * m_middle,bool * m_right) const240 bool Screen::getMouseState(int *m_x, int *m_y, bool *m_left, bool *m_middle, bool *m_right) const {
241 	// SDL_GetMouseState doesn't play well with touch events
242 	/*int m_b = SDL_GetMouseState(m_x, m_y);
243 	*m_left = ( m_b & SDL_BUTTON(1) ) != 0;
244 	*m_middle = ( m_b & SDL_BUTTON(2) ) != 0;
245 	*m_right = ( m_b & SDL_BUTTON(3) ) != 0;
246 	// need to convert from window space to logical space
247 #if SDL_MAJOR_VERSION == 1
248 #else
249 	this->convertWindowToLogical(m_x, m_y);
250 #endif*/
251 	*m_x = this->m_pos_x;
252 	*m_y = this->m_pos_y;
253 	*m_left = this->m_down_left;
254 	*m_middle = this->m_down_middle;
255 	*m_right = this->m_down_right;
256 	/*if( *m_left ) {
257 		LOG("mouse down at: %d, %d\n", *m_x, *m_y);
258 	}*/
259 	//LOG("Screen::getMouseState: %d, %d\n", *m_x, *m_y);
260 	return ( *m_left || *m_middle || *m_right );
261 }
262 
Application()263 Application::Application() : quit(false), blank_mouse(false), compute_fps(false), fps(0.0f), last_time(0) {
264 	// uncomment to display fps
265 	//compute_fps = true;
266 }
267 
~Application()268 Application::~Application() {
269 	LOG("quit SDL\n");
270 	SDL_Quit();
271 }
272 
init()273 bool Application::init() {
274 #if defined(_WIN32)
275 	_putenv("SDL_VIDEO_CENTERED=0,0");
276 #elif defined(__MORPHOS__)
277 #else
278 	setenv("SDL_VIDEO_CENTERED", "0,0", 1);
279 #endif
280 	if( SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) == -1 ) {
281 		LOG("SDL_Init failed\n");
282 		return false;
283 	}
284 	return true;
285 }
286 
getTicks() const287 unsigned int Application::getTicks() const {
288 	return SDL_GetTicks();
289 }
290 
delay(unsigned int time)291 void Application::delay(unsigned int time) {
292 	SDL_Delay(time);
293 }
294 
295 const int TICK_INTERVAL = 16; // 62.5 fps max
296 
wait()297 void Application::wait() {
298 	unsigned int now = game_g->getApplication()->getTicks();
299 	// use unsigned int and compare by subtraction to avoid overflow problems - see http://blogs.msdn.com/b/oldnewthing/archive/2005/05/31/423407.aspx, http://www.gammon.com.au/millis
300 	unsigned int elapsed = now - last_time;
301 	unsigned int delay = 0;
302 	if( elapsed >= TICK_INTERVAL ) {
303 		// fine, we've already passed TICK_INTERVAL
304 	}
305 	else {
306 		delay = TICK_INTERVAL - elapsed;
307 		game_g->getApplication()->delay(delay);
308 	}
309 	last_time = now + delay;
310 }
311 
runMainLoop()312 void Application::runMainLoop() {
313 	unsigned int elapsed_time = game_g->getApplication()->getTicks();
314 
315 	SDL_Event event;
316 	quit = false;
317 	int last_fps_time = clock();
318 	const int fps_frames_c = 50;
319 	int frames = 0;
320 	while(!quit) {
321 		if( compute_fps && frames == fps_frames_c ) {
322 			int new_fps_time = clock();
323 			float t = ((float)(new_fps_time - last_fps_time)) / (float)CLOCKS_PER_SEC;
324 			this->fps = fps_frames_c / t;
325 			//LOG("FPS: %f\n", fps);
326 			frames = 0;
327 			last_fps_time = new_fps_time;
328 		}
329 		frames++;
330 
331 		updateSound();
332 
333 		// draw screen
334 		game_g->drawGame();
335 
336 		/* wait() to avoid 100% CPU - it's debatable whether we should do this,
337 		 * due to risk of SDL_Delay waiting too long, but since Gigalomania
338 		 * doesn't really need high frame rate, might as well avoid using full
339 		 * CPU. Also good for battery-life on mobile platforms.
340 		 * This also has the side-effect of meaning we don't call updateGame()
341 		 * with too small timestep - we have a minimum step of at least
342 		 * TICK_INTERVAL.
343 		 */
344 		wait();
345 
346 		unsigned int new_time = game_g->getApplication()->getTicks();
347 		//LOG("%d, %d\n", new_time, new_time - elapsed_time);
348 		if( !game_g->isPaused() ) {
349 			game_g->updateTime(new_time - elapsed_time);
350 		}
351 		else
352 			game_g->updateTime(0);
353 		elapsed_time = new_time;
354 
355 		// user input
356 		while( SDL_PollEvent(&event) == 1 ) {
357 			switch (event.type) {
358 			case SDL_QUIT:
359 				// SDL_QUIT may mean a message from the OS (e.g., OS shutting down) - so we quit immediately, saving the current state
360 				// It will also be sent if the user clicks the window close button, but reasonable to also interpret this as "quit immediately"
361 				// Also important for Android to quit immediately, as SDL_QUIT is sent when the screen is locked - if we don't quit immediately,
362 				// the app hangs when the screen is unlocked.
363 				game_g->requestQuit(true);
364 				break;
365 			case SDL_KEYDOWN:
366 				{
367 #if SDL_MAJOR_VERSION == 1
368 					SDL_keysym key = event.key.keysym;
369 #else
370 					SDL_Keysym key = event.key.keysym;
371 #endif
372 					if( key.sym == SDLK_ESCAPE || key.sym == SDLK_q
373 #if SDL_MAJOR_VERSION == 1
374 #else
375 						|| key.sym == SDLK_AC_BACK // SDLK_AC_BACK required for Android
376 #endif
377 						) {
378 						game_g->requestQuit(false);
379 					}
380 					else if( key.sym == SDLK_p ) {
381 						game_g->togglePause();
382 					}
383 					else if( key.sym == SDLK_RETURN ) {
384 						game_g->keypressReturn();
385 					}
386 					break;
387 				}
388 			case SDL_MOUSEBUTTONDOWN:
389 				{
390 					//LOG("received mouse down\n");
391 					int m_x = event.button.x;
392 					int m_y = event.button.y;
393 					game_g->getScreen()->setMousePos(m_x, m_y);
394 					bool m_left = false, m_middle = false, m_right = false;
395 					Uint8 button = event.button.button;
396 					if( button == SDL_BUTTON_LEFT ) {
397 						m_left = true;
398 						game_g->getScreen()->setMouseLeft(true);
399 					}
400 					else if( button == SDL_BUTTON_MIDDLE ) {
401 						m_middle = true;
402 						game_g->getScreen()->setMouseMiddle(true);
403 					}
404 					else if( button == SDL_BUTTON_RIGHT ) {
405 						m_right = true;
406 						game_g->getScreen()->setMouseRight(true);
407 					}
408 
409 					if( game_g->isPaused() ) {
410 						// click automatically unpaused (needed to work without keyboard!)
411 						game_g->togglePause();
412 					}
413 					else if( m_left || m_middle || m_right ) {
414 						/*int m_x = 0, m_y = 0;
415 						game_g->getScreen()->getMouseCoords(&m_x, &m_y);*/
416 						//LOG("received mouse click: %d, %d\n", m_x, m_y);
417 						game_g->mouseClick(m_x, m_y, m_left, m_middle, m_right, true);
418 					}
419 
420 					break;
421 				}
422 			case SDL_MOUSEBUTTONUP:
423 				{
424 					//LOG("received mouse up\n");
425 					Uint8 button = event.button.button;
426 					if( button == SDL_BUTTON_LEFT ) {
427 						game_g->getScreen()->setMouseLeft(false);
428 					}
429 					else if( button == SDL_BUTTON_MIDDLE ) {
430 						game_g->getScreen()->setMouseMiddle(false);
431 					}
432 					else if( button == SDL_BUTTON_RIGHT ) {
433 						game_g->getScreen()->setMouseRight(false);
434 					}
435 					break;
436 				}
437 			case SDL_MOUSEMOTION:
438 				{
439 					int old_m_x = 0, old_m_y = 0;
440 					game_g->getScreen()->getMouseCoords(&old_m_x, &old_m_y);
441 					int m_x = event.motion.x;
442 					int m_y = event.motion.y;
443 					//LOG("    mouse motion %d, %d\n", m_x, m_y);
444 					//LOG("    old %d, %d\n", old_m_x, old_m_y);
445 					// can't use SDL_TOUCH_MOUSEID, as event.motion.which doesn't seem to be supported for Windows 8
446 					// need to allow some tolerance, as sometimes we get a rounding issue when calculating the coordinates from tfinger, when the window size isn't 1:1
447 					int diff_x = abs(m_x - old_m_x);
448 					int diff_y = abs(m_y - old_m_y);
449 					if( diff_x > 1 || diff_y > 1 ) {
450 						//LOG("    unblank\n");
451 						//LOG("    mouse motion %d, %d\n", m_x, m_y);
452 						//LOG("    old %d, %d\n", old_m_x, old_m_y);
453 						this->blank_mouse = false;
454 					}
455 					game_g->getScreen()->setMousePos(m_x, m_y);
456 					break;
457 				}
458 #if SDL_MAJOR_VERSION == 1
459 #else
460 			// support for touchscreens
461 			// when a touch even occurs, we receive SDL_FINGERDOWN
462 			// when the touch is released, we receive SDL_FINGERUP, followed by SDL_MOUSEBUTTONDOWN then SDL_MOUSEBUTTONUP, then SDL_MOUSEMOTION
463 			case SDL_FINGERDOWN:
464 				{
465 					//LOG("received fingerdown: %f , %f\n", event.tfinger.x, event.tfinger.y);
466 					int window_width = 0, window_height = 0;
467 					game_g->getScreen()->getWindowSize(&window_width, &window_height);
468 					int m_x = (int)(event.tfinger.x*window_width);
469 					int m_y = (int)(event.tfinger.y*window_height);
470 					//LOG("    %d, %d\n", m_x, m_y);
471 					game_g->getScreen()->convertWindowToLogical(&m_x, &m_y);
472 					//LOG("    logical %d, %d\n", m_x, m_y);
473 					game_g->getScreen()->setMousePos(m_x, m_y);
474 					game_g->getScreen()->setMouseLeft(true);
475 					this->blank_mouse = true;
476 					break;
477 				}
478 			case SDL_FINGERUP:
479 				{
480 					//LOG("received fingerup\n");
481 					game_g->getScreen()->setMouseLeft(false);
482 					this->blank_mouse = true;
483 					// n.b., "click" is handled via SDL_MOUSEBUTTONUP
484 					break;
485 				}
486 			case SDL_FINGERMOTION:
487 				{
488 					//LOG("received fingermotion: %f , %f\n", event.tfinger.x, event.tfinger.y);
489 					int window_width = 0, window_height = 0;
490 					game_g->getScreen()->getWindowSize(&window_width, &window_height);
491 					int m_x = (int)(event.tfinger.x*window_width);
492 					int m_y = (int)(event.tfinger.y*window_height);
493 					//LOG("    %d, %d\n", m_x, m_y);
494 					game_g->getScreen()->convertWindowToLogical(&m_x, &m_y);
495 					//LOG("    logical %d, %d\n", m_x, m_y);
496 					game_g->getScreen()->setMousePos(m_x, m_y);
497 					this->blank_mouse = true;
498 					break;
499 				}
500 #endif
501 #if SDL_MAJOR_VERSION == 1
502 			case SDL_ACTIVEEVENT:
503 #ifndef AROS
504 				// disabled for AROS, as we receive inactive events when the mouse goes outside the window!
505 				if( (event.active.state & SDL_APPINPUTFOCUS) != 0 || (event.active.state & SDL_APPACTIVE) != 0 ) {
506 					if( event.active.gain == 1 ) {
507 						// activate
508 						game_g->activate();
509 					}
510 					else if( event.active.gain == 0 ) {
511 						// inactive
512 						game_g->deactivate();
513 					}
514 				}
515 #endif
516 				break;
517 #else
518 			case SDL_WINDOWEVENT:
519 				if( event.window.event == SDL_WINDOWEVENT_SHOWN || event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ) {
520 					// activate
521 					game_g->activate();
522 				}
523 				else if( event.window.event == SDL_WINDOWEVENT_HIDDEN || event.window.event == SDL_WINDOWEVENT_FOCUS_LOST ) {
524 					// inactive
525 					game_g->deactivate();
526 				}
527 				break;
528 #endif
529 			}
530 		}
531 		SDL_PumpEvents();
532 
533 		game_g->updateGame();
534 	}
535 }
536 
537 //#endif
538