1 /***************************************************************************
2 sdltools.cpp - description
3 -------------------
4 begin : Fri Jul 21 2000
5 copyright : (C) 2006 by Immi
6 email : cuyo@pcpool.mathematik.uni-freiburg.de
7
8 Modified 2006-2008,2010,2011,2014 by the cuyo developers
9
10 ***************************************************************************/
11
12 /***************************************************************************
13 * *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 ***************************************************************************/
20
21 #include <cstdlib>
22
23 #include "stringzeug.h"
24 #include "sdltools.h"
25 #include "global.h"
26 #include "fehler.h"
27 #include "layout.h"
28 #include "bilddatei.h"
29
30
31 /* Right now, the picture scale funcition still has a fixed
32 scale_base=4 build in. Apart from that, it should be possible
33 to increase scale_base to 8 or 16 (so that more different scalings
34 are possible) */
35
36 #define videomode_flags_w \
37 (SDL_SWSURFACE/*|SDL_DOUBLEBUF*/|SDL_ANYFORMAT|SDL_VIDEORESIZE)
38 #define videomode_flags_f \
39 (SDL_SWSURFACE/*|SDL_DOUBLEBUF*/|SDL_ANYFORMAT|SDL_FULLSCREEN)
40
41
42
43 namespace Area {
44 void init();
45 }
46
47
48
49 namespace SDLTools {
50
51
52 /* A 32-Bit-surface, to copy the pixel format from */
53 SDL_Surface * gSampleSurface32 = 0;
54
55 /* The pixel format used for maskedDisplayFormat() */
56 SDL_PixelFormat gMaskedFormat;
57 /* The replacement colour for the ColourKey, if necessary */
58 Uint8 gColkeyErsatz;
59
60 /* gScale == scale_base means original size */
61 int gScale;
62 int gShiftX, gShiftY;
63
64 /* Virtual Window-size, i.e. size how it looks for the cuyo program
65 (In contrast to screen size, which may be scaled).
66 Start with sensible default size. */
67 int gVirtualWidth = L_usual_width;
68 int gVirtualHeight = L_usual_height;
69
70 bool gFullScreen = false;
71
72
myQuitSDL()73 void myQuitSDL() {
74 if (gSampleSurface32)
75 SDL_FreeSurface(gSampleSurface32);
76 if (gMaskedFormat.palette) {
77 delete[] gMaskedFormat.palette->colors;
78 delete gMaskedFormat.palette;
79 }
80 SDL_Quit();
81 }
82
83
84
85 /* Compute the value of gScale for a window of size w x h */
getScale(int w,int h)86 int getScale(int w, int h) {
87 int sc_w = w / (gVirtualWidth / scale_base);
88 int sc_h = h / (gVirtualHeight / scale_base);
89 int sc = sc_w < sc_h ? sc_w : sc_h;
90 if (sc < 1) sc = 1;
91 return sc;
92 }
93
94
computeScaleAndShift()95 void computeScaleAndShift() {
96 SDL_Surface * s = SDL_GetVideoSurface();
97 gScale = getScale(s->w, s->h);
98 gShiftX = (s->w - gVirtualWidth * gScale / scale_base) / 2;
99 gShiftY = (s->h - gVirtualHeight * gScale / scale_base) / 2;
100
101 /* Rescale all icons */
102 Bilddatei::resizeEvent();
103 }
104
105
106
107 /* In fullscreen mode: find out optimal resolution to choose;
108 (this is also used to get an idea on the optimal window size
109 in windowed mode);
110 returns false, if there seem to be no fullscreen mode available */
fullscreenSize(int & w,int & h)111 bool fullscreenSize(int & w, int & h) {
112 SDL_Rect **modes;
113 /* format = 0: search for best video mode */
114 modes = SDL_ListModes(0, videomode_flags_f);
115
116 /* No size possible ?!? Let's hope that this is just because there
117 is no fullscreen mode available. (As fullscreenSize() is called
118 even in windowed mode, it is important that we do not just crash.) */
119 if (modes == (SDL_Rect**) 0) {
120 /* In case we just tried to guess a good window size, let's
121 just return a value which we hope to be good. */
122 w = L_preferred_width;
123 h = L_preferred_height;
124 return false;
125 }
126
127 /* Any size possible in fullscreen mode... */
128 if (modes == (SDL_Rect**) -1) {
129 w = L_preferred_width;
130 h = L_preferred_height;
131 }
132
133 /* Return the smallest resolution which is as least as
134 big as L_usual_width x L_usual_height,
135 or otherwise the biggest existing one */
136 bool found = false;
137 for (int i = 0; modes[i]; i++) {
138 if (modes[i]->w >= L_usual_width && modes[i]->h >= L_usual_height || !found) {
139 w = modes[i]->w;
140 h = modes[i]->h;
141 found = true;
142 }
143 }
144 return true;
145 }
146
147
148 /* Set/change (screen, i.e. real) window size */
setVideoMode(int w,int h)149 void setVideoMode(int w, int h) {
150
151 /* In fullscreen mode or if no window size was given, find
152 a good default window size */
153 if (gFullScreen || w == -1) {
154 if (!fullscreenSize(w, h)) {
155 /* No fullscreen mode available? Then fall back to windowed
156 mode. */
157 gFullScreen = false;
158
159 } else if (!gFullScreen) {
160 /* Not fullscreen? Then use the resolution which would be optimal
161 in fullscreen mode, but shrink the window to get rid of the
162 black borders */
163 int sc = getScale(w, h);
164 int pref_w = L_preferred_width * sc / scale_base;
165 int pref_h = L_preferred_height * sc / scale_base;
166 if (w > pref_w) w = pref_w;
167 if (h > pref_h) h = pref_h;
168 }
169 }
170
171 /* Initialize the display
172 requesting a software surface
173 BitsPerPixel = 0: Take the current BitsPerPixel
174 SDL_ANYFORMAT: Other pixel depths are ok, too. */
175 SDL_Surface * s = SDL_SetVideoMode(w, h, 0,
176 gFullScreen ? videomode_flags_f : videomode_flags_w);
177 SDLASSERT(s);
178
179 computeScaleAndShift();
180 }
181
182
computeMaskedFormat()183 void computeMaskedFormat() {
184
185 CASSERT(gSampleSurface32);
186
187 SDL_Surface * s = SDL_GetVideoSurface();
188
189 gMaskedFormat = *(s->format);
190 if (gMaskedFormat.palette) {
191 int ncolours = gMaskedFormat.palette->ncolors;
192
193 /* Erst mal eine Kopie der Palette anlegen. B�se Dinge k�nnten passieren,
194 wenn das displaysurface sie �ndert. */
195 SDL_Color * neufarben = new SDL_Color[ncolours];
196 memcpy((void*) neufarben, (void*) gMaskedFormat.palette->colors,
197 ncolours * sizeof(SDL_Color));
198 SDL_Palette * neupal = new SDL_Palette;
199 neupal->ncolors = ncolours;
200 neupal->colors = neufarben;
201 gMaskedFormat.palette = neupal;
202
203 if (ncolours < 255) {
204 gMaskedFormat.colorkey = ncolours + 1;
205 gColkeyErsatz = 0; // Wird eh nicht benutzt...
206 }
207 else {
208 /* Jetzt m�ssen wir eine entbehrliche Farbe suchen */
209 int dmin = 1000;
210 int c1best=0, c2best=0;
211 // Initialisierung ist unn�tig, spart aber Warnungen
212
213 SDL_Color * colours = gMaskedFormat.palette->colors;
214
215 #ifdef DIST
216 #error Vorsicht, DIST gibt es schon!
217 #endif
218 #define DIST(dim) (colours[c1].dim<colours[c2].dim \
219 ? colours[c2].dim-colours[c1].dim \
220 : colours[c1].dim-colours[c2].dim)
221
222 for (int c1=0; c1<ncolours; c1++)
223 for (int c2=c1+1; c2<ncolours; c2++) {
224 int d = DIST(r)+DIST(g)+DIST(b);
225 if (d<dmin) {
226 dmin=d;
227 c1best=c1;
228 c2best=c2;
229 }
230 }
231
232 #undef DIST
233
234 gMaskedFormat.colorkey = c2best;
235 gColkeyErsatz = c1best;
236 }
237 }
238 else
239 gMaskedFormat = *(gSampleSurface32->format);
240 }
241
242
243 /* opt_w, opt_h: size of window, as given by command line option;
244 or -1,-1 to automatically choose window size */
initSDL(int opt_w,int opt_h)245 void initSDL(int opt_w, int opt_h) {
246
247 /* Initialize the SDL library */
248 /* Audio will be initialized by our sound system */
249 SDLASSERT(SDL_Init(SDL_INIT_VIDEO) == 0);
250
251 /* Clean up on exit */
252 atexit(myQuitSDL);
253
254 /* Set the name of the window (and the icon) */
255 setMainTitle();
256
257 setVideoMode(opt_w, opt_h);
258
259 gSampleSurface32 = createSurface32(1, 1);
260 SDLASSERT(gSampleSurface32);
261
262 computeMaskedFormat();
263
264
265 SDL_EventState(SDL_KEYUP, SDL_IGNORE);
266 SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE);
267
268 /* We need the characters corresponding to key-events for the menus:
269 For example, on a French keyboard, shift-& is 1 */
270 SDL_EnableUNICODE(1);
271
272 Area::init();
273 }
274
275
276
277
278 /* Call while cuyo is already running is not yet supported
279 (but not difficult to implement) */
setFullscreen(bool fs)280 void setFullscreen(bool fs) {
281 gFullScreen = fs;
282 }
283
284
285 /* Change the size of the window from the view of the cuyo program.
286 Does *not* change the real window size; instead, scaling may
287 change.
288 I propose that this should only be used in such a way that when
289 the real window size is the preferred one (L_preferred_xxx),
290 then scaling should never change.
291 Note that changing the scaling always takes some time.
292 */
setVirtualWindowSize(int w,int h)293 void setVirtualWindowSize(int w, int h) {
294 gVirtualWidth = w;
295 gVirtualHeight = h;
296 computeScaleAndShift();
297 }
298
299
setWindowTitle(const char * title)300 void setWindowTitle(const char * title) {
301 char * title_ = convert_for_window_title(title);
302 char * icon = convert_for_window_title("Cuyo");
303 SDL_WM_SetCaption(title_,icon);
304 free(title_);
305 free(icon);
306 }
307
setMainTitle()308 void setMainTitle() {
309 // TRANSLATORS: "cuyo" is the program's name
310 setWindowTitle(gDebug ? _("Cuyo - debug mode") : "Cuyo");
311 }
312
setLevelTitle(const Str & levelname)313 void setLevelTitle(const Str & levelname) {
314 // TRANSLATORS: This is a window title
315 setWindowTitle(_sprintf(_("Cuyo - level %s"),levelname.data()).data());
316 }
317
318 /* Convert Qt-Key into SDL-Key; don't use Qt constants: we don't want to depend on
319 Qt just to be able to read old .cuyo files. */
qtKey2sdlKey(int qtk)320 SDLKey qtKey2sdlKey(int qtk) {
321
322 /* Letters are uppercase in Qt and lowercase in SDL */
323 if (qtk >= 'A' && qtk <= 'Z')
324 return (SDLKey) (qtk - 'A' + 'a');
325
326 /* Don't change other Ascii Characters.
327 (Maybe �, �, �, etc are a problem) */
328 if (qtk <= 255)
329 return (SDLKey) qtk;
330
331 /* Other important keys */
332 switch (qtk) {
333 case 0x1000: return SDLK_ESCAPE;
334 case 0x1001: return SDLK_TAB;
335 case 0x1003: return SDLK_BACKSPACE;
336 case 0x1004: return SDLK_RETURN;
337 case 0x1006: return SDLK_INSERT;
338 case 0x1007: return SDLK_DELETE;
339 case 0x1008: return SDLK_PAUSE;
340 case 0x1009: return SDLK_PRINT;
341 case 0x100a: return SDLK_SYSREQ;
342 case 0x100b: return SDLK_CLEAR;
343 case 0x1010: return SDLK_HOME;
344 case 0x1011: return SDLK_END;
345 case 0x1012: return SDLK_LEFT;
346 case 0x1013: return SDLK_UP;
347 case 0x1014: return SDLK_RIGHT;
348 case 0x1015: return SDLK_DOWN;
349 case 0x1016: return SDLK_PAGEUP;
350 case 0x1017: return SDLK_PAGEDOWN;
351
352 case 0x1030: return SDLK_F1;
353 case 0x1031: return SDLK_F2;
354 case 0x1032: return SDLK_F3;
355 case 0x1033: return SDLK_F4;
356 case 0x1034: return SDLK_F5;
357 case 0x1035: return SDLK_F6;
358 case 0x1036: return SDLK_F7;
359 case 0x1037: return SDLK_F8;
360 case 0x1038: return SDLK_F9;
361 case 0x1039: return SDLK_F10;
362 case 0x103a: return SDLK_F11;
363 case 0x103b: return SDLK_F12;
364 case 0x103c: return SDLK_F13;
365 case 0x103d: return SDLK_F14;
366 case 0x103e: return SDLK_F15;
367
368 case 0x1055: return SDLK_MENU;
369 case 0x1058: return SDLK_HELP;
370 //case : return SDLK_BREAK;
371 //case : return SDLK_POWER;
372 //case : return SDLK_EURO;
373 //case : return SDLK_UNDO;
374 default: return SDLK_UNKNOWN;
375 }
376
377 /* Modifier keys are missing */
378 }
379
380
381
rect(int x,int y,int w,int h)382 SDL_Rect rect(int x, int y, int w, int h) {
383 SDL_Rect ret;
384 ret.x = x; ret.y = y; ret.w = w; ret.h = h;
385 return ret;
386 }
387
intersection(const SDL_Rect & a,const SDL_Rect & b,SDL_Rect & ret)388 bool intersection(const SDL_Rect & a, const SDL_Rect & b, SDL_Rect & ret) {
389 ret.x = a.x > b.x ? a.x : b.x;
390 ret.y = a.y > b.y ? a.y : b.y;
391 int xplusw = a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w;
392 int yplush = a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h;
393 if (xplusw<=ret.x || yplush<=ret.y)
394 return false;
395 ret.w = xplusw-ret.x;
396 ret.h = yplush-ret.y;
397 return true;
398 }
399
400
401 /* Returns true if a is contained in b */
contained(const SDL_Rect & a,const SDL_Rect & b)402 bool contained(const SDL_Rect & a, const SDL_Rect & b) {
403 return a.x >= b.x && a.y >= b.y &&
404 a.x + a.w <= b.x + b.w && a.y + a.h <= b.y + b.h;
405 }
406
407
408 /* Creates a 32-bit-surface with alpha. After filling it with your
409 data, you should convert it to screen format */
createSurface32(int w,int h)410 SDL_Surface * createSurface32(int w, int h) {
411
412 union { Uint32 f; Uint8 k[4];} rmask, gmask, bmask, amask;
413
414 /* Die richtigen Bits der Masken auf 1 setzen; das Problem ist, dass
415 welches die richtigen Bits sind von der Endianness abhaengen.
416 Das folgende macht's richtig: */
417 rmask.f = gmask.f = bmask.f = amask.f = 0;
418 rmask.k[0] = gmask.k[1] = bmask.k[2] = amask.k[3] = 0xff;
419
420 SDL_Surface * s = SDL_CreateRGBSurface(SDL_HWSURFACE, w, h, 32, rmask.f, gmask.f, bmask.f, amask.f);
421 SDLASSERT(s);
422 return s;
423 }
424
425
426 /* Converts a surface to a 32-bit-surface with alpha. The original surface
427 is deleted. */
convertSurface32(SDL_Surface * & s)428 void convertSurface32(SDL_Surface *& s) {
429 /* Silly: The only way I know to create an SDL_PixelFormat is using
430 SDL_CreateRGBSurface; so we need a "sample surface" to get the
431 format from... */
432 SDL_Surface * tmp = SDL_ConvertSurface(s, gSampleSurface32->format, SDL_SWSURFACE);
433 SDLASSERT(tmp);
434 SDL_FreeSurface(s);
435 s = tmp;
436 }
437
438
439 /* Return a reference to the pixel at (x, y);
440 assumes that the surface is 32-Bit.
441 NOTE: The surface must be locked before calling this! */
getPixel32(SDL_Surface * surface,int x,int y)442 Uint32 & getPixel32(SDL_Surface *surface, int x, int y) {
443 int bpp = surface->format->BytesPerPixel;
444 return *(Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * bpp);
445 }
446
447
448 /* Converts the surface to a format suitable for fast blitting onto the
449 display surface. Contrary to SDL_DisplayFormat, transparency is
450 respected, at least where it is full transparency. Contrary to
451 SDL_DisplayFormatAlpha, it uses ColourKey for paletted surfaces. */
maskedDisplayFormat(SDL_Surface * src)452 SDL_Surface * maskedDisplayFormat(SDL_Surface * src) {
453 SDL_Surface * ret = SDL_ConvertSurface(src,&gMaskedFormat,0);
454 SDLASSERT(ret);
455
456 if (gMaskedFormat.palette) {
457 /* SDL sieht nicht vor, da� Alpha zu ColourKey konvertiert wird.
458 Seufz. Also selber nachbearbeiten. */
459 SDLASSERT(!SDL_MUSTLOCK(src) && !SDL_MUSTLOCK(ret));
460
461 ret->flags |= SDL_SRCCOLORKEY;
462 Uint8 colkey = gMaskedFormat.colorkey;
463 ret->format->colorkey = colkey;
464 Uint32 amask = src->format->Amask;
465 Uint8 * srcrow = (Uint8 *) src->pixels;
466 Uint8 * retrow = (Uint8 *) ret->pixels;
467 int w = src->w;
468 int p1 = src->pitch;
469 int p2 = ret->pitch;
470 for (int i=src->h; i; i--, srcrow+=p1, retrow+=p2) {
471 Uint32 * srcpix = (Uint32 *) srcrow;
472 Uint8 * retpix = retrow;
473 for (int j=w; j; j--, srcpix++, retpix++)
474 if (((*srcpix) & amask)==0)
475 *retpix = colkey;
476 else if ((*retpix)==colkey)
477 *retpix = gColkeyErsatz;
478 }
479 }
480 return ret;
481 }
482
createMaskedDisplaySurface(int w,int h)483 SDL_Surface * createMaskedDisplaySurface(int w, int h) {
484 return SDL_CreateRGBSurface(0,w,h,gMaskedFormat.BitsPerPixel,
485 gMaskedFormat.Rmask,gMaskedFormat.Gmask,
486 gMaskedFormat.Bmask,gMaskedFormat.Amask);
487 }
488
489
490 /* Scales the surface according to our window size;
491 s must be 32 bit.
492 Warning: It is possible that a new surface is created and
493 returned, but is is also possible that just a pointer to s
494 is returned. */
scaleSurface(SDL_Surface * s)495 SDL_Surface * scaleSurface(SDL_Surface * s) {
496
497 if (!gScale == scale_base)
498 return s;
499
500 int w2 = s->w * gScale / scale_base;
501 int h2 = s->h * gScale / scale_base;
502 SDL_Surface * ret = SDLTools::createSurface32(w2+1, h2+1);
503
504 SDL_LockSurface(s);
505 SDL_LockSurface(ret);
506
507 /* Fill the safety margin (which is there to buffer rounding errors in
508 Area::blitSurface().). */
509 Uint32 trans=SDL_MapRGBA(ret->format,0,0,0,0);
510 SDL_Rect r=rect(w2,0,1,h2+1);
511 SDL_FillRect(ret,&r,trans);
512 r=rect(0,h2,w2,1);
513 SDL_FillRect(ret,&r,trans);
514
515 for (int y = 0; y < h2; y++) {
516 for (int x = 0; x < w2; x++) {
517 Uint8 * dst = (Uint8 *)ret->pixels + ret->pitch * y + 4 * x;
518 Uint8 * src = (Uint8 *)s->pixels + s->pitch * (y * 4 / gScale) + 4 * (x * 4 / gScale);
519
520 Uint32 srcpix[16];
521 Uint32 anteil[16];
522 int numpix;
523
524 /* Scaling is not yet very beautiful; in particular not enlarging */
525 if (gScale >= 4) {
526 numpix = 1;
527 srcpix[0] = * (Uint32*) src;
528 anteil[0] = 16;
529 } else if (gScale == 3) {
530 numpix = 4;
531 srcpix[0] = * (Uint32*) src;
532 srcpix[1] = * (Uint32*) (src + 4);
533 srcpix[2] = * (Uint32*) (src + s->pitch);
534 srcpix[3] = * (Uint32*) (src + s->pitch + 4);
535 anteil[0] = (3 - x % 3) * (3 - y % 3);
536 anteil[1] = (1 + x % 3) * (3 - y % 3);
537 anteil[2] = (3 - x % 3) * (1 + y % 3);
538 anteil[3] = (1 + x % 3) * (1 + y % 3);
539 } else if (gScale == 2) {
540 numpix = 4;
541 srcpix[0] = * (Uint32*) src;
542 srcpix[1] = * (Uint32*) (src + 4);
543 srcpix[2] = * (Uint32*) (src + s->pitch);
544 srcpix[3] = * (Uint32*) (src + s->pitch + 4);
545 anteil[0] = anteil[1] = anteil[2] = anteil[3] = 4;
546 } else {/* 1 */
547 numpix = 16;
548 for (int j = 0; j < 16; j++) {
549 srcpix[j] = * (Uint32*) (src + s->pitch * (j / 4) + 4 * (j % 4));
550 anteil[j] = 1;
551 }
552 }
553 /* Hier ist erstmal Summe der Anteile = 16 */
554
555 /* Alpha-Kanal einrechnen */
556 Uint32 gesAlpha = 0;
557 for (int j = 0; j < numpix; j++) {
558 Uint32 sp = srcpix[j];
559 anteil[j] *= ((Uint8 *) &sp)[3];
560 gesAlpha += anteil[j];
561 }
562
563 Uint8 dstpix[4];
564 dstpix[3] = gesAlpha / 16;
565 if (dstpix[3] > 0) {
566 for (int i = 0; i < 3; i++) {
567 Uint32 komponente = 0;
568 for (int j = 0; j < numpix; j++) {
569 Uint32 sp = srcpix[j];
570 komponente += anteil[j] * ((Uint8 *) &sp)[i];
571 }
572 komponente /= gesAlpha;
573 dstpix[i] = komponente;
574 }
575 }
576 memcpy(dst,dstpix,4);
577 }
578 }
579
580 SDL_UnlockSurface(s);
581 SDL_UnlockSurface(ret);
582
583 return ret;
584 }
585
586
getScale()587 int getScale() {
588 return gScale;
589 }
590
591
592 /* Like SDL_PollEvent, but the event is already scaled.
593 And for resize events, the window and scaling is already
594 prepared. */
pollEvent(SDL_Event & evt)595 bool pollEvent(SDL_Event & evt) {
596 if (!SDL_PollEvent(&evt))
597 return false;
598
599 if (evt.type == SDL_VIDEORESIZE) {
600 setVideoMode(evt.resize.w, evt.resize.h);
601 } else if (evt.type == SDL_MOUSEMOTION) {
602 evt.motion.x -= gShiftX;
603 evt.motion.y -= gShiftY;
604 /* We have to ensure:
605 motion.xy = old-motion.xy + motion.xyrel
606 Without rounding errors. Thus we cannot just
607 multiply motion.xyrel by scale_base/gScale */
608 Uint16 xold, yold;
609 xold = evt.motion.x - evt.motion.xrel;
610 yold = evt.motion.y - evt.motion.yrel;
611 evt.motion.x = evt.motion.x * scale_base / gScale;
612 evt.motion.y = evt.motion.y * scale_base / gScale;
613 xold = xold * scale_base / gScale;
614 yold = yold * scale_base / gScale;
615 evt.motion.xrel = evt.motion.x - xold;
616 evt.motion.yrel = evt.motion.y - yold;
617 } else if (evt.type == SDL_MOUSEBUTTONDOWN ||
618 evt.type == SDL_MOUSEBUTTONUP) {
619 evt.button.x -= gShiftX;
620 evt.button.y -= gShiftY;
621 evt.button.x = evt.button.x * scale_base / gScale;
622 evt.button.y = evt.button.y * scale_base / gScale;
623 }
624
625 return true;
626 }
627
628
629 } // namespace SDLTools
630
631 /**************************************************************************/
632
633 #define max_area_depth 5
634
635 namespace Area {
636
637 /* When drawing, three coordinate transformation are done:
638
639 Area coordinates
640 translate according to the origin of the current Area
641 virtual coordinates
642 scale coordinates
643 Translate to get the black border around the window
644 screen coordinates
645
646 The two translation cannot really well be merged because one is in
647 virtual pixels and the other one in screen pixels
648
649 To avoid rounding errors during scaling:
650 The virtual coordinate x corresponds to the screen coordinate
651 floor(x * gScale / scale_base); in screen coordinates, translations
652 aren't exactly linear anymore. So instead of
653 (a) scale(x) + scale(w)
654 always do
655 (b) scale(x+w)
656 There is one exception in which you may use (a): When w is
657 divisible by scale_base; this means that drawing Surfaces or
658 parts of surfaces with size divisible by scale_base is allowed
659 */
660
661
662
663 /* The bounds of each area in the area stack.
664 mBounds[0] is always the whole virtual window.
665 mBounds is in virtual coordinates */
666 SDL_Rect mBounds[max_area_depth];
667 /* mBounds[mActDepth] are the present bounds */
668 int mActDepth;
669
670 /* I don't use an stl-vector for the updateRects, because:
671 a) I don't want the vector to be resized to a small one
672 each time the updateRects are cleared
673 b) I need direct access to the memory area with the rects
674 to pass them to SDL
675 The mUpdateRects are in screen coordinates
676 */
677 SDL_Rect * mUpdateRects;
678 int mNumUpdateRects;
679 int mReservedUpdateRects;
680 bool mUpdateAll;
681
682 SDL_Surface * mBackground;
683 SDL_Rect mBackgroundRect; /* screen coordinates */
684 int mBackgroundContext; /* value -1 for "undefined" */
685
686 /*** Private functions ***/
687
688
needMoreUpdateRects()689 void needMoreUpdateRects() {
690 mReservedUpdateRects *= 2;
691 mUpdateRects = (SDL_Rect *) realloc(mUpdateRects,
692 sizeof(SDL_Rect) * mReservedUpdateRects);
693 }
694
695
scale(int & x,int & y)696 void scale(int & x, int & y) {
697 x = x * SDLTools::gScale / scale_base;
698 y = y * SDLTools::gScale / scale_base;
699 }
scale(Sint16 & x,Sint16 & y)700 void scale(Sint16 & x, Sint16 & y) {
701 x = x * SDLTools::gScale / scale_base;
702 y = y * SDLTools::gScale / scale_base;
703 }
704
705
706 /* Area coordinates to Screen coordinates */
transformA2S(int & x,int & y)707 void transformA2S(int & x, int & y) {
708 x += mBounds[mActDepth].x;
709 y += mBounds[mActDepth].y;
710 scale(x, y);
711 x += SDLTools::gShiftX;
712 y += SDLTools::gShiftY;
713 }
714
715
716 /* Area coordinates to Virtual coordinates */
transformA2V(SDL_Rect & r)717 void transformA2V(SDL_Rect & r) {
718 r.x += mBounds[mActDepth].x;
719 r.y += mBounds[mActDepth].y;
720 }
721
scale(SDL_Rect & r)722 void scale(SDL_Rect & r) {
723 int right = r.x + r.w;
724 int bot = r.y + r.h;
725 scale(r.x, r.y);
726 scale(right, bot);
727 r.w = right - r.x;
728 r.h = bot - r.y;
729 }
730
731 /* Virtual coordinates to Screen coordinates */
transformV2S(SDL_Rect & r)732 void transformV2S(SDL_Rect & r) {
733 scale(r);
734 r.x += SDLTools::gShiftX;
735 r.y += SDLTools::gShiftY;
736 }
737
738
739 /* Area coordinates to Screen coordinates */
transformA2S(SDL_Rect & r)740 void transformA2S(SDL_Rect & r) {
741 transformA2V(r);
742 transformV2S(r);
743 }
744
745
746 /* Clip the (screen) coordinates so that they fit into the window. */
boundS(int & x,int & y)747 void boundS(int & x, int & y) {
748 if (x<0) x=0;
749 if (y<0) y=0;
750 SDL_Surface * screen = SDL_GetVideoSurface();
751 if (x>screen->w) x=screen->w;
752 if (y>screen->h) y=screen->h;
753 }
754
boundS(SDL_Rect & r)755 void boundS(SDL_Rect & r) {
756 int x0=r.x;
757 int y0=r.y;
758 int x1=r.x+r.w;
759 int y1=r.y+r.h;
760 boundS(x0,y0);
761 boundS(x1,y1);
762 r.x=x0; r.y=y0; r.w=x1-x0; r.h=y1-y0;
763 }
764
765 /* r is in virtual coordinates */
setClipRectV(SDL_Rect r)766 void setClipRectV(SDL_Rect r) {
767 transformV2S(r);
768 boundS(r);
769 SDL_SetClipRect(SDL_GetVideoSurface(), &r);
770 }
771
772
773 /*** Public functions ***/
774
775
776 /* This is called from initSDL() */
init()777 void init() {
778 /* 20 updateRects will never be enough, but I prefer that
779 needMoreUpdateRects is called early enough so I notice if
780 there's a bug inside. */
781 mReservedUpdateRects = 20;
782 mUpdateRects = (SDL_Rect *) malloc(sizeof(SDL_Rect) * mReservedUpdateRects);
783 mNumUpdateRects = 0;
784 mUpdateAll = false;
785
786 mActDepth = 0;
787 /* Only the top-left corner mBounds[0] should be used.
788 Make this rectangle empty, in the hope that this will
789 make it easier to detect bugs when it is falsely used */
790 mBounds[mActDepth] = SDLTools::rect(0, 0, 0, 0);
791 noClip();
792
793 mBackgroundContext = -1;
794 }
795
destroy()796 void destroy() {
797 free(mUpdateRects);
798 }
799
800
enter(SDL_Rect r)801 void enter(SDL_Rect r) {
802 transformA2V(r);
803
804 mActDepth++;
805 CASSERT(mActDepth < max_area_depth);
806 if (mActDepth > 1)
807 CASSERT(SDLTools::contained(r, mBounds[mActDepth - 1]));
808 mBounds[mActDepth] = r;
809 noClip();
810 }
811
leave()812 void leave() {
813 CASSERT(mActDepth > 0);
814 if (mBackgroundContext==mActDepth) mBackgroundContext=-1;
815 mActDepth--;
816 noClip();
817 }
818
setClip(SDL_Rect r)819 void setClip(SDL_Rect r) {
820 transformA2V(r);
821 if (mActDepth == 0)
822 setClipRectV(r);
823 else {
824 SDL_Rect inter;
825 SDLTools::intersection(r,mBounds[mActDepth],inter);
826 setClipRectV(inter);
827 }
828 }
829
noClip()830 void noClip() {
831 if (mActDepth == 0) {
832 /* Outer most area is the only one which allows
833 to draw outside */
834 SDL_Surface * s = SDL_GetVideoSurface();
835 SDL_Rect r = SDLTools::rect(0, 0, s->w, s->h);
836 SDL_SetClipRect(s, &r);
837 } else
838 setClipRectV(mBounds[mActDepth]);
839 }
840
setBackground(const SDL_Rect & r,SDL_Surface * s)841 void setBackground(const SDL_Rect & r, SDL_Surface * s) {
842 mBackground = s;
843 mBackgroundRect = r;
844 transformA2S(mBackgroundRect);
845 mBackgroundContext = mActDepth;
846 }
847
maskBackground(const Maske * mask,SDL_Rect mr,int x,int y)848 void maskBackground(const Maske * mask, SDL_Rect mr, int x, int y) {
849 if (mBackgroundContext!=-1 && !mask->is_empty()) {
850 int x2 = x + mr.w;
851 int y2 = y + mr.h;
852 transformA2S(x,y);
853 transformA2S(x2,y2);
854 scale(mr.x, mr.y);
855 SDL_Rect dr = SDLTools::rect(x,y,x2-x,y2-y);
856 SDL_Rect ir;
857 if (SDLTools::intersection(mBackgroundRect,dr,ir)) {
858 boundS(ir);
859 mr.x += ir.x-dr.x;
860 mr.y += ir.y-dr.y;
861 mr.w = ir.w;
862 mr.h = ir.h;
863 mask->masked_blit(mBackground,
864 ir.x-mBackgroundRect.x, ir.y-mBackgroundRect.y,
865 mr,
866 SDL_GetVideoSurface(),ir);
867 }
868 }
869 }
870
871
872
873 /* If the coordinates of srcrect are not a multiple of scale_base, then
874 rounding is done in such a way that the result is correct in the
875 *destination*, not in the source. */
blitSurface(SDL_Surface * src,SDL_Rect srcrect,int dstx,int dsty)876 void blitSurface(SDL_Surface *src, SDL_Rect srcrect, int dstx, int dsty) {
877 int dstx2 = dstx + srcrect.w;
878 int dsty2 = dsty + srcrect.h;
879 transformA2S(dstx, dsty);
880 transformA2S(dstx2, dsty2);
881 SDL_Rect dstrect = SDLTools::rect(dstx, dsty, dstx2-dstx, dsty2-dsty);
882 boundS(dstrect);
883 scale(srcrect.x, srcrect.y);
884 srcrect.x+=dstrect.x-dstx;
885 srcrect.y+=dstrect.y-dsty;
886 srcrect.w = dstrect.w;
887 srcrect.h = dstrect.h;
888
889 SDL_BlitSurface(src, &srcrect, SDL_GetVideoSurface(), &dstrect);
890 }
891
blitSurface(SDL_Surface * src,int dstx,int dsty)892 void blitSurface(SDL_Surface *src, int dstx, int dsty) {
893 transformA2S(dstx, dsty);
894 SDL_Rect dstrect = SDLTools::rect(dstx,dsty,src->w,src->h);
895 boundS(dstrect);
896 SDL_Rect srcrect = SDLTools::rect(dstrect.x-dstx,dstrect.y-dsty,
897 dstrect.w,dstrect.h);
898 SDL_BlitSurface(src, &srcrect, SDL_GetVideoSurface(), &dstrect);
899 }
900
901
fillRect(SDL_Rect dst,const Color & c)902 void fillRect(SDL_Rect dst, const Color & c) {
903 SDL_Surface * s = SDL_GetVideoSurface();
904 transformA2S(dst);
905 boundS(dst);
906 if (((Sint16) dst.w) < 0 || ((Sint16) dst.h) < 0)
907 print_to_stderr("Probably trying to fill rectangle of negative size; this causes an overflow.\n");
908 SDL_FillRect(s, &dst, c.getPixel(s->format));
909
910 }
911
fillRect(int x,int y,int w,int h,const Color & c)912 void fillRect(int x, int y, int w, int h, const Color & c) {
913 fillRect(SDLTools::rect(x, y, w, h), c);
914 }
915
916 /* Fills everything outside the current virtual window */
fillBorder(const Color & c)917 void fillBorder(const Color & c) {
918 SDL_Rect r = SDLTools::rect(0, 0,
919 SDLTools::gVirtualWidth, SDLTools::gVirtualHeight);
920 transformV2S(r);
921
922 SDL_Surface * s = SDL_GetVideoSurface();
923 SDL_Rect dst;
924
925 dst = SDLTools::rect(0, 0, r.x, s->h);
926 boundS(dst);
927 SDL_FillRect(s, &dst, c.getPixel(s->format));
928 dst = SDLTools::rect(r.x + r.w, 0, s->w - (r.x + r.w), s->h);
929 boundS(dst);
930 SDL_FillRect(s, &dst, c.getPixel(s->format));
931 dst = SDLTools::rect(r.x, 0, r.w, r.y);
932 boundS(dst);
933 SDL_FillRect(s, &dst, c.getPixel(s->format));
934 dst = SDLTools::rect(r.x, r.y + r.h, r.w, s->h - (r.y + r.h));
935 boundS(dst);
936 SDL_FillRect(s, &dst, c.getPixel(s->format));
937 }
938
939
940 /* You have to call the following methods to make your drawing operations
941 really visible on the screen. (However, the update will take place only
942 at the next call to doUpdate) */
updateRect(SDL_Rect dst)943 void updateRect(SDL_Rect dst) {
944 if (!mUpdateAll) {
945 transformA2V(dst);
946 if (mActDepth != 0) {
947 SDL_Rect inter;
948 if (!SDLTools::intersection(dst,mBounds[mActDepth],inter))
949 return;
950 dst = inter;
951 }
952
953 transformV2S(dst);
954 boundS(dst);
955 if (mNumUpdateRects >= mReservedUpdateRects)
956 needMoreUpdateRects();
957 mUpdateRects[mNumUpdateRects++] = dst;
958 }
959 }
960
961
updateRect(int x,int y,int w,int h)962 void updateRect(int x, int y, int w, int h) {
963 updateRect(SDLTools::rect(x, y, w, h));
964 }
965
966 /* Better than calling updateRect(bigRect): Stops collecting small
967 rectangles. All means really all, not only active area. */
updateAll()968 void updateAll() {
969 mUpdateAll = true;
970 }
971
972
973
974 /* To be called only by ui.cpp */
doUpdate()975 void doUpdate() {
976 if (mUpdateAll)
977 SDL_UpdateRect(SDL_GetVideoSurface(), 0, 0, 0, 0);
978 else {
979 //print_to_stderr(_sprintf("%d\n", mNumUpdateRects));
980 SDL_UpdateRects(SDL_GetVideoSurface(), mNumUpdateRects, mUpdateRects);
981 }
982 mUpdateAll = false;
983 mNumUpdateRects = 0;
984 }
985
986 }
987
988