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:
+139
-45
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user