Files

144 lines
5.8 KiB
Lua

-- AD Status View
-- Ren logik for statusvinduet på skærmen (slås til/fra med en tast, se adStatusHud.lua): øjebliksbilledet
-- -> færdige linjer {text, level, header}. Sektioner i fast rækkefølge Flows/Marker/Kæder/Advarsler, kun med
-- indhold; højst maxLines linjer (resten "+k mere"); hver tekst højst MAX_CHARS tegn (UTF-8-sikkert, "..").
--
-- snapshot = {
-- flows = {{vehicle, action, fillPercent (0-100|nil), level}},
-- fields = {{fieldId, work, vehicle|nil, progress (0-1|nil), level}},
-- chains = {{text, level}}, -- standard rød
-- warnings = {{text, level}}, -- standard gul
-- }
-- level = "running"|"waiting"|"error"|"stopped" (farver: ADFlowView.LEVEL_COLORS); overskrifter har level "header".
ADStatusView = {}
ADStatusView.MAX_LINES = 18
ADStatusView.MAX_CHARS = 48
ADStatusView.VEHICLE_CHARS = 18 -- traktornavnet må ikke æde hele linjen
ADStatusView.MIN_FLEX_CHARS = 6
ADStatusView.SEPARATOR = " · "
ADStatusView.ELLIPSIS = ".."
local LEVELS = {running = true, waiting = true, error = true, stopped = true}
local SECTIONS = {
{key = "flows", header = "spu_status_flows", defaultLevel = "running"},
{key = "fields", header = "spu_status_fields", defaultLevel = "running"},
{key = "chains", header = "spu_status_chains", defaultLevel = "error"},
{key = "warnings", header = "spu_status_warnings", defaultLevel = "waiting"},
}
-- ---- UTF-8-sikker længde/afkortning (æøå er flere bytes) ----
function ADStatusView.textLength(text)
local count = 0
for _ in tostring(text):gmatch("[^\128-\191]") do count = count + 1 end
return count
end
local function utf8Prefix(text, chars)
local result, count = {}, 0
for char in text:gmatch("[^\128-\191][\128-\191]*") do
if count >= chars then break end
table.insert(result, char)
count = count + 1
end
return table.concat(result)
end
function ADStatusView.fit(text, chars)
text = tostring(text or "")
if ADStatusView.textLength(text) <= chars then return text end
local ellipsis = ADStatusView.ELLIPSIS
return utf8Prefix(text, math.max(0, chars - ADStatusView.textLength(ellipsis))) .. ellipsis
end
-- Dele samlet med " · "; delen på flexIndex får den plads der er tilbage, så fx fyldprocenten bevares.
local function joinFitted(parts, flexIndex)
local sep = ADStatusView.SEPARATOR
local fixed = ADStatusView.textLength(sep) * (#parts - 1)
for index, part in ipairs(parts) do
if index ~= flexIndex then fixed = fixed + ADStatusView.textLength(part) end
end
local budget = math.max(ADStatusView.MIN_FLEX_CHARS, ADStatusView.MAX_CHARS - fixed)
local fitted = {}
for index, part in ipairs(parts) do
fitted[index] = index == flexIndex and ADStatusView.fit(part, budget) or part
end
return ADStatusView.fit(table.concat(fitted, sep), ADStatusView.MAX_CHARS)
end
local function percentText(value)
return string.format("%d %%", math.floor(value + 0.5))
end
-- ---- linjer pr. sektion ----
local function flowText(entry)
local parts = {ADStatusView.fit(entry.vehicle or "?", ADStatusView.VEHICLE_CHARS), tostring(entry.action or "")}
if type(entry.fillPercent) == "number" then table.insert(parts, percentText(entry.fillPercent)) end
return joinFitted(parts, 2)
end
local function fieldText(entry)
local parts = {string.format("F%s %s", tostring(entry.fieldId or "?"), tostring(entry.work or ""))}
if entry.vehicle ~= nil and entry.vehicle ~= "" then
table.insert(parts, ADStatusView.fit(entry.vehicle, ADStatusView.VEHICLE_CHARS))
end
if type(entry.progress) == "number" then table.insert(parts, percentText(entry.progress * 100)) end
return joinFitted(parts, 1)
end
local function plainText(entry)
return ADStatusView.fit(entry.text or "", ADStatusView.MAX_CHARS)
end
local TEXT_OF_SECTION = {flows = flowText, fields = fieldText, chains = plainText, warnings = plainText}
local function levelOf(entry, default)
return LEVELS[entry.level] and entry.level or default
end
-- Alle linjer uden loft.
local function allLines(snapshot, textOf)
local lines = {}
for _, section in ipairs(SECTIONS) do
local entries = snapshot[section.key] or {}
if #entries > 0 then
table.insert(lines, {text = ADStatusView.fit(textOf(section.header), ADStatusView.MAX_CHARS), level = "header", header = true})
for _, entry in ipairs(entries) do
table.insert(lines, {text = TEXT_OF_SECTION[section.key](entry), level = levelOf(entry, section.defaultLevel), header = false})
end
end
end
return lines
end
-- Afkort til maxLines: sidste plads bliver "+k mere" (k = skjulte indholdslinjer); en overskrift uden
-- indhold lige før afkortningen droppes.
local function capLines(lines, maxLines, textOf)
if #lines <= maxLines then return lines end
local kept = {}
for index = 1, math.max(0, maxLines - 1) do kept[index] = lines[index] end
while #kept > 0 and kept[#kept].header do table.remove(kept) end
local hidden = 0
for index = #kept + 1, #lines do
if not lines[index].header then hidden = hidden + 1 end
end
local ok, text = pcall(string.format, textOf("spu_status_more"), hidden)
table.insert(kept, {text = ADStatusView.fit(ok and text or ("+" .. hidden), ADStatusView.MAX_CHARS), level = "stopped", header = false})
return kept
end
function ADStatusView.lines(snapshot, textOf, maxLines)
snapshot = snapshot or {}
textOf = textOf or function(key) return key end
maxLines = math.max(1, math.floor(maxLines or ADStatusView.MAX_LINES))
local lines = allLines(snapshot, textOf)
if #lines == 0 then
return {{text = ADStatusView.fit(textOf("spu_status_idle"), ADStatusView.MAX_CHARS), level = "stopped", header = false}}
end
return capLines(lines, maxLines, textOf)
end