feat(display): 优化外接显示器小屏自适应显示方案

- 新增 textScale 自动分级:根据显示器实际尺寸在 1.0/0.75/0.5 间自适应选择
- 实现 L/M/S 三档布局模式:L(大屏双栏完整)、M(中屏单栏精简)、S(小屏极简核心信息)
- 小屏模式隐藏日志、即将到达列车等次要信息,优先展示当前列车和发车倒计时
- 音频状态标签在小屏下进一步压缩缩写 (Sta Ann / Sp Ann)
- 版本升级至 v1.0.113
This commit is contained in:
2026-08-17 19:17:40 +08:00
parent 0335e245e1
commit 8c9c72fcba
+139 -45
View File
@@ -1,6 +1,6 @@
-- 1. Configuration & Constants -- 1. Configuration & Constants
local APP_VERSION_TEXT = "v1.0.112" local APP_VERSION_TEXT = "v1.0.113"
local CONFIG = { local CONFIG = {
CLOUD_URL = "ws://stabadmin.fse-media.group", CLOUD_URL = "ws://stabadmin.fse-media.group",
@@ -125,7 +125,23 @@ function Peripherals.init()
local monitors = { peripheral.find("monitor") } local monitors = { peripheral.find("monitor") }
if #monitors > 0 then if #monitors > 0 then
Peripherals.primaryMonitor = monitors[1] Peripherals.primaryMonitor = monitors[1]
Peripherals.primaryMonitor.setTextScale(0.5) local mw, mh = Peripherals.primaryMonitor.getSize()
local scale = 0.5
if mw >= 80 and mh >= 25 then
scale = 1.0
elseif mw >= 55 and mh >= 18 then
scale = 0.75
elseif mw >= 38 and mh >= 13 then
scale = 0.5
else
scale = 0.5
pcall(function() Peripherals.primaryMonitor.setTextScale(1) end)
local testW, testH = Peripherals.primaryMonitor.getSize()
if testW >= 26 and testH >= 9 then
scale = 1
end
end
pcall(function() Peripherals.primaryMonitor.setTextScale(scale) end)
State.hasPrimaryMonitor = true State.hasPrimaryMonitor = true
else else
Peripherals.primaryMonitor = nil Peripherals.primaryMonitor = nil
@@ -3089,16 +3105,23 @@ function Display.render()
local w, h = mon.getSize() local w, h = mon.getSize()
mon.clear() mon.clear()
local hasSchedulePanel = State.currentTrain ~= nil local layoutMode = "S"
if w >= 38 and h >= 16 then
layoutMode = "L"
elseif w >= 26 and h >= 10 then
layoutMode = "M"
end
local hasSchedulePanel = State.currentTrain ~= nil and layoutMode == "L"
local leftW = w local leftW = w
local sepX = nil local sepX = nil
local rightX = nil local rightX = nil
local rightW = 0 local rightW = 0
if hasSchedulePanel and w >= 30 then if hasSchedulePanel then
rightW = math.min(math.max(math.floor(w * 0.38), 18), 40) rightW = math.min(math.max(math.floor(w * 0.38), 18), 40)
leftW = w - rightW - 1 leftW = w - rightW - 1
if leftW < 12 then if leftW < 14 then
leftW = w leftW = w
rightW = 0 rightW = 0
else else
@@ -3107,14 +3130,14 @@ function Display.render()
end end
end end
Utils.log("DEBUG", string.format("Monitor size W=%d H=%d, schedulePanel=%s leftW=%d rightW=%d", w, h, tostring(rightX ~= nil), leftW, rightW)) Utils.log("DEBUG", string.format("Monitor W=%d H=%d Mode=%s Panel=%s LW=%d RW=%d", w, h, layoutMode, tostring(rightX ~= nil), leftW, rightW))
local function classifyAudioSource(source, priority) local function classifyAudioSource(source, priority)
local s = tostring(source or ""):lower() local s = tostring(source or ""):lower()
if s == "train" or s == "station" or s == "broadcast" then return "Station Announcement" end if s == "train" or s == "station" or s == "broadcast" then return "Sta Ann" end
if s ~= "" then return "Special Announcement(" .. tostring(source) .. ")" end if s ~= "" then return "Sp Ann(" .. tostring(source) .. ")" end
if (tonumber(priority) or 0) >= 2 then return "Special Announcement" end if (tonumber(priority) or 0) >= 2 then return "Sp Ann" end
return "Special Announcement" return "Sp Ann"
end end
local function truncL(str) local function truncL(str)
@@ -3151,14 +3174,30 @@ function Display.render()
end end
end end
drawLeft(1, "STATION: " .. State.stationName .. " " .. APP_VERSION_TEXT, COLORS.INFO) local y = 1
drawLeft(2, "CODE: " .. tostring(State.stationCode) .. " P:" .. tostring(State.stationPlatform) .. " " .. tostring(State.stationIdentityMode), COLORS.INFO)
drawLeft(3, "TIME: " .. os.date("%H:%M:%S"), COLORS.INFO)
if layoutMode == "L" then
drawLeft(y, "STATION: " .. State.stationName .. " " .. APP_VERSION_TEXT, COLORS.INFO)
y = y + 1
drawLeft(y, "CODE: " .. tostring(State.stationCode) .. " P:" .. tostring(State.stationPlatform) .. " " .. tostring(State.stationIdentityMode), COLORS.INFO)
y = y + 1
drawLeft(y, "TIME: " .. os.date("%H:%M:%S"), COLORS.INFO)
y = y + 1
elseif layoutMode == "M" then
drawLeft(y, State.stationName .. " [" .. tostring(State.stationCode) .. "/P" .. tostring(State.stationPlatform) .. "] " .. os.date("%H:%M:%S"), COLORS.INFO)
y = y + 1
else
drawLeft(y, tostring(State.stationCode) .. "/P" .. tostring(State.stationPlatform) .. " " .. os.date("%H:%M"), COLORS.INFO)
y = y + 1
end
local audioLine = "AUDIO: FREE(Q" .. tostring(#State.audioQueue) .. ")"
local audioColor = COLORS.INFO
if State.currentPlaying then if State.currentPlaying then
local p = tonumber(State.currentPlaying.priority) or 0 local p = tonumber(State.currentPlaying.priority) or 0
local kind = classifyAudioSource(State.currentPlaying.source, p) local kind = classifyAudioSource(State.currentPlaying.source, p)
drawLeft(4, "AUDIO: " .. kind .. " Playing (P=" .. tostring(p) .. ", Q=" .. tostring(#State.audioQueue) .. ")", COLORS.SUCCESS) audioLine = "PLAY: " .. kind .. "(P" .. tostring(p) .. " Q" .. tostring(#State.audioQueue) .. ")"
audioColor = COLORS.SUCCESS
elseif State.audioRx then elseif State.audioRx then
local p = tonumber(State.audioRx.priority) or 0 local p = tonumber(State.audioRx.priority) or 0
local kind = classifyAudioSource(State.audioRx.source, p) local kind = classifyAudioSource(State.audioRx.source, p)
@@ -3166,28 +3205,54 @@ function Display.render()
local total = tonumber(State.audioRx.expectedBytes) or 0 local total = tonumber(State.audioRx.expectedBytes) or 0
local pct = "" local pct = ""
if total > 0 then pct = " " .. tostring(math.floor((rx / total) * 100)) .. "%" end if total > 0 then pct = " " .. tostring(math.floor((rx / total) * 100)) .. "%" end
drawLeft(4, "AUDIO: " .. kind .. " Receiving" .. pct .. " (Q=" .. tostring(#State.audioQueue) .. ")", COLORS.DEBUG) audioLine = "RX: " .. kind .. pct .. "(Q" .. tostring(#State.audioQueue) .. ")"
else audioColor = COLORS.DEBUG
drawLeft(4, "AUDIO: FREE (Q=" .. tostring(#State.audioQueue) .. ")", COLORS.INFO)
end end
if layoutMode == "S" then
audioLine = audioLine:gsub("AUDIO: ", ""):gsub("PLAY: ", ">"):gsub("RX: ", ">>")
end
drawLeft(y, audioLine, audioColor)
y = y + 1
local contentBottom = 4 local contentBottom = y - 1
if State.currentTrain then if State.currentTrain then
drawLeft(5, "CURRENT TRAIN:", COLORS.SUCCESS) if layoutMode == "L" then
drawLeft(6, State.currentTrain.id, COLORS.INFO) drawLeft(y, "CURRENT TRAIN:", COLORS.SUCCESS)
drawLeft(7, "Dest: " .. State.currentTrain.dest, COLORS.INFO) y = y + 1
drawLeft(y, State.currentTrain.id, COLORS.INFO)
y = y + 1
drawLeft(y, "Dest: " .. State.currentTrain.dest, COLORS.INFO)
y = y + 1
elseif layoutMode == "M" then
drawLeft(y, "TRAIN: " .. State.currentTrain.id, COLORS.SUCCESS)
y = y + 1
drawLeft(y, ">> " .. State.currentTrain.dest, COLORS.INFO)
y = y + 1
else
drawLeft(y, State.currentTrain.id, COLORS.SUCCESS)
y = y + 1
drawLeft(y, ">" .. Utils.sanitizeDisplay(State.currentTrain.dest), COLORS.INFO)
y = y + 1
end
if State.currentTrain.departureTime then if State.currentTrain.departureTime then
local remaining = math.max(0, State.currentTrain.departureTime - os.epoch("utc") / 1000) local remaining = math.max(0, State.currentTrain.departureTime - os.epoch("utc") / 1000)
if remaining > 0 then if remaining > 0 then
drawLeft(9, string.format("Departing in: %02d:%02d", math.floor(remaining/60), math.floor(remaining%60)), COLORS.WARNING) local mm = math.floor(remaining / 60)
local ss = math.floor(remaining % 60)
if layoutMode == "S" then
drawLeft(y, string.format("DEP %02d:%02d", mm, ss), COLORS.WARNING)
else
drawLeft(y, string.format("Departing in: %02d:%02d", mm, ss), COLORS.WARNING)
end
else else
drawLeft(9, "Departing...", COLORS.ERROR) drawLeft(y, "DEPARTING...", COLORS.ERROR)
end end
contentBottom = 9 y = y + 1
contentBottom = y - 1
else else
contentBottom = 7 contentBottom = y - 1
end end
if rightX then if rightX then
@@ -3313,40 +3378,57 @@ function Display.render()
end end
end end
else else
if Peripherals.station then if layoutMode == "S" then
drawLeft(5, "NO TRAIN AT PLATFORM", COLORS.WARNING) if Peripherals.station then
drawLeft(y, "NO TRAIN", COLORS.WARNING)
else
drawLeft(y, "SLAVE MODE", COLORS.WARNING)
end
else else
drawLeft(5, "SLAVE NODE MODE", COLORS.WARNING) if Peripherals.station then
drawLeft(y, "NO TRAIN AT PLATFORM", COLORS.WARNING)
else
drawLeft(y, "SLAVE NODE MODE", COLORS.WARNING)
end
end end
contentBottom = 5 y = y + 1
contentBottom = y - 1
end end
local logsStartY = h local logsStartY = h
local upcomingStartY = contentBottom + 1 local upcomingStartY = contentBottom + 1
if Peripherals.blockReader or Peripherals.createTarget then local allowUpcoming = (Peripherals.blockReader or Peripherals.createTarget)
if allowUpcoming and layoutMode ~= "S" then
local hasUpcoming = State.upcomingTrains and #State.upcomingTrains > 0 local hasUpcoming = State.upcomingTrains and #State.upcomingTrains > 0
drawLeft(upcomingStartY, "UPCOMING TRAINS:", COLORS.DEBUG) local upcomingLabel = "UPCOMING:"
if layoutMode == "M" then upcomingLabel = "NEXT:" end
drawLeft(upcomingStartY, upcomingLabel, COLORS.DEBUG)
if hasUpcoming then if hasUpcoming then
local visibleCount = math.min(3, #State.upcomingTrains) local maxUpcoming = 3
if layoutMode == "M" then maxUpcoming = 2 end
local visibleCount = math.min(maxUpcoming, #State.upcomingTrains)
for i = 1, visibleCount do for i = 1, visibleCount do
local lineY = upcomingStartY + i local lineY = upcomingStartY + i
if lineY > h then break end if lineY > h then break end
local t = State.upcomingTrains[i] local t = State.upcomingTrains[i]
local suffix = "" local suffix = ""
if t.cancelled then if t.cancelled then
suffix = " [CANCEL]" suffix = " [X]"
elseif (tonumber(t.delay) or 0) > 0 then elseif (tonumber(t.delay) or 0) > 0 then
local reason = t.reason and (" " .. tostring(t.reason)) or "" suffix = " [+" .. tostring(t.delay) .. "m]"
suffix = " [+" .. tostring(t.delay) .. "m" .. reason .. "]"
elseif t.reason then
suffix = " [" .. tostring(t.reason) .. "]"
end end
local pfx = "" local pfx = ""
if t.platform and tostring(t.platform) ~= "" then if t.platform and tostring(t.platform) ~= "" then
pfx = "P" .. tostring(t.platform) .. " " pfx = "P" .. tostring(t.platform) .. " "
end end
local etaLabel = tostring(t.etaLabel or (tostring(t.eta or 0) .. "s")) local etaLabel = tostring(t.etaLabel or (tostring(t.eta or 0) .. "s"))
local lineText = string.format("%s%s->%s ETA:%s%s", pfx, t.trainName, t.dest, etaLabel, suffix) local lineText
if layoutMode == "M" then
lineText = string.format("%s%s>%s %s%s", pfx, Utils.sanitizeDisplay(t.trainName), Utils.sanitizeDisplay(t.dest), etaLabel, suffix)
else
lineText = string.format("%s%s->%s ETA:%s%s", pfx, t.trainName, t.dest, etaLabel, suffix)
end
drawLeft(lineY, lineText, COLORS.INFO) drawLeft(lineY, lineText, COLORS.INFO)
end end
logsStartY = math.max(logsStartY, upcomingStartY + visibleCount + 1) logsStartY = math.max(logsStartY, upcomingStartY + visibleCount + 1)
@@ -3359,13 +3441,25 @@ function Display.render()
end end
end end
local maxLogLines = math.min(3, math.max(0, h - logsStartY + 1)) if layoutMode == "L" then
for i = 1, maxLogLines do local maxLogLines = math.min(3, math.max(0, h - logsStartY + 1))
local logIdx = i + State.logScrollOffset for i = 1, maxLogLines do
local entry = State.logs[logIdx] local logIdx = i + State.logScrollOffset
if entry then local entry = State.logs[logIdx]
local lineY = logsStartY + i - 1 if entry then
drawLeft(lineY, Utils.sanitizeDisplay(entry.text), COLORS[entry.level] or colors.white) local lineY = logsStartY + i - 1
drawLeft(lineY, Utils.sanitizeDisplay(entry.text), COLORS[entry.level] or colors.white)
end
end
elseif layoutMode == "M" then
local maxLogLines = math.min(1, math.max(0, h - logsStartY + 1))
for i = 1, maxLogLines do
local logIdx = i + State.logScrollOffset
local entry = State.logs[logIdx]
if entry then
local lineY = logsStartY + i - 1
drawLeft(lineY, Utils.sanitizeDisplay(entry.text), COLORS[entry.level] or colors.white)
end
end end
end end
end end