TIC-80-guile/src/menu.c

533 lines
12 KiB
C

// MIT License
// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "menu.h"
#include "fs.h"
#define DIALOG_WIDTH (TIC80_WIDTH/2)
#define DIALOG_HEIGHT (TIC80_HEIGHT/2)
static const char* Rows[] =
{
"RESUME GAME",
"RESET GAME",
"GAMEPAD CONFIG",
"",
"CLOSE GAME",
"QUIT TIC-80",
};
static void resumeGame(Menu* menu)
{
hideGameMenu();
}
static void resetGame(Menu* menu)
{
tic_mem* tic = menu->tic;
tic->api.reset(tic);
setStudioMode(TIC_RUN_MODE);
}
static void gamepadConfig(Menu* menu)
{
playSystemSfx(2);
menu->mode = GAMEPAD_MENU_MODE;
menu->gamepad.tab = 0;
}
static void closeGame(Menu* menu)
{
exitFromGameMenu();
}
static void(*const MenuHandlers[])(Menu*) = {resumeGame, resetGame, gamepadConfig, NULL, closeGame, exitStudio};
static tic_rect getRect(Menu* menu)
{
tic_rect rect = {(TIC80_WIDTH - DIALOG_WIDTH)/2, (TIC80_HEIGHT - DIALOG_HEIGHT)/2, DIALOG_WIDTH, DIALOG_HEIGHT};
rect.x -= menu->pos.x;
rect.y -= menu->pos.y;
return rect;
}
static void drawDialog(Menu* menu)
{
tic_rect rect = getRect(menu);
tic_mem* tic = menu->tic;
tic_rect header = {rect.x, rect.y-(TOOLBAR_SIZE-1), rect.w, TOOLBAR_SIZE};
if(checkMousePos(&header))
{
setCursor(tic_cursor_hand);
if(checkMouseDown(&header, tic_mouse_left))
{
if(!menu->drag.active)
{
menu->drag.start.x = getMouseX() + menu->pos.x;
menu->drag.start.y = getMouseY() + menu->pos.y;
menu->drag.active = true;
}
}
}
if(menu->drag.active)
{
setCursor(tic_cursor_hand);
menu->pos.x = menu->drag.start.x - getMouseX();
menu->pos.y = menu->drag.start.y - getMouseY();
tic_rect rect = {0, 0, TIC80_WIDTH, TIC80_HEIGHT};
if(!checkMouseDown(&rect, tic_mouse_left))
menu->drag.active = false;
}
rect = getRect(menu);
tic->api.rect(tic, rect.x, rect.y, rect.w, rect.h, (tic_color_blue));
tic->api.rect_border(tic, rect.x, rect.y, rect.w, rect.h, (tic_color_white));
tic->api.line(tic, rect.x, rect.y+DIALOG_HEIGHT, rect.x+DIALOG_WIDTH-1, rect.y+DIALOG_HEIGHT, (tic_color_black));
tic->api.rect(tic, rect.x, rect.y-(TOOLBAR_SIZE-2), rect.w, TOOLBAR_SIZE-2, (tic_color_white));
tic->api.line(tic, rect.x+1, rect.y-(TOOLBAR_SIZE-1), rect.x+DIALOG_WIDTH-2, rect.y-(TOOLBAR_SIZE-1), (tic_color_white));
{
static const char Label[] = "GAME MENU";
s32 size = tic->api.text(tic, Label, 0, -TIC_FONT_HEIGHT, 0, false);
tic->api.text(tic, Label, rect.x + (DIALOG_WIDTH - size)/2, rect.y-(TOOLBAR_SIZE-2), (tic_color_gray), false);
}
{
u8 chromakey = 14;
tic->api.sprite_ex(tic, &getConfig()->cart->bank0.tiles, 0, rect.x+6, rect.y-4, 2, 2, &chromakey, 1, 1, tic_no_flip, tic_no_rotate);
}
}
static void drawTabDisabled(Menu* menu, s32 x, s32 y, s32 id)
{
enum{Width = 15, Height = 7};
tic_mem* tic = menu->tic;
tic_rect rect = {x, y, Width, Height};
bool over = false;
if(menu->gamepad.tab != id && checkMousePos(&rect))
{
setCursor(tic_cursor_hand);
over = true;
if(checkMouseDown(&rect, tic_mouse_left))
{
menu->gamepad.tab = id;
menu->gamepad.selected = -1;
return;
}
}
tic->api.rect(tic, x, y-1, Width, Height+1, (tic_color_dark_gray));
tic->api.pixel(tic, x, y+Height-1, (tic_color_blue));
tic->api.pixel(tic, x+Width-1, y+Height-1, (tic_color_blue));
{
char buf[] = "#1";
sprintf(buf, "#%i", id+1);
tic->api.fixed_text(tic, buf, x+2, y, (over ? tic_color_light_blue : tic_color_gray), false);
}
}
static void drawTab(Menu* menu, s32 x, s32 y, s32 id)
{
enum{Width = 15, Height = 7};
tic_mem* tic = menu->tic;
tic->api.rect(tic, x, y-2, Width, Height+2, (tic_color_white));
tic->api.pixel(tic, x, y+Height-1, (tic_color_black));
tic->api.pixel(tic, x+Width-1, y+Height-1, (tic_color_black));
tic->api.rect(tic, x+1, y+Height, Width-2 , 1, (tic_color_black));
{
char buf[] = "#1";
sprintf(buf, "#%i", id+1);
tic->api.fixed_text(tic, buf, x+2, y, (tic_color_gray), false);
}
}
static void drawPlayerButtons(Menu* menu, s32 x, s32 y)
{
tic_mem* tic = menu->tic;
u8 chromakey = 0;
tic_key* codes = getKeymap();
enum {Width = 41, Height = TIC_SPRITESIZE, Rows = 4, Cols = 2, MaxChars = 5, Buttons = 8};
for(s32 i = 0; i < Buttons; i++)
{
tic_rect rect = {x + i / Rows * (Width+2), y + (i%Rows)*(Height+1), Width, TIC_SPRITESIZE};
bool over = false;
s32 index = i+menu->gamepad.tab * Buttons;
if(checkMousePos(&rect))
{
setCursor(tic_cursor_hand);
over = true;
if(checkMouseClick(&rect, tic_mouse_left))
{
menu->gamepad.selected = menu->gamepad.selected != index ? index : -1;
}
}
if(menu->gamepad.selected == index && menu->ticks % TIC_FRAMERATE < TIC_FRAMERATE / 2)
continue;
tic->api.sprite_ex(tic, &getConfig()->cart->bank0.tiles, 8+i, rect.x, rect.y, 1, 1, &chromakey, 1, 1, tic_no_flip, tic_no_rotate);
static const char* const Names[tic_keys_count] = {"...", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "minus", "equals", "leftbracket", "rightbracket", "backslash", "semicolon", "apostrophe", "grave", "comma", "period", "slash", "space", "tab", "return", "backspace", "delete", "insert", "pageup", "pagedown", "home", "end", "up", "down", "left", "right", "capslock", "ctrl", "shift", "alt", "escape", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12"};
s32 code = codes[index];
char label[32];
strcpy(label, Names[code]);
if(strlen(label) > MaxChars)
label[MaxChars] = '\0';
tic->api.text(tic, label, rect.x+10, rect.y+2, (over ? tic_color_gray : tic_color_black), false);
}
}
static void drawGamepadSetupTabs(Menu* menu, s32 x, s32 y)
{
enum{Width = 90, Height = 41, Tabs = TIC_GAMEPADS, TabWidth = 16};
tic_mem* tic = menu->tic;
tic->api.rect(tic, x, y, Width, Height, (tic_color_white));
tic->api.pixel(tic, x, y, (tic_color_blue));
tic->api.pixel(tic, x+Width-1, y, (tic_color_blue));
tic->api.pixel(tic, x, y+Height-1, (tic_color_black));
tic->api.pixel(tic, x+Width-1, y+Height-1, (tic_color_black));
tic->api.rect(tic, x+1, y+Height, Width-2 , 1, (tic_color_black));
for(s32 i = 0; i < Tabs; i++)
(menu->gamepad.tab == i ? drawTab : drawTabDisabled)(menu, x + 73 - i*TabWidth, y + 43, i);
drawPlayerButtons(menu, x + 3, y + 3);
}
static void drawGamepadMenu(Menu* menu)
{
drawDialog(menu);
tic_mem* tic = menu->tic;
tic_rect dlgRect = getRect(menu);
static const char Label[] = "BACK";
tic_rect rect = {dlgRect.x + 25, dlgRect.y + 56, (sizeof(Label)-1)*TIC_FONT_WIDTH, TIC_FONT_HEIGHT};
bool over = false;
bool down = false;
if(checkMousePos(&rect))
{
setCursor(tic_cursor_hand);
over = true;
if(checkMouseDown(&rect, tic_mouse_left))
{
down = true;
}
if(checkMouseClick(&rect, tic_mouse_left))
{
menu->gamepad.selected = -1;
menu->mode = MAIN_MENU_MODE;
playSystemSfx(2);
return;
}
}
if(down)
{
tic->api.text(tic, Label, rect.x, rect.y+1, (tic_color_light_blue), false);
}
else
{
tic->api.text(tic, Label, rect.x, rect.y+1, (tic_color_black), false);
tic->api.text(tic, Label, rect.x, rect.y, (over ? tic_color_light_blue : tic_color_white), false);
}
{
static const u8 Icon[] =
{
0b10000000,
0b11000000,
0b11100000,
0b11000000,
0b10000000,
0b00000000,
0b00000000,
0b00000000,
};
drawBitIcon(rect.x-7, rect.y+1, Icon, (tic_color_black));
drawBitIcon(rect.x-7, rect.y, Icon, (tic_color_white));
}
drawGamepadSetupTabs(menu, dlgRect.x+25, dlgRect.y+4);
}
static void drawMainMenu(Menu* menu)
{
tic_mem* tic = menu->tic;
drawDialog(menu);
tic_rect rect = getRect(menu);
{
for(s32 i = 0; i < COUNT_OF(Rows); i++)
{
if(!*Rows[i])continue;
tic_rect label = {rect.x + 22, rect.y + (TIC_FONT_HEIGHT+1)*i + 16, 86, TIC_FONT_HEIGHT+1};
bool over = false;
bool down = false;
if(checkMousePos(&label))
{
setCursor(tic_cursor_hand);
over = true;
if(checkMouseDown(&label, tic_mouse_left))
{
down = true;
menu->main.focus = i;
}
if(checkMouseClick(&label, tic_mouse_left))
{
MenuHandlers[i](menu);
return;
}
}
if(down)
{
tic->api.text(tic, Rows[i], label.x, label.y+1, (tic_color_light_blue), false);
}
else
{
tic->api.text(tic, Rows[i], label.x, label.y+1, (tic_color_black), false);
tic->api.text(tic, Rows[i], label.x, label.y, (over ? tic_color_light_blue : tic_color_white), false);
}
if(i == menu->main.focus)
{
static const u8 Icon[] =
{
0b10000000,
0b11000000,
0b11100000,
0b11000000,
0b10000000,
0b00000000,
0b00000000,
0b00000000,
};
drawBitIcon(label.x-7, label.y+1, Icon, (tic_color_black));
drawBitIcon(label.x-7, label.y, Icon, (tic_color_white));
}
}
}
}
static void processGamedMenuGamepad(Menu* menu)
{
if(menu->gamepad.selected < 0)
{
enum
{
Up, Down, Left, Right, A, B, X, Y
};
if(menu->tic->api.btnp(menu->tic, A, -1, -1))
{
menu->mode = MAIN_MENU_MODE;
playSystemSfx(2);
}
}
}
static void processMainMenuGamepad(Menu* menu)
{
enum{Count = COUNT_OF(Rows), Hold = 30, Period = 5};
enum
{
Up, Down, Left, Right, A, B, X, Y
};
if(menu->tic->api.btnp(menu->tic, Up, Hold, Period))
{
do
{
if(--menu->main.focus < 0)
menu->main.focus = Count - 1;
menu->main.focus = menu->main.focus % Count;
} while(!*Rows[menu->main.focus]);
playSystemSfx(2);
}
if(menu->tic->api.btnp(menu->tic, Down, Hold, Period))
{
do
{
menu->main.focus = (menu->main.focus+1) % Count;
} while(!*Rows[menu->main.focus]);
playSystemSfx(2);
}
if(menu->tic->api.btnp(menu->tic, A, -1, -1))
{
MenuHandlers[menu->main.focus](menu);
}
}
static void saveMapping(Menu* menu)
{
fsSaveRootFile(menu->fs, KEYMAP_DAT_PATH, getKeymap(), KEYMAP_SIZE, true);
}
static void processKeyboard(Menu* menu)
{
tic_mem* tic = menu->tic;
if(tic->ram.input.keyboard.data == 0) return;
if(menu->gamepad.selected < 0)
return;
if(keyWasPressed(tic_key_escape));
else if(anyKeyWasPressed())
{
for(s32 i = 0; i < TIC80_KEY_BUFFER; i++)
{
tic_key key = tic->ram.input.keyboard.keys[i];
if(tic->api.keyp(tic, key, -1, -1))
{
tic_key* codes = getKeymap();
codes[menu->gamepad.selected] = key;
saveMapping(menu);
break;
}
}
}
menu->gamepad.selected = -1;
}
static void tick(Menu* menu)
{
menu->ticks++;
processKeyboard(menu);
if(getStudioMode() != TIC_MENU_MODE)
return;
if(!menu->init)
{
playSystemSfx(0);
menu->init = true;
}
memcpy(menu->tic->ram.vram.screen.data, menu->bg, sizeof menu->tic->ram.vram.screen.data);
switch(menu->mode)
{
case MAIN_MENU_MODE:
processMainMenuGamepad(menu);
drawMainMenu(menu);
break;
case GAMEPAD_MENU_MODE:
processGamedMenuGamepad(menu);
drawGamepadMenu(menu);
break;
}
}
void initMenu(Menu* menu, tic_mem* tic, FileSystem* fs)
{
*menu = (Menu)
{
.init = false,
.fs = fs,
.tic = tic,
.tick = tick,
.bg = menu->bg,
.ticks = 0,
.pos = {0, 0},
.main =
{
.focus = 0,
},
.gamepad =
{
.tab = 0,
.selected = -1,
},
.drag =
{
.start = {0, 0},
.active = 0,
},
.mode = MAIN_MENU_MODE,
};
enum{Size = sizeof tic->ram.vram.screen.data};
if(!menu->bg)
menu->bg = malloc(Size);
if(menu->bg)
memcpy(menu->bg, tic->ram.vram.screen.data, Size);
}