1 // This code is D 1.0
2 
3 module arsd.screen;
4 
5 import sdl.SDL;
6 import sdl.SDL_image;
7 import sdl.SDL_ttf;
8 import std.string;
9 
10 import std.stdio;
11 import std.format;
12 
13 import arsd.engine;
14 
15 version(D_Version2)
16 static import stdcstring = core.stdc.string;
17 else
18 static import stdcstring = std.c.string;
19 
20 version(none)
21 char[] fmt(...){
22     char[] o;
23     void putc(dchar c)
24     {
25     	o ~= c;
26     }
27 
28     std.format.doFormat(&putc, _arguments, _argptr);
29 
30 	return o;
31 }
32 
33 
34 extern(C){
35 	void glGetIntegerv(int, void*);
36 	void glMatrixMode(int);
37 	void glPushMatrix();
38 	void glLoadIdentity();
39 	void glOrtho(double, double, double, double, double, double);
40 	void glPopMatrix();
41 	void glEnable(int);
42 	void glDisable(int);
43 	void glClear(int);
44 	void glBegin(int);
45 	void glVertex2f(float, float);
46 	void glEnd();
47 	void glColor3b(ubyte, ubyte, ubyte);
48 	void glColor3i(int, int, int);
49 	void glColor3f(float, float, float);
50 	void glColor4f(float, float, float, float);
51 	void glTranslatef(float, float, float);
52 
53 	void glRotatef(float, float, float, float);
54 
55 	uint glGetError();
56 
57 	void glDeleteTextures(int, uint*);
58 
59 	char* gluErrorString(uint);
60 
61 	void glRasterPos2i(int, int);
62 	void glDrawPixels(int, int, uint, uint, void*);
63 	void glClearColor(float, float, float, float);
64 
65 
66 
67 	void glGenTextures(uint, uint*);
68 	void glBindTexture(int, int);
69 	void glTexParameteri(uint, uint, int);
70 	void glTexImage2D(int, int, int, int, int, int, int, int, void*);
71 
72 
73 	void glTexCoord2f(float, float);
74 	void glVertex2i(int, int);
75 	void glBlendFunc (int, int);
76 	void glViewport(int, int, int, int);
77 
78 	void glReadBuffer(uint);
79 	void glReadPixels(int, int, int, int, int, int, void*);
80 
81 
82 	const uint GL_FRONT = 0x0404;
83 
84 	const uint GL_BLEND = 0x0be2;
85 	const uint GL_SRC_ALPHA = 0x0302;
86 	const uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;
87 
88 
89 	const uint GL_UNSIGNED_BYTE = 0x1401;
90 	const uint GL_RGB = 0x1907;
91 	const uint GL_BGRA = 0x80e1;
92 	const uint GL_RGBA = 0x1908;
93 	const uint GL_TEXTURE_2D =   0x0DE1;
94 	const uint GL_TEXTURE_MIN_FILTER = 0x2801;
95 	const uint GL_NEAREST = 0x2600;
96 	const uint GL_LINEAR = 0x2601;
97 	const uint GL_TEXTURE_MAG_FILTER = 0x2800;
98 
99 	const uint GL_NO_ERROR = 0;
100 
101 
102 
103 	const int GL_VIEWPORT = 0x0BA2;
104 	const int GL_MODELVIEW = 0x1700;
105 	const int GL_TEXTURE = 0x1702;
106 	const int GL_PROJECTION = 0x1701;
107 	const int GL_DEPTH_TEST = 0x0B71;
108 
109 	const int GL_COLOR_BUFFER_BIT = 0x00004000;
110 	const int GL_ACCUM_BUFFER_BIT = 0x00000200;
111 	const int GL_DEPTH_BUFFER_BIT = 0x00000100;
112 
113 	const int GL_POINTS = 0x0000;
114 	const int GL_LINES =  0x0001;
115 	const int GL_LINE_LOOP = 0x0002;
116 	const int GL_LINE_STRIP = 0x0003;
117 	const int GL_TRIANGLES = 0x0004;
118 	const int GL_TRIANGLE_STRIP = 5;
119 	const int GL_TRIANGLE_FAN = 6;
120 	const int GL_QUADS = 7;
121 	const int GL_QUAD_STRIP = 8;
122 	const int GL_POLYGON = 9;
123 
124 }
125 
126 public struct Point{
127 	int x;
128 	int y;
129 	Point opAddAssign(Point p){
130 		x += p.x;
131 		y += p.y;
132 		version(D_Version2)
133 			return this;
134 		else
135 			return *this;
136 	}
137 
138 	Point opAdd(Point p){
139 		Point a;
140 		a.x = x + p.x;
141 		a.y = y + p.y;
142 		return a;
143 	}
144 
145 	Point opSub(Point p){
146 		Point a;
147 		a.x = x - p.x;
148 		a.y = y - p.y;
149 		return a;
150 	}
151 }
152 
153 Point XY(int x, int y){
154 	Point p;
155 	p.x = x;
156 	p.y = y;
157 	return p;
158 }
159 
160 Point XY(float x, float y){
161 	Point p;
162 	p.x = cast(int)x;
163 	p.y = cast(int)y;
164 	return p;
165 }
166 
167 public struct Color{
168 	int r;
169 	int g;
170 	int b;
171 	int a;
172 
173 	uint toInt(){
174 		return r << 24 | g << 16 | b << 8 | a;
175 	}
176 
177 	void fromInt(uint i){
178 		r = i >> 24;
179 		g = (i >> 16) & 0x0ff;
180 		b = (i >> 8) & 0x0ff;
181 		a = i & 0x0ff;
182 	}
183 }
184 
185 Color white = {255, 255, 255, 255};
186 Color black = {0, 0, 0, 255};
187 
188 Color RGB(int r, int g, int b){
189 	Color c;
190 	c.r = r;
191 	c.g = g;
192 	c.b = b;
193 	c.a = 255;
194 	return c;
195 }
196 
197 Color RGBA(int r, int g, int b, int a){
198 	Color c;
199 	c.r = r;
200 	c.g = g;
201 	c.b = b;
202 	c.a = a;
203 	return c;
204 }
205 
206 Color XRGB(Color c, int alpha = -1){
207 	Color a;
208 	a.r = c.r ^ 255;
209 	a.g = c.g ^ 255;
210 	a.b = c.b ^ 255;
211 	if(alpha == -1)
212 		a.a = c.a;// ^ 255;
213 	else
214 		a.a = alpha;
215 	return a;
216 }
217 
218 class FontEngine{
219   public:
220 
221 	static FontEngine instance;
222 
223 	~this(){
224 		foreach(a; fonts)
225 			if(a != null)
226 				TTF_CloseFont(a);
227 		TTF_Quit();
228 	}
229 
230 	void loadFont(in char[] font, int size = 12, int index = 0){
231 		if(fonts[index] != null)
232 			freeFont(index);
233 		TTF_Font* temp;
234 		temp = TTF_OpenFont(std..string.toStringz(font), size);
235 		if(temp == null)
236 			throw new Exception("load font");
237 
238 		fonts[index] = temp;
239 	}
240 
241 	void freeFont(int index = 0){
242 		if(fonts[index] != null){
243 			TTF_CloseFont(fonts[index]);
244 			fonts[index] = null;
245 		}
246 	}
247 
248 	Image renderText(in char[] text, Color foreground = RGB(255,255,255), int font = 0){
249 		Image* a = immutableString(text) in cache[font];
250 		if(a !is null)
251 			return *a;
252 		SDL_Color f;
253 		f.r = cast(ubyte) foreground.r;
254 		f.g = cast(ubyte) foreground.g;
255 		f.b = cast(ubyte) foreground.b;
256 		f.unused = cast(ubyte) foreground.a;
257 
258 		SDL_Surface* s = TTF_RenderText_Blended(fonts[font], std..string.toStringz(text), f);
259 		Image i = new Image(s);
260 		cache[font][text]/*[font]*/ = i;
261 
262 		return i;
263 	}
264 
265 	int textHeight(in char[] text=" ",int font = 0){
266 		int w, h;
267 		TTF_SizeText(FontEngine.instance.fonts[font], std..string.toStringz(text), &w, &h);
268 		return h;
269 	}
270 
271 	void textSize(in char[] text, out int w, out int h, int font = 0){
272 		TTF_SizeText(fonts[font], std..string.toStringz(text), &w, &h);
273 	}
274   private:
275 	static this() {
276 		instance = new FontEngine;
277 	}
278 
279 	this(){
280 		if(TTF_Init() == -1)
281 			throw new Exception("TTF_Init");
282 
283 	}
284 
285 	TTF_Font*[8] fonts;
286 	Image[char[]][8] cache;
287 }
288 
289 interface Drawable{
290   public:
291 	void flip();
292 	int width();
293 	int height();
294 	int bpp();
295 	/*
296 	uint toGL();
297 	float texWidth();
298 	float texHeight();
299 	*/
300   protected:
301 	SDL_Surface* surface();
302 }
303 
304 int total = 0;
305 
306 
307 class Image : Drawable{
308   public:
309   	this(SDL_Surface* s){
310 		if(s == null)
311 			throw new Exception("Image");
312 		sur = s;
313 	}
314 
315 	/// Loads an image with the filename checking to see if it has already been loaded into the cache
316 	/// loads it as read-only
317 //	static Image load(char[] filename){
318 
319 //	}
320 
321 	this(char[] filename){
322 		sur = IMG_Load(std..string.toStringz(filename));
323 		if(sur == null)
324 			throw new Exception(immutableString("Load " ~ filename));
325 		name = filename;
326 	}
327 
328 
329 	void replace(char[] filename){
330 		if(t){
331 			glDeleteTextures(1, &tex);
332 			total--;
333 			writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total);
334 			t = 0;
335 		}
336 		if(sur){
337 			SDL_FreeSurface(sur);
338 			sur = null;
339 		}
340 		sur = IMG_Load(std..string.toStringz(filename));
341 		if(sur == null)
342 			throw new Exception(immutableString("Load " ~ filename));
343 		name = filename;
344 	}
345 
346 
347 	// loads a slice of an image
348 	this(char[] filename, int x, int y, int wid, int hei){
349 	/*
350 		Image i = new Image(filename);
351 		this(wid, hei);
352 
353 		scope Painter p = new Painter(this);
354 		for(int a = 0; a < wid; a++)
355 		for(int b = 0; b < hei; b++)
356 			p.putpixel(XY(a, b), i.getPixel(XY(a + x, b + y)));
357 	*/
358 	
359 		SDL_Surface* s1;
360 
361 		s1 = IMG_Load(std..string.toStringz(filename));
362 		if(s1 == null)
363 			throw new Exception(immutableString("Loading " ~ filename));
364 		scope(exit)
365 			SDL_FreeSurface(s1);
366 
367 
368 		sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000);
369 		if(sur == null)
370 			throw new Exception(immutableString("Create"));
371 
372 
373 		for(int b = 0; b < hei; b++){
374 			for(int a = 0; a < wid; a++){
375 			if(b+y >= s1.h || a+x >= s1.w){
376 				break;
377 			//	throw new Exception("eat my cum");
378 			}
379 				ubyte* wtf;
380 				if(s1.format.BitsPerPixel == 32){
381 					wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 4);
382 				}
383 				else
384 				if(s1.format.BitsPerPixel == 24)
385 					wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 3);
386 				else
387 					throw new Exception("fuck me in the ass");
388 
389 				ubyte* good = cast(ubyte*)(cast(ubyte*)sur.pixels + b*sur.pitch + a * 4);
390 
391 				good[0] = wtf[2];
392 				good[1] = wtf[1];
393 				good[2] = wtf[0];
394 				good[3] = wtf[3];
395 
396 			}
397 		}
398 
399 
400 /*
401 		SDL_Rect r;
402 		r.x = x;
403 		r.y = y;
404 		r.w = wid;
405 		r.h = hei;
406 
407 		SDL_Rect r2;
408 		r2.x = 0;
409 		r2.y = 0;
410 		r2.w = wid;
411 		r2.h = hei;
412 		if(SDL_BlitSurface(s1, &r, sur, &r2))
413 			throw new Exception("Blit");
414 */
415 	}
416 
417 	this(int wid, int hei){
418 		sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000);
419 		if(sur == null)
420 			throw new Exception("Create");
421 		t = false;
422 	}
423 
424 	~this(){
425 		if(t){
426 			glDeleteTextures(1, &tex);
427 			total--;
428 			writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total);
429 		}
430 		if(sur)
431 			SDL_FreeSurface(sur);
432 	}
433 
434 	void flip(){
435 
436 	}
437 
438 	int width(){
439 		return surface.w;
440 	}
441 
442 	int height(){
443 		return surface.h;
444 	}
445 
446 	int bpp(){
447 		return sur.format.BitsPerPixel;
448 	}
449 
450 	Color getPixel(Point p){
451 		ubyte* bufp;
452 		Color a;
453 
454 		if(bpp == 32){
455 			bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 4);
456 			a.a = bufp[3];
457 		        a.r = bufp[2];
458 		        a.g = bufp[1];
459         		a.b = bufp[0];
460 		}else{
461 			bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 3);
462 		        a.a = 255;
463 			a.r = bufp[2];
464 		       	a.g = bufp[1];
465 	        	a.b = bufp[0];
466 		}
467 	
468 		return a;
469 	}
470 
471 	uint toGL(){
472 		if(t)
473 			return tex;
474 		else{
475 			float[4] f;
476 			tex = SDL_GL_LoadTexture(surface, f.ptr);
477 			t = true;
478 			total++;
479 			texWidth = f[2];
480 			texHeight = f[3];
481 
482 //			total++;
483 //			writef("OpenGL texture created %d. %d exist\n", tex, total);
484 
485 			return tex;
486 		}
487 	}
488   protected:
489 	SDL_Surface* surface(){
490 		return sur;
491 	}
492   private:
493   	SDL_Surface* sur;
494 	uint tex;
495 	bool t;
496 	float texWidth;
497 	float texHeight;
498 	char[] name;
499 }
500 
501 bool useGL;
502 
503 class Screen : Drawable{
504   public:
505 	this(int xres = 1024, int yres = 768, int bpp = 24, bool oGL = false, bool fullScreen = false){//true){
506 //	oGL = false;
507 	oGL = true;
508 		if(!oGL)
509 			screen = SDL_SetVideoMode(xres, yres, bpp/*32*/, SDL_SWSURFACE);
510 		else{
511 			SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
512 			SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
513 			SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
514 			SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
515 			SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
516 			if(fullScreen){
517 			screen = SDL_SetVideoMode(xres, yres, 24, SDL_OPENGL| SDL_FULLSCREEN);
518 			}
519 			else
520 			screen = SDL_SetVideoMode(xres, yres, 0, SDL_OPENGL);
521 			//screen = SDL_SetVideoMode(xres, yres, bpp, SDL_OPENGL);
522 			if(screen is null)
523 				throw new Exception("screen");
524 
525    			glMatrixMode(GL_PROJECTION);
526    			glLoadIdentity();
527    			//glOrtho(0, 1000, yres, 0, 0, 1);
528    			glOrtho(0, xres, yres, 0, 0, 1);
529    			glMatrixMode(GL_MODELVIEW);
530 
531 			glDisable(GL_DEPTH_TEST);
532 
533 			glEnable(GL_TEXTURE_2D);
534 
535 
536 			glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
537 
538 
539 			glClearColor(0,0,0,0);
540 
541 //			glViewport(0,0,1024,1024);
542 		}
543 
544 		useGL = oGL;
545 
546 		if(screen == null)
547 			throw new Exception("screen");
548 
549 //		SDL_SetAlpha(screen, SDL_SRCALPHA | SDL_RLEACCEL, 128);
550 
551 		xr = xres;
552 		yr = yres;
553 	}
554 
555 	void switchSplitScreenMode(int player, int numberOfPlayers, bool horizontal){
556 			switch(numberOfPlayers){
557 				default: assert(0);
558 				case 1:
559 					return;
560 //					glViewport(0, 0, xr, yr);
561 				break;
562 				case 2:
563 					switch(player){
564 						default: assert(0);
565 						case 0:
566 							if(horizontal)
567 								glViewport(0, yr / 2, xr, yr / 2);
568 							else
569 								glViewport(0, 0, xr / 2, yr);
570 						break;
571 						case 1:
572 							if(horizontal)
573 								glViewport(0, 0, xr, yr / 2);
574 							else
575 								glViewport(xr / 2, 0, xr / 2, yr);
576 						break;
577 					}
578 				break;
579 				case 3:
580 				case 4:
581 					switch(player){
582 						default: assert(0);
583 					  case 0:
584 						glViewport(0, yr / 2, xr / 2, yr / 2);
585 					  break;
586 					  case 1:
587 						glViewport(xr / 2, yr / 2, xr / 2, yr / 2);
588 					  break;
589 					  case 2:
590 						glViewport(0, 0, xr / 2, yr / 2);
591 					  break;
592 					  case 3:
593 						glViewport(xr / 2, 0, xr / 2, yr / 2);
594 					  break;
595 					}
596 
597 				break;
598 			}
599 			glMatrixMode(GL_PROJECTION);
600 			glLoadIdentity();
601 			glOrtho(0, xr, yr, 0, 0, 1);
602 	}
603 
604 
605 	~this(){
606 		delete FontEngine.instance;
607 	}
608 
609 	Image screenshot(){
610 		if(!useGL)
611 			throw new Exception("Not yet implemented");
612 		Image image = new Image(xr, yr);
613 		glReadBuffer(GL_FRONT);
614 		glReadPixels(0, 0, xr, yr, GL_BGRA, GL_UNSIGNED_BYTE, image.sur.pixels);
615 
616 		Image temp = new Image(xr, yr);
617 
618 
619 	// FIXME
620 	version(Windows)
621 		return image;
622 
623 		// FIXME: this crashes on Windows
624 		for(int i = 0; i < yr; i++)
625 			stdcstring.memcpy(temp.sur.pixels + 4 * xr * i, image.sur.pixels + 4 * xr * (yr-1 - i), 4 * xr);
626 //        memcpy(image.sur.pixels, tem.psur.pixels, xres * yres * 4);
627 
628 		return temp;
629 	}
630 
631 
632 
633 
634 	void flip(){
635 		if(useGL)
636 			SDL_GL_SwapBuffers();
637 		else
638 			SDL_Flip(screen);
639 	}
640 
641 	int width(){
642 		return xr;
643 	}
644 
645 	int height(){
646 		return yr;
647 	}
648 
649 	int bpp(){
650 		return 124;
651 	}
652 	/*
653 	uint toGL(){
654 		throw new Error;
655 	}
656 	float texWidth(){
657 		return 1.0;
658 	}
659 	float texHeight(){
660 		return 1.0;
661 	}
662 */
663   protected:
664 	SDL_Surface* surface(){
665 		return screen;
666 	}
667 
668   private:
669 	SDL_Surface* screen;
670 	int xr;
671 	int yr;
672 }
673 
674 scope class Painter{
675   public:
676 	bool special;
677 	bool manualFlipped;
678 	Point translate;
679 	this(Painter p, Point t){
680 		s = p.s;
681 		special = true;
682 		translate = t;
683 	}
684 
685 	this(Drawable d){
686 	/+
687 		in {
688 			assert(!(s is null));
689 		}
690 	+/
691 		s = d;
692 		if(s is null)
693 			throw new Exception("christ what were you thinking");
694 
695 		if ( !(useGL
696 		&& s.bpp() == 124)
697 		&& SDL_MUSTLOCK(s.surface()) ) {
698 			if ( SDL_LockSurface(s.surface()) < 0 ) {
699 				throw new Exception("locking");
700 			}
701 			locked = true;
702 		}
703 	}
704 
705 	~this(){
706 		if(!manualFlipped){
707 		if(glbegin)
708 			endDrawingShapes();
709 		if(!special){
710 		if (locked){
711 			SDL_UnlockSurface(s.surface);
712 		}
713 		s.flip();
714 		}
715 		}
716 	}
717 
718 	void manualFlip(){
719 		if(glbegin)
720 			endDrawingShapes();
721 		if(!special){
722 		if (locked){
723 			SDL_UnlockSurface(s.surface);
724 		}
725 		s.flip();
726 		}
727 		manualFlipped = true;
728 	}
729 
730 	void setGLColor(Color color){
731 		if(useGL && s.bpp == 124){
732 			glColor4f(cast(float)color.r/255.0, cast(float)color.g/255.0, cast(float)color.b/255.0, cast(float)color.a / 255.0);
733 			return;
734 		}
735 	}
736 
737 	void putpixel(Point where, Color color){
738 		if(special) where += translate;
739 		if(glbegin)
740 			throw new Exception("Must end shape before doing anything else");
741 
742 //		if(color.a == 255)
743 //			return;
744 		int x = where.x;
745 		int y = where.y;
746 		if(x < 0 || x >= s.width || y < 0 || y >= s.height)
747 			return;
748 
749 
750 
751 		if(useGL && s.bpp == 124){
752 		//	y = 480 - y;
753 			glBegin(GL_POINTS);
754 			setGLColor(color);
755 			glVertex2f(cast(float)x, cast(float)y);
756 			glEnd();
757 			return;
758 		}
759 
760 
761 		ubyte *bufp;
762 
763 		if(s.bpp == 32){
764 			bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 4);
765 				bufp[3] = cast(ubyte) color.a;
766 			        bufp[2] = cast(ubyte) color.r;
767 			        bufp[1] = cast(ubyte) color.g;
768         			bufp[0] = cast(ubyte) color.b;
769 		}else{
770 			bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 3);
771 			if(color.a == 255){
772 			        bufp[2] = cast(ubyte) color.r;
773 		        	bufp[1] = cast(ubyte) color.g;
774 	        		bufp[0] = cast(ubyte) color.b;
775 			}
776 			else{
777 			        bufp[2] = cast(ubyte)(bufp[2] * (255-color.a) + (color.r * (color.a)) / 255);
778 			        bufp[1] = cast(ubyte)(bufp[1] * (255-color.a) + (color.g * (color.a)) / 255);
779 		        	bufp[0] = cast(ubyte)(bufp[0] * (255-color.a) + (color.b * (color.a)) / 255);
780 			}
781 		}
782 	}
783 
784 	void beginDrawingLines(){
785 		if(glbegin)
786 			throw new Exception("Can only draw one kind at a time");
787 		glbegin = true;
788 		if(useGL && s.bpp == 124)
789 			glBegin(GL_LINES); 
790 	}
791 	void beginDrawingConnectedLines(){
792 		if(glbegin)
793 			throw new Exception("Can only draw one kind at a time");
794 		glbegin = true;
795 		if(useGL && s.bpp == 124)
796 			glBegin(GL_LINE_STRIP); 
797 	}
798 	void beginDrawingPolygon(){
799 		if(glbegin)
800 			throw new Exception("Can only draw one kind at a time");
801 		glbegin = true;
802 		if(useGL && s.bpp == 124)
803 			glBegin(GL_POLYGON); 
804 	}
805 	void beginDrawingTriangles(){
806 		if(glbegin)
807 			throw new Exception("Can only draw one kind at a time");
808 		glbegin = true;
809 		if(useGL && s.bpp == 124)
810 			glBegin(GL_TRIANGLES); 
811 	}
812 	void beginDrawingBoxes(){
813 		if(glbegin)
814 			throw new Exception("Can only draw one kind at a time");
815 		glbegin = true;
816 		if(useGL && s.bpp == 124)
817 			glBegin(GL_QUADS); 
818 	}
819 	void beginDrawingPoints(){
820 		if(glbegin)
821 			throw new Exception("Can only draw one kind at a time");
822 		glbegin = true;
823 		if(useGL && s.bpp == 124)
824 			glBegin(GL_POINTS); 
825 	}
826 
827 	void endDrawingShapes(){
828 		if(!glbegin)
829 			return;
830 		glbegin = false;
831 		if(useGL && s.bpp == 124)
832 			glEnd();
833 	}
834 	void vertex(Point p){
835 		if(special) p += translate;
836 		if(!glbegin)
837 			throw new Exception("Can't use vertex without beginning first");
838 		if(useGL && s.bpp == 124)
839 			glVertex2i(p.x, p.y);
840 	}
841 	bool glbegin;
842 
843 	void drawImageRotated(Point where, Image i, float a, Color color = RGB(255,255,255)){
844 		if(i is null)
845 			return;
846 		glPushMatrix();
847 	//	glRotatef(a, cast(float)(where.x + 32) / s.width, cast(float)(where.y + 32) / s.height, 1);
848 		glTranslatef(where.x, where.y, 0);
849 		glRotatef(a, 0,0, 1);
850 		drawImage(XY(-i.width/2,-i.height/2), i, i.width, i.height, color);
851 		glPopMatrix();
852 	}
853 
854 	void drawImage(Point where, Image i, Color c){
855 		drawImage(where, i, 0, 0, c);
856 	}
857 
858 	void drawImage(Point where, Image i, int W = 0, int H = 0, Color c = RGBA(255,255,255,255)){
859 		if(i is null)
860 			return;
861 
862 		if(special) where += translate;
863 		if(glbegin)
864 			throw new Exception("Must end shape before doing anything else");
865 		if(useGL && s.bpp == 124){
866 			int x = where.x;
867 			int y = where.y;
868 			int w = W == 0 ? i.width : W;
869 			int h = H == 0 ? i.height : H;
870 
871 //			glColor4f(.5,.5,.5,1);
872 			setGLColor(c);
873 			glBindTexture(GL_TEXTURE_2D, i.toGL);
874 			glBegin(GL_QUADS); 
875 				glTexCoord2f(0, 0); 			glVertex2i(x, y);
876 				glTexCoord2f(i.texWidth, 0); 		glVertex2i(x+w, y); 
877 				glTexCoord2f(i.texWidth, i.texHeight); 	glVertex2i(x+w, y+h); 
878 				glTexCoord2f(0, i.texHeight); 		glVertex2i(x, y+h); 
879 			glEnd();
880 
881 			glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture... I guess
882 				// I don't actually understand why that is needed
883 				// but without it, everything drawn after it is wrong (too light or dark)
884 			return;
885 		}
886 
887 		if((W == 0 && H == 0) || (i.width == W && i.height == H)){
888 		SDL_Rect r;
889 		r.x = cast(short)( where.x);
890 		r.y = cast(short)( where.y);
891 		r.w = cast(short)( i.width);
892 		r.h = cast(short)( i.height);
893 		if(locked)
894 			SDL_UnlockSurface(s.surface);
895 
896 		if(SDL_BlitSurface(i.surface, null, s.surface, &r) == -1)
897 			throw new Exception("blit");
898 
899 		if ( SDL_MUSTLOCK(s.surface) ) {
900 			if ( SDL_LockSurface(s.surface) < 0 ) {
901 				throw new Exception("lock");
902 			}
903 			locked = true;
904 		}
905 		} else { // quick and dirty scaling needed
906 			float dx = cast(float)i.width / cast(float)W;
907 			float dy = cast(float)i.height / cast(float)H;
908 			int X = where.x, Y = where.y;
909 
910 			for(float y = 0; y < i.height; y += dy){
911 				for(float x = 0; x < i.width; x += dx){
912 					putpixel(XY(X, Y), i.getPixel(XY(cast(int) x, cast(int) y)));
913 					X++;
914 				}
915 				X = where.x;
916 				Y++;
917 			}
918 
919 		}
920 
921 	}
922 
923 	void drawText(Point where, in char[] text, Color foreground = RGB(255,255,255), int font = 0){
924 		if(glbegin)
925 			throw new Exception("Must end shape before doing anything else");
926 
927 		if(useGL && s.bpp == 124){
928 			Image i = FontEngine.instance.renderText(text, RGB(255,255,255), font);
929 			drawImage(where, i, foreground);
930 		}else{
931 			Image i = FontEngine.instance.renderText(text, foreground, font);
932 			drawImage(where, i);
933 		}
934 	}
935 
936 	version(D_Version2) {
937 		import std.format;
938 		void drawTextf(T...)(Point where, T args) {
939 			char[] t;
940 			t.length = 80;
941 			int a = 0;
942 			void putc(dchar c){
943 				if(a == t.length)
944 					t.length = t.length + 80;
945 				t[a] = cast(char) c;
946 				a++;
947 			}
948 			formattedWrite(&putc, args);
949 			t.length = a;
950 
951 			drawText(where, t);
952 		}
953 	} else
954 	void drawTextf(Point where, ...){
955 		char[] t;
956 		t.length = 80;
957 		int a = 0;
958 		void putc(dchar c){
959 			if(a == t.length)
960 				t.length = t.length + 80;
961 			t[a] = cast(char) c;
962 			a++;
963 		}
964 		std.format.doFormat(&putc, _arguments, _argptr);
965 		t.length = a;
966 
967 		drawText(where, t);
968 	}
969 
970 	int wordLength(in char[] w, int font = 0){
971 		int a,b;
972 		FontEngine.instance.textSize(w, a, b, font);
973 		return a;
974 	}
975 
976 	int drawTextBoxed(Point where, char[] text, int width, int height, Color foreground = RGB(255,255,255), int font = 0){
977 		if(glbegin)
978 			throw new Exception("Must end shape before doing anything else");
979 
980 
981 		int xc;
982 		int yc = TTF_FontLineSkip(FontEngine.instance.fonts[font]);
983 
984 		int w = 0;
985 		int h = 0;
986 
987 		int l;
988 
989 char[] getWord(){
990 	int a = l;
991 	while(a < text.length && text[a] != ' ' && text[a] != '\n' && text[a] != '\t')
992 		a++;
993 	return text[l..a];
994 }
995 
996 int wordLength(in char[] w){
997 	int a,b;
998 	FontEngine.instance.textSize(w, a, b, font);
999 	return a;
1000 }
1001 
1002 		Point ww = where;
1003 		while(l < text.length){
1004 			if(text[l] == '\n'){
1005 				l++;
1006 				goto newline;
1007 			}
1008 			if(wordLength(getWord()) + w > width){
1009 				goto newline;
1010 			}
1011 
1012 			if(!(w == 0 && text[l] == ' ')){
1013 				TTF_GlyphMetrics(FontEngine.instance.fonts[font], text[l], null,null,null,null,&xc);
1014 				drawText(ww, text[l..(l+1)], foreground, font);
1015 				w+=xc;
1016 				ww.x += xc;
1017 			}
1018 			l++;
1019 			if(w > (width - xc)){
1020 			newline:
1021 				w = 0;
1022 				h += yc;
1023 				ww.x = cast(short)(where.x);
1024 				ww.y += cast(short)(yc);
1025 
1026 				if(h > (height - yc))
1027 					break;
1028 			}
1029 		}
1030 		return l;
1031 	}
1032 
1033 	void drawTextCenteredHoriz(int top, char[] text, Color foreground, int font = 0){
1034 		Point where;
1035 		where.y = top;
1036 		int w, h;
1037 		TTF_SizeText(FontEngine.instance.fonts[font], std..string.toStringz(text), &w, &h);
1038 		where.x = (s.width - w) / 2;
1039 		drawText(where, text, foreground, font);
1040 	}
1041 
1042 	void line(Point start, Point end, Color color){
1043 		if(special){ start += translate; end += translate; }
1044 		if(glbegin)
1045 			throw new Exception("Must end shape before doing anything else");
1046 
1047 		if(useGL && s.bpp == 124){
1048 			setGLColor(color);
1049 			glBegin(GL_LINES);
1050 			glVertex2i(start.x, start.y);
1051 			glVertex2i(end.x, end.y);
1052 			glEnd();
1053 		}
1054 	}
1055 	
1056 	void hline(Point start, int width, Color color){
1057 	if(useGL && s.bpp == 124){
1058 		line(start, XY(start.x + width, start.y), color);
1059 		return;
1060 	}
1061 		Point point = start;
1062 		for(int a = 0; a < width; a++){
1063 			putpixel(point, color);
1064 			point.x++;
1065 		}
1066 	}
1067 
1068 	void vline(Point start, int height, Color color){
1069 	if(useGL && s.bpp == 124){
1070 		line(start, XY(start.x, start.y + height), color);
1071 		return;
1072 	}
1073 
1074 		Point point = start;
1075 		for(int a = 0; a < height; a++){
1076 			putpixel(point, color);
1077 			point.y++;
1078 		}
1079 	}
1080 	
1081 
1082 	void circle(Point center, int radius, Color color){
1083 		if(special) center += translate;
1084 		if(glbegin)
1085 			throw new Exception("Must end shape before doing anything else");
1086 
1087 	}
1088 
1089 	void arc(Point center, int radius, float start, float end, Color color){
1090 		if(special) center += translate;
1091 		if(glbegin)
1092 			throw new Exception("Must end shape before doing anything else");
1093 
1094 //		for(float a = start; a <= end; a+= (3.14159265358 / 50.0))
1095 //			putpixel((int)(cos(a) * (float)radius + center.x()),(int)( sin(a) * (float) radius + center.y()), color);
1096 	}
1097 
1098 	void box(Point upperLeft, Point lowerRight, Color color){
1099 		if(special) { upperLeft += translate; lowerRight += translate; }
1100 		if(glbegin)
1101 			throw new Exception("Must end shape before doing anything else");
1102 
1103 		if(useGL && s.bpp == 124){
1104 			int x1 = upperLeft.x;
1105 			int y1 = upperLeft.y;
1106 			int x2 = lowerRight.x;
1107 			int y2 = lowerRight.y;
1108 			glBegin(GL_QUADS);
1109 			//glColor3b(color.r, color.g, color.b);
1110 			setGLColor(color);
1111 			//glColor4f(1,1,1,1);
1112 			glVertex2i(x1, y1);
1113 			glVertex2i(x2, y1);
1114 			glVertex2i(x2, y2);
1115 			glVertex2i(x1, y2);
1116 			glEnd();
1117 			return;
1118 		}
1119 		SDL_Rect r;
1120 		r.x = cast(short) upperLeft.x;
1121 		r.y = cast(short) upperLeft.y;
1122 		r.w = cast(short) (lowerRight.x - upperLeft.x);
1123 		r.h = cast(short) (lowerRight.y - upperLeft.y);
1124 		if(s.bpp == 32)
1125 			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
1126 		else
1127 			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
1128 	}
1129 
1130 	void rect(Point upperLeft, Point lowerRight, Color color){
1131 		if(special) { upperLeft += translate; lowerRight += translate; }
1132 		if(glbegin)
1133 			throw new Exception("Must end shape before doing anything else");
1134 
1135 		if(useGL && s.bpp == 124){
1136 			int x1 = upperLeft.x;
1137 			int y1 = upperLeft.y;
1138 			int x2 = lowerRight.x;
1139 			int y2 = lowerRight.y;
1140 			glBegin(GL_LINE_LOOP);
1141 			//glColor3b(color.r, color.g, color.b);
1142 			setGLColor(color);
1143 			//glColor4f(1,1,1,1);
1144 			glVertex2i(x1, y1);
1145 			glVertex2i(x2+1, y1);
1146 			glVertex2i(x2, y2);
1147 			glVertex2i(x1, y2);
1148 			glEnd();
1149 			return;
1150 		}
1151 		/*
1152 		SDL_Rect r;
1153 		r.x = upperLeft.x;
1154 		r.y = upperLeft.y;
1155 		r.w = lowerRight.x - upperLeft.x;
1156 		r.h = lowerRight.y - upperLeft.y;
1157 		if(s.bpp == 32)
1158 			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
1159 		else
1160 			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
1161 		*/
1162 	}
1163 
1164 	void gbox(Point upperLeft, Point lowerRight, Color color1, Color color2, Color color3, Color color4){
1165 		if(special) { upperLeft += translate; lowerRight += translate; }
1166 		if(glbegin)
1167 			throw new Exception("Must end shape before doing anything else");
1168 
1169 		Color color = color1;
1170 		if(useGL && s.bpp == 124){
1171 			int x1 = upperLeft.x;
1172 			int y1 = upperLeft.y;
1173 			int x2 = lowerRight.x;
1174 			int y2 = lowerRight.y;
1175 			glBegin(GL_QUADS);
1176 			setGLColor(color1);
1177 			glVertex2i(x1, y1);
1178 			setGLColor(color2);
1179 			glVertex2i(x2, y1);
1180 			setGLColor(color4);
1181 			glVertex2i(x2, y2);
1182 			setGLColor(color3);
1183 			glVertex2i(x1, y2);
1184 			glEnd();
1185 		return;
1186 		}
1187 		SDL_Rect r;
1188 		r.x = cast(short) upperLeft.x;
1189 		r.y = cast(short) upperLeft.y;
1190 		r.w = cast(short) (lowerRight.x - upperLeft.x);
1191 		r.h = cast(short) (lowerRight.y - upperLeft.y);
1192 		if(s.bpp == 32)
1193 			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
1194 		else
1195 			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
1196 	}
1197 
1198 
1199 	void clear(){
1200 		if(useGL && s.bpp == 124){
1201 			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
1202 			return;
1203 		}
1204 		box(XY(0,0), XY(s.width, s.height), RGB(0,0,0));
1205 	}
1206 
1207 	void fill(Color color){
1208 		box(XY(0,0), XY(s.width, s.height), color);
1209 	}
1210 
1211 	void blend(Color color){
1212 		if(useGL && s.bpp == 124){
1213 			box(XY(0,0), XY(s.width, s.height), color);
1214 			return;
1215 		}
1216 
1217 		ubyte *bufp;
1218 
1219 		bufp = cast(ubyte*)s.surface.pixels;
1220 		for(int y = 0; y < s.height; y++)
1221 			for(int x = 0; x < s.width; x++){
1222 			
1223 				bufp[2] = cast(ubyte)((bufp[2] * (255-color.a) + color.r * color.a) / 255);
1224 			        bufp[1] = cast(ubyte)((bufp[1] * (255-color.a) + color.g * color.a) / 255);
1225 		        	bufp[0] = cast(ubyte)((bufp[0] * (255-color.a) + color.b * color.a) / 255);
1226 
1227 				bufp += (s.bpp == 24 ? 3 : 4);
1228 			}
1229 	}
1230 
1231   private:
1232   	Drawable s;
1233 	bool locked;
1234 }
1235 
1236 
1237 
1238 
1239 
1240 
1241 
1242 int SDL_BlitSurface
1243 			(SDL_Surface *src, SDL_Rect *srcrect,
1244 			 SDL_Surface *dst, SDL_Rect *dstrect)
1245 {
1246 	return SDL_UpperBlit(src, srcrect, dst, dstrect);
1247 }
1248 
1249 bit SDL_MUSTLOCK(SDL_Surface *surface)
1250 {
1251 	return surface.offset || ((surface.flags &
1252 		(SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_RLEACCEL)) != 0);
1253 }
1254 
1255 /* Quick utility function for texture creation */
1256 int power_of_two(int input)
1257 {
1258     int value = 1;
1259 
1260     while ( value < input ) {
1261         value <<= 1;
1262     }
1263     return value;
1264 }
1265 
1266 uint SDL_GL_LoadTexture(SDL_Surface *surface, float *texcoord)
1267 {
1268     uint texture;
1269     int w, h;
1270     SDL_Surface *image;
1271     SDL_Rect area;
1272     uint saved_flags;
1273     ubyte  saved_alpha;
1274 
1275     /* Use the surface width and height expanded to powers of 2 */
1276     w = power_of_two(surface.w);
1277     h = power_of_two(surface.h);
1278     texcoord[0] = 0.0f;         /* Min X */
1279     texcoord[1] = 0.0f;         /* Min Y */
1280     texcoord[2] = cast(float)surface.w / cast(float)w;  /* Max X */
1281     texcoord[3] = cast(float)surface.h / cast(float)h;  /* Max Y */
1282 
1283     image = SDL_CreateRGBSurface(
1284             SDL_SWSURFACE,
1285             w, h,
1286             32,
1287             0x000000FF,
1288             0x0000FF00,
1289             0x00FF0000,
1290             0xFF000000
1291                );
1292     if ( image == null) {
1293         throw new Exception("make image");
1294     }
1295 
1296 
1297     /* Save the alpha blending attributes */
1298     saved_flags = surface.flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
1299     saved_alpha = surface.format.alpha;
1300     if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
1301         SDL_SetAlpha(surface, 0, 0);
1302     }
1303 
1304     /* Copy the surface into the GL texture image */
1305     area.x = 0;
1306     area.y = 0;
1307     area.w = cast(ushort) surface.w;
1308     area.h = cast(ushort) surface.h;
1309     SDL_BlitSurface(surface, &area, image, &area);
1310 
1311     /* Restore the alpha blending attributes */
1312     if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
1313         SDL_SetAlpha(surface, saved_flags, saved_alpha);
1314     }
1315 
1316     /* Create an OpenGL texture for the image */
1317     glGenTextures(1, &texture);
1318 
1319     glBindTexture(GL_TEXTURE_2D, texture);
1320     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1321     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1322 
1323     glTexImage2D(GL_TEXTURE_2D,
1324              0,
1325              GL_RGBA,
1326              w, h,
1327              0,
1328              GL_RGBA,
1329              GL_UNSIGNED_BYTE,
1330              image.pixels);
1331     SDL_FreeSurface(image); /* No longer needed */
1332 
1333 
1334 
1335     return texture;
1336 }
1337 
1338 
1339 
1340 
1341 
1342 
1343 		Color c1;
1344 		Color c2;
1345 		Color c3;
1346 		Color c4;
1347 static this(){
1348 	c1 = RGBA(0,0,255,160);
1349 	c2 = RGBA(0,0,255,160);
1350 	c3 = RGBA(0,0,255,160);
1351 	c4 = RGBA(0,0,0,160);
1352 }
1353 
1354 void drawHighlightBox(Painter p, Point where, int width, int height = 16){
1355 	p.gbox(where, where + XY(width, height), XRGB(c1, 128), XRGB(c2, 128), XRGB(c3, 128), XRGB(c4, 128));
1356 }
1357 
1358 // Real size is width + 8, height + 8. Size given if of the client area
1359 /*
1360 Point drawShadedRect(Painter p, Point where, int width, int height){
1361 	int x = where.x;
1362 	int y = where.y;
1363 
1364 	Color gray = RGB(128,128,128);
1365 
1366 	p.box(XY(x + 2, y), XY( x + 2 + width + 2, y + 4), gray);
1367 	p.box(XY(x + 2, y + height + 4), XY( x + 2 + width + 2, y + 4 + height + 4 ), gray);
1368 
1369 	p.box(XY(x, y + 2), XY(x + 4, y + 2 + height + 2), gray);
1370 	p.box(XY(x + 4 + width, y + 2), XY(x + 4 + width + 4, y + 2 + height + 2), gray);
1371 
1372 //	p.putpixel(XY(x + 1, y + 1), gray);
1373 //	p.putpixel(XY(x + 1, y + 4 + 3 + height), gray);
1374 //	p.putpixel(XY(x + 4 + width + 3, y + 1), gray);
1375 //	p.putpixel(XY(x + 4 + width + 3, y + 4 + 3 + height), gray);
1376 
1377 
1378 	p.hline(XY(x + 4, y + 1),              width + 2, white);
1379 	p.hline(XY(x + 4 - 2, y + 4 + height + 1), width + 2 + 1, white);
1380 
1381 	p.vline(XY(x + 1 - 1, y + 3),             height + 2, white);
1382 	p.vline(XY(x + 4 + width + 3, y + 3), height + 2, white);
1383 
1384 	p.gbox(XY(x + 4, y + 4), XY(x + width + 4, y + height + 4), c1, c2, c3, c4);
1385 
1386 	return XY(x + 4, y + 4);
1387 }
1388 */
1389 
1390 const int BORDER_WIDTH = 4;
1391 
1392 Point drawShadedRect(Painter p, Point where, int width, int height){
1393 	Color gray = RGB(128,128,128);
1394 
1395 	Point w;
1396 	
1397 	// top section
1398 	w = where + XY( BORDER_WIDTH, 0);
1399 		p.box( 	w + XY(0, 				0 * BORDER_WIDTH / 4),
1400 			w + XY(width, 				1 * BORDER_WIDTH / 4),
1401 			gray);
1402 		p.box( 	w + XY(-BORDER_WIDTH / 2, 		1 * BORDER_WIDTH / 4),
1403 			w + XY(BORDER_WIDTH / 2 + width, 	3 * BORDER_WIDTH / 4),
1404 			white);
1405 		p.box( 	w + XY( -1 * BORDER_WIDTH / 4, 		3 * BORDER_WIDTH / 4),
1406 			w + XY( 1 * BORDER_WIDTH / 4 + width , 		4 * BORDER_WIDTH / 4),
1407 			black);
1408 	// bottom section
1409 	w = where + XY(BORDER_WIDTH, height + BORDER_WIDTH);
1410 		p.box( 	w + XY(-1 * BORDER_WIDTH / 4,		0 * BORDER_WIDTH / 4),
1411 			w + XY(1 * BORDER_WIDTH / 4 + width,			1 * BORDER_WIDTH / 4),
1412 			black);
1413 		p.box( 	w + XY(-BORDER_WIDTH / 2, 		1 * BORDER_WIDTH / 4),
1414 			w + XY(BORDER_WIDTH / 2 + width, 	3 * BORDER_WIDTH / 4),
1415 			white);
1416 		p.box( 	w + XY(-1 *BORDER_WIDTH / 4,		3 * BORDER_WIDTH / 4),
1417 			w + XY( 1 *BORDER_WIDTH / 4 + width, 	4 * BORDER_WIDTH / 4),
1418 			gray);
1419 			
1420 	// left section
1421 	w = where + XY( 0, BORDER_WIDTH);
1422 		p.box( 	w + XY(0 * BORDER_WIDTH / 4, -1),
1423 			w + XY(1 * BORDER_WIDTH / 4, height + 1),
1424 			gray);
1425 		p.box( 	w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2),
1426 			w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height),
1427 			white);
1428 		p.box( 	w + XY(3 * BORDER_WIDTH / 4, 0),
1429 			w + XY(4 * BORDER_WIDTH / 4, height),
1430 			black);
1431 
1432 	// right section
1433 	w = where + XY( BORDER_WIDTH + width, BORDER_WIDTH);
1434 		p.box( 	w + XY(0 * BORDER_WIDTH / 4, 0),
1435 			w + XY(1 * BORDER_WIDTH / 4, height),
1436 			black);
1437 		p.box( 	w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2),
1438 			w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height),
1439 			white);
1440 		p.box( 	w + XY(3 * BORDER_WIDTH / 4, -1),
1441 			w + XY(4 * BORDER_WIDTH / 4, 1 + height),
1442 			gray);
1443 	w = where + XY(BORDER_WIDTH, BORDER_WIDTH);
1444 	p.gbox(w, w + XY(width, height), c1, c2, c3, c4);
1445 	return w;
1446 }
1447