TIC-80-guile/src/system.c

1158 lines
26 KiB
C
Raw Normal View History

2018-02-14 13:16:33 +01:00
#include "system.h"
2018-02-07 16:21:24 +01:00
#include "net.h"
2018-02-14 13:44:27 +01:00
#include "tools.h"
#include <stdlib.h>
#include <stdio.h>
2018-02-14 14:22:33 +01:00
#include <time.h>
2018-02-06 20:15:56 +01:00
#include <SDL.h>
2018-02-14 14:22:33 +01:00
#if defined(__EMSCRIPTEN__)
#include <emscripten.h>
#endif
2018-01-24 10:20:05 +01:00
#define STUDIO_UI_SCALE 3
#define STUDIO_PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888
#define TEXTURE_SIZE (TIC80_FULLWIDTH)
#define OFFSET_LEFT ((TIC80_FULLWIDTH-TIC80_WIDTH)/2)
#define OFFSET_TOP ((TIC80_FULLHEIGHT-TIC80_HEIGHT)/2)
static struct
{
2018-01-24 12:01:19 +01:00
Studio* studio;
2018-01-24 10:20:05 +01:00
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
struct
{
SDL_AudioSpec spec;
SDL_AudioDeviceID device;
SDL_AudioCVT cvt;
} audio;
2018-01-24 15:56:55 +01:00
SDL_Joystick* joysticks[TIC_GAMEPADS];
struct
{
tic80_gamepads touch;
tic80_gamepads joystick;
SDL_Texture* texture;
bool show;
s32 counter;
s32 alpha;
struct
{
s32 size;
SDL_Point axis;
SDL_Point a;
SDL_Point b;
SDL_Point x;
SDL_Point y;
} part;
} gamepad;
2018-02-13 17:25:39 +01:00
struct
{
SDL_Texture* texture;
const u8* src;
} mouse;
2018-02-14 12:31:10 +01:00
Net* net;
2018-01-24 10:20:05 +01:00
bool missedFrame;
2018-02-13 10:44:16 +01:00
bool fullscreen;
2018-01-24 10:20:05 +01:00
} platform;
static void initSound()
{
SDL_AudioSpec want =
{
.freq = 44100,
.format = AUDIO_S16,
.channels = 1,
.userdata = NULL,
};
platform.audio.device = SDL_OpenAudioDevice(NULL, 0, &want, &platform.audio.spec, SDL_AUDIO_ALLOW_ANY_CHANGE);
SDL_BuildAudioCVT(&platform.audio.cvt, want.format, want.channels, platform.audio.spec.freq, platform.audio.spec.format, platform.audio.spec.channels, platform.audio.spec.freq);
if(platform.audio.cvt.needed)
{
platform.audio.cvt.len = platform.audio.spec.freq * sizeof(s16) / TIC_FRAMERATE;
platform.audio.cvt.buf = SDL_malloc(platform.audio.cvt.len * platform.audio.cvt.len_mult);
}
}
2018-02-15 10:03:03 +01:00
static u8* getSpritePtr(tic_tile* tiles, s32 x, s32 y)
2018-02-13 11:28:05 +01:00
{
enum { SheetCols = (TIC_SPRITESHEET_SIZE / TIC_SPRITESIZE) };
return tiles[x / TIC_SPRITESIZE + y / TIC_SPRITESIZE * SheetCols].data;
}
2018-02-15 10:03:03 +01:00
static u8 getSpritePixel(tic_tile* tiles, s32 x, s32 y)
2018-02-13 11:28:05 +01:00
{
2018-02-15 10:03:03 +01:00
return tic_tool_peek4(getSpritePtr(tiles, x, y), (x % TIC_SPRITESIZE) + (y % TIC_SPRITESIZE) * TIC_SPRITESIZE);
2018-02-13 11:28:05 +01:00
}
2018-01-24 10:20:05 +01:00
static void setWindowIcon()
{
2018-02-13 11:28:05 +01:00
enum{ Size = 64, TileSize = 16, ColorKey = 14, Cols = TileSize / TIC_SPRITESIZE, Scale = Size/TileSize};
platform.studio->tic->api.clear(platform.studio->tic, 0);
2018-01-24 10:20:05 +01:00
2018-02-13 11:28:05 +01:00
u32* pixels = SDL_malloc(Size * Size * sizeof(u32));
2018-01-24 10:20:05 +01:00
2018-02-13 11:28:05 +01:00
const u32* pal = tic_palette_blit(&platform.studio->tic->config.palette);
2018-01-24 10:20:05 +01:00
2018-02-13 11:28:05 +01:00
for(s32 j = 0, index = 0; j < Size; j++)
for(s32 i = 0; i < Size; i++, index++)
{
2018-02-15 10:03:03 +01:00
u8 color = getSpritePixel(platform.studio->tic->config.bank0.tiles.data, i/Scale, j/Scale);
2018-02-13 11:28:05 +01:00
pixels[index] = color == ColorKey ? 0 : pal[color];
}
2018-01-24 10:20:05 +01:00
2018-02-13 11:28:05 +01:00
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(pixels, Size, Size,
sizeof(s32) * BITS_IN_BYTE, Size * sizeof(s32),
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
2018-01-24 10:20:05 +01:00
2018-02-13 11:28:05 +01:00
SDL_SetWindowIcon(platform.window, surface);
SDL_FreeSurface(surface);
SDL_free(pixels);
2018-01-24 10:20:05 +01:00
}
static void updateGamepadParts()
{
2018-01-24 15:56:55 +01:00
s32 tileSize = TIC_SPRITESIZE;
s32 offset = 0;
SDL_Rect rect;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
const s32 JoySize = 3;
SDL_GetWindowSize(platform.window, &rect.w, &rect.h);
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
if(rect.w < rect.h)
{
tileSize = rect.w / 2 / JoySize;
offset = (rect.h * 2 - JoySize * tileSize) / 3;
}
else
{
tileSize = rect.w / 5 / JoySize;
offset = (rect.h - JoySize * tileSize) / 2;
}
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
platform.gamepad.part.size = tileSize;
platform.gamepad.part.axis = (SDL_Point){0, offset};
platform.gamepad.part.a = (SDL_Point){rect.w - 2*tileSize, 2*tileSize + offset};
platform.gamepad.part.b = (SDL_Point){rect.w - 1*tileSize, 1*tileSize + offset};
platform.gamepad.part.x = (SDL_Point){rect.w - 3*tileSize, 1*tileSize + offset};
platform.gamepad.part.y = (SDL_Point){rect.w - 2*tileSize, 0*tileSize + offset};
2018-01-24 10:20:05 +01:00
}
static void transparentBlit(u32* out, s32 pitch)
{
2018-01-24 15:56:55 +01:00
const u8* in = platform.studio->tic->ram.vram.screen.data;
const u8* end = in + sizeof(platform.studio->tic->ram.vram.screen);
const u32* pal = tic_palette_blit(&platform.studio->tic->config.palette);
const u32 Delta = (pitch/sizeof *out - TIC80_WIDTH);
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
s32 col = 0;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
while(in != end)
{
u8 low = *in & 0x0f;
u8 hi = (*in & 0xf0) >> TIC_PALETTE_BPP;
*out++ = low ? (*(pal + low) | 0xff000000) : 0;
*out++ = hi ? (*(pal + hi) | 0xff000000) : 0;
in++;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
col += BITS_IN_BYTE / TIC_PALETTE_BPP;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
if (col == TIC80_WIDTH)
{
col = 0;
out += Delta;
}
}
2018-01-24 10:20:05 +01:00
}
static void initTouchGamepad()
{
2018-01-24 15:56:55 +01:00
if (!platform.renderer)
return;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
platform.studio->tic->api.map(platform.studio->tic, &platform.studio->tic->config.bank0.map, &platform.studio->tic->config.bank0.tiles, 0, 0, TIC_MAP_SCREEN_WIDTH, TIC_MAP_SCREEN_HEIGHT, 0, 0, -1, 1);
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
if(!platform.gamepad.texture)
{
platform.gamepad.texture = SDL_CreateTexture(platform.renderer, STUDIO_PIXEL_FORMAT, SDL_TEXTUREACCESS_STREAMING, TEXTURE_SIZE, TEXTURE_SIZE);
SDL_SetTextureBlendMode(platform.gamepad.texture, SDL_BLENDMODE_BLEND);
}
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
{
void* pixels = NULL;
s32 pitch = 0;
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
SDL_LockTexture(platform.gamepad.texture, NULL, &pixels, &pitch);
transparentBlit(pixels, pitch);
SDL_UnlockTexture(platform.gamepad.texture);
}
2018-01-24 10:20:05 +01:00
2018-01-24 15:56:55 +01:00
updateGamepadParts();
2018-01-24 10:20:05 +01:00
}
2018-01-24 12:55:13 +01:00
static void calcTextureRect(SDL_Rect* rect)
{
SDL_GetWindowSize(platform.window, &rect->w, &rect->h);
if (rect->w * TIC80_HEIGHT < rect->h * TIC80_WIDTH)
{
s32 discreteWidth = rect->w - rect->w % TIC80_WIDTH;
s32 discreteHeight = TIC80_HEIGHT * discreteWidth / TIC80_WIDTH;
rect->x = (rect->w - discreteWidth) / 2;
rect->y = rect->w > rect->h
? (rect->h - discreteHeight) / 2
: OFFSET_LEFT*discreteWidth/TIC80_WIDTH;
rect->w = discreteWidth;
rect->h = discreteHeight;
}
else
{
s32 discreteHeight = rect->h - rect->h % TIC80_HEIGHT;
s32 discreteWidth = TIC80_WIDTH * discreteHeight / TIC80_HEIGHT;
rect->x = (rect->w - discreteWidth) / 2;
rect->y = (rect->h - discreteHeight) / 2;
rect->w = discreteWidth;
rect->h = discreteHeight;
}
}
static void processMouse()
{
s32 mx = 0, my = 0;
s32 mb = SDL_GetMouseState(&mx, &my);
2018-01-24 14:13:46 +01:00
tic80_input* input = &platform.studio->tic->ram.input;
2018-01-24 12:55:13 +01:00
{
2018-01-24 14:13:46 +01:00
input->mouse.x = input->mouse.y = 0;
2018-01-24 12:55:13 +01:00
SDL_Rect rect = {0, 0, 0, 0};
calcTextureRect(&rect);
2018-01-24 14:13:46 +01:00
if(rect.w) input->mouse.x = (mx - rect.x) * TIC80_WIDTH / rect.w;
if(rect.h) input->mouse.y = (my - rect.y) * TIC80_HEIGHT / rect.h;
2018-01-24 12:55:13 +01:00
}
{
2018-02-14 09:36:56 +01:00
input->mouse.left = mb & SDL_BUTTON_LMASK ? 1 : 0;
input->mouse.middle = mb & SDL_BUTTON_MMASK ? 1 : 0;
input->mouse.right = mb & SDL_BUTTON_RMASK ? 1 : 0;
2018-01-24 12:55:13 +01:00
}
}
2018-01-24 14:13:46 +01:00
static void processKeyboard()
2018-01-24 10:20:05 +01:00
{
2018-01-24 14:13:46 +01:00
static const u8 KeyboardCodes[] =
{
#include "keycodes.c"
};
tic80_input* input = &platform.studio->tic->ram.input;
input->keyboard.data = 0;
const u8* keyboard = SDL_GetKeyboardState(NULL);
for(s32 i = 0, c = 0; i < COUNT_OF(KeyboardCodes) && c < COUNT_OF(input->keyboard.keys); i++)
if(keyboard[i] && KeyboardCodes[i] > tic_key_unknown)
input->keyboard.keys[c++] = KeyboardCodes[i];
}
2018-01-24 15:56:55 +01:00
#if !defined(__EMSCRIPTEN__) && !defined(__MACOSX__)
static bool checkTouch(const SDL_Rect* rect, s32* x, s32* y)
{
s32 devices = SDL_GetNumTouchDevices();
s32 width = 0, height = 0;
SDL_GetWindowSize(platform.window, &width, &height);
for (s32 i = 0; i < devices; i++)
{
SDL_TouchID id = SDL_GetTouchDevice(i);
// very strange, but on Android id always == 0
//if (id)
{
s32 fingers = SDL_GetNumTouchFingers(id);
if(fingers)
{
platform.gamepad.counter = 0;
if (!platform.gamepad.show)
{
2018-02-14 13:30:13 +01:00
platform.gamepad.alpha = platform.studio->config()->theme.gamepad.touch.alpha;
2018-01-24 15:56:55 +01:00
SDL_SetTextureAlphaMod(platform.gamepad.texture, platform.gamepad.alpha);
platform.gamepad.show = true;
return false;
}
}
for (s32 f = 0; f < fingers; f++)
{
SDL_Finger* finger = SDL_GetTouchFinger(id, f);
if (finger && finger->pressure > 0.0f)
{
SDL_Point point = { (s32)(finger->x * width), (s32)(finger->y * height) };
if (SDL_PointInRect(&point, rect))
{
*x = point.x;
*y = point.y;
return true;
}
}
}
}
}
return false;
}
static void processTouchGamepad()
{
platform.gamepad.touch.data = 0;
const s32 size = platform.gamepad.part.size;
s32 x = 0, y = 0;
{
SDL_Rect axis = {platform.gamepad.part.axis.x, platform.gamepad.part.axis.y, size*3, size*3};
if(checkTouch(&axis, &x, &y))
{
x -= axis.x;
y -= axis.y;
s32 xt = x / size;
s32 yt = y / size;
if(yt == 0) platform.gamepad.touch.first.up = true;
else if(yt == 2) platform.gamepad.touch.first.down = true;
if(xt == 0) platform.gamepad.touch.first.left = true;
else if(xt == 2) platform.gamepad.touch.first.right = true;
if(xt == 1 && yt == 1)
{
xt = (x - size)/(size/3);
yt = (y - size)/(size/3);
if(yt == 0) platform.gamepad.touch.first.up = true;
else if(yt == 2) platform.gamepad.touch.first.down = true;
if(xt == 0) platform.gamepad.touch.first.left = true;
else if(xt == 2) platform.gamepad.touch.first.right = true;
}
}
}
{
SDL_Rect a = {platform.gamepad.part.a.x, platform.gamepad.part.a.y, size, size};
if(checkTouch(&a, &x, &y)) platform.gamepad.touch.first.a = true;
}
{
SDL_Rect b = {platform.gamepad.part.b.x, platform.gamepad.part.b.y, size, size};
if(checkTouch(&b, &x, &y)) platform.gamepad.touch.first.b = true;
}
{
SDL_Rect xb = {platform.gamepad.part.x.x, platform.gamepad.part.x.y, size, size};
if(checkTouch(&xb, &x, &y)) platform.gamepad.touch.first.x = true;
}
{
SDL_Rect yb = {platform.gamepad.part.y.x, platform.gamepad.part.y.y, size, size};
if(checkTouch(&yb, &x, &y)) platform.gamepad.touch.first.y = true;
}
}
#endif
static s32 getAxisMask(SDL_Joystick* joystick)
{
s32 mask = 0;
s32 axesCount = SDL_JoystickNumAxes(joystick);
for (s32 a = 0; a < axesCount; a++)
{
s32 axe = SDL_JoystickGetAxis(joystick, a);
if (axe)
{
if (a == 0)
{
if (axe > 16384) mask |= SDL_HAT_RIGHT;
else if(axe < -16384) mask |= SDL_HAT_LEFT;
}
else if (a == 1)
{
if (axe > 16384) mask |= SDL_HAT_DOWN;
else if (axe < -16384) mask |= SDL_HAT_UP;
}
}
}
return mask;
}
static s32 getJoystickHatMask(s32 hat)
{
tic80_gamepads gamepad;
gamepad.data = 0;
gamepad.first.up = hat & SDL_HAT_UP;
gamepad.first.down = hat & SDL_HAT_DOWN;
gamepad.first.left = hat & SDL_HAT_LEFT;
gamepad.first.right = hat & SDL_HAT_RIGHT;
return gamepad.data;
}
static void processJoysticks()
{
platform.gamepad.joystick.data = 0;
s32 index = 0;
for(s32 i = 0; i < COUNT_OF(platform.joysticks); i++)
{
SDL_Joystick* joystick = platform.joysticks[i];
if(joystick && SDL_JoystickGetAttached(joystick))
{
tic80_gamepad* gamepad = NULL;
switch(index)
{
case 0: gamepad = &platform.gamepad.joystick.first; break;
case 1: gamepad = &platform.gamepad.joystick.second; break;
2018-02-14 17:35:43 +01:00
case 2: gamepad = &platform.gamepad.joystick.third; break;
case 3: gamepad = &platform.gamepad.joystick.fourth; break;
2018-01-24 15:56:55 +01:00
}
if(gamepad)
{
gamepad->data |= getJoystickHatMask(getAxisMask(joystick));
for (s32 h = 0; h < SDL_JoystickNumHats(joystick); h++)
gamepad->data |= getJoystickHatMask(SDL_JoystickGetHat(joystick, h));
s32 numButtons = SDL_JoystickNumButtons(joystick);
if(numButtons >= 2)
{
gamepad->a = SDL_JoystickGetButton(joystick, 0);
gamepad->b = SDL_JoystickGetButton(joystick, 1);
if(numButtons >= 4)
{
gamepad->x = SDL_JoystickGetButton(joystick, 2);
gamepad->y = SDL_JoystickGetButton(joystick, 3);
2018-02-13 16:45:54 +01:00
for(s32 i = 5; i < numButtons; i++)
{
s32 back = SDL_JoystickGetButton(joystick, i);
if(back)
{
tic_mem* tic = platform.studio->tic;
for(s32 i = 0; i < TIC80_KEY_BUFFER; i++)
{
if(!tic->ram.input.keyboard.keys[i])
{
tic->ram.input.keyboard.keys[i] = tic_key_escape;
break;
}
}
}
}
2018-01-24 15:56:55 +01:00
}
}
index++;
}
}
}
}
static void processGamepad()
{
#if !defined(__EMSCRIPTEN__) && !defined(__MACOSX__)
processTouchGamepad();
#endif
processJoysticks();
{
platform.studio->tic->ram.input.gamepads.data = 0;
platform.studio->tic->ram.input.gamepads.data |= platform.gamepad.touch.data;
platform.studio->tic->ram.input.gamepads.data |= platform.gamepad.joystick.data;
}
}
2018-01-24 14:13:46 +01:00
static void pollEvent()
{
2018-02-12 11:31:52 +01:00
tic80_input* input = &platform.studio->tic->ram.input;
2018-02-12 12:51:57 +01:00
{
input->mouse.btns = 0;
}
2018-02-12 11:31:52 +01:00
2018-02-14 17:35:43 +01:00
static SDL_Event event;
2018-01-24 10:20:05 +01:00
2018-02-12 11:31:52 +01:00
while(SDL_PollEvent(&event))
2018-01-24 10:20:05 +01:00
{
switch(event.type)
{
2018-02-12 11:31:52 +01:00
case SDL_MOUSEWHEEL:
{
input->mouse.scrollx = event.wheel.x;
input->mouse.scrolly = event.wheel.y;
}
break;
2018-01-25 06:51:15 +01:00
case SDL_JOYDEVICEADDED:
{
s32 id = event.jdevice.which;
2018-01-24 10:20:05 +01:00
2018-01-25 06:51:15 +01:00
if (id < TIC_GAMEPADS)
{
if(platform.joysticks[id])
SDL_JoystickClose(platform.joysticks[id]);
2018-01-24 10:20:05 +01:00
2018-01-25 06:51:15 +01:00
platform.joysticks[id] = SDL_JoystickOpen(id);
}
}
break;
2018-01-24 10:20:05 +01:00
2018-01-25 06:51:15 +01:00
case SDL_JOYDEVICEREMOVED:
{
s32 id = event.jdevice.which;
2018-01-24 10:20:05 +01:00
2018-01-25 06:51:15 +01:00
if (id < TIC_GAMEPADS && platform.joysticks[id])
{
SDL_JoystickClose(platform.joysticks[id]);
platform.joysticks[id] = NULL;
}
}
break;
2018-01-24 15:56:55 +01:00
case SDL_WINDOWEVENT:
switch(event.window.event)
{
case SDL_WINDOWEVENT_RESIZED: updateGamepadParts(); break;
2018-02-14 13:14:05 +01:00
case SDL_WINDOWEVENT_FOCUS_GAINED: platform.studio->updateProject(); break;
2018-01-24 15:56:55 +01:00
}
break;
2018-01-24 10:20:05 +01:00
case SDL_QUIT:
2018-02-14 13:23:26 +01:00
platform.studio->exit();
2018-01-24 10:20:05 +01:00
break;
default:
break;
}
}
2018-01-24 14:13:46 +01:00
processMouse();
processKeyboard();
2018-02-13 16:45:54 +01:00
processGamepad();
2018-01-24 10:20:05 +01:00
}
static void blitTexture()
{
SDL_Rect rect = {0, 0, 0, 0};
calcTextureRect(&rect);
void* pixels = NULL;
s32 pitch = 0;
SDL_LockTexture(platform.texture, NULL, &pixels, &pitch);
2018-02-14 13:14:05 +01:00
platform.studio->tick(pixels);
2018-01-24 10:20:05 +01:00
SDL_UnlockTexture(platform.texture);
{
enum {Header = OFFSET_TOP};
SDL_Rect srcRect = {0, 0, TIC80_FULLWIDTH, Header};
SDL_Rect dstRect = {0};
SDL_GetWindowSize(platform.window, &dstRect.w, &dstRect.h);
dstRect.h = rect.y;
SDL_RenderCopy(platform.renderer, platform.texture, &srcRect, &dstRect);
}
{
enum {Header = OFFSET_TOP};
SDL_Rect srcRect = {0, TIC80_FULLHEIGHT - Header, TIC80_FULLWIDTH, Header};
SDL_Rect dstRect = {0};
SDL_GetWindowSize(platform.window, &dstRect.w, &dstRect.h);
dstRect.y = rect.y + rect.h;
dstRect.h = rect.y;
SDL_RenderCopy(platform.renderer, platform.texture, &srcRect, &dstRect);
}
{
enum {Header = OFFSET_TOP};
enum {Left = OFFSET_LEFT};
SDL_Rect srcRect = {0, Header, Left, TIC80_HEIGHT};
SDL_Rect dstRect = {0};
SDL_GetWindowSize(platform.window, &dstRect.w, &dstRect.h);
dstRect.y = rect.y;
dstRect.h = rect.h;
SDL_RenderCopy(platform.renderer, platform.texture, &srcRect, &dstRect);
}
{
enum {Top = OFFSET_TOP};
enum {Left = OFFSET_LEFT};
SDL_Rect srcRect = {Left, Top, TIC80_WIDTH, TIC80_HEIGHT};
SDL_RenderCopy(platform.renderer, platform.texture, &srcRect, &rect);
}
}
2018-01-24 12:01:19 +01:00
static void blitSound()
{
tic_mem* tic = platform.studio->tic;
SDL_PauseAudioDevice(platform.audio.device, 0);
if(platform.audio.cvt.needed)
{
SDL_memcpy(platform.audio.cvt.buf, tic->samples.buffer, tic->samples.size);
SDL_ConvertAudio(&platform.audio.cvt);
SDL_QueueAudio(platform.audio.device, platform.audio.cvt.buf, platform.audio.cvt.len_cvt);
}
else SDL_QueueAudio(platform.audio.device, tic->samples.buffer, tic->samples.size);
}
2018-01-24 15:56:55 +01:00
static void renderGamepad()
{
if(platform.gamepad.show || platform.gamepad.alpha); else return;
const s32 tileSize = platform.gamepad.part.size;
const SDL_Point axis = platform.gamepad.part.axis;
typedef struct { bool press; s32 x; s32 y;} Tile;
const Tile Tiles[] =
{
{platform.studio->tic->ram.input.gamepads.first.up, axis.x + 1*tileSize, axis.y + 0*tileSize},
{platform.studio->tic->ram.input.gamepads.first.down, axis.x + 1*tileSize, axis.y + 2*tileSize},
{platform.studio->tic->ram.input.gamepads.first.left, axis.x + 0*tileSize, axis.y + 1*tileSize},
{platform.studio->tic->ram.input.gamepads.first.right, axis.x + 2*tileSize, axis.y + 1*tileSize},
{platform.studio->tic->ram.input.gamepads.first.a, platform.gamepad.part.a.x, platform.gamepad.part.a.y},
{platform.studio->tic->ram.input.gamepads.first.b, platform.gamepad.part.b.x, platform.gamepad.part.b.y},
{platform.studio->tic->ram.input.gamepads.first.x, platform.gamepad.part.x.x, platform.gamepad.part.x.y},
{platform.studio->tic->ram.input.gamepads.first.y, platform.gamepad.part.y.x, platform.gamepad.part.y.y},
};
enum {ButtonsCount = 8};
for(s32 i = 0; i < COUNT_OF(Tiles); i++)
{
const Tile* tile = Tiles + i;
SDL_Rect src = {(tile->press ? ButtonsCount + i : i) * TIC_SPRITESIZE, 0, TIC_SPRITESIZE, TIC_SPRITESIZE};
SDL_Rect dest = {tile->x, tile->y, tileSize, tileSize};
SDL_RenderCopy(platform.renderer, platform.gamepad.texture, &src, &dest);
}
if(!platform.gamepad.show && platform.gamepad.alpha)
{
enum {Step = 3};
if(platform.gamepad.alpha - Step >= 0) platform.gamepad.alpha -= Step;
else platform.gamepad.alpha = 0;
SDL_SetTextureAlphaMod(platform.gamepad.texture, platform.gamepad.alpha);
}
platform.gamepad.counter = platform.gamepad.touch.data ? 0 : platform.gamepad.counter+1;
// wait 5 seconds and hide touch gamepad
if(platform.gamepad.counter >= 5 * TIC_FRAMERATE)
platform.gamepad.show = false;
}
2018-02-13 17:25:39 +01:00
static void blitCursor(const u8* in)
{
if(!platform.mouse.texture)
{
platform.mouse.texture = SDL_CreateTexture(platform.renderer, STUDIO_PIXEL_FORMAT, SDL_TEXTUREACCESS_STREAMING, TIC_SPRITESIZE, TIC_SPRITESIZE);
SDL_SetTextureBlendMode(platform.mouse.texture, SDL_BLENDMODE_BLEND);
}
if(platform.mouse.src != in)
{
platform.mouse.src = in;
void* pixels = NULL;
s32 pitch = 0;
SDL_LockTexture(platform.mouse.texture, NULL, &pixels, &pitch);
2018-02-13 14:02:05 +01:00
2018-02-13 17:25:39 +01:00
{
const u8* end = in + sizeof(tic_tile);
const u32* pal = tic_palette_blit(&platform.studio->tic->ram.vram.palette);
u32* out = pixels;
while(in != end)
{
u8 low = *in & 0x0f;
u8 hi = (*in & 0xf0) >> TIC_PALETTE_BPP;
*out++ = low ? (*(pal + low) | 0xff000000) : 0;
*out++ = hi ? (*(pal + hi) | 0xff000000) : 0;
in++;
}
}
SDL_UnlockTexture(platform.mouse.texture);
}
SDL_Rect rect = {0, 0, 0, 0};
calcTextureRect(&rect);
s32 scale = rect.w / TIC80_WIDTH;
SDL_Rect src = {0, 0, TIC_SPRITESIZE, TIC_SPRITESIZE};
SDL_Rect dst = {0, 0, TIC_SPRITESIZE * scale, TIC_SPRITESIZE * scale};
SDL_GetMouseState(&dst.x, &dst.y);
2018-02-14 13:30:13 +01:00
if(platform.studio->config()->theme.cursor.pixelPerfect)
2018-02-13 17:25:39 +01:00
{
dst.x -= (dst.x - rect.x) % scale;
dst.y -= (dst.y - rect.y) % scale;
}
if(SDL_GetWindowFlags(platform.window) & SDL_WINDOW_MOUSE_FOCUS)
SDL_RenderCopy(platform.renderer, platform.mouse.texture, &src, &dst);
}
2018-02-13 14:02:05 +01:00
2018-02-12 12:51:57 +01:00
static void renderCursor()
{
if(platform.studio->tic->ram.vram.vars.cursor.system)
{
switch(platform.studio->tic->ram.vram.vars.cursor.sprite)
{
2018-02-13 17:25:39 +01:00
case tic_cursor_hand:
{
2018-02-14 13:30:13 +01:00
if(platform.studio->config()->theme.cursor.hand >= 0)
2018-02-13 17:25:39 +01:00
{
SDL_ShowCursor(SDL_DISABLE);
2018-02-14 13:30:13 +01:00
blitCursor(platform.studio->tic->config.bank0.tiles.data[platform.studio->config()->theme.cursor.hand].data);
2018-02-13 17:25:39 +01:00
}
else
{
SDL_ShowCursor(SDL_ENABLE);
SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND));
}
}
break;
case tic_cursor_ibeam:
{
2018-02-14 13:30:13 +01:00
if(platform.studio->config()->theme.cursor.ibeam >= 0)
2018-02-13 17:25:39 +01:00
{
SDL_ShowCursor(SDL_DISABLE);
2018-02-14 13:30:13 +01:00
blitCursor(platform.studio->tic->config.bank0.tiles.data[platform.studio->config()->theme.cursor.ibeam].data);
2018-02-13 17:25:39 +01:00
}
else
{
SDL_ShowCursor(SDL_ENABLE);
SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM));
}
}
break;
default:
{
2018-02-14 13:30:13 +01:00
if(platform.studio->config()->theme.cursor.arrow >= 0)
2018-02-13 17:25:39 +01:00
{
SDL_ShowCursor(SDL_DISABLE);
2018-02-14 13:30:13 +01:00
blitCursor(platform.studio->tic->config.bank0.tiles.data[platform.studio->config()->theme.cursor.arrow].data);
2018-02-13 17:25:39 +01:00
}
else
{
SDL_ShowCursor(SDL_ENABLE);
SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW));
}
}
2018-02-12 12:51:57 +01:00
}
}
else
{
2018-02-13 17:25:39 +01:00
SDL_ShowCursor(SDL_DISABLE);
blitCursor(platform.studio->tic->ram.sprites.data[platform.studio->tic->ram.vram.vars.cursor.sprite].data);
2018-02-12 12:51:57 +01:00
}
2018-02-13 17:25:39 +01:00
// if(platform.mode == TIC_RUN_MODE && !platform.studio.tic->input.mouse)
2018-02-12 12:51:57 +01:00
// {
// SDL_ShowCursor(SDL_DISABLE);
// return;
// }
}
2018-01-24 10:20:05 +01:00
static void tick()
{
pollEvent();
2018-02-14 11:58:46 +01:00
if(platform.studio->quit)
{
#if defined __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
return;
}
2018-01-24 10:20:05 +01:00
SDL_RenderClear(platform.renderer);
2018-01-24 15:56:55 +01:00
blitTexture();
2018-02-12 12:51:57 +01:00
renderCursor();
2018-01-24 15:56:55 +01:00
renderGamepad();
2018-01-24 10:20:05 +01:00
SDL_RenderPresent(platform.renderer);
2018-01-24 12:01:19 +01:00
blitSound();
2018-01-24 10:20:05 +01:00
}
2018-02-07 16:21:24 +01:00
static const char* getAppFolder()
{
static char appFolder[FILENAME_MAX];
#if defined(__EMSCRIPTEN__)
strcpy(appFolder, "/" TIC_PACKAGE "/" TIC_NAME "/");
#elif defined(__ANDROID__)
strcpy(appFolder, SDL_AndroidGetExternalStoragePath());
const char AppFolder[] = "/" TIC_NAME "/";
strcat(appFolder, AppFolder);
mkdir(appFolder, 0700);
#else
char* path = SDL_GetPrefPath(TIC_PACKAGE, TIC_NAME);
strcpy(appFolder, path);
2018-02-14 11:58:46 +01:00
SDL_free(path);
2018-02-07 16:21:24 +01:00
#endif
return appFolder;
}
2018-02-14 13:14:05 +01:00
static void setClipboardText(const char* text)
2018-02-07 16:21:24 +01:00
{
SDL_SetClipboardText(text);
}
2018-02-14 13:14:05 +01:00
static bool hasClipboardText()
2018-02-07 16:21:24 +01:00
{
return SDL_HasClipboardText();
}
2018-02-14 13:14:05 +01:00
static char* getClipboardText()
2018-02-07 16:21:24 +01:00
{
return SDL_GetClipboardText();
}
2018-02-14 13:14:05 +01:00
static u64 getPerformanceCounter()
2018-02-07 16:21:24 +01:00
{
return SDL_GetPerformanceCounter();
}
2018-02-14 13:14:05 +01:00
static u64 getPerformanceFrequency()
2018-02-07 16:21:24 +01:00
{
return SDL_GetPerformanceFrequency();
}
2018-02-14 13:14:05 +01:00
static void goFullscreen()
2018-02-13 10:44:16 +01:00
{
platform.fullscreen = !platform.fullscreen;
SDL_SetWindowFullscreen(platform.window, platform.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}
2018-02-14 13:14:05 +01:00
static void showMessageBox(const char* title, const char* message)
2018-02-13 11:33:55 +01:00
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, title, message, NULL);
}
2018-02-14 13:14:05 +01:00
static void setWindowTitle(const char* title)
2018-02-13 11:38:11 +01:00
{
SDL_SetWindowTitle(platform.window, title);
}
2018-02-13 17:39:26 +01:00
#if defined(__WINDOWS__) || defined(__LINUX__) || defined(__MACOSX__)
2018-02-14 13:14:05 +01:00
static void openSystemPath(const char* path)
2018-02-13 17:39:26 +01:00
{
char command[FILENAME_MAX];
#if defined(__WINDOWS__)
sprintf(command, "explorer \"%s\"", path);
wchar_t wcommand[FILENAME_MAX];
mbstowcs(wcommand, command, FILENAME_MAX);
_wsystem(wcommand);
#elif defined(__LINUX__)
sprintf(command, "xdg-open \"%s\"", path);
system(command);
#elif defined(__MACOSX__)
sprintf(command, "open \"%s\"", path);
system(command);
#endif
}
#else
2018-02-14 13:14:05 +01:00
static void openSystemPath(const char* path) {}
2018-02-13 17:39:26 +01:00
#endif
2018-02-14 13:14:05 +01:00
static void* getUrlRequest(const char* url, s32* size)
2018-02-14 12:31:10 +01:00
{
return netGetRequest(platform.net, url, size);
}
2018-02-14 14:22:33 +01:00
static void preseed()
{
#if defined(__MACOSX__)
srandom(time(NULL));
random();
#else
srand(time(NULL));
rand();
#endif
}
2018-02-14 13:14:05 +01:00
static System systemInterface =
2018-02-07 16:21:24 +01:00
{
2018-02-14 13:14:05 +01:00
.setClipboardText = setClipboardText,
.hasClipboardText = hasClipboardText,
.getClipboardText = getClipboardText,
.getPerformanceCounter = getPerformanceCounter,
.getPerformanceFrequency = getPerformanceFrequency,
2018-02-07 16:21:24 +01:00
2018-02-14 13:14:05 +01:00
.getUrlRequest = getUrlRequest,
2018-02-08 12:26:17 +01:00
2018-02-14 13:14:05 +01:00
.fileDialogLoad = file_dialog_load,
.fileDialogSave = file_dialog_save,
2018-02-13 10:44:16 +01:00
2018-02-14 13:14:05 +01:00
.goFullscreen = goFullscreen,
.showMessageBox = showMessageBox,
.setWindowTitle = setWindowTitle,
2018-02-13 17:39:26 +01:00
2018-02-14 13:14:05 +01:00
.openSystemPath = openSystemPath,
2018-02-14 14:22:33 +01:00
.preseed = preseed,
2018-02-15 15:06:03 +01:00
.poll = pollEvent,
2018-02-07 16:21:24 +01:00
};
2018-02-14 11:58:46 +01:00
#if defined(__EMSCRIPTEN__)
static void emstick()
{
static double nextTick = -1.0;
platform.missedFrame = false;
if(nextTick < 0.0)
nextTick = emscripten_get_now();
nextTick += 1000.0/TIC_FRAMERATE;
tick();
double delay = nextTick - emscripten_get_now();
if(delay < 0.0)
{
nextTick -= delay;
platform.missedFrame = true;
}
else
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, delay);
}
#endif
static s32 start(s32 argc, char **argv, const char* folder)
2018-01-24 10:20:05 +01:00
{
SDL_SetHint(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, "1");
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
initSound();
2018-02-14 12:31:10 +01:00
platform.net = createNet();
2018-01-24 10:20:05 +01:00
platform.window = SDL_CreateWindow( TIC_TITLE, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
(TIC80_FULLWIDTH) * STUDIO_UI_SCALE,
(TIC80_FULLHEIGHT) * STUDIO_UI_SCALE,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
#if defined(__CHIP__)
| SDL_WINDOW_FULLSCREEN_DESKTOP
#endif
);
2018-02-14 13:14:05 +01:00
platform.studio = studioInit(argc, argv, platform.audio.spec.freq, folder, &systemInterface);
2018-02-13 11:28:05 +01:00
2018-01-24 10:20:05 +01:00
// set the window icon before renderer is created (issues on Linux)
setWindowIcon();
platform.renderer = SDL_CreateRenderer(platform.window, -1,
#if defined(__CHIP__)
SDL_RENDERER_SOFTWARE
#else
2018-02-14 13:30:13 +01:00
SDL_RENDERER_ACCELERATED | (platform.studio->config()->useVsync ? SDL_RENDERER_PRESENTVSYNC : 0)
2018-01-24 10:20:05 +01:00
#endif
);
platform.texture = SDL_CreateTexture(platform.renderer, STUDIO_PIXEL_FORMAT, SDL_TEXTUREACCESS_STREAMING, TEXTURE_SIZE, TEXTURE_SIZE);
2018-01-24 15:56:55 +01:00
initTouchGamepad();
2018-01-24 10:20:05 +01:00
#if defined(__EMSCRIPTEN__)
2018-02-06 18:05:31 +01:00
2018-02-14 13:30:13 +01:00
emscripten_set_main_loop(platform.studio->config()->useVsync ? tick : emstick, 0, 1);
2018-02-14 11:58:46 +01:00
2018-01-24 10:20:05 +01:00
#else
{
u64 nextTick = SDL_GetPerformanceCounter();
const u64 Delta = SDL_GetPerformanceFrequency() / TIC_FRAMERATE;
2018-01-24 17:09:22 +01:00
while (!platform.studio->quit)
2018-01-24 10:20:05 +01:00
{
platform.missedFrame = false;
nextTick += Delta;
tick();
{
s64 delay = nextTick - SDL_GetPerformanceCounter();
if(delay < 0)
{
nextTick -= delay;
platform.missedFrame = true;
}
2018-02-06 18:05:31 +01:00
else SDL_Delay((u32)(delay * 1000 / SDL_GetPerformanceFrequency()));
2018-01-24 10:20:05 +01:00
}
}
}
#endif
2018-02-14 13:14:05 +01:00
platform.studio->close();
2018-01-24 10:20:05 +01:00
2018-02-14 12:31:10 +01:00
closeNet(platform.net);
2018-01-24 12:01:19 +01:00
if(platform.audio.cvt.buf)
SDL_free(platform.audio.cvt.buf);
2018-02-13 17:25:39 +01:00
if(platform.mouse.texture)
SDL_DestroyTexture(platform.mouse.texture);
2018-01-24 12:55:13 +01:00
2018-01-24 15:56:55 +01:00
SDL_DestroyTexture(platform.gamepad.texture);
2018-01-24 10:20:05 +01:00
SDL_DestroyTexture(platform.texture);
SDL_DestroyRenderer(platform.renderer);
SDL_DestroyWindow(platform.window);
SDL_CloseAudioDevice(platform.audio.device);
return 0;
}
2018-02-14 11:58:46 +01:00
#if defined(__EMSCRIPTEN__)
#define DEFAULT_CART "cart.tic"
static struct
{
s32 argc;
char **argv;
const char* folder;
} startVars;
static void onEmscriptenWget(const char* file)
{
startVars.argv[1] = DEFAULT_CART;
start(startVars.argc, startVars.argv, startVars.folder);
}
static void onEmscriptenWgetError(const char* error) {}
static void emsStart(s32 argc, char **argv, const char* folder)
{
if(argc == 2)
{
startVars.argc = argc;
startVars.argv = argv;
startVars.folder = folder;
emscripten_async_wget(argv[1], DEFAULT_CART, onEmscriptenWget, onEmscriptenWgetError);
}
else start(argc, argv, folder);
}
#endif
s32 main(s32 argc, char **argv)
{
const char* folder = getAppFolder();
#if defined(__EMSCRIPTEN__)
EM_ASM_
(
{
var dir = "";
Module.Pointer_stringify($0).split("/").forEach(function(val)
{
if(val.length)
{
dir += "/" + val;
FS.mkdir(dir);
}
});
FS.mount(IDBFS, {}, dir);
FS.syncfs(true, function()
{
Runtime.dynCall('viii', $1, [$2, $3, $0]);
});
}, folder, emsStart, argc, argv
);
#else
return start(argc, argv, folder);
#endif
}