Love Loader v2.0
This commit is contained in:
parent
9513a5c471
commit
f562b23197
16 changed files with 933 additions and 207 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
dev
|
||||
build/
|
||||
games/*
|
26
lib/chroot.lua
Normal file
26
lib/chroot.lua
Normal file
|
@ -0,0 +1,26 @@
|
|||
return function(ll)
|
||||
|
||||
ll.mdir = nil
|
||||
ll.mgme = nil
|
||||
|
||||
function ll.mount(gme)
|
||||
--[[
|
||||
ll.mdir = gme.base .. gme.dir
|
||||
ll.mgme = gme
|
||||
--[=[
|
||||
if gme.main then
|
||||
print('Load', ll.mdir ..'/'.. gme.main)
|
||||
end
|
||||
--]=]
|
||||
--]]
|
||||
error 'unimplemented'
|
||||
end
|
||||
|
||||
function ll.umount()
|
||||
if ll.mdir ~= nil then
|
||||
ll.mdir = nil
|
||||
error 'unimplemented'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
100
lib/game.lua
Normal file
100
lib/game.lua
Normal file
|
@ -0,0 +1,100 @@
|
|||
return function(ll)
|
||||
|
||||
ll.games = {}
|
||||
|
||||
local function parse(conf)
|
||||
local r = {}
|
||||
local k, v
|
||||
|
||||
local buf = ''
|
||||
local t = 'str'
|
||||
local esc = false
|
||||
local prg = true
|
||||
|
||||
local i = 1
|
||||
repeat
|
||||
local ch = conf:sub(i, i)
|
||||
|
||||
if ch == '\\'
|
||||
then esc = true
|
||||
|
||||
elseif ch == '#' then
|
||||
repeat i = i + 1
|
||||
ch = conf:sub(i, i)
|
||||
until ch == '' or ch == '\n' or ch == '\r'
|
||||
|
||||
elseif ch == '[' then
|
||||
buf = buf:match '%s*(.*)%s*'
|
||||
assert(#buf == 0, 'Unexpected array usage')
|
||||
t = 'arr'
|
||||
prg = true
|
||||
v = {}
|
||||
|
||||
elseif ch == ''
|
||||
or (ch == '=' and not k)
|
||||
or (ch == ']' and t == 'arr')
|
||||
or (ch == ';' and t == 'arr')
|
||||
or ch == '\n' or ch == '\r' then
|
||||
buf = buf:match '^%s*(.-)%s*$'
|
||||
|
||||
if ch == '=' then
|
||||
assert(t == 'str', 'Cannot use other types than string for key')
|
||||
end
|
||||
|
||||
if t == 'str'
|
||||
or (t == 'arr' and ch == ']')
|
||||
then prg = false end
|
||||
|
||||
if not prg or ch == ';' then
|
||||
if #buf ~= 0 then
|
||||
if k then
|
||||
if t == 'str'
|
||||
then v = buf
|
||||
elseif t == 'arr'
|
||||
then table.insert(v, buf)
|
||||
else error 'wut?' end
|
||||
else k = buf
|
||||
end
|
||||
buf = ''
|
||||
elseif ch ~= ''
|
||||
and ch ~= '\r'
|
||||
and ch ~= '\n'
|
||||
then error 'empty buffer'
|
||||
end
|
||||
end
|
||||
if k and v and not prg then
|
||||
r[k] = v
|
||||
k = nil
|
||||
v = nil
|
||||
buf = ''
|
||||
t = 'str'
|
||||
end
|
||||
|
||||
elseif esc then
|
||||
buf = buf .. ch
|
||||
esc = false
|
||||
|
||||
else buf = buf .. ch
|
||||
end
|
||||
i = i + 1
|
||||
until i >= #conf + 1
|
||||
return r
|
||||
end
|
||||
|
||||
function ll.gameNew(conf, file, base, dir)
|
||||
local cfg = parse(conf or '')
|
||||
local gme = {
|
||||
name = cfg.name or dir or 'No name',
|
||||
desc = cfg.desc or 'No description provided.',
|
||||
base = base,
|
||||
dir = dir,
|
||||
main = cfg.main or 'main.lua',
|
||||
screens = cfg.screens or cfg.pics or nil,
|
||||
scrcur = 1,
|
||||
scrprv = 1,
|
||||
dat = nil,
|
||||
}
|
||||
return gme
|
||||
end
|
||||
|
||||
end
|
9
lib/load.lua
Normal file
9
lib/load.lua
Normal file
|
@ -0,0 +1,9 @@
|
|||
return function(ll)
|
||||
|
||||
function ll.gameAdd(conf, file, base, dir)
|
||||
local gme = ll.gameNew(conf, file, base, dir)
|
||||
table.insert(ll.games, gme)
|
||||
return gme
|
||||
end
|
||||
|
||||
end
|
33
lib/love/chroot.lua
Normal file
33
lib/love/chroot.lua
Normal file
|
@ -0,0 +1,33 @@
|
|||
return function(ll)
|
||||
|
||||
local ffi = require 'ffi'
|
||||
ffi.cdef [[
|
||||
int PHYSFS_mount(const char *, const char *, int);
|
||||
int PHYSFS_unmount(const char *);
|
||||
]]
|
||||
|
||||
function ll.mount(gme)
|
||||
local mdir = gme.base .. gme.dir
|
||||
|
||||
love.filesystem.setRequirePath(''
|
||||
.. mdir .. '/?.lua;'
|
||||
.. mdir .. '/?/init.lua;'
|
||||
.. '?.lua;?/init.lua;'
|
||||
)
|
||||
-- FIXME: Bug may appear in Linux. Recompile Love2D or use official PPA.
|
||||
if ffi.C.PHYSFS_mount(mdir, '/', 0) == 0
|
||||
then error 'Cannot mount'
|
||||
else
|
||||
ll.mdir = mdir
|
||||
ll.mgme = gme
|
||||
end
|
||||
end
|
||||
|
||||
function ll.umount()
|
||||
if ll.mdir ~= nil then
|
||||
ffi.C.PHYSFS_unmount(ll.mdir)
|
||||
ll.mdir = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
28
lib/love/load.lua
Normal file
28
lib/love/load.lua
Normal file
|
@ -0,0 +1,28 @@
|
|||
return function(ll)
|
||||
|
||||
function ll.gameAdd(conf, file, base, dir)
|
||||
local gme = ll.gameNew(conf, file, base, dir)
|
||||
gme.dat = {}
|
||||
|
||||
if gme.screens and gme.screens[1] then
|
||||
gme.dat.scr = {}
|
||||
for i = 1, #gme.screens do
|
||||
table.insert(gme.dat.scr, love.graphics.newImage(ll.cfg.root .. gme.dir ..'/'.. gme.screens[i]))
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(ll.games, gme)
|
||||
return gme
|
||||
end
|
||||
|
||||
for _, dir in pairs(love.filesystem.getDirectoryItems(ll.cfg.root)) do
|
||||
local file = ll.cfg.root .. dir..'/'.. 'info.ll'
|
||||
ll.gameAdd(
|
||||
love.filesystem.read(file),
|
||||
file,
|
||||
love.filesystem.getSource():match '(.*)[\\/]*' ..'/'.. ll.cfg.root, -- TODO: AppData folders support
|
||||
dir
|
||||
)
|
||||
end
|
||||
|
||||
end
|
27
lib/main.lua
Normal file
27
lib/main.lua
Normal file
|
@ -0,0 +1,27 @@
|
|||
local ll = {}
|
||||
|
||||
ll.cfg = {
|
||||
root = 'games/',
|
||||
}
|
||||
|
||||
require 'lib.game' (ll)
|
||||
require 'lib.chroot' (ll)
|
||||
require 'lib.load' (ll)
|
||||
|
||||
function ll.home()
|
||||
ll.umount()
|
||||
error 'go to home'
|
||||
end
|
||||
|
||||
if love then
|
||||
require 'lib.love.chroot' (ll)
|
||||
require 'lib.love.load' (ll)
|
||||
|
||||
function ll.home()
|
||||
ll.umount()
|
||||
love.event.push('quit', 'restart')
|
||||
end
|
||||
llHome = ll.home
|
||||
end
|
||||
|
||||
return ll
|
16
luapack.lua
Normal file
16
luapack.lua
Normal file
|
@ -0,0 +1,16 @@
|
|||
return {
|
||||
entry = 'main.lua',
|
||||
output = 'build/main.lua',
|
||||
plug = {
|
||||
require 'plug.minify' {
|
||||
extGlob = {
|
||||
'love',
|
||||
'llUsed',
|
||||
'llHome',
|
||||
'resize',
|
||||
'COLDIV',
|
||||
'W', 'H',
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
238
main.lua
238
main.lua
|
@ -1,219 +1,71 @@
|
|||
local ll = require 'lib.main'
|
||||
error = love.errhand or love.errorhandler
|
||||
|
||||
local root = 'games/'
|
||||
|
||||
-- games library and selection
|
||||
local gms = {}
|
||||
local sel = 1
|
||||
local sgme
|
||||
|
||||
-- some internals
|
||||
local cx, cy, cw, ch
|
||||
local f, bf
|
||||
|
||||
local ffi = require 'ffi'
|
||||
local fd
|
||||
ffi.cdef [[
|
||||
int PHYSFS_mount(const char *, const char *, int);
|
||||
int PHYSFS_unmount(const char *);
|
||||
]]
|
||||
|
||||
local function llResz()
|
||||
cw, ch = W / 1.25, H / 1.25
|
||||
cx, cy = (W - cw) / 2, (H - ch) / 2
|
||||
|
||||
local th = math.min(W, H) / 8
|
||||
|
||||
f = love.graphics.newFont(th / 3)
|
||||
bf = love.graphics.newFont(th / 2)
|
||||
function splash()
|
||||
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])
|
||||
love.graphics.draw(img, 0, 0, 0, W / img:getWidth(), H / img:getHeight())
|
||||
end
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
love.graphics.print('Loading '..ll.mgme.name, W / 2, H / 2)
|
||||
end
|
||||
resize = llResz
|
||||
|
||||
ll.skin = require 'skins.psp' (ll)
|
||||
|
||||
require 'll-min'
|
||||
llUsed = true
|
||||
|
||||
local function chroot(dir, main)
|
||||
fd = love.filesystem.getSource() .. dir
|
||||
love.filesystem.setRequirePath(
|
||||
'?.lua;?/init.lua;'
|
||||
.. dir .. '/?.lua;'
|
||||
.. dir .. '/?/init.lua;'
|
||||
)
|
||||
-- FIXME: Bug in Linux (Debian)
|
||||
ffi.C.PHYSFS_mount(fd, '/', 0);
|
||||
if main then
|
||||
load(love.filesystem.read(dir ..'/'.. main), main)()
|
||||
end
|
||||
end
|
||||
|
||||
local function escChroot()
|
||||
if fd then
|
||||
ffi.C.PHYSFS_unmount(fd);
|
||||
fd = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function gmeNew(cont, dir, file)
|
||||
local gme = {
|
||||
name = 'No name',
|
||||
desc = 'No description provided.',
|
||||
dir = dir,
|
||||
main = 'main.lua',
|
||||
pics = nil,
|
||||
psel = 2,
|
||||
ppsl = 1,
|
||||
}
|
||||
local fi = false
|
||||
cont = cont or ''
|
||||
cont = cont:gsub('[^\\]#[^\n]*', '')
|
||||
for v in cont:gmatch '([^\n]+)' do
|
||||
local k, v = v:match '^%s*([%w_]+)%s*=%s*(.*)%s*$'
|
||||
if k == 'name'
|
||||
or k == 'desc'
|
||||
or k == 'main'
|
||||
then gme[k] = v
|
||||
fi = true
|
||||
elseif k == 'pic' then
|
||||
gme.pics = gme.pics or {}
|
||||
table.insert(gme.pics, v)
|
||||
fi = true
|
||||
elseif k == 'pics' then
|
||||
local t = {}
|
||||
v = v:sub(2, -2)
|
||||
for v in v:gmatch '%s*([^;]+%a+)%s*;?'
|
||||
do table.insert(t, v) end
|
||||
gme[k] = t
|
||||
fi = true
|
||||
elseif k
|
||||
then error('unknown field "'.. k ..'" in "'.. file ..'"')
|
||||
end
|
||||
end
|
||||
if gme.pics then
|
||||
for k, v in pairs(gme.pics) do
|
||||
gme.pics[k] = love.graphics.newImage(root .. dir ..'/'.. v)
|
||||
end
|
||||
gme.psel = math.min(2, #gme.pics)
|
||||
end
|
||||
if not fi then
|
||||
gme.name = dir
|
||||
end
|
||||
table.insert(gms, gme)
|
||||
end
|
||||
|
||||
for _, v in pairs(love.filesystem.getDirectoryItems(root)) do
|
||||
local file = v..'/'.. 'info.ll'
|
||||
gmeNew(love.filesystem.read(root .. file), v, file)
|
||||
end
|
||||
|
||||
local pf, mx, mb, mpb
|
||||
|
||||
local pcht = 60 * 5
|
||||
local pchat = math.floor(1 / pcht * 2 * 255 + 0.5)
|
||||
local pchv, pcha = 0, 0
|
||||
local function llUpdate()
|
||||
mx = love.mouse.getX()
|
||||
mpb = mb
|
||||
mb = 0
|
||||
if love.mouse.isDown(1) then mb = 1
|
||||
end
|
||||
|
||||
if mpb == 1 and mb == 0 then
|
||||
if mx <= cx then
|
||||
sel = sel - 1
|
||||
if sel < 1 then sel = #gms end
|
||||
elseif mx >= cx + cw then
|
||||
sel = sel + 1
|
||||
if sel > #gms then sel = 1 end
|
||||
else sgme = gms[sel]
|
||||
end
|
||||
end
|
||||
|
||||
pchv = pchv + 1
|
||||
if pchv >= pcht then
|
||||
pchv, pcha = 0, 0
|
||||
for _, v in pairs(gms) do
|
||||
if v.pics then
|
||||
v.ppsl = v.psel
|
||||
v.psel = v.psel + 1
|
||||
if v.psel > #v.pics
|
||||
then v.psel = 1 end
|
||||
end
|
||||
end
|
||||
else pcha = math.min(255, pcha + pchat)
|
||||
end
|
||||
end
|
||||
|
||||
local function llDraw()
|
||||
love.graphics.polygon('fill',
|
||||
8, H / 2,
|
||||
32, H / 2 - 32,
|
||||
32, H / 2 + 32)
|
||||
|
||||
love.graphics.polygon('fill',
|
||||
W - 8, H / 2,
|
||||
W - 32, H / 2 - 32,
|
||||
W - 32, H / 2 + 32)
|
||||
|
||||
local oy, t = 0, ''
|
||||
local gme = gms[sel]
|
||||
if gme then
|
||||
local ph = ch / 1.5
|
||||
if gme.pics then
|
||||
local p, n = gme.pics[gme.ppsl], gme.pics[gme.psel]
|
||||
love.graphics.draw(p, cx, cy, 0, cw / p:getWidth(), ph / p:getHeight())
|
||||
love.graphics.setColor(255, 255, 255, pcha / COLDIV)
|
||||
love.graphics.draw(n, cx, cy, 0, cw / n:getWidth(), ph / n:getHeight())
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
end
|
||||
love.graphics.rectangle('line', cx, cy, cw, ph)
|
||||
love.graphics.rectangle('line', cx, cy, cw, ch)
|
||||
|
||||
oy = cy + ph
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print(gme.name, cx, oy)
|
||||
oy = oy + bf:getHeight(gme.name)
|
||||
love.graphics.setFont(f)
|
||||
love.graphics.print(gme.desc, cx, oy)
|
||||
|
||||
else oy = H / 2
|
||||
t = 'No games'
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print(t, (W - bf:getWidth(t)) / 2, oy)
|
||||
oy = oy + bf:getHeight(t)
|
||||
love.graphics.setFont(f)
|
||||
t = 'There is no projects/games to run'
|
||||
love.graphics.print(t, (W - f:getWidth(t)) / 2, oy)
|
||||
end
|
||||
end
|
||||
|
||||
function llHome()
|
||||
escChroot()
|
||||
love.event.push('quit', 'restart')
|
||||
if love.errorhandler
|
||||
then love.errorhandler = error
|
||||
else love.errhand = error
|
||||
end
|
||||
|
||||
local brk = false
|
||||
while not brk and not sgme do
|
||||
while not brk and not ll.mdir do
|
||||
-- event handling
|
||||
love.event.pump()
|
||||
for n, a,b,c,d,e,f in love.event.poll() do
|
||||
if n == 'quit' then
|
||||
escChroot()
|
||||
for n, a,b,c in love.event.poll() do
|
||||
if n == 'quit'
|
||||
or (n == 'keypressed' and a == 'escape')
|
||||
then ll.umount()
|
||||
love.event.push('quit')
|
||||
brk = true; break
|
||||
end
|
||||
love.handlers[n](a,b,c,d,e,f)
|
||||
love.handlers[n](a,b,c)
|
||||
end
|
||||
|
||||
-- update and drawing
|
||||
llUpdate()
|
||||
ll.skin.update()
|
||||
love.graphics.origin()
|
||||
love.graphics.clear(0, 0, 0)
|
||||
llDraw()
|
||||
ll.skin.draw()
|
||||
love.graphics.present()
|
||||
love.timer.sleep(0.001)
|
||||
end
|
||||
|
||||
if sgme then
|
||||
if ll.mdir then
|
||||
love.graphics.setNewFont()
|
||||
resize = nil
|
||||
chroot(root .. sgme.dir, sgme.main)
|
||||
|
||||
love.graphics.clear(0, 0, 0)
|
||||
splash()
|
||||
love.graphics.present()
|
||||
|
||||
if ll.skin.lovecb then
|
||||
for _, v in pairs(ll.skin.lovecb)
|
||||
do love[v] = nil
|
||||
end
|
||||
end
|
||||
|
||||
love.filesystem.setIdentity(ll.mgme.dir)
|
||||
local f, err = load(love.filesystem.read(ll.mgme.main), ll.mgme.name)
|
||||
if not f then error(err)
|
||||
else xpcall(f, function(e)
|
||||
error(e)
|
||||
llHome()
|
||||
end)
|
||||
end
|
||||
|
||||
love.resize(love.graphics.getDimensions())
|
||||
end
|
||||
|
|
75
readme.md
75
readme.md
|
@ -2,31 +2,78 @@
|
|||
|
||||
Custom menu for selecting game from multiple for Love2D.
|
||||
|
||||
![PSP Style](./scr/psp.png)
|
||||
|
||||
[Other screenshots](./scr)
|
||||
|
||||
# How it works?
|
||||
|
||||
If simple: just place games in `games` folder with structure like `games/yourgame/main.lua`
|
||||
Just place games into `games` folder! (like `this_folder/games/game/main.lua`)
|
||||
|
||||
If technically: creates local variables functions and variables.
|
||||
Creates the loop until game wasn't selected with manual event handling and redrawing.
|
||||
Technically, this creates the loop until game wasn't selected or user wants to exit
|
||||
with custom event handling and redrawing.
|
||||
|
||||
# Things left or API
|
||||
# LibLL
|
||||
|
||||
We are not keep environment super clear (except local variables ;))
|
||||
so there are some variables can be used in game:
|
||||
Love Loader from v2.0 includes library to simplify creating custom interfaces.
|
||||
It is like a backend for Love Loader.
|
||||
|
||||
`W` and `H` variables: width and height of the screen, controlled by love.resize function.
|
||||
It have not so many functions and fields:
|
||||
|
||||
`love.resize` and optional `resize` payload: functions called when screen size when changed and at boot.
|
||||
- `ll.games` - field for games, which have this structure:
|
||||
```lua
|
||||
{
|
||||
name = 'string', -- Friendly name for game or placeholder
|
||||
desc = 'string', -- Description for game or placeholder
|
||||
base = 'string', -- base directory used in game mounting, must end with `/`
|
||||
dir = 'string', -- directory name, used if no name was defined
|
||||
main = 'string', -- main file to execute or `main.lua`
|
||||
screens = {'array of', 'path to screenshots'},
|
||||
scrcur = 1, --[[number]] -- current index from screenshots
|
||||
scrprv = 1, --[[number]] -- previous index from screenshots
|
||||
dat = nil, --[[any]] -- maybe platform-dependent data to reduce operations
|
||||
}
|
||||
```
|
||||
|
||||
`love.event.quit`: function to quit to menu screen
|
||||
- `ll.mdir` - string or nil, contains full mounted directory.
|
||||
|
||||
`COLDIV`: color divider (1 or 255) to setColor function
|
||||
- `ll.mgme` - game or nil, contains mounted game.
|
||||
|
||||
`llUsed`: is Love Loader used
|
||||
- `ll.gameNew(configuration --[[string]], fileName --[[string, not used]], base --[[string]], directory --[[string]])`
|
||||
|
||||
`llHome`: function to quit to menu screen
|
||||
Creates game object (defined above) and returns it.
|
||||
|
||||
They also can be used without Love Loader if load `ll-min.lua`
|
||||
- `ll.gameAdd(conf, file, base, dir)` - same as `ll.gameNew`, but inserts game into `ll.games`.
|
||||
|
||||
- `ll.mount(game)` - mounts game.
|
||||
|
||||
Can throw an error.
|
||||
|
||||
Sets `ll.mdir` and `ll.mgme`.
|
||||
|
||||
- `ll.umount()` - unmounts game if can.
|
||||
|
||||
Unsets `ll.mdir` and `ll.mgme`.
|
||||
|
||||
- `ll.home()` - calls `llHome`
|
||||
|
||||
# API
|
||||
|
||||
To simplify task to the game developers, this loader creates some global variables to use.
|
||||
|
||||
You can also use it without Love Loader by including `ll-min.lua` file.
|
||||
|
||||
`W` and `H`: width and height of the screen which controls by custom love.resize function.
|
||||
|
||||
`love.resize` and optional `resize` payload: functions called when screen size was changed and on booting.
|
||||
|
||||
`love.event.quit`: function to exit to main screen.
|
||||
|
||||
`llHome`: function called by `love.event.quit` (broken for now).
|
||||
|
||||
`llUsed`: is Love Loader (not minimal API) used.
|
||||
|
||||
`COLDIV`: color divider (1 or 255) to `love.graphics.setColor` function.
|
||||
|
||||
# Fill game information
|
||||
|
||||
|
@ -40,5 +87,5 @@ name = New awesome game using Love Loader
|
|||
desc = Some descripion about the game.
|
||||
# main = optional main file instead of `main.lua`
|
||||
pic = screen.png
|
||||
pics = [ screen.png; screen2.png ] # wow array
|
||||
pics = [ screen.png; screen2.png ] # wow arrays
|
||||
```
|
||||
|
|
BIN
scr/base.png
Normal file
BIN
scr/base.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
scr/gui.png
Normal file
BIN
scr/gui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
scr/psp.png
Normal file
BIN
scr/psp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
190
skins/base.lua
Normal file
190
skins/base.lua
Normal file
|
@ -0,0 +1,190 @@
|
|||
return function(ll)
|
||||
|
||||
-- Basic cmd-like style UI
|
||||
-- FIXME: Console scrolling
|
||||
|
||||
local cw, ch, ctw, cth
|
||||
local ct = 'Love Loader v2.0\n(c) Er2 2022\tZlib License\n\n'
|
||||
local cce = false
|
||||
local buf = ''
|
||||
|
||||
local mb, mpb, mt = 0, 0, 0
|
||||
|
||||
local function ccp()
|
||||
local argv = {}
|
||||
for v in buf:gmatch '%S+'
|
||||
do table.insert(argv, v)
|
||||
end
|
||||
local cmd = (argv[1] or ''):lower()
|
||||
if cmd == '' then return '' -- nothing
|
||||
elseif cmd == 'help' then return 'Available commands:'
|
||||
.. '\nhelp\t\tThis command'
|
||||
.. '\nls\t\tList of games'
|
||||
.. '\ndir\t\tSame as ls'
|
||||
.. '\ncat\t<path>\tInformation about game'
|
||||
.. '\ninfo\t<path>\tSame as cat'
|
||||
.. '\nrun\t<path>\tStart game'
|
||||
.. '\nmount\t<path>\tSame as run'
|
||||
.. '\nchroot\t<path>\tSame as run'
|
||||
.. '\nclear\t\tClear screen'
|
||||
.. '\neval\t<code>\tExecute code'
|
||||
elseif cmd == 'clear' then
|
||||
ct = ''
|
||||
return ''
|
||||
elseif cmd == 'eval' then
|
||||
local code = buf:match '^%S+%s+(.*)'
|
||||
local f, err = load(code, 'test.lua')
|
||||
if not f then return err
|
||||
else return tostring(f() or 'nil')
|
||||
end
|
||||
elseif cmd == 'ls'
|
||||
or cmd == 'dir' then
|
||||
local r = ''
|
||||
if #ll.games == 0 then
|
||||
return ('%s: No games found. Add some to the library.'):format(argv[1])
|
||||
end
|
||||
for i = 1, #ll.games do
|
||||
local v = ll.games[i]
|
||||
r = r .. ('%s\t(%s)\t - %s\n'):format(v.dir, v.name, v.desc)
|
||||
end
|
||||
return r .. ('\nTotal: %3d games\n'):format(#ll.games)
|
||||
elseif cmd == 'cat'
|
||||
or cmd == 'info' then
|
||||
if not argv[2]
|
||||
then return ('%s: Game directory missing'):format(argv[1])
|
||||
end
|
||||
local r = ''
|
||||
for _, v in pairs(ll.games) do
|
||||
if argv[2] == v.dir
|
||||
or argv[2] == v.base .. v.dir
|
||||
then r = r..
|
||||
( '\nFriendly name:\t%s'
|
||||
..'\nDescription:\t%s'
|
||||
..'\nLocation:\t%s%s'
|
||||
..'\nMain file:\t%s'
|
||||
..'\n\n\t\tScreenshots are not supported yet.'
|
||||
..'\n'):format(v.name, v.desc, v.base, v.dir, v.main)
|
||||
end
|
||||
end
|
||||
if r == '' then r = 'Not found' end
|
||||
return r
|
||||
elseif cmd == 'run'
|
||||
or cmd == 'mount'
|
||||
or cmd == 'chroot' then
|
||||
local gme
|
||||
for _, v in pairs(ll.games) do
|
||||
if argv[2] == v.dir
|
||||
or argv[2] == v.base .. v.dir
|
||||
then if gme then
|
||||
return 'Error: multiple paths found, use full location'
|
||||
end
|
||||
gme = v end
|
||||
end
|
||||
if gme
|
||||
then ll.mount(gme)
|
||||
love.keyboard.setTextInput(false)
|
||||
return ''
|
||||
else return 'Not found'
|
||||
end
|
||||
else return 'Unknown command "'.. cmd .. '"'
|
||||
end
|
||||
end
|
||||
|
||||
local function update()
|
||||
mpb = mb
|
||||
if love.mouse.isDown(1) then mb = 1
|
||||
if mpb ~= 1
|
||||
then mt = os.time()
|
||||
end
|
||||
else mb = 0
|
||||
end
|
||||
|
||||
if mb == 0 and mpb == 1
|
||||
and os.time() - mt <= 1 then
|
||||
if love.system.getOS() == 'Android' then
|
||||
love.keyboard.setTextInput(
|
||||
not love.keyboard.hasTextInput(),
|
||||
0, H, W, ch)
|
||||
end
|
||||
end
|
||||
|
||||
if not cce then
|
||||
cce = true
|
||||
local r = ccp()
|
||||
if #r ~= 0 then ct = ct.. '\n'.. r end
|
||||
ct = ct.. '\n> '
|
||||
buf = ''
|
||||
end
|
||||
end
|
||||
|
||||
local utf8 = require 'utf8'
|
||||
local function draw()
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
|
||||
local x, y = 0, 0
|
||||
for p, cde in utf8.codes(ct) do
|
||||
local chr = utf8.char(cde)
|
||||
if chr == '\n'
|
||||
then x, y = 0, y + 1
|
||||
elseif chr == '\r'
|
||||
then x = 0
|
||||
elseif chr == '\t'
|
||||
then x = x + 8 - (x % 8)
|
||||
elseif chr == '\v'
|
||||
then y = y + 1
|
||||
else love.graphics.print(chr, x * cw, y * ch)
|
||||
if x >= ctw
|
||||
then x, y = x - ctw, y + 1
|
||||
else x = x + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if os.time() % 2 == 0
|
||||
then love.graphics.rectangle('fill', x * cw, y * ch, cw, ch)
|
||||
end
|
||||
end
|
||||
|
||||
function resize()
|
||||
local f = love.graphics.newFont(math.min(W, H) / 32)
|
||||
love.graphics.setFont(f)
|
||||
cw, ch = f:getWidth 'm', f:getHeight 'A'
|
||||
ctw, cth =
|
||||
math.floor(W / cw) - 1,
|
||||
math.floor(H / ch) - 1
|
||||
|
||||
love.keyboard.setTextInput(true, 0, H, W, ch)
|
||||
end
|
||||
|
||||
function love.textinput(txt)
|
||||
ct = ct.. txt
|
||||
buf = buf.. txt
|
||||
end
|
||||
|
||||
function love.keypressed(k)
|
||||
if k == 'backspace' then
|
||||
local off = utf8.offset(buf, -1)
|
||||
if off then
|
||||
buf = buf:sub(1, off - 1)
|
||||
off = utf8.offset(ct, -1)
|
||||
if off
|
||||
then ct = ct:sub(1, off - 1)
|
||||
end
|
||||
end
|
||||
elseif k == 'return'
|
||||
then cce = false
|
||||
end
|
||||
end
|
||||
|
||||
love.keyboard.setKeyRepeat(true)
|
||||
love.window.setMode(800, 600, {resizable = true})
|
||||
|
||||
return {
|
||||
update = update,
|
||||
draw = draw,
|
||||
lovecb = {
|
||||
'textinput',
|
||||
'keypressed',
|
||||
},
|
||||
}
|
||||
|
||||
end
|
220
skins/gui.lua
Normal file
220
skins/gui.lua
Normal file
|
@ -0,0 +1,220 @@
|
|||
return function(ll)
|
||||
|
||||
-- Classic UI from Love Loader v1
|
||||
|
||||
ll.cfg.pcht = ll.cfg.pcht or 60 * 5
|
||||
|
||||
local pikchv, pikcha = 0, 0
|
||||
local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5)
|
||||
|
||||
local cx, cy, cw, ch
|
||||
local f, bf
|
||||
|
||||
local mx, mb, mpb = 0, 0, 0
|
||||
local cdir, sel = 0, 1
|
||||
|
||||
function resize()
|
||||
cw = W / 1.25
|
||||
ch = H / 1.25
|
||||
cx = (W - cw) / 2
|
||||
cy = (H - ch) / 2
|
||||
|
||||
local th = math.min(W, H) / 8
|
||||
|
||||
f = love.graphics.newFont(th / 3)
|
||||
bf = love.graphics.newFont(th / 2)
|
||||
end
|
||||
|
||||
local function update()
|
||||
mx = love.mouse.getX()
|
||||
mpb = mb
|
||||
if love.mouse.isDown(1) then mb = 1
|
||||
else mb = 0 end
|
||||
|
||||
local sdi = 0
|
||||
if mpb == 1 and mb == 0 then
|
||||
if mx <= cx
|
||||
then sdi = 1
|
||||
elseif mx >= cx + cw
|
||||
then sdi = 2
|
||||
else sdi = 3
|
||||
end
|
||||
end
|
||||
|
||||
if love.keyboard.isScancodeDown('left', 'a')
|
||||
then sdi = 1
|
||||
elseif love.keyboard.isScancodeDown('right', 'd')
|
||||
then sdi = 2
|
||||
elseif love.keyboard.isScancodeDown('return', 'space')
|
||||
then sdi = 3
|
||||
end
|
||||
|
||||
if cdir ~= sdi then
|
||||
cdir = sdi
|
||||
if sdi == 1
|
||||
then sel = sel - 1
|
||||
elseif sdi == 2
|
||||
then sel = sel + 1
|
||||
elseif sdi == 3 and ll.games[sel]
|
||||
then ll.mount(ll.games[sel])
|
||||
end
|
||||
|
||||
if sel < 1 then sel = #ll.games end
|
||||
if sel > #ll.games then sel = 1 end
|
||||
end
|
||||
|
||||
pikchv = pikchv + 1
|
||||
if pikchv >= ll.cfg.pcht then
|
||||
pikchv = 0
|
||||
pikcha = 0
|
||||
for _, v in pairs(ll.games) do
|
||||
if v.dat.scr then
|
||||
v.scrprv = v.scrcur
|
||||
v.scrcur = v.scrcur + 1
|
||||
if v.scrcur > #v.dat.scr
|
||||
then v.scrcur = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else pikcha = math.min(255, pikcha + pikchao)
|
||||
end
|
||||
|
||||
if love.keyboard.isDown 'menu'
|
||||
and mx >= W - 8
|
||||
then pcall(select(2, pcall(require, 'dev.devtools')), ll)
|
||||
end
|
||||
end
|
||||
|
||||
local tm = 0
|
||||
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, 100 / COLDIV)
|
||||
love.graphics.setLineWidth(8)
|
||||
love.graphics.setFont(bf)
|
||||
local t = 'Love Loader'
|
||||
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 oy = H / 2
|
||||
for x = 0, W + 8, 8 do
|
||||
c = c + 0.1
|
||||
local c, s = math.cos(c), math.sin(c)
|
||||
local cy, ncy, sy, nsy =
|
||||
pc * H / 12,
|
||||
c * H / 12,
|
||||
ps * H / 12,
|
||||
s * H / 12
|
||||
|
||||
love.graphics.line(x - 8, sy + oy, x-1, nsy + oy)
|
||||
love.graphics.line(x - 8, sy/2 + oy, x-1, nsy/2 + oy)
|
||||
love.graphics.line(x - 8, cy/1.5 + oy, x-1, ncy/1.5 + oy)
|
||||
love.graphics.line(x - 8, cy*1.5 + oy, x-1, ncy*1.5 + oy)
|
||||
pc, ps = c, s
|
||||
end
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
love.graphics.setLineWidth(1)
|
||||
|
||||
local oy, t = 0, ''
|
||||
local gme = ll.games[sel]
|
||||
if gme then
|
||||
love.graphics.polygon('fill',
|
||||
8, H / 2,
|
||||
32, H / 2 - 32,
|
||||
32, H / 2 + 32)
|
||||
|
||||
love.graphics.polygon('fill',
|
||||
W - 8, H / 2,
|
||||
W - 32, H / 2 - 32,
|
||||
W - 32, H / 2 + 32)
|
||||
|
||||
love.graphics.stencil(function()
|
||||
love.graphics.rectangle('fill', cx, cy, cw, ch, 16)
|
||||
end)
|
||||
love.graphics.setStencilTest('greater', 0)
|
||||
love.graphics.setColor(50 / COLDIV, 50 / COLDIV, 50 / COLDIV, 100 / COLDIV)
|
||||
love.graphics.rectangle('fill', cx, cy, cw, ch)
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
|
||||
if gme.dat.scr then
|
||||
local p, n = gme.dat.scr[gme.scrprv], gme.dat.scr[gme.scrcur]
|
||||
love.graphics.draw(p, cx, cy, 0, cw / p:getWidth(), ch / p:getHeight())
|
||||
love.graphics.setColor(255, 255, 255, pikcha / COLDIV)
|
||||
love.graphics.draw(n, cx, cy, 0, cw / n:getWidth(), ch / n:getHeight())
|
||||
love.graphics.setColor(0, 0, 0, 150 / COLDIV)
|
||||
love.graphics.rectangle('fill', cx, cy, cw, ch)
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
end
|
||||
|
||||
oy = cy + ch / 1.25
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print(gme.name, cx + 16, oy)
|
||||
oy = oy + bf:getHeight(gme.name)
|
||||
love.graphics.setFont(f)
|
||||
love.graphics.print(gme.desc, cx + 16, oy)
|
||||
love.graphics.setStencilTest()
|
||||
love.graphics.rectangle('line', cx, cy, cw, ch, 16)
|
||||
|
||||
elseif ll.games[1] then
|
||||
sel = 1
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
love.graphics.print('Sorry :)', W / 2, H / 2)
|
||||
else love.graphics.setColor(255, 255, 255, 255)
|
||||
oy = H / 2.5
|
||||
t = 'No games'
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print(t, (W - bf:getWidth(t)) / 2, oy)
|
||||
oy = oy + bf:getHeight(t)
|
||||
love.graphics.setFont(f)
|
||||
t = 'There are no projects/games to run'
|
||||
love.graphics.print(t, (W - f:getWidth(t)) / 2, oy)
|
||||
end
|
||||
end
|
||||
|
||||
function error(msg)
|
||||
msg = tostring(msg)
|
||||
|
||||
love.graphics.reset()
|
||||
love.graphics.origin()
|
||||
local bf = love.graphics.newFont(64)
|
||||
local f = love.graphics.setNewFont(16)
|
||||
local perc = 0
|
||||
|
||||
local q
|
||||
repeat
|
||||
perc = math.min(100, perc + 0.1 * math.random(50))
|
||||
|
||||
love.graphics.clear(17/COLDIV, 113/COLDIV, 172/COLDIV)
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print(':(', 64, 16)
|
||||
love.graphics.setFont(f)
|
||||
love.graphics.print(('%d%% complete'):format(perc), 64, 100)
|
||||
love.graphics.printf(msg, 64, 132, W - 64)
|
||||
love.graphics.print('(c) Love Loader and Er2', 8, H - 16 - 8)
|
||||
love.graphics.present()
|
||||
|
||||
love.event.pump()
|
||||
for n, a,b,c in love.event.poll() do
|
||||
if n == 'quit'
|
||||
or n == 'mousereleased'
|
||||
or (n == 'keypressed' and a == 'escape')
|
||||
then q = true end
|
||||
end
|
||||
|
||||
love.timer.sleep(0.5)
|
||||
until q or perc == 100
|
||||
|
||||
-- Unfortunately, we can't restart Love in error handler
|
||||
llHome() -- but can outside of love.errorhandler
|
||||
return function() return 1 end
|
||||
end
|
||||
|
||||
return {
|
||||
update = update,
|
||||
draw = draw,
|
||||
}
|
||||
|
||||
end
|
175
skins/psp.lua
Normal file
175
skins/psp.lua
Normal file
|
@ -0,0 +1,175 @@
|
|||
return function(ll)
|
||||
|
||||
-- New UI from Sony PSP
|
||||
|
||||
local sel = 1
|
||||
local psel
|
||||
|
||||
local cx, cy, cw, ch, cg
|
||||
local css, rcss = 2, 0
|
||||
|
||||
local f, bf
|
||||
|
||||
ll.cfg.pcht = ll.cfg.pcht or 60 * 5
|
||||
|
||||
local pikchv, pikcha = 0, 0
|
||||
local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5)
|
||||
|
||||
function resize()
|
||||
r = math.min(W, H) / 30
|
||||
|
||||
cg = H / 64
|
||||
cw = W / 5
|
||||
ch = (H - cg) / 6
|
||||
cx = W / 10
|
||||
cy = -H / 2
|
||||
|
||||
f = love.graphics.setNewFont(math.min(W, H) / 30)
|
||||
bf = love.graphics.newFont(math.min(W, H) / 20)
|
||||
end
|
||||
|
||||
love.window.setMode(800, 600, {resizable = true})
|
||||
|
||||
local my, mb, mpb
|
||||
|
||||
local sdir = 0
|
||||
local function update()
|
||||
local ty = H / 2 - (ch + cg) * sel
|
||||
cy = cy + (ty - cy) * 0.1
|
||||
css = css + (2 - css) * 0.2
|
||||
rcss = 2 - css + 1
|
||||
|
||||
local sdi
|
||||
|
||||
my = love.mouse.getY()
|
||||
mpb = mb
|
||||
if love.mouse.isDown(1) then mb = 1
|
||||
else mb = 0
|
||||
end
|
||||
if mb == 0 and mpb == 1 then
|
||||
if my <= H / 2 - ch
|
||||
then sdi = 1
|
||||
elseif my >= H / 2 + ch + cg
|
||||
then sdi = 2
|
||||
else sdi = 3
|
||||
end
|
||||
elseif love.keyboard.isScancodeDown('up', 'w')
|
||||
then sdi = 1
|
||||
elseif love.keyboard.isScancodeDown('down', 's')
|
||||
then sdi = 2
|
||||
elseif love.keyboard.isScancodeDown('return', 'space')
|
||||
then sdi = 3
|
||||
else sdi = 0
|
||||
end
|
||||
|
||||
if sdi ~= sdir then
|
||||
if sdi ~= 0
|
||||
then rcss = css
|
||||
css = 1
|
||||
if sdi == 3
|
||||
then css = 1.5
|
||||
end
|
||||
psel = sel
|
||||
end
|
||||
sdir = sdi
|
||||
if sdi == 1
|
||||
then sel = sel - 1
|
||||
elseif sdi == 2
|
||||
then sel = sel + 1
|
||||
elseif sdi == 3 and ll.games[sel]
|
||||
then ll.mount(ll.games[sel])
|
||||
end
|
||||
|
||||
if sel < 1 then sel = #ll.games end
|
||||
if sel > #ll.games then sel = 1 end
|
||||
end
|
||||
|
||||
pikchv = pikchv + 1
|
||||
if pikchv >= ll.cfg.pcht then
|
||||
pikchv = 0
|
||||
pikcha = 0
|
||||
for _, v in pairs(ll.games) do
|
||||
if v.dat.scr then
|
||||
v.scrprv = v.scrcur
|
||||
v.scrcur = v.scrcur + 1
|
||||
if v.scrcur > #v.dat.scr
|
||||
then v.scrcur = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else pikcha = math.min(255, pikcha + pikchao)
|
||||
end
|
||||
end
|
||||
|
||||
local function scrCard(gme, x, y, w, h, a)
|
||||
if not gme.dat.scr then return end
|
||||
love.graphics.setColor(255, 255, 255, a / COLDIV)
|
||||
local p, n = gme.dat.scr[gme.scrprv], gme.dat.scr[gme.scrcur]
|
||||
love.graphics.draw(p, x, y, 0, w / p:getWidth(), h / p:getHeight())
|
||||
love.graphics.setColor(255, 255, 255, math.min(a, pikcha) / COLDIV)
|
||||
love.graphics.draw(n, x, y, 0, w / n:getWidth(), h / n:getHeight())
|
||||
love.graphics.setColor(0, 0, 0, 150 / COLDIV)
|
||||
love.graphics.rectangle('fill', x, y, w, h)
|
||||
end
|
||||
|
||||
local function draw()
|
||||
love.graphics.setColor(0 / COLDIV, 50 / COLDIV, 75 / COLDIV)
|
||||
love.graphics.rectangle('fill', 0, 0, W, H)
|
||||
if ll.games[sel] then
|
||||
local cur = ll.games[sel]
|
||||
scrCard(cur, 0, 0, W, H, (css - 1) * 255)
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.printf(cur.desc, cx + cw * 2, cx, W - (cx + cw * 2))
|
||||
else love.graphics.setColor(255, 255, 255, 255)
|
||||
sel = #ll.games
|
||||
end
|
||||
love.graphics.setFont(f)
|
||||
local oy = 0
|
||||
for k, v in pairs(ll.games) do
|
||||
local x, w, h, g = cx, cw, ch, cg
|
||||
if k == sel then
|
||||
x = cx / css
|
||||
w = cw * css
|
||||
h = ch * css
|
||||
g = cg * 3
|
||||
oy = oy + cg * css
|
||||
elseif k == psel then
|
||||
x = cx / rcss
|
||||
w = cw * rcss
|
||||
h = ch * rcss
|
||||
oy = oy + cg * rcss
|
||||
end
|
||||
|
||||
love.graphics.stencil(function()
|
||||
love.graphics.rectangle('fill', x, cy + oy, w, h, r)
|
||||
end)
|
||||
love.graphics.setStencilTest('greater', 0)
|
||||
|
||||
love.graphics.setColor(50 / COLDIV, 50 / COLDIV, 50 / COLDIV, 100 / COLDIV)
|
||||
love.graphics.rectangle('fill', x, cy + oy, w, h)
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
|
||||
scrCard(v, x, cy + oy, w, h, 255)
|
||||
love.graphics.setColor(255, 255, 255, 255)
|
||||
|
||||
local th = f:getHeight(v.name)
|
||||
love.graphics.print(v.name, x + cg, cy + oy + h - th - cg)
|
||||
|
||||
love.graphics.setStencilTest()
|
||||
love.graphics.rectangle('line', x, cy + oy, w, h, r)
|
||||
oy = oy + h + g
|
||||
end
|
||||
if #ll.games == 0 then
|
||||
love.graphics.print('No games', cx, cy - ch)
|
||||
love.graphics.setFont(bf)
|
||||
love.graphics.print('Add some to the library', cx, cy)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
update = update,
|
||||
draw = draw,
|
||||
}
|
||||
|
||||
end
|
Loading…
Reference in a new issue