From 9535e202b9e7017503a2e1e722ef92c1604eeeba Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Mon, 30 Mar 2020 20:20:40 -0500 Subject: [PATCH] First working menu script. --- .gitignore | 2 + singe/Framework.singe | 14 ++++++ singe/Manual.odt | 4 +- singe/Menu.singe | 113 +++++++++++++++++++++++++++++++++--------- singe/buildRelease.sh | 2 +- singe/embedded.h | 1 + singe/main.c | 26 ++++++++-- singe/preBuild.sh | 3 ++ singe/singe.c | 33 ++++++++---- singe/singe.pro | 1 + singe/util.c | 14 ++++++ singe/util.h | 1 + 12 files changed, 175 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 7e8d07263..dd4a3b07e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ Makefile.in singe/menuBackground.mkv singe/menuBackground_mkv.h singe/Menu_singe.h +singe/FreeSansBold_ttf.h +singe/Manual_pdf.h diff --git a/singe/Framework.singe b/singe/Framework.singe index 7e65e01e3..d6604dc40 100644 --- a/singe/Framework.singe +++ b/singe/Framework.singe @@ -56,6 +56,20 @@ function utilDump(o) end +function utilGetTableSize(t) + local count = 0 + for _, __ in pairs(t) do + count = count + 1 + end + return count +end + + +function utilTrim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + + SCANCODE = { A = { name = "A", value = 4 }, B = { name = "B", value = 5 }, diff --git a/singe/Manual.odt b/singe/Manual.odt index e37674188..2ece389e5 100644 --- a/singe/Manual.odt +++ b/singe/Manual.odt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d129eae8e8ca6f0263e20b679c14005a1b05de2af3a6173078dfc664cda866ce -size 13701 +oid sha256:1e736b38a360a6f5fe56e9dd5dbdc17922337a0043b2710329037e105440bf7f +size 13740 diff --git a/singe/Menu.singe b/singe/Menu.singe index b5fbef902..b3f59d5f5 100644 --- a/singe/Menu.singe +++ b/singe/Menu.singe @@ -29,28 +29,13 @@ function compareTitles(a, b) end -function box(x1, y1, x2, y2) - - overlayLine(x1, y1, x2, y1) - overlayLine(x2, y1, x2, y2) - overlayLine(x2, y2, x1, y2) - overlayLine(x1, y2, x1, y1) - -end - - --- Remove whitespace from string -function trim(s) - return (s:gsub("^%s*(.-)%s*$", "%1")) -end - - function wrapText(text, maxWidth) local words = {} local line = "" local lastLine = "" local lastWord = "" local newLine = false + local trimBreak = utilTrim(WRAP_BREAK) -- Break input into words for w in text:gmatch("%S+") do @@ -66,14 +51,26 @@ function wrapText(text, maxWidth) line = lastWord newLine = false end - line = trim(line .. " " .. word) + line = utilTrim(line .. " " .. word) -- Create a temporary sprite to see how wide this is if string.len(line) > 0 then spriteTemp = fontToSprite(line) - if spriteGetWidth(spriteTemp) > maxWidth then + if spriteGetWidth(spriteTemp) > maxWidth or word == trimBreak then + -- Was the wrap forced? + if word == trimBreak then + word = "" + if spriteGetWidth(spriteTemp) <= maxWidth then + lastLine = lastLine .. lastWord + end + end -- We wrapped - Create sprite from this line before the last word was added - table.insert(TEXT_SPRITE_LIST, fontToSprite(lastLine)) + if string.len(lastLine) > 0 then + table.insert(TEXT_SPRITE_LIST, fontToSprite(lastLine)) + else + -- Blank line? + table.insert(TEXT_SPRITE_LIST, -1) + end -- Get ready for the next line line = "" lastWord = word @@ -102,7 +99,9 @@ function loadGameAssets(firstGame) spriteUnload(SPRITE_MARQUEE) videoUnload(VIDEO_ATTRACT) for _, handle in ipairs(TEXT_SPRITE_LIST) do - spriteUnload(handle) + if handle >= 0 then + spriteUnload(handle) + end end TEXT_SPRITE_LIST = {} end @@ -113,13 +112,38 @@ function loadGameAssets(firstGame) videoPlay(VIDEO_ATTRACT) videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START) videoSetVolume(VIDEO_ATTRACT, 0, 0) - wrapText(GAME_LIST[GAME_SELECTED].DESCRIPTION, TEXT_W) + + -- Build text sprites + local textBox = GAME_LIST[GAME_SELECTED].DESCRIPTION .. WRAP_BREAK .. WRAP_BREAK .. + "Year: " .. GAME_LIST[GAME_SELECTED].YEAR .. WRAP_BREAK .. + "Genere: " .. GAME_LIST[GAME_SELECTED].GENERE .. WRAP_BREAK .. + "Platform: " .. GAME_LIST[GAME_SELECTED].PLATFORM .. WRAP_BREAK .. + "Developer: " .. GAME_LIST[GAME_SELECTED].DEVELOPER .. WRAP_BREAK .. + "Publisher: " .. GAME_LIST[GAME_SELECTED].PUBLISHER .. WRAP_BREAK .. WRAP_BREAK .. + "Singe Port: " .. GAME_LIST[GAME_SELECTED].CREATOR .. WRAP_BREAK .. + "Source: " .. GAME_LIST[GAME_SELECTED].SOURCE + wrapText(textBox, TEXT_W) + + TEXT_LINE_COUNT = utilGetTableSize(TEXT_SPRITE_LIST) + TEXT_LINE_TOP = 1 end function onInputPressed(what) + if what == SWITCH_UP then + if TEXT_LINE_TOP > 1 then + TEXT_LINE_TOP = TEXT_LINE_TOP - 1 + end + end + + if what == SWITCH_DOWN then + if TEXT_LINE_TOP < TEXT_LINE_COUNT - TEXT_LINE_LIMIT + 1 then + TEXT_LINE_TOP = TEXT_LINE_TOP + 1 + end + end + if what == SWITCH_LEFT then GAME_SELECTED = GAME_SELECTED - 1 if GAME_SELECTED < 1 then @@ -137,6 +161,11 @@ function onInputPressed(what) end if what == SWITCH_START1 or what == SWITCH_START2 or what == SWITCH_BUTTON1 or what == SWITCH_BUTTON2 or what == SWITCH_BUTTON3 or what == SWITCH_BUTTON4 then + -- Save what game we're currently viewing + local cfg = io.open(CONFIG_FILE, "w") + cfg:write("GAME_SELECTED = " .. GAME_SELECTED .. "\n") + cfg:close() + -- Start next game scriptPush(GAME_LIST[GAME_SELECTED]) end @@ -147,6 +176,8 @@ function onOverlayUpdate() local x = 0 local y = 0 + local c = 0 + local t = 0 overlayClear() @@ -169,9 +200,27 @@ function onOverlayUpdate() -- Game Description colorForeground(255, 255, 255, 255) y = TEXT_Y + c = 0 + t = 1 for _, handle in ipairs(TEXT_SPRITE_LIST) do - spriteDraw(TEXT_X, y, handle) - y = y + spriteGetHeight(handle) + 1 + -- Find height of font and number of lines that fit + if TEXT_LINE_HEIGHT == 0 then + if (handle >= 0) then + TEXT_LINE_HEIGHT = spriteGetHeight(handle) + TEXT_LINE_LIMIT = math.floor(TEXT_H / TEXT_LINE_HEIGHT) + end + end + -- Only display what is visible in the window + if (t >= TEXT_LINE_TOP) then + if (c < TEXT_LINE_LIMIT) then + if (handle >= 0) then + spriteDraw(TEXT_X, y, handle) + end + y = y + TEXT_LINE_HEIGHT + 1 + c = c + 1 + end + end + t = t + 1 end return(OVERLAY_UPDATED) @@ -252,8 +301,24 @@ debugPrint(" Video is " .. VIDEO_W .. "x" .. VIDEO_H) debugPrint(" Text is " .. TEXT_W .. "x" .. TEXT_H) --]] +WRAP_BREAK = " [!wb!] " +TEXT_LINE_TOP = 1 +TEXT_LINE_LIMIT = 0 +TEXT_LINE_COUNT = 0 +TEXT_LINE_HEIGHT = 0 TEXT_SPRITE_LIST = {} +-- Load configuration +CONFIG_FILE = singeGetDataPath() .. "menu.dat" +local confattr = lfs.attributes(CONFIG_FILE) +if confattr then + dofile(CONFIG_FILE) + if GAME_SELECTED > GAME_COUNT then + GAME_SELECTED = GAME_COUNT + end +else + GAME_SELECTED = 1 +end + -- Prime the pump -GAME_SELECTED = 1 loadGameAssets(true) diff --git a/singe/buildRelease.sh b/singe/buildRelease.sh index fe62a8c87..68fdc6e6c 100755 --- a/singe/buildRelease.sh +++ b/singe/buildRelease.sh @@ -65,7 +65,7 @@ function doBuild() { echo "Compressing ${TARGET}..." ${CROSS}-strip "${TARGET}" - upx -q -9 "${TARGET}" + upx -9 "${TARGET}" popd } diff --git a/singe/embedded.h b/singe/embedded.h index 6b89c7d46..fe4ddbb90 100644 --- a/singe/embedded.h +++ b/singe/embedded.h @@ -34,6 +34,7 @@ #include "Framework_singe.h" #include "controls_cfg.h" #include "Menu_singe.h" +#include "FreeSansBold_ttf.h" #include "menuBackground_mkv.h" #include "Manual_pdf.h" diff --git a/singe/main.c b/singe/main.c index b38bc20df..4fd8af456 100644 --- a/singe/main.c +++ b/singe/main.c @@ -440,7 +440,7 @@ bool extractFile(char *filename, unsigned char *data, int32_t length) { if (!out) utilDie("Unable to create %s", filename); fwrite(data, length, 1, out); fclose(out); - utilSay("Created File: %s", filename); + utilSay(">>> Created File: %s", filename); return true; } @@ -652,6 +652,7 @@ void queueScript(ConfigT *conf) { __attribute__((noreturn)) void showUsage(char *name, char *message) { char *temp = NULL; + char *data = NULL; bool created = false; int32_t result = 0; @@ -700,17 +701,36 @@ void showUsage(char *name, char *message) { free(temp); // Singe/Manual.pdf temp = utilCreateString("Singe%Manual.pdf", utilGetPathSeparator()); - created |= extractFile(temp, Manual_pdf, Manual_pdf_len); + //created |= extractFile(temp, Manual_pdf, Manual_pdf_len); free(temp); // Singe/Menu.singe temp = utilCreateString("Singe%cMenu.singe", utilGetPathSeparator()); created |= extractFile(temp, Menu_singe, Menu_singe_len); free(temp); + // Singe/FreeSansBold.ttf + temp = utilCreateString("Singe%cFreeSansBold.ttf", utilGetPathSeparator()); + created |= extractFile(temp, FreeSansBold_ttf, FreeSansBold_ttf_len); + free(temp); // Singe/menuBackground.mkv temp = utilCreateString("Singe%cmenuBackground.mkv", utilGetPathSeparator()); created |= extractFile(temp, menuBackground_mkv, menuBackground_mkv_len); free(temp); + // Script to start menu system + if (utilGetPathSeparator() == '/') { + // Unix-ish + temp = utilCreateString("Menu.sh"); + data = utilCreateString("#!/bin/bash\n\n./%s -k -x 720 -y 480 -d data -v Singe/menuBackground.mkv Singe/Menu.singe\n", utilGetLastPathComponent(name)); + } else { + // Winders + temp = utilCreateString("Menu.bat"); + data = utilCreateString("@echo off\n\r\n\r%s -k -x 720 -y 480 -d data -v Singe\\menuBackground.mkv Singe\\Menu.singe\n\r", utilGetLastPathComponent(name)); + } + created |= extractFile(temp, (unsigned char *)data, strlen(data)); + utilChMod(temp, 0777); + free(data); + data = NULL; + temp = NULL; if (created) utilSay(""); @@ -741,9 +761,9 @@ int main(int argc, char *argv[]) { while (_scriptQueue) { q = _scriptQueue; conf = (ConfigT *)&q->conf; + LL_DELETE(_scriptQueue, q); launcher(exeName, conf); destroyConf(&conf); - LL_DELETE(_scriptQueue, q); } return 0; diff --git a/singe/preBuild.sh b/singe/preBuild.sh index c6f76f9d1..fa1657b1c 100755 --- a/singe/preBuild.sh +++ b/singe/preBuild.sh @@ -589,6 +589,9 @@ createEmbeddedBinary controls.cfg controls_cfg.h CONTROLS_CFG_H # === Singe Menu App === createEmbeddedBinary Menu.singe Menu_singe.h MENU_SINGE_H +# === Singe Menu Font === +createEmbeddedBinary FreeSansBold.ttf FreeSansBold_ttf.h FREESANSBOLD_TTF_H + # === Singe Menu Background Video === if [[ ! -f menuBackground_mkv.h ]]; then ffmpeg -i 180503_01_PurpleGrid.mp4 -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a copy menuBackground.mkv diff --git a/singe/singe.c b/singe/singe.c index 72afe628c..967633ce1 100644 --- a/singe/singe.c +++ b/singe/singe.c @@ -280,6 +280,7 @@ int32_t apiScriptPush(lua_State *L); int32_t apiSingeDisablePauseKey(lua_State *L); int32_t apiSingeEnablePauseKey(lua_State *L); +int32_t apiSingeGetDataPath(lua_State *L); int32_t apiSingeGetPauseFlag(lua_State *L); int32_t apiSingeGetScriptPath(lua_State *L); int32_t apiSingeSetGameName(lua_State *L); @@ -2131,9 +2132,14 @@ int32_t apiVideoLoad(lua_State *L) { name = lua_tostring(L, 1); // Create data directory based on video path. data = utilCreateString("%s%s", _global.conf.dataDirBase, utilGetUpToLastPathComponent((char *)name)); + // Be sure it exists. + utilMkDirP(data, 0777); + if (!utilPathExists(data)) { + luaDie(L, "videoLoad", "Unable to create data directory: %s", data); + } + // Load this video. video = (VideoT *)calloc(1, sizeof(VideoT)); if (!video) luaDie(L, "videoLoad", "Unable to allocate new video."); - // Load this video. video->handle = videoLoad((char *)name, data, false, _global.renderer); if (video->handle < 0) luaDie(L, "videoLoad", "Failed to load video: %s", name); video->id = _global.nextVideoId; @@ -2674,6 +2680,13 @@ int32_t apiSingeVersion(lua_State *L) { } +int32_t apiSingeGetDataPath(lua_State *L) { + luaTrace(L, "singeGetDataPath", "%s", _global.conf.dataDir); + lua_pushstring(L, _global.conf.dataDir); + return 1; +} + + int32_t apiSingeSetGameName(lua_State *L) { // Adds the name of the singe game to the window's title bar. // Valid value is a string no longer than 25 characters. @@ -2709,13 +2722,7 @@ int32_t apiSingeSetGameName(lua_State *L) { int32_t apiSingeGetScriptPath(lua_State *L) { - // Returns the path to the singe script. - // e.g. lua code, - // - // sGameDirectory = singeGetScriptPath() - // - - luaTrace(L, "singeSetScriptPath", "%s", _global.conf.scriptFile); + luaTrace(L, "singeGetScriptPath", "%s", _global.conf.scriptFile); lua_pushstring(L, _global.conf.scriptFile); return 1; } @@ -3313,7 +3320,6 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Set up globals memset(&_global, 0, sizeof(GlobalT)); - memcpy(&_global.conf, conf, sizeof(ConfigT)); _global.colorForeground.r = 255; _global.colorForeground.g = 255; _global.colorForeground.b = 255; @@ -3331,6 +3337,14 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { _global.discStopped = true; _global.mouseEnabled = true; + // Deep copy conf + memcpy(&_global.conf, conf, sizeof(ConfigT)); + _global.conf.dataDir = strdup(conf->dataDir); + _global.conf.dataDirBase = strdup(conf->dataDirBase); + _global.conf.scriptFile = strdup(conf->scriptFile); + _global.conf.videoFile = strdup(conf->videoFile); + + // Input mappings _global.controlMappings[INPUT_UP].name = "INPUT_UP"; _global.controlMappings[INPUT_LEFT].name = "INPUT_LEFT"; _global.controlMappings[INPUT_DOWN].name = "INPUT_DOWN"; @@ -3510,6 +3524,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { lua_register(_global.luaContext, "singeDisablePauseKey", apiSingeDisablePauseKey); lua_register(_global.luaContext, "singeEnablePauseKey", apiSingeEnablePauseKey); + lua_register(_global.luaContext, "singeGetDataPath", apiSingeGetDataPath); lua_register(_global.luaContext, "singeGetHeight", apiDaphneGetHeight); lua_register(_global.luaContext, "singeGetPauseFlag", apiSingeGetPauseFlag); lua_register(_global.luaContext, "singeGetScriptPath", apiSingeGetScriptPath); diff --git a/singe/singe.pro b/singe/singe.pro index 704b29636..65af7b260 100644 --- a/singe/singe.pro +++ b/singe/singe.pro @@ -156,6 +156,7 @@ HEADERS += \ indexing.h \ controls_cfg.h \ Menu_singe.h \ + FreeSansBold_ttf.h \ menuBackground_mkv.h \ Manual_pdf.h diff --git a/singe/util.c b/singe/util.c index 3e248b969..69fe8ef34 100644 --- a/singe/util.c +++ b/singe/util.c @@ -46,6 +46,20 @@ static bool _consoleEnabled = true; static FILE *_utilTraceFile = NULL; +bool utilChMod(const char *path, const mode_t mode) { + bool result = true; + +#ifdef _WIN32 + (void)path; + (void)mode; +#else + result = (chmod(path, mode) >= 0); +#endif + + return result; +} + + char *utilCreateString(char *format, ...) { va_list args; char *string; diff --git a/singe/util.h b/singe/util.h index 46e490bda..738b365cf 100644 --- a/singe/util.h +++ b/singe/util.h @@ -35,6 +35,7 @@ #define UTIL_PATH_MAX 1024 +bool utilChMod(const char *path, const mode_t mode); char *utilCreateString(char *format, ...); char *utilCreateStringVArgs(char *format, va_list args); void utilDie(char *fmt, ...);