From 0bfc7bbe099c7bba20cf1194b3ffad9471d0c7a4 Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Fri, 2 Jun 2023 13:30:54 +0200 Subject: Rework graphical train HUD code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A basic texture manipulation API is added; currently this is only a (selected) subset of texture modifiers provided by MT; the goal is to avoid writing (potentially incorrect) texture strings by hand; - The graphical HUD code is cleaned up; in particular, most code used for generating texture patterns are moved to texture.lua so that the code can be used outside of the HUD; - Inactive elements are given the darkslategray background. A basic unittest is added; however, it needs to be expanded for better coverage. Reported-by: Lars Müller --- advtrains/texture.lua | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 advtrains/texture.lua (limited to 'advtrains/texture.lua') diff --git a/advtrains/texture.lua b/advtrains/texture.lua new file mode 100644 index 0000000..1ecc91b --- /dev/null +++ b/advtrains/texture.lua @@ -0,0 +1,228 @@ +local tx = {} +setmetatable(tx, {__call = function(_, ...) return tx.base(...) end}) + +function tx.escape(str) + return (string.gsub(tostring(str), [[([%^:\])]], [[\%1]])) +end + +local function getargs(...) + return select("#", ...), {...} +end + +local function curry(f, x) + return function(...) + return f(x, ...) + end +end + +local function xmkmodifier(func) + return function(self, ...) + table.insert(self, (func(...))) + return self + end +end + +local function mkmodifier(fmt, spec) + return xmkmodifier(function(...) + local count = select("#", ...) + local args = {...} + for k, f in pairs(spec) do + args[k] = f(args[k]) + end + return string.format(fmt, unpack(args, 1, count)) + end) +end + +-- Texture object +local tx_lib = {} +local tx_mt = { + __index = tx_lib, + __tostring = function(self) + return table.concat(self, "^") + end, + __concat = function(a, b) + return tx.raw(("%s^%s"):format(tostring(a), tostring(b))) + end, +} + +function tx.raw(str) + return setmetatable({str}, tx_mt) +end +function tx.base(str) + return tx.raw(tx.escape(str)) +end +-- TODO: use [fill when 5.8.0 becomes widely used client-side +function tx.fill(w, h, color) + return tx"advtrains_hud_bg.png":resize(w, h):colorize(color) +end + +-- Most texture modifiers +tx_lib.colorize = xmkmodifier(function(c, a) + local str = ("[colorize:%s"):format(tx.escape(c)) + if a then + str = str .. ":" .. a + end + return str +end) +tx_lib.multiply = mkmodifier("[multiply:%s", {tx.escape}) +tx_lib.resize = mkmodifier("[resize:%dx%d", {}) +tx_lib.transform = mkmodifier("[transform%s", {tx.escape}) + +-- [combine + +local combine = {} + +function combine:add(x, y, ent) + table.insert(self.st, ([[%d,%d=%s]]):format(x, y, tx.escape(tostring(ent)))) + return self +end + +local combine_mt = { + __index = combine, + __tostring = function(self) + return table.concat(self.st, ":") + end, +} + +function tx.combine(w, h, bg) + local base = ("[combine:%dx%d"):format(w, h) + local obj = setmetatable({width = w, height = h, st = {base}}, combine_mt) + if bg then + obj:add_fill(0, 0, w, h, bg) + end + return obj +end + +function combine:add_fill(x, y, ...) + return self:add(x, y, tx.fill(...)) +end + +local function add_multicolor_fill(n, self, x, y, w, h, ...) + local argc, argv = getargs(...) + local t = 0 + for k = 1, argc, 2 do + t = t + argv[k] + end + local newargs = {x, y, w, h} + local sk, wk = n, n+2 + local s = newargs[wk]/t + for k = 1, argc, 2 do + local v = argv[k] * s + newargs[wk] = v + newargs[5] = argv[k+1] + self:add_fill(unpack(newargs)) + newargs[sk] = newargs[sk] + v + end + return self +end +combine.add_multicolor_fill_topdown = curry(add_multicolor_fill, 2) +combine.add_multicolor_fill_leftright = curry(add_multicolor_fill, 1) + +local function add_segmentbar(n, self, x, y, w, h, m, c, ...) + local argc, argv = getargs(...) + local baseargs = {x, y, w, h} + local ss = (baseargs[n+2]+m)/c + local bs = ss - m + for k = 1, argc, 3 do + local lower, upper, fill = argv[k], argv[k+1], argv[k+2] + lower = math.max(0, math.floor(lower)+1) + upper = math.min(c, math.floor(upper)) + if lower < upper then + local args = {x, y, w, h, fill} + args[n+2] = bs + args[n] = args[n] + ss*lower + for i = lower, upper do + self:add_fill(unpack(args)) + args[n] = args[n] + ss + end + end + end + return self +end +combine.add_segmentbar_topdown = curry(add_segmentbar, 2) +combine.add_segmentbar_leftright = curry(add_segmentbar, 1) + +local function add_lever(n, self, x, y, w, h, hs, ss, val, hf, sf) + local baseargs = {x, y, w, h} + local sargs = {x, y, w, h, sf} + sargs[5-n] = ss + sargs[n+2] = baseargs[n+2] + ss - hs + for k = 1, 2 do + sargs[k] = baseargs[k] + (baseargs[k+2] - sargs[k+2])/2 + end + self:add_fill(unpack(sargs)) + local hargs = {x, y, w, h, hf} + hargs[n+2] = hs + hargs[n] = baseargs[n] + (baseargs[n+2]-hs)*val + self:add_fill(unpack(hargs)) + return self +end +combine.add_lever_topdown = curry(add_lever, 2) +combine.add_lever_leftright = curry(add_lever, 1) + +--[[ Seven-segment display + -1- +6 2 + -7- +5 3 + -4- +--]] +local sevenseg_digits = { + ["0"] = {1, 2, 3, 4, 5, 6}, + ["1"] = {2, 3}, + ["2"] = {1, 2, 4, 5, 7}, + ["3"] = {1, 2, 3, 4, 7}, + ["4"] = {2, 3, 6, 7}, + ["5"] = {1, 3, 4, 6, 7}, + ["6"] = {1, 3, 4, 5, 6, 7}, + ["7"] = {1, 2, 3}, + ["8"] = {1, 2, 3, 4, 5, 6, 7}, + ["9"] = {1, 2, 3, 4, 6, 7}, +} + +function combine:add_str7seg(x0, y0, tw, th, str, fill) + --[[ w and h (as width/height of individual (horizontal) segments) have the following properties: + tw = n(w+3h)-h + th = 2w+3h + --]] + local len = #str + local h = (2*tw-len*th)/(3*len-2) + local w = (th-3*h)/2 + local ws = w+3*h + local segs = { + {h, 0, w, h}, + {w+h, h, h, w}, + {w+h, w+2*h, h, w}, + {h, 2*(w+h), w, h}, + {0, w+2*h, h, w}, + {0, h, h, w}, + {h, w+h, w, h}, + } + for i = 1, len do + for _, k in pairs(sevenseg_digits[string.sub(str, i, i)] or {}) do + local s = segs[k] + self:add_fill(s[1]+x0, s[2]+y0, s[3], s[4], fill) + end + x0 = x0 + ws + end + return self +end + +function combine:add_n7seg(x, y, w, h, n, prec, ...) + if not (type(n) == "number" and type(prec) == "number") then + error("passed non-numeric value or precision to numeric display") + elseif prec < 0 then + error("negative length") + end + local pfx = "" + if n >= 0 then + n = math.min(10^prec-1, n) + else + n = math.min(10^(prec-1)-1, -n) + pfx = "-" + end + local str = ("%d"):format(n) + return self:add_str7seg(x, y, w, h, pfx .. ("0"):rep(prec-#str-#pfx) .. str, ...) +end + +return tx -- cgit v1.2.3