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
build/
games/*
!games/.gitkeep

View File

@ -3,23 +3,34 @@ 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)
--[[
ll.mdir = gme.base .. gme.dir
ll.mgme = gme
--[=[
if gme.main then
print('Load', ll.mdir ..'/'.. gme.main)
end
--]=]
--]]
error 'unimplemented'
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
error 'unimplemented'
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)
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

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/',
}
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()
error 'go to home'
love.event.push('quit', 'restart')
end
if love then
require 'lib.love.chroot' (ll)
require 'lib.love.load' (ll)
function ll.home()
ll.umount()
love.event.push('quit', 'restart')
ll.dt = false
function ll.devtools()
if not ll.dt then
ll.dt = true
__LL = ll
pcall(function() require 'dev.tools' end)
end
llHome = ll.home
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

View File

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

View File

@ -67,5 +67,53 @@ if ll.mdir then
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())
end

View File

@ -2,6 +2,8 @@
Custom menu for selecting game from multiple for Love2D.
Works from Love2D 0.10.
![PSP Style](./scr/psp.png)
[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`)
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.
# LibLL
Love Loader from v2.0 includes library to simplify creating custom interfaces.
It is like a backend for Love Loader.
Love Loader from 2.0 includes backend API to simplify creating custom skins.
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
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
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
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
@ -89,3 +128,4 @@ desc = Some descripion about the game.
pic = screen.png
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)
-- Classic UI from Love Loader v1
ll.cfg.pcht = ll.cfg.pcht or 60 * 5
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 f, bf
local mx, mb, mpb = 0, 0, 0
local cdir, sel = 0, 1
local sel = 1
local cdir
function resize()
cw = W / 1.25
@ -23,40 +21,25 @@ function resize()
f = love.graphics.newFont(th / 3)
bf = love.graphics.newFont(th / 2)
ll.kbInit('h', cx, cx + cw)
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
local sdi = ll.kbGet()
if cdir ~= sdi then
cdir = sdi
if sdi == 1
if sdi == '<'
then sel = sel - 1
elseif sdi == 2
elseif sdi == '>'
then sel = sel + 1
elseif sdi == 3 and ll.games[sel]
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
@ -78,11 +61,6 @@ local function update()
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

View File

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