diff --git a/.gitignore b/.gitignore index 81b0c56..8500a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -dev -build/ -games/* -!games/.gitkeep +/.cache +/dev/ +/build/ +/games/* +!/games/.gitkeep diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f06b433 --- /dev/null +++ b/build.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +echo General packaging +rm -r build +mkdir build +cd build + +mv ../dev ../deva +lua ~/proj/luapack/main.lua ../luapack.lua +mv ../deva ../dev + +#cp -r lib skins ll-min.lua build/ + +zip -9 -r ../game.love . +rm * +mv ../game.love . + +echo Windows packaging +VER=11.4 +BITS=64 +ARCH=x86_64 +if [ ! -d ../.cache/love-$VER-win$BITS ]; then + mkdir -p ../.cache/love-$VER-win$BITS + cd ../.cache + wget https://github.com/love2d/love/releases/download/$VER/love-$VER-win$BITS.zip + unzip love-$VER-win$BITS.zip + cd - +fi +mkdir win && cd win +cp -r ../../.cache/love-$VER-win$BITS/* . +cat love.exe ../game.love > game.exe +rm love.exe lovec.exe *.ico *.txt +zip -r ../win.zip * +cd .. +rm -r win + +echo Linux packaging +if [ ! -e ../.cache/love-$VER-$ARCH.AppImage ]; then + cd ../.cache + wget https://github.com/love2d/love/releases/download/$VER/love-$VER-$ARCH.AppImage + chmod a+x love-$VER-$ARCH.AppImage + cd - +fi +if [ ! -e ../.cache/appimagetool-$ARCH.AppImage ]; then + cd ../.cache + wget https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-$ARCH.AppImage + chmod a+x appimagetool-$ARCH.AppImage + cd - +fi +mkdir lin && cd lin +../../.cache/love-$VER-$ARCH.AppImage --appimage-extract > /dev/null +cd squashfs-root +cat ../../game.love >> bin/love +chmod a+x bin/love +cd .. +../../.cache/appimagetool-$ARCH.AppImage squashfs-root ../linux.AppImage > /dev/null +rm -r squashfs-root +chmod a+x ../linux.AppImage +cd .. +rm -r lin + diff --git a/lib/chroot.lua b/lib/chroot.lua index f0abb42..c1e0061 100644 --- a/lib/chroot.lua +++ b/lib/chroot.lua @@ -3,33 +3,51 @@ return function(ll) ll.mdir = nil ll.mgme = nil -local ffi = require 'ffi' -ffi.cdef [[ +if love.getVersion() >= 12 then + function ll._mntdir(dir) + return love.filesystem.mountFullPath(dir, '/') + end + function ll._umntdir(dir) + love.filesystem.unmountFullPath(dir) + end +else + local ffi = require 'ffi' + local liblove = ffi.os == 'Windows' + and ffi.load 'love' + or ffi.C -- thanks to slime73, Love2D developer :) + + ffi.cdef [[ int PHYSFS_mount(const char *, const char *, int); - int PHYSFS_unmount(const char *); -]] + int PHYSFS_unmount(const char *);]] + + -- FIXME: Bug may appear in Linux. Recompile Love2D or use official PPA. + function ll._mntdir(dir) + return liblove.PHYSFS_mount(dir, '/', 0) ~= 0 + end + function ll._umntdir(dir) + liblove.PHYSFS_unmount(dir) + end +end local baseReq = '?.lua;?/init.lua;' function ll.mount(gme) local mdir = gme.base .. gme.dir - ll.mgme = gme - love.filesystem.setRequirePath('' - .. mdir .. '/?.lua;' + love.filesystem.setRequirePath( + mdir .. '/?.lua;' .. mdir .. '/?/init.lua;' .. baseReq ) - -- FIXME: Bug may appear in Linux. Recompile Love2D or use official PPA. - if ffi.C.PHYSFS_mount(mdir, '/', 0) == 0 - then error('Cannot mount '..mdir) + if ll._mntdir(mdir) + then ll.mdir, ll.mgme = mdir, gme + else error('Cannot mount '..mdir) love.filesystem.setRequirePath(baseReq) - else ll.mdir = mdir end end function ll.umount() if ll.mdir ~= nil then - ffi.C.PHYSFS_unmount(ll.mdir) + ll._umntdir(ll.mdir) ll.mdir = nil end end diff --git a/lib/keyb.lua b/lib/keyb.lua index e3c0719..84b2e52 100644 --- a/lib/keyb.lua +++ b/lib/keyb.lua @@ -1,13 +1,14 @@ return function(ll) local mx, my, mb, mpb -local dir, sc1, sc2, sclm +local dir, sc1, sc2, sclm1, sclm2 --- d - direction (h, v, x, y, *) --- c1 - coordinate before card (mouse) (be left or top) --- c2 - coordinate after card (mouse) (be right or bottom) --- clm - other coordinate limit (mouse) (set -1 to disable) -function ll.kbInit(d, c1, c2, clim) +-- d - direction (h, v, x, y, *) +-- c1 - coordinate before card (mouse) (be left or top) +-- c2 - coordinate after card (mouse) (be right or bottom) +-- clim1 - other coordinate limit before (mouse) (set -1 to disable) +-- clim2 - other coordinate limit after (mouse) (set -1 to disable) +function ll.kbInit(d, c1, c2, clim1, clim2) if d == 'h' or d == 'v' or d == '*' then dir = d elseif d == 'y' @@ -17,19 +18,37 @@ function ll.kbInit(d, c1, c2, clim) else error 'Direction must be *, h (x) or v (y)' end + ll.kbSetCC(c1, c2) + ll.kbSetOC(clim1, clim2) +end + +-- set card coordinate +-- c1 - coordinate before card (mouse) (be left or top) +-- c2 - coordinate after card (mouse) (be right or bottom) +function ll.kbSetCC(c1, c2) c1, c2 = tonumber(c1) or 0, tonumber(c2) or 0 - sc1, sc2, sclm = + + sc1, sc2 = math.min(c1, c2), - math.max(c1, c2), - tonumber(clim) or -1 + math.max(c1, c2) +end + +-- set other coordinate limit +-- clim1 - other coordinate limit before (mouse) (set -1 to disable) +-- clim2 - other coordinate limit after (mouse) (set -1 to disable) +function ll.kbSetOC(clim1, clim2) + sclm1, sclm2 = + tonumber(clim1) or -1, + tonumber(clim2) or -1 end -- returns: <, >, o, m, nil -- ^ and v if dir is * function ll.kbGet() - assert(dir, 'Call ll.kbInit(dir, coord1, coord2, coordlimit) before') + assert(dir, 'Call ll.kbInit() before') + assert(love, 'Use it with LÖVE engine') mx, my = love.mouse.getPosition() mpb = mb if love.mouse.isDown(1) then mb = 1 @@ -54,9 +73,10 @@ function ll.kbGet() elseif love.keyboard.isDown 'menu' then return 'm' - elseif mb == 0 and mpb == 1 then -- unpressed + elseif mb == 0 and mpb == 1 then -- mouse unpressed if dir == 'h' then - if sclm < 0 or my <= sclm then + if (sclm1 < 0 or my > sclm1) + and (sclm2 < 0 or my <= sclm2) then if mx <= sc1 then return '<' elseif mx >= sc2 @@ -65,7 +85,8 @@ function ll.kbGet() end end else - if sclm < 0 or mx <= sclm then + if (sclm1 < 0 or mx > sclm1) + and (sclm2 < 0 or mx <= sclm2) then if my <= sc1 then return '<' elseif my >= sc2 diff --git a/lib/main.lua b/lib/main.lua index dee7506..f119d65 100644 --- a/lib/main.lua +++ b/lib/main.lua @@ -4,24 +4,33 @@ ll.cfg = { root = 'games/', } -require 'lib.fs' (ll) -require 'lib.game' (ll) -require 'lib.chroot' (ll) -require 'lib.load' (ll) -require 'lib.keyb' (ll) +require 'lib.fs' (ll) +require 'lib.game' (ll) +if love then + require 'lib.chroot' (ll) + -- TODO: CLI interface? + require 'lib.load' (ll) +end +require 'lib.keyb' (ll) -- except ll.kbGet() function ll.home() + if not love then return end ll.umount() love.event.push('quit', 'restart') end ll.dt = false function ll.devtools() + if not love then return end + if not ll.dt then ll.dt = true __LL = ll pcall(function() require 'dev.tools' end) + return true end + return false end return ll + diff --git a/ll-min.lua b/ll-min.lua index eafd105..a47f01a 100644 --- a/ll-min.lua +++ b/ll-min.lua @@ -1,13 +1,35 @@ -- Minimal Love Loader API --- Version 2.1 +-- Version 3.0 -- (c) Er2 2022 -- Zlib License if not llUsed then COLDIV = love.getVersion() == 0 and 1 or 255 -MOBILE = love.system.getOS() == 'Android' +if love.system +then MOBILE = love.system.getOS() == 'Android' or love.system.getOS() == 'iOS' +else MOBILE = false +end + +function llHome() + love.event.push('quit', 'restart') +end + +if love.getVersion() >= 12 then + function love.graphics.stencil(fn) + love.graphics.setColorMask(false) + love.graphics.setStencilMode('replace', 'always', 1) + fn() + love.graphics.setColorMask(true) + end + function love.graphics.setStencilTest(cmp, val) + if cmp + then love.graphics.setStencilMode('keep', cmp, val) + else love.graphics.setStencilMode() + end + end +end if MOBILE then love.window.setFullscreen(true) @@ -22,9 +44,6 @@ function love.resize(x, y) end love.resize(love.graphics.getDimensions()) -function llHome() - love.event.push('quit') -end function love.event.quit() llHome() end diff --git a/luapack.lua b/luapack.lua index ab4dec7..b4787e4 100644 --- a/luapack.lua +++ b/luapack.lua @@ -1,6 +1,9 @@ return { - entry = 'main.lua', - output = 'build/main.lua', + entry = { + 'main.lua', + 'll-min.lua', + }, + output = 'build/$name.lua', plug = { require 'plug.minify' { extGlob = { diff --git a/main.lua b/main.lua index dd4e61c..6364fcd 100644 --- a/main.lua +++ b/main.lua @@ -1,7 +1,10 @@ local ll = require 'lib.main' error = love.errhand or love.errorhandler +ll.skin = require 'skins.nx' (ll) + function splash() + if not ll.mgme then return end love.graphics.setColor(255, 255, 255, 100 / COLDIV) if ll.mgme.screens and ll.mgme.screens[1] then local img = love.graphics.newImage(ll.mgme.screens[1]) @@ -11,16 +14,26 @@ function splash() love.graphics.print('Loading '..ll.mgme.name, W / 2, H / 2) end -ll.skin = require 'skins.psp' (ll) - require 'll-min' llUsed = true -if love.errorhandler +if love.getVersion() >= 12 then + function llHome() + ll.umount() + love.event.restart() + end +else + function llHome() + ll.umount() + love.event.push('quit', 'restart') + end +end +if love.getVersion() >= 11 then love.errorhandler = error else love.errhand = error end +local dt = love.timer and love.timer.step() or 0 local brk = false while not brk and not ll.mdir do -- event handling @@ -31,12 +44,29 @@ while not brk and not ll.mdir do then ll.umount() love.event.push('quit') brk = true; break + elseif n == 'filedropped' then + love.graphics.clear() + local x, y, g = 32, 32, 16 + local w, h = 84, 96 + love.graphics.rectangle('line', x, y, w, h, w / 8) + love.graphics.line(x + w / 1.5, y, x + w, y + h / 3) + if ll.skin.addGame then + a:open 'r' + ll.skin.addGame(a:getFilename(), a:read(), x + w + g, y) + else love.graphics.print('This skin does not support file dropping', x + w + g, y) + end + love.graphics.present() + love.timer.sleep(2) end love.handlers[n](a,b,c) end -- update and drawing - ll.skin.update() + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + ll.skin.update(dt) love.graphics.origin() love.graphics.clear(0, 0, 0) ll.skin.draw() diff --git a/skins/base.lua b/skins/base.lua index ca31268..a772dbe 100644 --- a/skins/base.lua +++ b/skins/base.lua @@ -86,6 +86,11 @@ local function ccp() return '' else return 'Not found' end + elseif cmd == 'devtools' then + if ll.devtools() + then return 'Enabled' + else return 'Already enabled' + end else return 'Unknown command "'.. cmd .. '"' end end @@ -117,6 +122,7 @@ local function update() end end +local oy = 0 local utf8 = require 'utf8' local function draw() love.graphics.setColor(255, 255, 255) @@ -132,15 +138,18 @@ local function draw() then x = x + 8 - (x % 8) elseif chr == '\v' then y = y + 1 - else love.graphics.print(chr, x * cw, y * ch) + else love.graphics.print(chr, x * cw, y * ch - oy) if x >= ctw then x, y = x - ctw, y + 1 else x = x + 1 end end end + if y * ch - oy >= H + then oy = oy + H / 4 + end if os.time() % 2 == 0 - then love.graphics.rectangle('fill', x * cw, y * ch, cw, ch) + then love.graphics.rectangle('fill', x * cw, y * ch - oy, cw, ch) end end diff --git a/skins/gui.lua b/skins/gui.lua index fdb2708..f817d66 100644 --- a/skins/gui.lua +++ b/skins/gui.lua @@ -1,9 +1,10 @@ return function(ll) -ll.cfg.pcht = ll.cfg.pcht or 60 * 5 +ll.cfg.pcht = ll.cfg.pcht or 60 * 10 local pikchv, pikcha = 0, 0 local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5) +local bg = 0 local cx, cy, cw, ch local f, bf @@ -25,7 +26,7 @@ function resize() ll.kbInit('h', cx, cx + cw) end -local function update() +local function update(dt) local sdi = ll.kbGet() if cdir ~= sdi then @@ -46,8 +47,8 @@ local function update() if sel > #ll.games then sel = 1 end end - pikchv = pikchv + 1 - if pikchv >= ll.cfg.pcht then + pikchv = pikchv + dt + if pikchv >= ll.cfg.pcht / 100 then pikchv = 0 pikcha = 0 for _, v in pairs(ll.games) do @@ -59,11 +60,12 @@ local function update() end end end - else pikcha = math.min(255, pikcha + pikchao) + else pikcha = math.min(255, pikcha + pikchao * 100 * dt) end + + bg = (bg + dt) % 6.28 end -local tm = 0 local function draw() love.graphics.setColor(0 / COLDIV, 50 / COLDIV, 75 / COLDIV) love.graphics.rectangle('fill', 0, 0, W, H) @@ -74,8 +76,7 @@ local function draw() local tw, th = bf:getWidth(t), bf:getHeight(t) love.graphics.print(t, W - tw - 8, H - th) - tm = (tm + 0.02) % 6.28 - local c, pc, ps = tm, math.cos(tm), math.sin(tm) + local c, pc, ps = bg, math.cos(bg), math.sin(bg) local oy = H / 2 for x = 0, W + 8, 8 do c = c + 0.1 diff --git a/skins/nx.lua b/skins/nx.lua new file mode 100644 index 0000000..1bbf750 --- /dev/null +++ b/skins/nx.lua @@ -0,0 +1,71 @@ +return function(ll) + +-- Nintendo NX (AKA Switch) UI + +local r + +os.setlocale('', 'time') + +function resize() + r = math.min(W, H) / 30 + + f = love.graphics.setNewFont(math.min(W, H) / 30) + bf = love.graphics.newFont(math.min(W, H) / 20) + + --ll.kbInit('h', H / 2 - ch, H / 2 + ch + cg, 0, cx + cw * 2) +end + +local function update(dt) +end + +local function roundRect(m, x, y, w, h, r) + if w < 1 or h < 1 then return end + + love.graphics.rectangle(m, x, y, w, h, r) + + if m == 'fill' + then roundRect('line', x, y, w, h, r) + end +end + +local function draw() + love.graphics.setColor(0 / COLDIV, 50 / COLDIV, 75 / COLDIV) + love.graphics.rectangle('fill', 0, 0, W, H) + love.graphics.setColor(255, 255, 255) + -- Indicators + love.graphics.push() + love.graphics.translate(W - W / 16, H / 16) + love.graphics.setLineWidth(r / 8) + local pst, pper = love.system.getPowerInfo() + if pst ~= 'nobattery' then + pper = pper or 0 + if pst == 'charging' + then pper = os.time() % 5 / 4 * 100 + end + roundRect('line', -r * 2.2, 0, r * 2, r * 1.2, r / 4) + roundRect('fill', -r / 6, r * 0.3, r / 4, r / 2, r / 16) + roundRect('fill', -r * 1.95, r * 0.3, r * 1.5 * pper / 100, r * 0.6, r / 16) + love.graphics.translate(-r * 2.8, 0) + end + local time = os.date('%X'):gsub('^(%d+.+%d+).+%d+', '%1') + local w = f:getWidth(time) + love.graphics.print(time, -w, -2) + love.graphics.pop() +end + +local function addGame(file, cont, x, y) + local msg, id = ll.addGame(file, cont) + love.graphics.print(msg, x, y) + if id then + sel = id + end +end + +return { + update = update, + draw = draw, + addGame = addGame, +} + +end + diff --git a/skins/psp.lua b/skins/psp.lua index 2427148..5a766c1 100644 --- a/skins/psp.lua +++ b/skins/psp.lua @@ -10,7 +10,7 @@ local css, rcss = 2, 0 local f, bf -ll.cfg.pcht = ll.cfg.pcht or 60 * 5 +ll.cfg.pcht = ll.cfg.pcht or 60 * 10 local pikchv, pikcha = 0, 0 local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5) @@ -18,6 +18,8 @@ local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5) local logger = '' local chc = '<>><' +os.setlocale('', 'time') + function resize() r = math.min(W, H) / 30 @@ -30,16 +32,14 @@ function resize() f = love.graphics.setNewFont(math.min(W, H) / 30) bf = love.graphics.newFont(math.min(W, H) / 20) - ll.kbInit('v', H / 2 - ch, H / 2 + ch + cg, cx + cw * 2) + ll.kbInit('v', H / 2 - ch, H / 2 + ch + cg, 0, cx + cw * 2) end -love.window.setMode(800, 600, {resizable = true}) - local sdir -local function update() +local function update(dt) local ty = H / 2 - (ch + cg) * sel - cy = cy + (ty - cy) * 0.1 - css = css + (2 - css) * 0.2 + cy = cy + (ty - cy) * 5 * dt + css = css + (2 - css) * 15 * dt rcss = 2 - css + 1 local sdi = ll.kbGet() @@ -68,10 +68,9 @@ local function update() if sel > #ll.games then sel = 1 end end - pikchv = pikchv + 1 - if pikchv >= ll.cfg.pcht then - pikchv = 0 - pikcha = 0 + pikchv = pikchv + dt + if pikchv >= ll.cfg.pcht / 100 then + pikchv, pikcha = 0, 0 for _, v in pairs(ll.games) do if v.dat.scr then v.scrprv = v.scrcur @@ -81,7 +80,7 @@ local function update() end end end - else pikcha = math.min(255, pikcha + pikchao) + else pikcha = math.min(255, pikcha + pikchao * 100 * dt) end end @@ -96,6 +95,16 @@ local function scrCard(gme, x, y, w, h, a) love.graphics.rectangle('fill', x, y, w, h) end +local function roundRect(m, x, y, w, h, r) + if w < 1 or h < 1 then return end + + love.graphics.rectangle(m, x, y, w, h, r) + + if m == 'fill' + then roundRect('line', x, y, w, h, r) + end +end + local function draw() love.graphics.setColor(0 / COLDIV, 50 / COLDIV, 75 / COLDIV) love.graphics.rectangle('fill', 0, 0, W, H) @@ -109,6 +118,28 @@ local function draw() sel = #ll.games end love.graphics.setFont(f) + + -- battery indicators + love.graphics.setColor(255, 255, 255) + love.graphics.push() + love.graphics.translate(W - r * 1.5, r) + love.graphics.setLineWidth(r / 8) + local pst, pper = love.system.getPowerInfo() + if pst ~= 'nobattery' then + pper = pper or 0 + if pst == 'charging' + then pper = os.time() % 5 / 4 * 100 + end + roundRect('line', -r * 2.2, 0, r * 2, r * 1.2, r / 4) + roundRect('fill', -r / 6, r * 0.3, r / 4, r / 2, r / 16) + roundRect('fill', -r * 1.95, r * 0.3, r * 1.5 * pper / 100, r * 0.6, r / 16) + love.graphics.translate(-r * 2.8, 0) + end + local time = os.date('%X'):gsub('^(%d+.+%d+).+%d+', '%1') + local w = f:getWidth(time) + love.graphics.print(time, -w, -2) + love.graphics.pop() + local oy = 0 for k, v in pairs(ll.games) do local x, w, h, g = cx, cw, ch, cg @@ -151,9 +182,18 @@ local function draw() end end +local function addGame(file, cont, x, y) + local msg, id = ll.addGame(file, cont) + love.graphics.print(msg, x, y) + if id then + sel = id + end +end + return { update = update, draw = draw, + addGame = addGame, } end