Compare commits

...

2 Commits
v2.0 ... main

Author SHA1 Message Date
Er2 45f3db4f47 Love Loader v2.2
Added conf.lua support.

Updated readme.

Updated screenshots.
2022-07-08 16:12:11 +03:00
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
16 changed files with 328 additions and 173 deletions

2
.gitignore vendored
View File

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

View File

@ -3,23 +3,34 @@ return function(ll)
ll.mdir = nil ll.mdir = nil
ll.mgme = 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) function ll.mount(gme)
--[[ local mdir = gme.base .. gme.dir
ll.mdir = gme.base .. gme.dir ll.mgme = gme
ll.mgme = gme
--[=[ love.filesystem.setRequirePath(''
if gme.main then .. mdir .. '/?.lua;'
print('Load', ll.mdir ..'/'.. gme.main) .. mdir .. '/?/init.lua;'
end .. baseReq
--]=] )
--]] -- FIXME: Bug may appear in Linux. Recompile Love2D or use official PPA.
error 'unimplemented' if ffi.C.PHYSFS_mount(mdir, '/', 0) == 0
then error('Cannot mount '..mdir)
love.filesystem.setRequirePath(baseReq)
else ll.mdir = mdir
end
end end
function ll.umount() function ll.umount()
if ll.mdir ~= nil then if ll.mdir ~= nil then
ffi.C.PHYSFS_unmount(ll.mdir)
ll.mdir = nil ll.mdir = nil
error 'unimplemented'
end 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

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

View File

@ -1,9 +1,51 @@
return function(ll) 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) function ll.gameAdd(conf, file, base, dir)
local gme = ll.gameNew(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) table.insert(ll.games, gme)
return gme return gme
end 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 end

View File

@ -1,33 +0,0 @@
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

View File

@ -1,28 +0,0 @@
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

View File

@ -4,24 +4,24 @@ ll.cfg = {
root = 'games/', root = 'games/',
} }
require 'lib.fs' (ll)
require 'lib.game' (ll) require 'lib.game' (ll)
require 'lib.chroot' (ll) require 'lib.chroot' (ll)
require 'lib.load' (ll) require 'lib.load' (ll)
require 'lib.keyb' (ll)
function ll.home() function ll.home()
ll.umount() ll.umount()
error 'go to home' love.event.push('quit', 'restart')
end end
if love then ll.dt = false
require 'lib.love.chroot' (ll) function ll.devtools()
require 'lib.love.load' (ll) if not ll.dt then
ll.dt = true
function ll.home() __LL = ll
ll.umount() pcall(function() require 'dev.tools' end)
love.event.push('quit', 'restart')
end end
llHome = ll.home
end end
return ll 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 if not llUsed then
COLDIV = love.getVersion() == 0 and 1 or 255 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) function love.resize(x, y)
W, H = x, y W, H = x, y

View File

@ -9,6 +9,7 @@ return {
'llHome', 'llHome',
'resize', 'resize',
'COLDIV', 'COLDIV',
'MOBILE',
'W', 'H', 'W', 'H',
} }
}, },

View File

@ -67,5 +67,53 @@ if ll.mdir then
end) end)
end end
local t = {
title = 'Untitled',
window = {
width = W, height = H,
fullscreen = love.window.getFullscreen(),
resizable = false,
},
audio = {
mixwithsystem = true,
mic = false,
},
modules = {},
console = false,
accelerometerjoystick = true,
gammacorrect = false,
}
if pcall(require, 'conf')
and love.conf
then pcall(love.conf, t)
end
if t.console and love._openConsole
then love._openConsole()
end
love._setGammaCorrect(t.gammacorrect)
if MOBILE then
love._setAccelerometerAsJoystick(t.accelerometerjoystick)
if love.isVersionCompatible '11.3' then
love._setAudioMixWithSystem(t.audio.mixwithsystem)
love._requestRecordingPermission(t.audio.mic)
end
love.filesystem._setAndroidSaveExternal(t.externalstorage)
end
if t.window then
love.window.setTitle(t.window.title or t.title)
local ww, wh, wi =
t.window.width, t.window.height,
t.window.icon
t.window.width, t.window.height, t.window.title, t.window.icon
= nil
assert(love.window.setMode(ww, wh, t.window), 'Error setting up window')
if wi
then love.window.setIcon(love.image.newImageData(wi))
end
end
love.resize(love.graphics.getDimensions()) love.resize(love.graphics.getDimensions())
end end

View File

