feat(status): statusvindue på skærmen slås til med en tast
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user