Love Loader v2.0

This commit is contained in:
Er2 2022-05-30 10:33:56 +03:00
parent 9513a5c471
commit f562b23197
16 changed files with 933 additions and 207 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dev
build/
games/*

26
lib/chroot.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
scr/gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
scr/psp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

190
skins/base.lua Normal file
View 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
View 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
View 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