@ -2,6 +2,8 @@
Custom menu for selecting game from multiple for Love2D. Custom menu for selecting game from multiple for Love2D.
Works from Love2D 0.10.
![PSP Style](./scr/psp.png) ![PSP Style](./scr/psp.png)
[Other screenshots](./scr) [Other screenshots](./scr)
@ -10,18 +12,17 @@ Custom menu for selecting game from multiple for Love2D.
Just place games into `games` folder! (like `this_folder/games/game/main.lua`) Just place games into `games` folder! (like `this_folder/games/game/main.lua`)
Technically, this creates the loop until game wasn't selected or user wants to exit Technically, this creates the loop until game wasn't selected or user wants to exit,
with custom event handling and redrawing. with custom event handling and redrawing.
# LibLL # LibLL
Love Loader from v2.0 includes library to simplify creating custom interfaces. Love Loader from 2.0 includes backend API to simplify creating custom skins.
It is like a backend for Love Loader.
It have not so many functions and fields: It have not so many functions and fields:
- `ll.games` - field for games, which have this structure: ```lua
```lua ll.games = { -- field for games with structure below
{ {
name = 'string', -- Friendly name for game or placeholder name = 'string', -- Friendly name for game or placeholder
desc = 'string', -- Description for game or placeholder desc = 'string', -- Description for game or placeholder
@ -33,47 +34,85 @@ It have not so many functions and fields:
scrprv = 1, --[[number]] -- previous index from screenshots scrprv = 1, --[[number]] -- previous index from screenshots
dat = nil, --[[any]] -- maybe platform-dependent data to reduce operations dat = nil, --[[any]] -- maybe platform-dependent data to reduce operations
} }
``` }
- `ll.mdir` - string or nil, contains full mounted directory. ll.mdir -- string or nil, contains full mounted directory.
ll.mgme -- game or nil, contains mounted game
- `ll.mgme` - game or nil, contains mounted game. -- check if game mounted by ll.mdir!
- `ll.gameNew(configuration --[[string]], fileName --[[string, not used]], base --[[string]], directory --[[string]])` function ll.gameNew(
configuration, --string
fileName, -- string, not used
baseDirectory, -- string
gameDirectory -- string
) -- creates game object (defined above) and returns it.
Creates game object (defined above) and returns it. function ll.gameAdd(...) -- same as ll.gameNew with insertion into ll.games.
- `ll.gameAdd(conf, file, base, dir)` - same as `ll.gameNew`, but inserts game into `ll.games`. function ll.addGame(fileName, fileContent) -- reserved function for file dropping.
- `ll.mount(game)` - mounts game. function ll.mount(gameObject) -- mounts game.
-- throws error.
-- Sets ll.mdir and ll.mgme
Can throw an error. function ll.umount() -- unmounts game if was mounted.
-- Unsets ll.mdir and ll.mgme
Sets `ll.mdir` and `ll.mgme`. ll.home = llHome
- `ll.umount()` - unmounts game if can. function ll.devtools() -- enable developer tools.
Unsets `ll.mdir` and `ll.mgme`. if ll.dt then -- is developer tools enabled?
__LL = ll -- global variable with Love Loader instance.
end
- `ll.home()` - calls `llHome` function ll.fsIsAbs(file) -- is file absolute? (/file)
function ll.fsIsRel(file) -- is file relative? (./file)
-- return not ll.fsIsAbs(file)
function ll.fsDir(path) -- get directory name (2 from /1/2/3.file)
function ll.fsFile(path) -- get file (including dividers after) (2 from /1/2)
function 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 -- number = -1, other coordinate limit (for mouse) (x for v, y for h), -1 to disable
) -- initialize keyboard module (inside skins).
function ll.kbGet() -- get key pressed.
-- need ll.kbInit(...) before
-- returns nil | string: '<', '>', 'o', 'm', ('^', 'v' if direction == '*')
```
# API # API
To simplify task to the game developers, this loader creates some global variables to use. To reduce things to do for game developers, this loader creates some global variables to use.
You can also use it without Love Loader by including `ll-min.lua` file. You can also use it without Love Loader (or if your game can distribute without loader)
using `require 'll-min'`
`W` and `H`: width and height of the screen which controls by custom love.resize function. ```lua
W, H = width, height -- of the screen.
`love.resize` and optional `resize` payload: functions called when screen size was changed and on booting. function love.resize() -- internal function that updates W, H and calls resize function if exists.
`love.event.quit`: function to exit to main screen. function resize() -- payload for love.resize function.
`llHome`: function called by `love.event.quit` (broken for now). function love.event.quit() -- *should* exit to Love Loader screen.
`llUsed`: is Love Loader (not minimal API) used. function llHome() -- function called by love.event.quit.
`COLDIV`: color divider (1 or 255) to `love.graphics.setColor` function. llUsed = false -- is Love Loader (not minimal API) used.
COLDIV = 1 -- or 255, color divider for love.graphics.setColor function (150/COLDIV).
MOBILE = false -- is this device runs Android or iOS?
```
# Fill game information # Fill game information
@ -89,3 +128,4 @@ desc = Some descripion about the game.
pic = screen.png pic = screen.png
pics = [ screen.png; screen2.png ] # wow arrays pics = [ screen.png; screen2.png ] # wow arrays
``` ```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,7 +1,5 @@
return function(ll) return function(ll)
-- Classic UI from Love Loader v1
ll.cfg.pcht = ll.cfg.pcht or 60 * 5 ll.cfg.pcht = ll.cfg.pcht or 60 * 5
local pikchv, pikcha = 0, 0 local pikchv, pikcha = 0, 0
@ -10,8 +8,8 @@ local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5)
local cx, cy, cw, ch local cx, cy, cw, ch
local f, bf local f, bf
local mx, mb, mpb = 0, 0, 0 local sel = 1
local cdir, sel = 0, 1 local cdir
function resize() function resize()
cw = W / 1.25 cw = W / 1.25
@ -23,40 +21,25 @@ function resize()
f = love.graphics.newFont(th / 3) f = love.graphics.newFont(th / 3)
bf = love.graphics.newFont(th / 2) bf = love.graphics.newFont(th / 2)
ll.kbInit('h', cx, cx + cw)
end end
local function update() local function update()
mx = love.mouse.getX() local sdi = ll.kbGet()
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 if cdir ~= sdi then
cdir = sdi cdir = sdi
if sdi == 1 if sdi == '<'
then sel = sel - 1 then sel = sel - 1
elseif sdi == 2 elseif sdi == '>'
then sel = sel + 1 then sel = sel + 1
elseif sdi == 3 and ll.games[sel] elseif sdi == 'o' and ll.games[sel]
then ll.mount(ll.games[sel]) then ll.mount(ll.games[sel])
elseif sdi == 'm'
and love.mouse.getX() >= W - 8
then ll.devtools()
end end
if sel < 1 then sel = #ll.games end if sel < 1 then sel = #ll.games end
@ -78,11 +61,6 @@ local function update()
end end
else pikcha = math.min(255, pikcha + pikchao) else pikcha = math.min(255, pikcha + pikchao)
end end
if love.keyboard.isDown 'menu'
and mx >= W - 8
then pcall(select(2, pcall(require, 'dev.devtools')), ll)
end
end end
local tm = 0 local tm = 0

