Compare commits

...

2 commits

Author SHA1 Message Date
Er2
e46bc230a6 Love Loader v2.1
Bugfixes.

Removed cross-engine support, only Love2D.

Added filesystem and keyboard modules in LibLL.

Remade input handling in skins.

Files and broken folders are not shows in game list.

Add MOBILE variable and auto-fullscreen on mobile phones.
2022-07-07 19:37:27 +03:00
Er2
f562b23197 Love Loader v2.0 2022-05-30 10:33:56 +03:00
17 changed files with 1023 additions and 208 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
dev
build/
games/*
!games/.gitkeep

37
lib/chroot.lua Normal file
View file

@ -0,0 +1,37 @@
return function(ll)
ll.mdir = nil
ll.mgme = nil
local ffi = require 'ffi'
ffi.cdef [[
int PHYSFS_mount(const char *, const char *, int);
int PHYSFS_unmount(const char *);
]]
local baseReq = '?.lua;?/init.lua;'
function ll.mount(gme)
local mdir = gme.base .. gme.dir
ll.mgme = gme
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)
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.mdir = nil
end
end
end

20
lib/fs.lua Normal file
View file

@ -0,0 +1,20 @@
return function(ll)
function ll.fsIsAbs(f)
f = f:sub(1, 1)
return f == '/' or f == '\\'
end
function ll.fsIsRel(f)
return not ll.fsIsAbs(f)
end
function ll.fsFile(f)
return f:match '([^/\\]*)[/\\]*$'
end
function ll.fsDir(f)
return f:match '^(.*)[/\\]+[^/\\]*[/\\]*$'
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

81
lib/keyb.lua Normal file
View file

@ -0,0 +1,81 @@
return function(ll)
local mx, my, mb, mpb
local dir, sc1, sc2, sclm
-- 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)
if d == 'h' or d == 'v' or d == '*'
then dir = d
elseif d == 'y'
then dir = 'v'
elseif d == 'x'
then dir = 'h'
else error 'Direction must be *, h (x) or v (y)'
end
c1, c2 =
tonumber(c1) or 0,
tonumber(c2) or 0
sc1, sc2, sclm =
math.min(c1, c2),
math.max(c1, c2),
tonumber(clim) 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')
mx, my = love.mouse.getPosition()
mpb = mb
if love.mouse.isDown(1) then mb = 1
else mb = 0
end
if love.keyboard.isScancodeDown('up', 'w')
then return dir == '*' and '^' or '<'
elseif love.keyboard.isScancodeDown('left', 'a')
then return '<'
elseif love.keyboard.isScancodeDown('down', 's')
then return dir == '*' and 'v' or '>'
elseif love.keyboard.isScancodeDown('right', 'd')
then return '>'
elseif love.keyboard.isScancodeDown('return', 'space')
then return 'o'
elseif love.keyboard.isDown 'menu'
then return 'm'
elseif mb == 0 and mpb == 1 then -- unpressed
if dir == 'h' then
if sclm < 0 or my <= sclm then
if mx <= sc1
then return '<'
elseif mx >= sc2
then return '>'
else return 'o'
end
end
else
if sclm < 0 or mx <= sclm then
if my <= sc1
then return '<'
elseif my >= sc2
then return '>'
else return 'o'
end
end
end
end
end
end

51
lib/load.lua Normal file
View file

@ -0,0 +1,51 @@
return function(ll)
function ll.addGame(file, cont)
local dir = ll.fsDir(file)
file = ll.fsFile(file)
local ext = file:match '%.(%w+)$'
print(file, ext, dir)
return 'NO!', nil
end
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
local lfs = love.filesystem
local info = lfs.getInfo
for _, dir in pairs(love.filesystem.getDirectoryItems(ll.cfg.root)) do
local isDir
if info
then isDir = info(ll.cfg.root .. dir).type == 'directory'
else isDir = lfs.isDirectory(ll.cfg.root .. dir)
end
if isDir then
local file = ll.cfg.root .. dir..'/'.. 'info.ll'
local realDir = love.filesystem.getRealDirectory(file)
or love.filesystem.getRealDirectory(ll.cfg.root .. dir..'/main.lua')
if realDir
then ll.gameAdd(
love.filesystem.read(file),
file,
realDir ..'/'.. ll.cfg.root,
dir
)
end
end
end
end

27
lib/main.lua Normal file
View file

@ -0,0 +1,27 @@
local ll = {}
ll.cfg = {
root = 'games/',
}
require 'lib.fs' (ll)
require 'lib.game' (ll)
require 'lib.chroot' (ll)
require 'lib.load' (ll)
require 'lib.keyb' (ll)
function ll.home()
ll.umount()
love.event.push('quit', 'restart')
end
ll.dt = false
function ll.devtools()
if not ll.dt then
ll.dt = true
__LL = ll
pcall(function() require 'dev.tools' end)
end
end
return ll

View file

@ -1,8 +1,17 @@
-- minimal Love Loader API
-- Minimal Love Loader API
-- Version 2.1
-- (c) Er2 2022 <er2@dismail.de>
-- Zlib License
if not llUsed then
COLDIV = love.getVersion() == 0 and 1 or 255
MOBILE = love.system.getOS() == 'Android'
or love.system.getOS() == 'iOS'
if MOBILE
then love.window.setFullscreen(true)
end
function love.resize(x, y)
W, H = x, y

17
luapack.lua Normal file
View file

@ -0,0 +1,17 @@
return {
entry = 'main.lua',
output = 'build/main.lua',
plug = {
require 'plug.minify' {
extGlob = {
'love',
'llUsed',
'llHome',
'resize',
'COLDIV',
'MOBILE',
'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,99 @@
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 2.0 includes backend API to simplify creating custom skins.
`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` with insertion into `ll.games`.
- `ll.addGame(fileName, fileContent)` - function for file dropping, reserved for v3.0.
- `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`
- `ll.dt` - is developer tools enabled?
- `__LL` - global variable of Love Loader instance when developer tools enabled.
- `ll.devtools()` - enable developer tools.
- `ll.fsIsAbs(file)` - is file absolute (/file)?
- `ll.fsIsRel(file)` - is file relative, inverted result of ll.fsIsAbs (./file).
- `ll.fsDir(path)` - get directory name (2 from /1/2/3.file).
- `ll.fsFile(path)` - get file (including dividers after) (2 from /1/2/).
- `ll.kbInit(direction --[[string: *, h, v, x, y]], c1 --[[number, coordinate before card for mouse (left/top)]], c2 --[[number, coordinate after card for mouse (right/bottom)]], clim --[[other coordinate limit for mouse or -1 to disable]])` - initialize keyboard module for skins.
- `ll.kbGet() --[[nil, string: <, >, o, m, ^ anv v if direction is *]]` - get key pressed.
# API
To reduce things to do for game developers, this loader creates some global variables to use.
You can also use it without Love Loader (or if your game can distribute without 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.
`MOBILE`: is this device runs Android or iOS?
# Fill game information
@ -40,5 +108,6 @@ 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

198
skins/gui.lua Normal file
View file

@ -0,0 +1,198 @@
return function(ll)
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 sel = 1
local cdir
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)
ll.kbInit('h', cx, cx + cw)
end
local function update()
local sdi = ll.kbGet()
if cdir ~= sdi then
cdir = sdi
if sdi == '<'
then sel = sel - 1
elseif sdi == '>'
then sel = sel + 1
elseif sdi == 'o' and ll.games[sel]
then ll.mount(ll.games[sel])
elseif sdi == 'm'
and love.mouse.getX() >= W - 8
then ll.devtools()
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 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

159
skins/psp.lua Normal file
View file

@ -0,0 +1,159 @@
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)
local logger = ''
local chc = '<>><m><'
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)
ll.kbInit('v', H / 2 - ch, H / 2 + ch + cg, cx + cw * 2)
end
love.window.setMode(800, 600, {resizable = true})
local sdir
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 = ll.kbGet()
if sdi ~= sdir then
if sdi
then rcss = css
css = 1
psel = sel
logger = logger .. sdi
if logger == chc then
resize()
ll.devtools()
end
end
sdir = sdi
if sdi == '<'
then sel = sel - 1
elseif sdi == '>'
then sel = sel + 1
elseif sdi == 'o' 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