From da73f5aeaba6932b27621ac4246d40cdbba05c15 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Fri, 11 Jan 2013 23:58:21 -0500 Subject: [PATCH] ugh symlinks again --- audio.d | 193 +++++++- engine.d | 1213 +++++++++++++++++++++++++++++++++++++++++++++- screen.d | 1423 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 2826 insertions(+), 3 deletions(-) mode change 120000 => 100644 audio.d mode change 120000 => 100644 engine.d mode change 120000 => 100644 screen.d diff --git a/audio.d b/audio.d deleted file mode 120000 index 737a1de..0000000 --- a/audio.d +++ /dev/null @@ -1 +0,0 @@ -/home/me/program/d/ndsdl/audio.d \ No newline at end of file diff --git a/audio.d b/audio.d new file mode 100644 index 0000000..cf28e3d --- /dev/null +++ b/audio.d @@ -0,0 +1,192 @@ +module arsd.audio; + +import sdl.SDL; +import sdl.SDL_mixer; + +import std.string; + +import arsd.engine; + +bool audioIsLoaded; // potential hack material + +class Sound { + public: + this(char[] filename){ + if(!audioIsLoaded) + return; + sfx = Mix_LoadWAV(std.string.toStringz(filename)); + if(sfx is null) + throw new Exception(immutableString("Sound load " ~ filename)); + } + + /* + this(Wav wav){ + auto w = wav.toMemory; + SDL_RWops* a = SDL_RWFromMem(w, w.length) + if(a is null) throw new Exception("sdl rw ops"); + scope(exit) SDL_FreeRW(a); + sfx = Mix_LoadWAV_RW(a, 0); + if(sfx is null) throw new Exception("loadwav rw"); + } + */ + + ~this(){ + if(sfx !is null) + Mix_FreeChunk(sfx); + } + + private: + Mix_Chunk* sfx; +} + +class Music { + public: + this(char[] filename){ + if(!audioIsLoaded) + return; + mus = Mix_LoadMUS(std.string.toStringz(filename)); + if(mus is null) + throw new Exception(immutableString("Music load " ~ filename)); + } + + ~this(){ + if(mus !is null) + Mix_FreeMusic(mus); + } + private: + Mix_Music* mus; +} + +class Audio{ + public: + this(bool act = true){ + if(audioIsLoaded) + throw new Exception("Cannot load audio twice"); + + if(!act){ + audioIsLoaded = false; + active = false; + return; + } + if(Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 4096/2 /* the /2 is new */) != 0){ + active = false; //throw new Error; + error = true; + audioIsLoaded = false; + } else { + active = true; + error = false; + audioIsLoaded = true; + } + + sfxChannel = 1; + + careAboutErrors = false; + } + + void activate(){ + if(!audioIsLoaded) return; + if(!error) + active = true; + } + + void deactivate(){ + if(!audioIsLoaded) return; + active = false; + } + + void toggleActivation(){ + if(!audioIsLoaded) return; + if(error) + return; + active = !active; + } + + ~this(){ + if(audioIsLoaded){ + Mix_HaltMusic(); + Mix_HaltChannel(-1); + Mix_CloseAudio(); + } + } + + void playEffect(Sound snd, bool loop = false){ + if(!active || snd is null) + return; + + //if(Mix_Playing(sfxChannel)) + // return; + + sfxChannel = Mix_PlayChannel(-1, snd.sfx, loop == true ? -1 : 0); + + } + void stopEffect(){ + if(!active) + return; + + Mix_HaltChannel(sfxChannel); + } + + void playMusic(Music mus, bool loop = true){ + if(!active || mus is null) + return; + + if(Mix_PlayMusic(mus.mus, loop == true ? -1 : 0) == -1) + throw new Exception("play music"); + // musicIsPlaying = false; + else + musicIsPlaying = true; + } + + void pauseMusic(){ + if(!active) + return; + + if(musicIsPlaying){ + Mix_PauseMusic(); + musicIsPaused = true; + } + } + + void unpauseMusic(){ + if(!active) + return; + + if(musicIsPaused){ + Mix_ResumeMusic(); + musicIsPaused = false; + } + } + + void stopMusic(){ + if(!active) + return; + + Mix_HaltMusic(); + } + + + void stopAll(){ + if(!active) + return; + + Mix_HaltMusic(); + Mix_HaltChannel(-1); + } + + private: + int sfxChannel; + bool active; + bool error; + + bool musicIsPaused; + bool musicIsPlaying; + + bool careAboutErrors; +} + + int Mix_PlayChannel(int channel, Mix_Chunk* chunk, int loops) { + return Mix_PlayChannelTimed(channel,chunk,loops,-1); + } + Mix_Chunk * Mix_LoadWAV(in char *file) { + return Mix_LoadWAV_RW(SDL_RWFromFile(file, "rb"), 1); + } diff --git a/engine.d b/engine.d deleted file mode 120000 index 1ae00af..0000000 --- a/engine.d +++ /dev/null @@ -1 +0,0 @@ -/home/me/program/d/ndsdl/engine.d \ No newline at end of file diff --git a/engine.d b/engine.d new file mode 100644 index 0000000..85af628 --- /dev/null +++ b/engine.d @@ -0,0 +1,1212 @@ +// This code is D 1.0 + +/*** + The base class from which a game engine should inherit. + + Version: 1.0 + License: GNU General Public License +*/ +module arsd.engine; //@-L-lSDL -L-lSDL_mixer -L-lSDL_ttf -L-lSDL_image -L-lGL -L-lSDL_net + +// FIXME: the difference between directions and buttons should be removed + + +import sdl.SDL; +import sdl.SDL_net; + +import std.string; +version(D_Version2) { + import random = core.stdc.stdlib; + alias random.srand srand; + + import std.conv; + char[] convToString(T)(T t) { return to!(char[])(t); } + string immutableString(in char[] a) { return a.idup; } +} else { + import random = std.random; + void srand(uint a) { + random.rand_seed(a, 0); + } + alias std.string.toString convToString; + + char[] immutableString(in char[] a) { return a; } +} +import std.math; +public import arsd.screen; +public import arsd.audio; + +public import sdl.SDL_keysym_; + +version(D_Version2) + import core.stdc.stdarg; +else + import std.stdarg; + +import std.stdio; +//version(linux) pragma(lib, "kbhit.o"); + +extern(C) bool kbhit(); + +int randomNumber(int min, int max){ + if(min == max) + return min; + max++; // make it inclusive + + uint a = random.rand(); + a %= (max - min); + a += min; + return a; +} + + +// HACK! + bool waiting; + uint globalTimer; +class Callable{ + public: + abstract void run(int timer); + // after is the other thing that is calling this to be paused and then unpaused + final void start(Callable after = null){ + After = after; + if(After !is null) + After.paused = true; + paused = false; + for(int a = 0; a < objs.length; a++) + if(objs[a] is null){ + objs[a] = this; + goto done; + + } + objs.length = objs.length + 1; + objs[objs.length - 1] = this; + + done: + frame(); + } + + final void queue(){ + int a; + paused = true; + for(a = 0; a < objs.length; a++) + if(objs[a] is null){ + objs[a] = this; + goto done; + + } + objs.length = a + 1; + objs[a] = this; + + done: + if(a == 1){ + objs[0].paused = true; + paused = false; + } else { + objs[a - 1].After = this; + } + After = objs[0]; + + frame(); + } + + + + + final void terminate(){ + for(int a = 0; a < objs.length; a++) + if(objs[a] !is null && objs[a] == this) + objs[a] = null; + if(After !is null){ + After.paused = false; + After.frame(); + } + } + + bool paused; + private: + void frame(){ + if(!paused){ + if(globalTimer > lastFrame){ + lastFrame = globalTimer; + run(Timer); + Timer++; + } + } + } + int Timer; + uint lastFrame; + Callable After; +} + +Callable[] objs; +// end HACK + +// enum {up = 0, down, left, right}; + enum {select = 8, start = 9, square = 3, cross = 2, circle = 1, triangle = 0, + R1 = 7, R2 = 5, L2 = 4, L1 = 6, L3 = 10, R3 = 11, special = 12, + up = 13, down = 14, left = 15, right = 16, // dpad and left stick + up2 = 17, down2 = 18, left2 = 19, right2 = 20}; // right stick + const int NUM_BUTTONS = 21; +class Engine{ + const int NoVideo = 0; + const int Video1024x768 = 1; + const int Video640x480 = 2; + const int Video800x600 = 3; + const int Video320x200 = 4; + const int Video512x512 = 5; + + const int VideoFullScreen = 32; + + + alias int Direction; + alias int Buttons; + + const int MAX_NET = 8; + const int NET_PORT = 7777; + + // For being a network server..... + bool isServer; + struct NetClient { + TCPsocket sock; + int numPlayers; + int startingPlayer; + int latency; // in milliseconds + + int state; // Only valid if sock is non null. + // 0: waiting on initial timestamp + // 1: waiting on lag response + // 2: ready for starting + } + NetClient[MAX_NET] clients; + int numberOfClients; + + int maxLatency; + + + // For being a network client + TCPsocket clientsock; + + // All done. + + void beginServing(){ + TCPsocket serversock; + + socketset = SDLNet_AllocSocketSet(MAX_NET+1); + if(socketset is null) + throw new Exception("AllocSocketSet"); + scope(failure) + SDLNet_FreeSocketSet(socketset); + + IPaddress serverIP; + + SDLNet_ResolveHost(&serverIP, null, NET_PORT); + serversock = SDLNet_TCP_Open(&serverIP); + if(serversock is null) + throw new Exception("Server sock"); + scope(exit) + SDLNet_TCP_Close(serversock); + + if(SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) serversock) < 0) + throw new Exception("addsocket"); + scope(exit) + SDLNet_DelSocket(socketset, cast(SDLNet_GenericSocket) serversock); + + writefln("Waiting for players to join the game.\nPress enter when everyone has joined to start the game."); + + + uint randomSeed = random.rand(); + srand(randomSeed); + writefln("TEST: %d", randomNumber(0, 100)); + + + bool loopingNeeeded = false; // potential FIXME for later + while(!kbhit() || loopingNeeeded){ + int n = SDLNet_CheckSockets(socketset, 10); + if(n < 0) + throw new Exception("Check sockets"); + if(n == 0) + continue; + if(SDLNet_SocketReady(serversock)){ + TCPsocket newsock; + + newsock = SDLNet_TCP_Accept(serversock); + if(newsock is null){ + continue; + } + + + SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) newsock); + + // accept the connection + + writefln("New player:"); + + clients[numberOfClients].sock = newsock; + numberOfClients++; + } + + // Check the rest of our sockets for data + + for(int a = 0; a < numberOfClients; a++){ + if(SDLNet_SocketReady(clients[a].sock)){ + byte[16] data; // this needs to be EXACTLY the size we are actually going to get. + if(SDLNet_TCP_Recv(clients[a].sock, data.ptr, 16) <= 0){ + // the connection was closed + for(int b = a; b < numberOfClients; b++) + clients[b] = clients[b+1]; + clients[numberOfClients] = NetClient.init; + numberOfClients--; + a--; + continue; + } + + // And handle the data here. + switch(clients[a].state){ + case 0: // this is the timestamp and stuff + int ts = SDLNet_Read32(data.ptr); + clients[a].numPlayers = SDLNet_Read32(data.ptr+4); + + int startingPlayer = numberOfPlayers; + numberOfPlayers+= clients[a].numPlayers; + clients[a].startingPlayer = startingPlayer; + + SDLNet_Write32(ts, data.ptr); + SDLNet_Write32(SDL_GetTicks(), data.ptr+4); + SDLNet_Write32(randomSeed, data.ptr+8); + SDLNet_Write32(startingPlayer, data.ptr+12); + + if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) <= 0) + throw new Exception("TCP send"); + + clients[a].state++; + break; + case 1: // this is telling of the latency + + clients[a].latency = SDLNet_Read32(data.ptr); + + if(clients[a].latency > maxLatency) + maxLatency = clients[a].latency; + + writefln("Latency: %d", clients[a].latency); + + clients[a].state++; + break; + case 2: + throw new Exception("unknown data came in"); + } + } + } + } + + + + isServer = true; + } + + void connectTo(in char[] whom){ + socketset = SDLNet_AllocSocketSet(1); + if(socketset is null) + throw new Exception("AllocSocketSet"); + scope(failure) + SDLNet_FreeSocketSet(socketset); + + IPaddress ip; + + if(SDLNet_ResolveHost(&ip, std.string.toStringz(whom), NET_PORT) == -1) + throw new Exception("Resolve host"); + + clientsock = SDLNet_TCP_Open(&ip); + + if(clientsock is null) + throw new Exception("open socket"); + + if(SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) clientsock) < 0) + throw new Exception("addsocket"); + scope(failure) SDLNet_DelSocket(socketset, cast(SDLNet_GenericSocket) clientsock); + + byte[16] data; + + int timeStamp = SDL_GetTicks(); + SDLNet_Write32(timeStamp, data.ptr); + SDLNet_Write32(numberOfLocalPlayers, data.ptr+4); + if(SDLNet_TCP_Send(clientsock, data.ptr, 16) <= 0) + throw new Exception("TCP send"); + + if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0) + throw new Exception("TCP recv"); + + int receivedTimeStamp = SDLNet_Read32(data.ptr); + int serverTimeStamp = SDLNet_Read32(data.ptr+4); + uint randomSeed = SDLNet_Read32(data.ptr+8); + firstLocalPlayer = SDLNet_Read32(data.ptr+12); + writefln("First local player = %d", firstLocalPlayer); + + srand(randomSeed); + writefln("TEST: %d", randomNumber(0, 100)); + + int ourLatency = SDL_GetTicks() - receivedTimeStamp; + + SDLNet_Write32(ourLatency, data.ptr); + SDLNet_Write32(serverTimeStamp, data.ptr+4); + + if(SDLNet_TCP_Send(clientsock, data.ptr, 16) <= 0) + throw new Exception("TCP send 2"); + + waiting = true; + } + + // This should be called AFTER most initialization, but BEFORE you initialize your players; you don't + // know the number of players for sure until this call returns. + void waitOnNetwork(){ + if(!net) + return; + + if(isServer){ + + // Calculate when to start, then send the signal to everyone. + int desiredLag = cast(int) round(cast(float) maxLatency / msPerTick) + 2;//1; + lag = desiredLag; + writefln("Lag = %d", lag); + + for(int a = 0; a < numberOfClients; a++){ + int delay = maxLatency - clients[a].latency; + + byte[16] data; + + // FIXME: We need to send all player data here! + + SDLNet_Write32(desiredLag, data.ptr); + SDLNet_Write32(delay, data.ptr + 4); + SDLNet_Write32(numberOfPlayers, data.ptr + 8); + + + if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) < 16) + throw new Exception("Sending failed"); + } + + SDL_Delay(maxLatency); // After waiting for the signal to reach everyone, we can now begin the game! + return; + } else { + // handle the data + byte[16] data; + + // FIXME: we need to read special per game player data here! + + if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0) + throw new Exception("Server closed the connection"); + + int lagAmount = SDLNet_Read32(data.ptr); + int delayAmount = SDLNet_Read32(data.ptr + 4); + numberOfPlayers = SDLNet_Read32(data.ptr+8); + + + lag = lagAmount; + writefln("Lag = %d", lag); + + SDL_Delay(delayAmount); + // And finally, we're done, and the game can begin. + waiting = false; + return; + } + } + + SDLNet_SocketSet socketset; + + int msPerTick; + + int numberOfPlayers; + int numberOfLocalPlayers; + int firstLocalPlayer; + + public: + int getNumberOfPlayers(){ // good for main looping and controls and such + return numberOfPlayers; + } + + // returns < 0 if the player is not local + int getLocalPlayerNumber(int absolutePlayerNumber){ // useful for split screening + if(absolutePlayerNumber >= firstLocalPlayer && absolutePlayerNumber < firstLocalPlayer + numberOfLocalPlayers) + return absolutePlayerNumber - firstLocalPlayer; + + return -1; + } + + int getNumberOfLocalPlayers(){ // only useful for deciding how to split the screen + return numberOfLocalPlayers; + } + + int getFirstLocalPlayerNumber(){ + return firstLocalPlayer; + } + + + this(int graphics = NoVideo, bool sound = false, int timerClick = 0, int numOfLocalPlayers = 1, in char[] network = null){ + bool joystick = true; + + int init = 0; + + numberOfPlayers = numberOfLocalPlayers = numOfLocalPlayers; + + + + if(graphics) + init |= SDL_INIT_VIDEO; + if(timerClick) + init |= SDL_INIT_TIMER; + if(sound) + init |= SDL_INIT_AUDIO; + if(joystick) + init |= SDL_INIT_JOYSTICK; + + msPerTick = timerClick; + + if(SDL_Init(init) == -1 ){ + throw new Exception("SDL_Init"); + } + scope(failure) SDL_Quit(); + + +// SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"), NULL); + + + if(network !is null){ + if(SDLNet_Init() < 0) + throw new Exception("SDLNet_Init"); + scope(failure) SDLNet_Quit(); + + + if(network == "SERVER") + beginServing(); + else + connectTo(network); + + net = true; + } + + switch(graphics & ~32){ + case NoVideo: + screen = null; + break; + case Video1024x768: + screen = new Screen(1024, 768, 24, true, (graphics & 32) ? true : false); + break; + case Video640x480: + screen = new Screen(640, 480); + break; + case Video800x600: + screen = new Screen(800, 600); + break; + case Video512x512: + screen = new Screen(512, 512); + break; + case Video320x200: + screen = new Screen(320, 200); + break; + default: + throw new Exception("Invalid screen type"); + } + scope(failure) delete screen; + + if(timerClick) + SDL_AddTimer(timerClick, &tcallback, null); + + if(sound) + audio = new Audio; + else + audio = new Audio(false); + + scope(failure) delete audio; + + if(joystick && SDL_NumJoysticks() > 0){ + SDL_JoystickEventState(SDL_ENABLE); + for(int a = 0; a < SDL_NumJoysticks(); a++) + joyStick[a] = SDL_JoystickOpen(a); + } + else + joyStick[0] = null; + + scope(failure){ for(int a = 0; a < 16; a++) if(joyStick[a]) SDL_JoystickClose(joyStick[a]); } + + + //SDL_ShowCursor(SDL_DISABLE); // FIXME: make this a call + +//*********************************************************************** + // FIXME: it should load controller maps from a config file + + // My playstation controller + for(int a = 0; a < 13; a++) + mapJoystickKeyToButton(a, cast(Buttons) a, 0, firstLocalPlayer); + + leftStickXAxis[0] = 0; + leftStickYAxis[0] = 1; + dpadXAxis[0] = 4; + dpadYAxis[0] = 5; + rightStickXAxis[0] = 2; + rightStickYAxis[0] = 3; + leftTriggerAxis[0] = -1; + rightTriggerAxis[0] = -1; + + // 360 controllers + for(int b = 1; b < 16; b++){ + mapJoystickKeyToButton(1, circle, b, firstLocalPlayer + b); + mapJoystickKeyToButton(0, cross, b, firstLocalPlayer + b); + mapJoystickKeyToButton(4, square, b, firstLocalPlayer + b); + mapJoystickKeyToButton(3, triangle, b, firstLocalPlayer + b); + + mapJoystickKeyToButton(16, select, b, firstLocalPlayer + b); + mapJoystickKeyToButton(8, start, b, firstLocalPlayer + b); + + mapJoystickKeyToButton(10, L3, b, firstLocalPlayer + b); + mapJoystickKeyToButton(11, R3, b, firstLocalPlayer + b); + + mapJoystickKeyToButton(6, L1, b, firstLocalPlayer + b); + mapJoystickKeyToButton(7, R1, b, firstLocalPlayer + b); + + mapJoystickKeyToButton(9, special, b, firstLocalPlayer + b); + + mapJoystickKeyToButton(15, left, b, firstLocalPlayer + b); + mapJoystickKeyToButton(12, up, b, firstLocalPlayer + b); + mapJoystickKeyToButton(13, right, b, firstLocalPlayer + b); + mapJoystickKeyToButton(14, down, b, firstLocalPlayer + b); + + leftStickXAxis[b] = 0; + dpadXAxis[b] = -1; + leftStickYAxis[b] = 1; + dpadYAxis[b] = -1; + rightStickXAxis[b] = 4; + rightStickYAxis[b] = 3; + leftTriggerAxis[b] = 2; + rightTriggerAxis[b] = 5; + } + + + + // Some sane keyboard defaults + + keyboardMap['s'] = InputMap(circle, firstLocalPlayer); + keyboardMap['x'] = InputMap(cross, firstLocalPlayer); + keyboardMap['w'] = InputMap(triangle, firstLocalPlayer); + keyboardMap['a'] = InputMap(square, firstLocalPlayer); + keyboardMap[' '] = InputMap(circle, firstLocalPlayer); + keyboardMap['d'] = InputMap(L1, firstLocalPlayer); + keyboardMap['f'] = InputMap(R1, firstLocalPlayer); + keyboardMap['e'] = InputMap(L2, firstLocalPlayer); + keyboardMap['r'] = InputMap(R2, firstLocalPlayer); + keyboardMap['c'] = InputMap(L3, firstLocalPlayer); + keyboardMap['v'] = InputMap(R3, firstLocalPlayer); + keyboardMap['['] = InputMap(start, firstLocalPlayer); + keyboardMap[']'] = InputMap(select, firstLocalPlayer); + keyboardMap['='] = InputMap(special, firstLocalPlayer); + + keyboardMap[SDLK_UP] = InputMap(up, firstLocalPlayer); + keyboardMap[SDLK_DOWN] = InputMap(down, firstLocalPlayer); + keyboardMap[SDLK_LEFT] = InputMap(left, firstLocalPlayer); + keyboardMap[SDLK_RIGHT] = InputMap(right, firstLocalPlayer); + + keyboardMap['k'] = InputMap(up, firstLocalPlayer); + keyboardMap['j'] = InputMap(down, firstLocalPlayer); + keyboardMap['h'] = InputMap(left, firstLocalPlayer); + keyboardMap['l'] = InputMap(right, firstLocalPlayer); + + + + } + + void moveMouse(Point pos){ + SDL_WarpMouse(cast(ushort) pos.x, cast(ushort) pos.y); + + } + + bool capturedInput = false; + + void captureInput(){ + if(capturedInput) + return; + SDL_WM_GrabInput(1); + capturedInput = true; + } + + void unCaptureInput(){ + if(!capturedInput) + return; + SDL_WM_GrabInput(0); + capturedInput = false; + } + + ~this(){ + unCaptureInput(); + if(net){ + SDLNet_FreeSocketSet(socketset); + SDLNet_Quit(); + } + for(int a = 0; a < 16; a++) + if(joyStick[a]) + SDL_JoystickClose(joyStick[a]); + delete audio; + delete screen; + + foreach(a; objs) + if(a !is null) + delete a; + SDL_Quit(); + } + + void run(){ + eventLoop(); + } + + void setTitle(in char[] title){ + SDL_WM_SetCaption(std.string.toStringz(title), null); + } + + bool buttonWasPressed(Buttons button, int which = 0){ + if(!buttonsChecked[button][which] && buttonsDown[button][which]){ + buttonsChecked[button][which] = true; + return true; + } + return false; + } + + bool buttonIsDown(Buttons button, int which = 0){ + if(button < NUM_BUTTONS && button >= 0) + return buttonsDown[button][which]; + return false; + } + + bool keyWasPressed(int button){ + if(button < 400 && button >= 0) + if(!keysChecked[button] && keysDown[button]){ + keysChecked[button] = true; + return true; + } + return false; + } + + bool keyIsDown(int button){ + if(button < 400 && button >= 0) + return keysDown[button]; + assert(0); + } + + int getStickX(int stick, int which = 0){ + if( stick >= 0 && stick < 2) + return stickX[stick][which]; + else + return 0; + } + + int getStickY(int stick, int which = 0){ + if( stick >= 0 && stick < 2) + return stickY[stick][which]; + else + return 0; + } + + int getMouseX(){ + return mouseX; + } + + int getMouseY(){ + return mouseY; + } + + int getMouseChangeX(){ + int a = mousedx; + mousedx = 0; + return a; + } + + int getMouseChangeY(){ + int a = mousedy; + mousedy = 0; + return a; + } + + bool mouseHasMoved(){ + return (getMouseChangeY != 0 || getMouseChangeX != 0); + } + + bool mouseButtonWasPressed(int button){ + if(!mouseButtonsChecked[button] && mouseButtonsDown[button]){ + mouseButtonsChecked[button] = true; + return true; + } + return false; + } + + bool mouseButtonIsDown(int button){ + if(button < 8 && button >= 0) + return mouseButtonsDown[button]; + return false; + } + + Point mouseLocation(){ + return XY(mouseX, mouseY); + } + + void quit(){ + wantToQuit = true; + } + + bool isAltDown(){ + return (SDL_GetModState() & KMOD_ALT) ? true : false; + } + bool isControlDown(){ + return (SDL_GetModState() & KMOD_CTRL) ? true : false; + } + bool isShiftDown(){ + return (SDL_GetModState() & KMOD_SHIFT) ? true : false; + } + + protected: + const int BUTTONDOWN = 0; + const int BUTTONUP = 1; + const int MOTION = 2; + + void keyEvent(int type, int keyCode, int character, int modifiers){ + defaultKeyEvent(type, keyCode, character, modifiers); + } + + void defaultKeyEvent(int type, int keyCode, int character, int modifiers){ + if(character == 'q' || keyCode == 'q') + quit(); + if(type == BUTTONUP && keyCode == SDLK_F3){ + if(capturedInput) + unCaptureInput(); + else + captureInput(); + } + } + + void mouseEvent(int type, int x, int y, int xrel, int yrel, int button, int flags){ + defaultMouseEvent(type, x, y, xrel, yrel, button, flags); + } + + final void defaultMouseEvent(int type, int x, int y, int xrel, int yrel, int button, int flags){ + + } + + void joystickEvent(int type, int whichStick, int button, int state){ + defaultJoystickEvent(type, whichStick, button, state); + } + + final void defaultJoystickEvent(int type, int whichStick, int button, int state){ + + } + + bool quitEvent(){ + return true; + } + + void timerEvent(){ + + // Need to add network receives and the lag timer loops + if(net) + getNetworkControllerData(); + + // do we actually need to lag here? hmmm..... + + globalTimer++; + foreach(a; objs){//copy){ + if(a is null) + continue; + a.frame(); + } + + if(lag) + updateControllers(); + + } + + public Screen screen; + public Audio audio; + + private: + + bool net; + + + SDL_Joystick*[16] joyStick; + struct InputMap{ + int button; // or direction + int which; // which player + } + InputMap[int] keyboardMap; + InputMap[int][16] joystickMap; // joystickMap[which][button] = translated val + + int leftStickXAxis[16]; + int dpadXAxis[16]; + int leftStickYAxis[16]; + int dpadYAxis[16]; + int rightStickXAxis[16]; + int rightStickYAxis[16]; + int leftTriggerAxis[16]; + int rightTriggerAxis[16]; + + + + + bool[400] keysDown; + bool[400] keysChecked; + + bool wantToQuit; + + bool buttonsDown[NUM_BUTTONS][16]; + bool buttonsChecked[NUM_BUTTONS][16]; + + const int LAG_QUEUE_SIZE = 10; + // This lag is used for network games. It sends you old data until the lag time is up, + // to try and keep all the players synchronized. + int buttonLagRemaining[NUM_BUTTONS][16][LAG_QUEUE_SIZE]; + + // This way we can queue up activities happening while the lag is waiting + int buttonLagQueueStart[NUM_BUTTONS][16]; + int buttonLagQueueEnd[NUM_BUTTONS][16]; + int buttonLagQueueLength[NUM_BUTTONS][16]; + + // These store what the state was before the lag began; it is what is returned while + // waiting on the lag to complete + bool lagbuttonsDown[NUM_BUTTONS][16][LAG_QUEUE_SIZE]; + + + + int stickX[3][16]; + int stickY[3][16]; + + bool mouseButtonsDown[8]; + bool mouseButtonsChecked[8]; + const int LEFT = SDL_BUTTON_LEFT;//1; + const int MIDDLE = SDL_BUTTON_MIDDLE;//2; + const int RIGHT = SDL_BUTTON_RIGHT;//3; + const int SCROLL_UP = 4; + const int SCROLL_DOWN = 5; + + int mouseX; + int mouseY; + int mousedx; + int mousedy; + + bool altDown; + bool controlDown; + bool shiftDown; + + void mapJoystickKeyToButton(int a, Buttons b, int whichJoystick, int whichPlayer){ + if(b > NUM_BUTTONS) + return; + joystickMap[whichJoystick][a] = InputMap(cast(int) b, whichPlayer); + } + + /* + How does this work? + + when and local are the fancy ones. + + Maybe when should always be globalTimer + 1. This way, you have a local wait of 1 frame + and the remote ones are set to go off one frame later, which gives them time to get down the wire. + + I think that works. + + */ + + + uint lag = 0; // should not be > 10 XXX + + void getNetworkControllerData(){ + int type, when, which, button; + + if(!net) return; + + int n = SDLNet_CheckSockets(socketset, 0); // timeout of 1000 might be good too + if(n < 0) + throw new Exception("Check sockets"); + if(n == 0) + return; + + if(isServer){ + for(int a = 0; a < numberOfClients; a++){ + if(SDLNet_SocketReady(clients[a].sock)){ + byte[16] data; + if(SDLNet_TCP_Recv(clients[a].sock, data.ptr, 16) <= 0){ + throw new Exception("someone closed"); + } + + type = SDLNet_Read32(data.ptr); + when = SDLNet_Read32(data.ptr+4); + which = SDLNet_Read32(data.ptr+8); + button = SDLNet_Read32(data.ptr+12); + + changeButtonState(cast(Buttons) button, type == 0 ? true : false, which, when); + + // don't forget to forward the data to everyone else + for(int b = 0; b< numberOfClients; b++) + if(b != a) + if(SDLNet_TCP_Send(clients[b].sock, data.ptr, 16) < 16) + throw new Exception("network send failure"); + + } + } + } else if(SDLNet_SocketReady(clientsock)) { + byte[16] data; + if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0){ + throw new Exception("connection closed"); + } + type = SDLNet_Read32(data.ptr); + when = SDLNet_Read32(data.ptr+4); + which = SDLNet_Read32(data.ptr+8); + button = SDLNet_Read32(data.ptr+12); + + changeButtonState(cast(Buttons) button, type == 0 ? true : false, which, when); + } + + + } + + void changeButtonState(Buttons button, bool type, int which, uint when, bool sendToNet = false){ + if(when <= lag) + return; + if(when > globalTimer){ + lagbuttonsDown[button][which][buttonLagQueueEnd[button][which]] = type; + buttonLagRemaining[button][which][buttonLagQueueEnd[button][which]] = when - globalTimer; + + buttonLagQueueLength[button][which]++; + buttonLagQueueEnd[button][which]++; + if(buttonLagQueueEnd[button][which] == LAG_QUEUE_SIZE) + buttonLagQueueEnd[button][which] = 0; + } else { + if(when < globalTimer) + throw new Exception(immutableString("Impossible control timing " ~ convToString(when) ~ " @ " ~ convToString(globalTimer))); + buttonsDown[button][which] = type; + buttonsChecked[button][which] = false; + } + + if(net && sendToNet){ + byte[16] data; + SDLNet_Write32(type ? 0 : 1, data.ptr); + SDLNet_Write32(when, data.ptr+4); + SDLNet_Write32(which, data.ptr+8); + SDLNet_Write32(button, data.ptr+12); + if(isServer){ + for(int a = 0; a< numberOfClients; a++) + if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) < 16) + throw new Exception("network send failure"); + + } else { + if(SDLNet_TCP_Send(clientsock, data.ptr, 16) < 16) + throw new Exception("network send failure"); + } + } + } + + void updateControllers(){ + for(int a = 0; a < 16; a++){ // FIXME: should be changed to number of players + for(int b = 0; b < NUM_BUTTONS; b++) + for(int co = 0, q = buttonLagQueueStart[b][a]; co < buttonLagQueueLength[b][a]; q++, co++){ + if(q == LAG_QUEUE_SIZE) + q = 0; + if(buttonLagRemaining[b][a][q]){ + buttonLagRemaining[b][a][q]--; + if(!buttonLagRemaining[b][a][q]){ + changeButtonState(cast(Buttons) b, lagbuttonsDown[b][a][q], a, globalTimer); + buttonLagQueueStart[b][a]++; + buttonLagQueueLength[b][a]--; + if(buttonLagQueueStart[b][a] == LAG_QUEUE_SIZE) + buttonLagQueueStart[b][a] = 0; + } + } + } + } + } + + int eventLoop(){ + SDL_Event event; + while(SDL_WaitEvent(&event) >= 0 && !wantToQuit){ + switch(event.type){ + case SDL_KEYUP: + case SDL_KEYDOWN: + bool type = event.key.type == SDL_KEYDOWN ? true : false; + if(event.key.keysym.sym in keyboardMap){ + int button = keyboardMap[event.key.keysym.sym].button; + int which = keyboardMap[event.key.keysym.sym].which; + changeButtonState(cast(Buttons) button, type, which, globalTimer + lag, true); + } + + if(event.key.keysym.sym < 400){ + keysDown[event.key.keysym.sym] = (event.key.type == SDL_KEYDOWN) ? true : false; + keysChecked[event.key.keysym.sym] = false; + } + + + keyEvent( + event.key.type == SDL_KEYDOWN ? BUTTONDOWN : BUTTONUP, + event.key.keysym.sym, + event.key.keysym.unicode, + event.key.keysym.mod + ); + break; + case SDL_JOYAXISMOTION: + // the things here are to avoid little changes around the center if the stick isn't perfect + if ( ( event.jaxis.value < -3200 ) || (event.jaxis.value > 3200 ) || event.jaxis.value == 0){ + int stick; + if(event.jaxis.axis >= 0 && event.jaxis.axis < 6) + + stick = event.jaxis.axis; + else + break; + + int which = event.jaxis.which; + if(stick == leftStickXAxis[which] || stick == dpadXAxis[which]){ + changeButtonState(left, event.jaxis.value < -28000, which, globalTimer + lag, true); + changeButtonState(right, event.jaxis.value > 28000, which, globalTimer + lag, true); + + stickX[0][which] = event.jaxis.value; + } + if(stick == leftStickYAxis[which] || stick == dpadYAxis[which]){ + changeButtonState(up, event.jaxis.value < -28000, which, globalTimer + lag, true); + changeButtonState(down, event.jaxis.value > 28000, which, globalTimer + lag, true); + + stickY[0][which] = event.jaxis.value; + } + if(stick == rightStickXAxis[which]){ + stickX[1][which] = event.jaxis.value; + changeButtonState(left2, event.jaxis.value < -28000, which, globalTimer + lag, true); + changeButtonState(right2, event.jaxis.value > 28000, which, globalTimer + lag, true); + } + if(stick == rightStickYAxis[which]){ + stickY[1][which] = event.jaxis.value; + changeButtonState(up2, event.jaxis.value < -28000, which, globalTimer + lag, true); + changeButtonState(down2, event.jaxis.value > 28000, which, globalTimer + lag, true); + } + // x-box 360 controller stuff + if(stick == leftTriggerAxis[which]){ + stickX[2][which] = event.jaxis.value; + + changeButtonState(L2, event.jaxis.value > 32000, which, globalTimer + lag, true); + } + if(stick == rightTriggerAxis[which]){ + stickY[2][which] = event.jaxis.value; + changeButtonState(R2, event.jaxis.value > 32000, which, globalTimer + lag, true); + } + + + joystickEvent( + MOTION, + event.jaxis.which, + event.jaxis.axis, + event.jaxis.value + ); + } + break; + case SDL_JOYHATMOTION: + /+ + joystickEvent( + HATMOTION, + event.jhat.which, + event.jhat.hat, + event.jhat.value + ); + +/ + break; + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + if(event.jbutton.button in joystickMap[event.jbutton.which]){ + int which = joystickMap[event.jbutton.which][event.jbutton.button].which; + int button = joystickMap[event.jbutton.which][event.jbutton.button].button; + + changeButtonState(cast(Buttons) button, event.jbutton.type == SDL_JOYBUTTONDOWN ? true : false, which, globalTimer + lag, true); + } + + joystickEvent( + event.jbutton.type == SDL_JOYBUTTONDOWN ? BUTTONDOWN : BUTTONUP, + event.jbutton.which, + event.jbutton.button, + event.jbutton.state + ); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + mouseButtonsDown[event.button.button] = event.button.type == SDL_MOUSEBUTTONDOWN ? true : false; + mouseButtonsChecked[event.button.button] = false; + mouseEvent( + event.button.type == SDL_MOUSEBUTTONDOWN ? BUTTONDOWN : BUTTONUP, + event.button.x, + event.button.y, + 0, // xrel + 0, // yrel + event.button.button, + 0 //state + ); + break; + case SDL_MOUSEMOTION: + mouseX = event.motion.x; + mouseY = event.motion.y; + mousedx += event.motion.xrel; + mousedy += event.motion.yrel; + + mouseEvent( + MOTION, + event.motion.x, + event.motion.y, + event.motion.xrel, + event.motion.yrel, + 0, + event.motion.state + ); + break; + + case SDL_USEREVENT: + timerEvent(); + break; + case SDL_QUIT: + if(quitEvent() == true) + quit(); + break; + default: + } + } + return 0; + } + +} +extern(C){ + Uint32 tcallback(Uint32 interval, void* param){ + if(waiting) + return interval; + SDL_Event event; + + event.type = SDL_USEREVENT; + event.user.code = 0; + event.user.data1 = null; + event.user.data2 = null; + SDL_PushEvent(&event); + + return interval; + } +} + +int SDLNet_SocketReady(void* sock) { + SDLNet_GenericSocket s = cast(SDLNet_GenericSocket)sock; + return sock != cast(TCPsocket)0 && s.ready; +} + + + +Engine engine; + +bool buttonWasPressed(Engine.Buttons button, int which = 0){ + return engine.buttonWasPressed(button, which); +} + +bool buttonIsDown(Engine.Buttons button, int which = 0){ + return engine.buttonIsDown(button, which); +} +/* +bool directionWasPressed(Engine.Direction direction, int which = 0){ + return engine.directionWasPressed(direction, which); +} + +bool directionIsDown(Engine.Direction d, int which = 0){ + return engine.directionIsDown(d, which); +} +*/ + + diff --git a/screen.d b/screen.d deleted file mode 120000 index af05e5e..0000000 --- a/screen.d +++ /dev/null @@ -1 +0,0 @@ -/home/me/program/d/ndsdl/screen.d \ No newline at end of file diff --git a/screen.d b/screen.d new file mode 100644 index 0000000..23b32a5 --- /dev/null +++ b/screen.d @@ -0,0 +1,1422 @@ +// This code is D 1.0 + +module arsd.screen; + +import sdl.SDL; +import sdl.SDL_image; +import sdl.SDL_ttf; +import std.string; + +import std.stdio; +import std.format; + +import arsd.engine; + +static import std.c.string; + +char[] fmt(...){ + char[] o; + void putc(dchar c) + { + o ~= c; + } + + std.format.doFormat(&putc, _arguments, _argptr); + + return o; +} + + +extern(C){ + void glGetIntegerv(int, void*); + void glMatrixMode(int); + void glPushMatrix(); + void glLoadIdentity(); + void glOrtho(double, double, double, double, double, double); + void glPopMatrix(); + void glEnable(int); + void glDisable(int); + void glClear(int); + void glBegin(int); + void glVertex2f(float, float); + void glEnd(); + void glColor3b(ubyte, ubyte, ubyte); + void glColor3i(int, int, int); + void glColor3f(float, float, float); + void glColor4f(float, float, float, float); + void glTranslatef(float, float, float); + + void glRotatef(float, float, float, float); + + uint glGetError(); + + void glDeleteTextures(int, uint*); + + char* gluErrorString(uint); + + void glRasterPos2i(int, int); + void glDrawPixels(int, int, uint, uint, void*); + void glClearColor(float, float, float, float); + + + + void glGenTextures(uint, uint*); + void glBindTexture(int, int); + void glTexParameteri(uint, uint, int); + void glTexImage2D(int, int, int, int, int, int, int, int, void*); + + + void glTexCoord2f(float, float); + void glVertex2i(int, int); + void glBlendFunc (int, int); + void glViewport(int, int, int, int); + + void glReadBuffer(uint); + void glReadPixels(int, int, int, int, int, int, void*); + + + const uint GL_FRONT = 0x0404; + + const uint GL_BLEND = 0x0be2; + const uint GL_SRC_ALPHA = 0x0302; + const uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; + + + const uint GL_UNSIGNED_BYTE = 0x1401; + const uint GL_RGB = 0x1907; + const uint GL_BGRA = 0x80e1; + const uint GL_RGBA = 0x1908; + const uint GL_TEXTURE_2D = 0x0DE1; + const uint GL_TEXTURE_MIN_FILTER = 0x2801; + const uint GL_NEAREST = 0x2600; + const uint GL_LINEAR = 0x2601; + const uint GL_TEXTURE_MAG_FILTER = 0x2800; + + const uint GL_NO_ERROR = 0; + + + + const int GL_VIEWPORT = 0x0BA2; + const int GL_MODELVIEW = 0x1700; + const int GL_TEXTURE = 0x1702; + const int GL_PROJECTION = 0x1701; + const int GL_DEPTH_TEST = 0x0B71; + + const int GL_COLOR_BUFFER_BIT = 0x00004000; + const int GL_ACCUM_BUFFER_BIT = 0x00000200; + const int GL_DEPTH_BUFFER_BIT = 0x00000100; + + const int GL_POINTS = 0x0000; + const int GL_LINES = 0x0001; + const int GL_LINE_LOOP = 0x0002; + const int GL_LINE_STRIP = 0x0003; + const int GL_TRIANGLES = 0x0004; + const int GL_TRIANGLE_STRIP = 5; + const int GL_TRIANGLE_FAN = 6; + const int GL_QUADS = 7; + const int GL_QUAD_STRIP = 8; + const int GL_POLYGON = 9; + +} + +public struct Point{ + int x; + int y; + Point opAddAssign(Point p){ + x += p.x; + y += p.y; + version(D_Version2) + return this; + else + return *this; + } + + Point opAdd(Point p){ + Point a; + a.x = x + p.x; + a.y = y + p.y; + return a; + } + + Point opSub(Point p){ + Point a; + a.x = x - p.x; + a.y = y - p.y; + return a; + } +} + +Point XY(int x, int y){ + Point p; + p.x = x; + p.y = y; + return p; +} + +Point XY(float x, float y){ + Point p; + p.x = cast(int)x; + p.y = cast(int)y; + return p; +} + +public struct Color{ + int r; + int g; + int b; + int a; + + uint toInt(){ + return r << 24 | g << 16 | b << 8 | a; + } + + void fromInt(uint i){ + r = i >> 24; + g = (i >> 16) & 0x0ff; + b = (i >> 8) & 0x0ff; + a = i & 0x0ff; + } +} + +Color white = {255, 255, 255, 255}; +Color black = {0, 0, 0, 255}; + +Color RGB(int r, int g, int b){ + Color c; + c.r = r; + c.g = g; + c.b = b; + c.a = 255; + return c; +} + +Color RGBA(int r, int g, int b, int a){ + Color c; + c.r = r; + c.g = g; + c.b = b; + c.a = a; + return c; +} + +Color XRGB(Color c, int alpha = -1){ + Color a; + a.r = c.r ^ 255; + a.g = c.g ^ 255; + a.b = c.b ^ 255; + if(alpha == -1) + a.a = c.a;// ^ 255; + else + a.a = alpha; + return a; +} + +class FontEngine{ + public: + + static FontEngine instance; + + ~this(){ + foreach(a; fonts) + if(a != null) + TTF_CloseFont(a); + TTF_Quit(); + } + + void loadFont(in char[] font, int size = 12, int index = 0){ + if(fonts[index] != null) + freeFont(index); + TTF_Font* temp; + temp = TTF_OpenFont(std.string.toStringz(font), size); + if(temp == null) + throw new Exception("load font"); + + fonts[index] = temp; + } + + void freeFont(int index = 0){ + if(fonts[index] != null){ + TTF_CloseFont(fonts[index]); + fonts[index] = null; + } + } + + Image renderText(in char[] text, Color foreground = RGB(255,255,255), int font = 0){ + Image* a = immutableString(text) in cache[font]; + if(a !is null) + return *a; + SDL_Color f; + f.r = cast(ubyte) foreground.r; + f.g = cast(ubyte) foreground.g; + f.b = cast(ubyte) foreground.b; + f.unused = cast(ubyte) foreground.a; + + SDL_Surface* s = TTF_RenderText_Blended(fonts[font], std.string.toStringz(text), f); + Image i = new Image(s); + cache[font][text]/*[font]*/ = i; + + return i; + } + + int textHeight(in char[] text=" ",int font = 0){ + int w, h; + TTF_SizeText(FontEngine.instance.fonts[font], std.string.toStringz(text), &w, &h); + return h; + } + + void textSize(in char[] text, out int w, out int h, int font = 0){ + TTF_SizeText(fonts[font], std.string.toStringz(text), &w, &h); + } + private: + static this() { + instance = new FontEngine; + } + + this(){ + if(TTF_Init() == -1) + throw new Exception("TTF_Init"); + + } + + TTF_Font* fonts[8]; + Image[char[]] cache[8]; +} + +interface Drawable{ + public: + void flip(); + int width(); + int height(); + int bpp(); + /* + uint toGL(); + float texWidth(); + float texHeight(); + */ + protected: + SDL_Surface* surface(); +} + +int total = 0; + + +class Image : Drawable{ + public: + this(SDL_Surface* s){ + if(s == null) + throw new Exception("Image"); + sur = s; + } + + /// Loads an image with the filename checking to see if it has already been loaded into the cache + /// loads it as read-only +// static Image load(char[] filename){ + +// } + + this(char[] filename){ + sur = IMG_Load(std.string.toStringz(filename)); + if(sur == null) + throw new Exception(immutableString("Load " ~ filename)); + name = filename; + } + + + void replace(char[] filename){ + if(t){ + glDeleteTextures(1, &tex); + total--; + writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total); + t = 0; + } + if(sur){ + SDL_FreeSurface(sur); + sur = null; + } + sur = IMG_Load(std.string.toStringz(filename)); + if(sur == null) + throw new Exception(immutableString("Load " ~ filename)); + name = filename; + } + + + // loads a slice of an image + this(char[] filename, int x, int y, int wid, int hei){ + /* + Image i = new Image(filename); + this(wid, hei); + + scope Painter p = new Painter(this); + for(int a = 0; a < wid; a++) + for(int b = 0; b < hei; b++) + p.putpixel(XY(a, b), i.getPixel(XY(a + x, b + y))); + */ + + SDL_Surface* s1; + + s1 = IMG_Load(std.string.toStringz(filename)); + if(s1 == null) + throw new Exception(immutableString("Loading " ~ filename)); + scope(exit) + SDL_FreeSurface(s1); + + + sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000); + if(sur == null) + throw new Exception(immutableString("Create")); + + + for(int b = 0; b < hei; b++){ + for(int a = 0; a < wid; a++){ + if(b+y >= s1.h || a+x >= s1.w){ + break; + // throw new Exception("eat my cum"); + } + ubyte* wtf; + if(s1.format.BitsPerPixel == 32){ + wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 4); + } + else + if(s1.format.BitsPerPixel == 24) + wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 3); + else + throw new Exception("fuck me in the ass"); + + ubyte* good = cast(ubyte*)(cast(ubyte*)sur.pixels + b*sur.pitch + a * 4); + + good[0] = wtf[2]; + good[1] = wtf[1]; + good[2] = wtf[0]; + good[3] = wtf[3]; + + } + } + + +/* + SDL_Rect r; + r.x = x; + r.y = y; + r.w = wid; + r.h = hei; + + SDL_Rect r2; + r2.x = 0; + r2.y = 0; + r2.w = wid; + r2.h = hei; + if(SDL_BlitSurface(s1, &r, sur, &r2)) + throw new Exception("Blit"); +*/ + } + + this(int wid, int hei){ + sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000); + if(sur == null) + throw new Exception("Create"); + t = false; + } + + ~this(){ + if(t){ + glDeleteTextures(1, &tex); + total--; + writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total); + } + if(sur) + SDL_FreeSurface(sur); + } + + void flip(){ + + } + + int width(){ + return surface.w; + } + + int height(){ + return surface.h; + } + + int bpp(){ + return sur.format.BitsPerPixel; + } + + Color getPixel(Point p){ + ubyte* bufp; + Color a; + + if(bpp == 32){ + bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 4); + a.a = bufp[3]; + a.r = bufp[2]; + a.g = bufp[1]; + a.b = bufp[0]; + }else{ + bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 3); + a.a = 255; + a.r = bufp[2]; + a.g = bufp[1]; + a.b = bufp[0]; + } + + return a; + } + + uint toGL(){ + if(t) + return tex; + else{ + float f[4]; + tex = SDL_GL_LoadTexture(surface, f.ptr); + t = true; + total++; + texWidth = f[2]; + texHeight = f[3]; + +// total++; +// writef("OpenGL texture created %d. %d exist\n", tex, total); + + return tex; + } + } + protected: + SDL_Surface* surface(){ + return sur; + } + private: + SDL_Surface* sur; + uint tex; + bool t; + float texWidth; + float texHeight; + char[] name; +} + +bool useGL; + +class Screen : Drawable{ + public: + this(int xres = 1024, int yres = 768, int bpp = 24, bool oGL = false, bool fullScreen = false){//true){ +// oGL = false; + oGL = true; + if(!oGL) + screen = SDL_SetVideoMode(xres, yres, bpp/*32*/, SDL_SWSURFACE); + else{ + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 ); + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 ); + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + if(fullScreen){ + screen = SDL_SetVideoMode(xres, yres, 24, SDL_OPENGL| SDL_FULLSCREEN); + } + else + screen = SDL_SetVideoMode(xres, yres, 0, SDL_OPENGL); + //screen = SDL_SetVideoMode(xres, yres, bpp, SDL_OPENGL); + if(screen is null) + throw new Exception("screen"); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + //glOrtho(0, 1000, yres, 0, 0, 1); + glOrtho(0, xres, yres, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + + glDisable(GL_DEPTH_TEST); + + glEnable(GL_TEXTURE_2D); + + + glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + + glClearColor(0,0,0,0); + +// glViewport(0,0,1024,1024); + } + + useGL = oGL; + + if(screen == null) + throw new Exception("screen"); + +// SDL_SetAlpha(screen, SDL_SRCALPHA | SDL_RLEACCEL, 128); + + xr = xres; + yr = yres; + } + + void switchSplitScreenMode(int player, int numberOfPlayers, bool horizontal){ + switch(numberOfPlayers){ + case 1: + return; +// glViewport(0, 0, xr, yr); + break; + case 2: + switch(player){ + case 0: + if(horizontal) + glViewport(0, yr / 2, xr, yr / 2); + else + glViewport(0, 0, xr / 2, yr); + break; + case 1: + if(horizontal) + glViewport(0, 0, xr, yr / 2); + else + glViewport(xr / 2, 0, xr / 2, yr); + break; + } + break; + case 3: + case 4: + switch(player){ + case 0: + glViewport(0, yr / 2, xr / 2, yr / 2); + break; + case 1: + glViewport(xr / 2, yr / 2, xr / 2, yr / 2); + break; + case 2: + glViewport(0, 0, xr / 2, yr / 2); + break; + case 3: + glViewport(xr / 2, 0, xr / 2, yr / 2); + break; + } + + break; + } + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, xr, yr, 0, 0, 1); + } + + + ~this(){ + delete FontEngine.instance; + } + + Image screenshot(){ + if(!useGL) + throw new Exception("Not yet implemented"); + Image image = new Image(xr, yr); + glReadBuffer(GL_FRONT); + glReadPixels(0, 0, xr, yr, GL_BGRA, GL_UNSIGNED_BYTE, image.sur.pixels); + + Image temp = new Image(xr, yr); + + + // FIXME + version(Windows) + return image; + + // FIXME: this crashes on Windows + for(int i = 0; i < yr; i++) + std.c.string.memcpy(temp.sur.pixels + 4 * xr * i, image.sur.pixels + 4 * xr * (yr-1 - i), 4 * xr); +// memcpy(image.sur.pixels, tem.psur.pixels, xres * yres * 4); + + return temp; + } + + + + + void flip(){ + if(useGL) + SDL_GL_SwapBuffers(); + else + SDL_Flip(screen); + } + + int width(){ + return xr; + } + + int height(){ + return yr; + } + + int bpp(){ + return 124; + } + /* + uint toGL(){ + throw new Error; + } + float texWidth(){ + return 1.0; + } + float texHeight(){ + return 1.0; + } +*/ + protected: + SDL_Surface* surface(){ + return screen; + } + + private: + SDL_Surface* screen; + int xr; + int yr; +} + +scope class Painter{ + public: + bool special; + bool manualFlipped; + Point translate; + this(Painter p, Point t){ + s = p.s; + special = true; + translate = t; + } + + this(Drawable d){ + /+ + in { + assert(!(s is null)); + } + +/ + s = d; + if(s is null) + throw new Exception("christ what were you thinking"); + + if ( !(useGL + && s.bpp() == 124) + && SDL_MUSTLOCK(s.surface()) ) { + if ( SDL_LockSurface(s.surface()) < 0 ) { + throw new Exception("locking"); + } + locked = true; + } + } + + ~this(){ + if(!manualFlipped){ + if(glbegin) + endDrawingShapes(); + if(!special){ + if (locked){ + SDL_UnlockSurface(s.surface); + } + s.flip(); + } + } + } + + void manualFlip(){ + if(glbegin) + endDrawingShapes(); + if(!special){ + if (locked){ + SDL_UnlockSurface(s.surface); + } + s.flip(); + } + manualFlipped = true; + } + + void setGLColor(Color color){ + if(useGL && s.bpp == 124){ + 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); + return; + } + } + + void putpixel(Point where, Color color){ + if(special) where += translate; + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + +// if(color.a == 255) +// return; + int x = where.x; + int y = where.y; + if(x < 0 || x >= s.width || y < 0 || y >= s.height) + return; + + + + if(useGL && s.bpp == 124){ + // y = 480 - y; + glBegin(GL_POINTS); + setGLColor(color); + glVertex2f(cast(float)x, cast(float)y); + glEnd(); + return; + } + + + ubyte *bufp; + + if(s.bpp == 32){ + bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 4); + bufp[3] = cast(ubyte) color.a; + bufp[2] = cast(ubyte) color.r; + bufp[1] = cast(ubyte) color.g; + bufp[0] = cast(ubyte) color.b; + }else{ + bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 3); + if(color.a == 255){ + bufp[2] = cast(ubyte) color.r; + bufp[1] = cast(ubyte) color.g; + bufp[0] = cast(ubyte) color.b; + } + else{ + bufp[2] = cast(ubyte)(bufp[2] * (255-color.a) + (color.r * (color.a)) / 255); + bufp[1] = cast(ubyte)(bufp[1] * (255-color.a) + (color.g * (color.a)) / 255); + bufp[0] = cast(ubyte)(bufp[0] * (255-color.a) + (color.b * (color.a)) / 255); + } + } + } + + void beginDrawingLines(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_LINES); + } + void beginDrawingConnectedLines(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_LINE_STRIP); + } + void beginDrawingPolygon(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_POLYGON); + } + void beginDrawingTriangles(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_TRIANGLES); + } + void beginDrawingBoxes(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_QUADS); + } + void beginDrawingPoints(){ + if(glbegin) + throw new Exception("Can only draw one kind at a time"); + glbegin = true; + if(useGL && s.bpp == 124) + glBegin(GL_POINTS); + } + + void endDrawingShapes(){ + if(!glbegin) + return; + glbegin = false; + if(useGL && s.bpp == 124) + glEnd(); + } + void vertex(Point p){ + if(special) p += translate; + if(!glbegin) + throw new Exception("Can't use vertex without beginning first"); + if(useGL && s.bpp == 124) + glVertex2i(p.x, p.y); + } + bool glbegin; + + void drawImageRotated(Point where, Image i, float a, Color color = RGB(255,255,255)){ + if(i is null) + return; + glPushMatrix(); + // glRotatef(a, cast(float)(where.x + 32) / s.width, cast(float)(where.y + 32) / s.height, 1); + glTranslatef(where.x, where.y, 0); + glRotatef(a, 0,0, 1); + drawImage(XY(-i.width/2,-i.height/2), i, i.width, i.height, color); + glPopMatrix(); + } + + void drawImage(Point where, Image i, Color c){ + drawImage(where, i, 0, 0, c); + } + + void drawImage(Point where, Image i, int W = 0, int H = 0, Color c = RGBA(255,255,255,255)){ + if(i is null) + return; + + if(special) where += translate; + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + if(useGL && s.bpp == 124){ + int x = where.x; + int y = where.y; + int w = W == 0 ? i.width : W; + int h = H == 0 ? i.height : H; + +// glColor4f(.5,.5,.5,1); + setGLColor(c); + glBindTexture(GL_TEXTURE_2D, i.toGL); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2i(x, y); + glTexCoord2f(i.texWidth, 0); glVertex2i(x+w, y); + glTexCoord2f(i.texWidth, i.texHeight); glVertex2i(x+w, y+h); + glTexCoord2f(0, i.texHeight); glVertex2i(x, y+h); + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture... I guess + // I don't actually understand why that is needed + // but without it, everything drawn after it is wrong (too light or dark) + return; + } + + if((W == 0 && H == 0) || (i.width == W && i.height == H)){ + SDL_Rect r; + r.x = cast(short)( where.x); + r.y = cast(short)( where.y); + r.w = cast(short)( i.width); + r.h = cast(short)( i.height); + if(locked) + SDL_UnlockSurface(s.surface); + + if(SDL_BlitSurface(i.surface, null, s.surface, &r) == -1) + throw new Exception("blit"); + + if ( SDL_MUSTLOCK(s.surface) ) { + if ( SDL_LockSurface(s.surface) < 0 ) { + throw new Exception("lock"); + } + locked = true; + } + } else { // quick and dirty scaling needed + float dx = cast(float)i.width / cast(float)W; + float dy = cast(float)i.height / cast(float)H; + int X = where.x, Y = where.y; + + for(float y = 0; y < i.height; y += dy){ + for(float x = 0; x < i.width; x += dx){ + putpixel(XY(X, Y), i.getPixel(XY(cast(int) x, cast(int) y))); + X++; + } + X = where.x; + Y++; + } + + } + + } + + void drawText(Point where, in char[] text, Color foreground = RGB(255,255,255), int font = 0){ + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + if(useGL && s.bpp == 124){ + Image i = FontEngine.instance.renderText(text, RGB(255,255,255), font); + drawImage(where, i, foreground); + }else{ + Image i = FontEngine.instance.renderText(text, foreground, font); + drawImage(where, i); + } + } + + void drawTextf(Point where, ...){ + char[] t; + t.length = 80; + int a = 0; + void putc(dchar c){ + if(a == t.length) + t.length = t.length + 80; + t[a] = cast(char) c; + a++; + } + std.format.doFormat(&putc, _arguments, _argptr); + t.length = a; + + drawText(where, t); + } + + int wordLength(in char[] w, int font = 0){ + int a,b; + FontEngine.instance.textSize(w, a, b, font); + return a; + } + + int drawTextBoxed(Point where, char[] text, int width, int height, Color foreground = RGB(255,255,255), int font = 0){ + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + + int xc; + int yc = TTF_FontLineSkip(FontEngine.instance.fonts[font]); + + int w = 0; + int h = 0; + + int l; + +char[] getWord(){ + int a = l; + while(a < text.length && text[a] != ' ' && text[a] != '\n' && text[a] != '\t') + a++; + return text[l..a]; +} + +int wordLength(in char[] w){ + int a,b; + FontEngine.instance.textSize(w, a, b, font); + return a; +} + + Point ww = where; + while(l < text.length){ + if(text[l] == '\n'){ + l++; + goto newline; + } + if(wordLength(getWord()) + w > width){ + goto newline; + } + + if(!(w == 0 && text[l] == ' ')){ + TTF_GlyphMetrics(FontEngine.instance.fonts[font], text[l], null,null,null,null,&xc); + drawText(ww, text[l..(l+1)], foreground, font); + w+=xc; + ww.x += xc; + } + l++; + if(w > (width - xc)){ + newline: + w = 0; + h += yc; + ww.x = cast(short)(where.x); + ww.y += cast(short)(yc); + + if(h > (height - yc)) + break; + } + } + return l; + } + + void drawTextCenteredHoriz(int top, char[] text, Color foreground, int font = 0){ + Point where; + where.y = top; + int w, h; + TTF_SizeText(FontEngine.instance.fonts[font], std.string.toStringz(text), &w, &h); + where.x = (s.width - w) / 2; + drawText(where, text, foreground, font); + } + + void line(Point start, Point end, Color color){ + if(special){ start += translate; end += translate; } + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + if(useGL && s.bpp == 124){ + setGLColor(color); + glBegin(GL_LINES); + glVertex2i(start.x, start.y); + glVertex2i(end.x, end.y); + glEnd(); + } + } + + void hline(Point start, int width, Color color){ + if(useGL && s.bpp == 124){ + line(start, XY(start.x + width, start.y), color); + return; + } + Point point = start; + for(int a = 0; a < width; a++){ + putpixel(point, color); + point.x++; + } + } + + void vline(Point start, int height, Color color){ + if(useGL && s.bpp == 124){ + line(start, XY(start.x, start.y + height), color); + return; + } + + Point point = start; + for(int a = 0; a < height; a++){ + putpixel(point, color); + point.y++; + } + } + + + void circle(Point center, int radius, Color color){ + if(special) center += translate; + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + } + + void arc(Point center, int radius, float start, float end, Color color){ + if(special) center += translate; + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + +// for(float a = start; a <= end; a+= (3.14159265358 / 50.0)) +// putpixel((int)(cos(a) * (float)radius + center.x()),(int)( sin(a) * (float) radius + center.y()), color); + } + + void box(Point upperLeft, Point lowerRight, Color color){ + if(special) { upperLeft += translate; lowerRight += translate; } + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + if(useGL && s.bpp == 124){ + int x1 = upperLeft.x; + int y1 = upperLeft.y; + int x2 = lowerRight.x; + int y2 = lowerRight.y; + glBegin(GL_QUADS); + //glColor3b(color.r, color.g, color.b); + setGLColor(color); + //glColor4f(1,1,1,1); + glVertex2i(x1, y1); + glVertex2i(x2, y1); + glVertex2i(x2, y2); + glVertex2i(x1, y2); + glEnd(); + return; + } + SDL_Rect r; + r.x = cast(short) upperLeft.x; + r.y = cast(short) upperLeft.y; + r.w = cast(short) (lowerRight.x - upperLeft.x); + r.h = cast(short) (lowerRight.y - upperLeft.y); + if(s.bpp == 32) + SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b); + else + SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b); + } + + void rect(Point upperLeft, Point lowerRight, Color color){ + if(special) { upperLeft += translate; lowerRight += translate; } + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + if(useGL && s.bpp == 124){ + int x1 = upperLeft.x; + int y1 = upperLeft.y; + int x2 = lowerRight.x; + int y2 = lowerRight.y; + glBegin(GL_LINE_LOOP); + //glColor3b(color.r, color.g, color.b); + setGLColor(color); + //glColor4f(1,1,1,1); + glVertex2i(x1, y1); + glVertex2i(x2+1, y1); + glVertex2i(x2, y2); + glVertex2i(x1, y2); + glEnd(); + return; + } + /* + SDL_Rect r; + r.x = upperLeft.x; + r.y = upperLeft.y; + r.w = lowerRight.x - upperLeft.x; + r.h = lowerRight.y - upperLeft.y; + if(s.bpp == 32) + SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b); + else + SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b); + */ + } + + void gbox(Point upperLeft, Point lowerRight, Color color1, Color color2, Color color3, Color color4){ + if(special) { upperLeft += translate; lowerRight += translate; } + if(glbegin) + throw new Exception("Must end shape before doing anything else"); + + Color color = color1; + if(useGL && s.bpp == 124){ + int x1 = upperLeft.x; + int y1 = upperLeft.y; + int x2 = lowerRight.x; + int y2 = lowerRight.y; + glBegin(GL_QUADS); + setGLColor(color1); + glVertex2i(x1, y1); + setGLColor(color2); + glVertex2i(x2, y1); + setGLColor(color4); + glVertex2i(x2, y2); + setGLColor(color3); + glVertex2i(x1, y2); + glEnd(); + return; + } + SDL_Rect r; + r.x = cast(short) upperLeft.x; + r.y = cast(short) upperLeft.y; + r.w = cast(short) (lowerRight.x - upperLeft.x); + r.h = cast(short) (lowerRight.y - upperLeft.y); + if(s.bpp == 32) + SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b); + else + SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b); + } + + + void clear(){ + if(useGL && s.bpp == 124){ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT); + return; + } + box(XY(0,0), XY(s.width, s.height), RGB(0,0,0)); + } + + void fill(Color color){ + box(XY(0,0), XY(s.width, s.height), color); + } + + void blend(Color color){ + if(useGL && s.bpp == 124){ + box(XY(0,0), XY(s.width, s.height), color); + return; + } + + ubyte *bufp; + + bufp = cast(ubyte*)s.surface.pixels; + for(int y = 0; y < s.height; y++) + for(int x = 0; x < s.width; x++){ + + bufp[2] = cast(ubyte)((bufp[2] * (255-color.a) + color.r * color.a) / 255); + bufp[1] = cast(ubyte)((bufp[1] * (255-color.a) + color.g * color.a) / 255); + bufp[0] = cast(ubyte)((bufp[0] * (255-color.a) + color.b * color.a) / 255); + + bufp += (s.bpp == 24 ? 3 : 4); + } + } + + private: + Drawable s; + bool locked; +} + + + + + + + +int SDL_BlitSurface + (SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect) +{ + return SDL_UpperBlit(src, srcrect, dst, dstrect); +} + +bit SDL_MUSTLOCK(SDL_Surface *surface) +{ + return surface.offset || ((surface.flags & + (SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_RLEACCEL)) != 0); +} + +/* Quick utility function for texture creation */ +int power_of_two(int input) +{ + int value = 1; + + while ( value < input ) { + value <<= 1; + } + return value; +} + +uint SDL_GL_LoadTexture(SDL_Surface *surface, float *texcoord) +{ + uint texture; + int w, h; + SDL_Surface *image; + SDL_Rect area; + uint saved_flags; + ubyte saved_alpha; + + /* Use the surface width and height expanded to powers of 2 */ + w = power_of_two(surface.w); + h = power_of_two(surface.h); + texcoord[0] = 0.0f; /* Min X */ + texcoord[1] = 0.0f; /* Min Y */ + texcoord[2] = cast(float)surface.w / cast(float)w; /* Max X */ + texcoord[3] = cast(float)surface.h / cast(float)h; /* Max Y */ + + image = SDL_CreateRGBSurface( + SDL_SWSURFACE, + w, h, + 32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000 + ); + if ( image == null) { + throw new Exception("make image"); + } + + + /* Save the alpha blending attributes */ + saved_flags = surface.flags&(SDL_SRCALPHA|SDL_RLEACCELOK); + saved_alpha = surface.format.alpha; + if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) { + SDL_SetAlpha(surface, 0, 0); + } + + /* Copy the surface into the GL texture image */ + area.x = 0; + area.y = 0; + area.w = cast(ushort) surface.w; + area.h = cast(ushort) surface.h; + SDL_BlitSurface(surface, &area, image, &area); + + /* Restore the alpha blending attributes */ + if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) { + SDL_SetAlpha(surface, saved_flags, saved_alpha); + } + + /* Create an OpenGL texture for the image */ + glGenTextures(1, &texture); + + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + w, h, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + image.pixels); + SDL_FreeSurface(image); /* No longer needed */ + + + + return texture; +} + + + + + + + Color c1; + Color c2; + Color c3; + Color c4; +static this(){ + c1 = RGBA(0,0,255,160); + c2 = RGBA(0,0,255,160); + c3 = RGBA(0,0,255,160); + c4 = RGBA(0,0,0,160); +} + +void drawHighlightBox(Painter p, Point where, int width, int height = 16){ + p.gbox(where, where + XY(width, height), XRGB(c1, 128), XRGB(c2, 128), XRGB(c3, 128), XRGB(c4, 128)); +} + +// Real size is width + 8, height + 8. Size given if of the client area +/* +Point drawShadedRect(Painter p, Point where, int width, int height){ + int x = where.x; + int y = where.y; + + Color gray = RGB(128,128,128); + + p.box(XY(x + 2, y), XY( x + 2 + width + 2, y + 4), gray); + p.box(XY(x + 2, y + height + 4), XY( x + 2 + width + 2, y + 4 + height + 4 ), gray); + + p.box(XY(x, y + 2), XY(x + 4, y + 2 + height + 2), gray); + p.box(XY(x + 4 + width, y + 2), XY(x + 4 + width + 4, y + 2 + height + 2), gray); + +// p.putpixel(XY(x + 1, y + 1), gray); +// p.putpixel(XY(x + 1, y + 4 + 3 + height), gray); +// p.putpixel(XY(x + 4 + width + 3, y + 1), gray); +// p.putpixel(XY(x + 4 + width + 3, y + 4 + 3 + height), gray); + + + p.hline(XY(x + 4, y + 1), width + 2, white); + p.hline(XY(x + 4 - 2, y + 4 + height + 1), width + 2 + 1, white); + + p.vline(XY(x + 1 - 1, y + 3), height + 2, white); + p.vline(XY(x + 4 + width + 3, y + 3), height + 2, white); + + p.gbox(XY(x + 4, y + 4), XY(x + width + 4, y + height + 4), c1, c2, c3, c4); + + return XY(x + 4, y + 4); +} +*/ + +const int BORDER_WIDTH = 4; + +Point drawShadedRect(Painter p, Point where, int width, int height){ + Color gray = RGB(128,128,128); + + Point w; + + // top section + w = where + XY( BORDER_WIDTH, 0); + p.box( w + XY(0, 0 * BORDER_WIDTH / 4), + w + XY(width, 1 * BORDER_WIDTH / 4), + gray); + p.box( w + XY(-BORDER_WIDTH / 2, 1 * BORDER_WIDTH / 4), + w + XY(BORDER_WIDTH / 2 + width, 3 * BORDER_WIDTH / 4), + white); + p.box( w + XY( -1 * BORDER_WIDTH / 4, 3 * BORDER_WIDTH / 4), + w + XY( 1 * BORDER_WIDTH / 4 + width , 4 * BORDER_WIDTH / 4), + black); + // bottom section + w = where + XY(BORDER_WIDTH, height + BORDER_WIDTH); + p.box( w + XY(-1 * BORDER_WIDTH / 4, 0 * BORDER_WIDTH / 4), + w + XY(1 * BORDER_WIDTH / 4 + width, 1 * BORDER_WIDTH / 4), + black); + p.box( w + XY(-BORDER_WIDTH / 2, 1 * BORDER_WIDTH / 4), + w + XY(BORDER_WIDTH / 2 + width, 3 * BORDER_WIDTH / 4), + white); + p.box( w + XY(-1 *BORDER_WIDTH / 4, 3 * BORDER_WIDTH / 4), + w + XY( 1 *BORDER_WIDTH / 4 + width, 4 * BORDER_WIDTH / 4), + gray); + + // left section + w = where + XY( 0, BORDER_WIDTH); + p.box( w + XY(0 * BORDER_WIDTH / 4, -1), + w + XY(1 * BORDER_WIDTH / 4, height + 1), + gray); + p.box( w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2), + w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height), + white); + p.box( w + XY(3 * BORDER_WIDTH / 4, 0), + w + XY(4 * BORDER_WIDTH / 4, height), + black); + + // right section + w = where + XY( BORDER_WIDTH + width, BORDER_WIDTH); + p.box( w + XY(0 * BORDER_WIDTH / 4, 0), + w + XY(1 * BORDER_WIDTH / 4, height), + black); + p.box( w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2), + w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height), + white); + p.box( w + XY(3 * BORDER_WIDTH / 4, -1), + w + XY(4 * BORDER_WIDTH / 4, 1 + height), + gray); + w = where + XY(BORDER_WIDTH, BORDER_WIDTH); + p.gbox(w, w + XY(width, height), c1, c2, c3, c4); + return w; +} +