View File

@ -15,6 +15,9 @@ ll.cfg.pcht = ll.cfg.pcht or 60 * 5
local pikchv, pikcha = 0, 0 local pikchv, pikcha = 0, 0
local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5) local pikchao = math.floor(1 / ll.cfg.pcht * 2 * 255 + 0.5)
local logger = ''
local chc = '<>><m><'
function resize() function resize()
r = math.min(W, H) / 30 r = math.min(W, H) / 30
@ -26,57 +29,38 @@ function resize()
f = love.graphics.setNewFont(math.min(W, H) / 30) f = love.graphics.setNewFont(math.min(W, H) / 30)
bf = love.graphics.newFont(math.min(W, H) / 20) bf = love.graphics.newFont(math.min(W, H) / 20)
ll.kbInit('v', H / 2 - ch, H / 2 + ch + cg, cx + cw * 2)
end end
love.window.setMode(800, 600, {resizable = true}) love.window.setMode(800, 600, {resizable = true})
local my, mb, mpb local sdir
local sdir = 0
local function update() local function update()
local ty = H / 2 - (ch + cg) * sel local ty = H / 2 - (ch + cg) * sel
cy = cy + (ty - cy) * 0.1 cy = cy + (ty - cy) * 0.1
css = css + (2 - css) * 0.2 css = css + (2 - css) * 0.2
rcss = 2 - css + 1 rcss = 2 - css + 1
local sdi local sdi = ll.kbGet()
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 ~= sdir then
if sdi ~= 0 if sdi
then rcss = css then rcss = css
css = 1 css = 1
if sdi == 3
then css = 1.5
end
psel = sel psel = sel
logger = logger .. sdi
if logger == chc then
resize()
ll.devtools()
end
end end
sdir = sdi sdir = sdi
if sdi == 1 if sdi == '<'
then sel = sel - 1 then sel = sel - 1
elseif sdi == 2 elseif sdi == '>'
then sel = sel + 1 then sel = sel + 1
elseif sdi == 3 and ll.games[sel] elseif sdi == 'o' and ll.games[sel]
then ll.mount(ll.games[sel]) then ll.mount(ll.games[sel])
end end