Built-in programs

devices
map of the connected devices
Type this at the prompt of a computer to copy it onto the medium:
> examples devices > edit devices
The program
-- Everything this computer can drive, drawn as a map: the block on each of
-- its six faces, the devices on its data cable, its radios, the Wi-Fi
-- networks and the computers in reach. Click a device to see its readings
-- and the functions a program can call. Tab or the arrows change page,
-- Q quits.
local C = term.colors
local size = term.get_size()
local W = size.w
local H = size.h
local COLOR = os.tier() >= 4 -- older screens have a single phosphor colour
local FACES = 1
local CABLE = 2
local RADIO = 3
local DETAIL = 4
-- 8 x 8 icons: one hex colour per pixel (the colours of term.colors), "." empty
local I_PC = {"77777777", "7ffffff7", "7f5ffff7", "7ff5fff7", "7f5f55f7", "7ffffff7", "77777777", "..7777.."}
local I_CHEST = {"cccccccc", "c111111c", "c111111c", "ccc88ccc", "c118811c", "c111111c", "c111111c", "cccccccc"}
local I_COG = {"...11...", ".1.11.1.", "..1cc1..", "11c77c11", "11c77c11", "..1cc1..", ".1.11.1.", "...11..."}
local I_TANK = {"88888888", "8......8", "8......8", "8bbbbbb8", "8b3bbbb8", "8bbbb3b8", "8bbbbbb8", "88888888"}
local I_SCREEN = {"77777777", "7bbbbbb7", "7b33bbb7", "7b3bbbb7", "7bbbbbb7", "77777777", "...77...", ".777777."}
local I_ROUTER = {".a....a.", ".a....a.", ".a....a.", "aaaaaaaa", "a5a5a5aa", "aaaaaaaa", ".7....7.", "........"}
local I_RADIO = {"a..7..a.", ".a.7.a..", "...7....", "..777...", "...7....", "..7.7...", ".7...7..", "7777777."}
local I_WIFI = {"..2222..", ".2....2.", "2..22..2", "..2..2..", "........", "...22...", "...22...", "........"}
local I_LIGHT = {"..7777..", "..7ee7..", "..7777..", "..7117..", "..7777..", "..7557..", "..7777..", "...77..."}
local I_LOOP = {"77777777", "7eeeeee7", "7e7777e7", "7e7777e7", "7e7777e7", "7e7777e7", "7eeeeee7", "77777777"}
local I_LINK = {"...ee...", "..e66e..", "...ee...", "...cc...", "...cc...", ".777777.", "7e7777e7", "77777777"}
local I_SENSOR = {"..8888..", ".8....8.", "8.....58", "8....5.8", "8..55..8", "8..55..8", ".8....8.", "..8888.."}
local I_DUST = {"...ee...", "...ee...", "...ee...", "eeeeeeee", "eeeeeeee", "...ee...", "...ee...", "...ee..."}
local I_CABLE = {"...777..", "...797..", "...797..", "7777977.", "9999997.", "7777777.", "........", "........"}
-- Colour and icon of each type of device (peripheral.type)
local KIND = {
traffic_light = {color = C.green, icon = I_LIGHT},
inductive_loop = {color = C.pink, icon = I_LOOP},
monitor = {color = C.cyan, icon = I_SCREEN},
wifi_router = {color = C.magenta, icon = I_ROUTER},
radio_modem = {color = C.purple, icon = I_RADIO},
redstone_link = {color = C.red, icon = I_LINK},
kinetic = {color = C.orange, icon = I_COG},
inventory = {color = C.yellow, icon = I_CHEST},
tank = {color = C.light_blue, icon = I_TANK},
}
local SENSOR = {color = C.lime, icon = I_SENSOR} -- the sensors of Create Aeronautics
-- Names short enough for a card (13 letters)
local NAMES = {
traffic_light = "Traffic light", inductive_loop = "Loop detector", monitor = "Monitor",
wifi_router = "Wi-Fi router", radio_modem = "Radio modem", redstone_link = "Redstone Link",
kinetic = "Kinetic block", inventory = "Inventory", tank = "Tank", altitude_sensor = "Altimeter",
gimbal_sensor = "Gyroscope", velocity_sensor = "Speed sensor", optical_sensor = "Optic sensor",
navigation_table = "Nav table", swivel_bearing = "Bearing", torsion_spring = "Spring",
hot_air_burner = "Burner",
}
local SIDE_NAME = {top = "Top", bottom = "Bottom", left = "Left", right = "Right", front = "Front", back = "Back"}
local ASPECTS = {off = "off", red = "red", red_amber = "red + amber", amber = "amber", green = "green", flashing = "flashing"}
local TABS = {"Faces", "Cable", "Wireless"}
local T = {
title = "Devices", back = "< Back", this = "Computer #", cable = "Cable", radio = "Wireless",
empty = "empty", noLabel = "no label", more = "details >", wifi = "Wi-Fi: ", blocks = " blocks",
device = "device", devices = "devices", computer = "computer", computers = "computers",
antenna = "antenna", antennas = "antennas", item = "item", items = "items", slot = "slot", slots = "slots",
router = "router", routers = "routers", rpm = " RPM", overstressed = "OVERSTRESSED", range = "range ",
noSsid = "no SSID", receiver = "receiver", transmitter = "transmitter", onIt = " on it", nothing = "nothing on it",
yours = "yours", alsoInv = "+ inventory", alsoTank = "+ tank", alsoKinetic = "+ Create", tank = "tank ",
name = "Name", readings = "Readings", contents = "Contents", functions = "Functions", noReading = "No readings.",
needMini = {"Available from", "the Mini-", "computer"}, needPc = {"Available from", "the Personal", "Computer"},
noAntenna = {"No antenna:", "place a modem", "or a router"},
cableDevice = "device on the cable", cableDevices = "devices on the cable",
cableComputer = "computer on the cable", cableComputers = "computers on the cable",
myAntenna = "antenna on this computer", myAntennas = "antennas on this computer",
network = "Wi-Fi network in reach", networks = "Wi-Fi networks in reach",
inReach = "computer in reach", inReachs = "computers in reach",
noCable = "Data cables need a Minicomputer or newer.",
noRadio = "Radio and Wi-Fi need a Personal Computer or newer.",
noneCable = "None yet: connect blocks to the computer with data cable.", none = "None.",
noneAntenna = "None: place a radio modem or a Wi-Fi router against the computer or on its cable.",
noneNetwork = "None: a Wi-Fi router gets a network name with set_ssid; a modem in range of a router joins its network.",
gone = "Disconnected: the device is not there anymore.",
hintFaces = "Click: details Tab: next page Q: quit",
hintList = "Click: details Up/Down: scroll Q: quit",
hintDetail = "Click or Backspace: back Up/Down: scroll",
}
-- The faces page is a 3 x 3 grid of cards, the computer in the middle. Depth goes
-- on the diagonal, like a cube seen from the front: back up right, front down left.
local GX = 1 -- columns between two cards
if W >= 60 then
GX = 2
end
local CW = (W - 2 * GX) // 3 -- size of a card in characters
local CH = math.min(5, (H - 2) // 3)
local GY = math.min(2, (H - 2 - 3 * CH) // 2)
local OX = (W - 3 * CW - 2 * GX) // 2 + 1
local OY = 2 + (H - 2 - 3 * CH - 2 * GY) // 2
local GRID = {
{col = 2, row = 1, side = "top"},
{col = 3, row = 1, side = "back"},
{col = 1, row = 2, side = "left"},
{col = 3, row = 2, side = "right"},
{col = 1, row = 3, side = "front"},
{col = 2, row = 3, side = "bottom"},
}
local A = (W - 5) * 2 // 5 -- columns of the lists: name, reading, place
local B = (W - 5) // 4
local KEY = math.min(18, W // 3)
-- Functions that only read, called on the detail page; those needing arguments fail and are skipped
local READERS = {"get", "count", "detected", "size", "speed", "stress", "capacity", "overstressed",
"range", "get_ssid", "is_receiver", "frequency", "height", "pressure", "angle_x", "angle_z",
"velocity", "hit", "distance", "block", "angle", "target_angle", "limit", "running"}
local page = FACES
local back = FACES -- the page a detail was opened from
local target = nil -- name of the device shown in detail
local scroll = 0
local faces = {}
local cable = {}
local antennas = {}
local wired = {}
local remote = {}
local networks = {}
local found = 0
local rows = {}
local hits = {} -- clickable areas of the screen
local tabs = {}
local shown = {} -- what each part of the screen shows, to redraw only changes
local counts = {}
local running = true
local scanned = -1
-- Text cut or padded with spaces to n characters
local function fit(s, n)
if #s > n then
return string.sub(s, 1, n)
end
return s .. string.rep(" ", n - #s)
end
local function right(s, n)
if #s > n then
return string.sub(s, 1, n)
end
return string.rep(" ", n - #s) .. s
end
local function put(x, y, s, fg)
term.set_cursor(x, y)
term.set_fg(fg)
term.write(s)
end
local function num(v)
if v == math.floor(v) then
return tostring(v)
end
return string.format("%.1f", v)
end
local function plural(n, one, many)
if n == 1 then
return num(n) .. " " .. one
end
return num(n) .. " " .. many
end
-- "minecraft:water" -> "water"
local function short(id)
local at = string.find(id, ":", 1, true)
if at then
return string.sub(id, at + 1)
end
return id
end
-- "inventory@12,64,-3" -> "12,64,-3"
local function coords(name)
local at = string.find(name, "@", 1, true)
if at then
return string.sub(name, at + 1)
end
return name
end
-- Calls a function of a device; nil when it fails (it needs arguments, the block is gone...)
local function try(p, method)
local f = p[method]
if f == nil then
return nil
end
local r = pcall(f)
if r.ok then
return r.value
end
return nil
end
local function show(v)
if type(v) == "number" then
return num(v)
elseif type(v) == "table" then
if v.w then
return num(v.w) .. " x " .. num(v.h)
elseif v.a then
return short(v.a) .. " + " .. short(v.b)
end
return "{...}"
end
return tostring(v)
end
-- Cuts a text into lines of at most n characters, between words
local function wrap(text, n)
local lines = {}
local line = ""
for _, word in ipairs(string.split(text, " ")) do
if line == "" then
line = word
elseif #line + 1 + #word <= n then
line = line .. " " .. word
else
table.insert(lines, line)
line = word
end
end
if line ~= "" then
table.insert(lines, line)
end
return lines
end
-- Redstone power received and emitted on a face, in a few words
local function redstone(power, output)
if power > 0 and output > 0 then return "in " .. power .. ", out " .. output end
if power > 0 then return "receives " .. power end
if output > 0 then return "emits " .. output end
return ""
end
local function who(c)
local s = "#" .. num(c.id)
if c.label then
s = s .. " " .. c.label
end
return s
end
-- One device, ready to draw: its name, colour, icon and two short readings
local function describe(name, p)
local t = p.type()
local k = KIND[t] or SENSOR
local d = {name = name, type = t, label = NAMES[t] or t, color = k.color, icon = k.icon, s1 = "", s2 = ""}
if t == "traffic_light" then
d.s1 = ASPECTS[try(p, "get") or "off"] or ""
elseif t == "inductive_loop" then
local n = try(p, "count") or 0
if n > 0 then
d.s1 = num(n) .. T.onIt
else
d.s1 = T.nothing
end
elseif t == "monitor" then
local s = try(p, "size")
if s then
d.s1 = num(s.w) .. " x " .. num(s.h)
end
elseif t == "wifi_router" then
local ssid = try(p, "get_ssid") or ""
if ssid == "" then
d.s1 = T.noSsid
else
d.s1 = "SSID " .. ssid
end
d.s2 = T.range .. num(try(p, "range") or 0)
elseif t == "radio_modem" then
d.s1 = T.range .. num(try(p, "range") or 0)
elseif t == "redstone_link" then
if try(p, "is_receiver") then
d.s1 = T.receiver
else
d.s1 = T.transmitter
end
local f = try(p, "frequency")
if f then
d.s2 = short(f.a) .. "+" .. short(f.b)
end
elseif t == "kinetic" then
d.s1 = num(math.abs(try(p, "speed") or 0)) .. T.rpm
if try(p, "overstressed") then
d.s2 = T.overstressed
else
d.s2 = num(try(p, "stress") or 0) .. " / " .. num(try(p, "capacity") or 0) .. " su"
end
elseif t == "inventory" then
d.s1 = plural(try(p, "count") or 0, T.item, T.items)
d.s2 = plural(try(p, "size") or 0, T.slot, T.slots)
elseif t == "tank" then
local list = try(p, "tanks")
if list and list[1] then
if list[1].fluid then
d.s1 = short(list[1].fluid)
else
d.s1 = T.empty
end
d.s2 = num(list[1].amount) .. " / " .. num(list[1].capacity) .. " mB"
end
else
-- a sensor: its first two readings
for key, value in pairs(try(p, "read") or {}) do
if d.s1 == "" then
d.s1 = key .. " " .. show(value)
elseif d.s2 == "" then
d.s2 = key .. " " .. show(value)
end
end
end
-- a block can be of several types: a millstone turns and holds items
if t ~= "inventory" and p.push then
d.s2 = T.alsoInv
elseif t ~= "tank" and p.tanks then
d.s2 = T.alsoTank
elseif t ~= "kinetic" and p.overstressed then
d.s2 = T.alsoKinetic
end
return d
end
local function isAntenna(d)
return d.type == "radio_modem" or d.type == "wifi_router"
end
-- Looks at everything in reach. Faces are named as seen facing the screen.
local function scan()
faces = {}
antennas = {}
found = 0
for _, side in ipairs(rs.sides()) do
local f = {side = side, power = rs.get(side), output = rs.get_output(side)}
local p = peripheral.wrap(side)
if p then
f.device = describe(side, p)
f.device.where = SIDE_NAME[side]
found = found + 1
if isAntenna(f.device) then
table.insert(antennas, f.device)
end
end
faces[side] = f
end
-- devices on the cable are named type@x,y,z
cable = {}
for _, name in ipairs(peripheral.list()) do
if string.find(name, "@", 1, true) then
local p = peripheral.wrap(name)
if p then
local d = describe(name, p)
d.where = coords(name)
table.insert(cable, d)
if isAntenna(d) then
table.insert(antennas, d)
end
end
end
end
found = found + #cable
wired = {}
remote = {}
networks = {}
if net then -- nil before the minicomputer
for _, c in ipairs(net.computers()) do
if c.via == "cable" then
table.insert(wired, c)
else
table.insert(remote, c)
end
end
networks = net.networks()
end
end
local function addHead(text, color)
table.insert(rows, {kind = "head", text = text, color = color})
end
local function addItem(icon, color, a, b, c, link)
table.insert(rows, {kind = "item", icon = icon, color = color, a = a, b = b, c = c, link = link})
end
local function addPair(key, value)
table.insert(rows, {kind = "pair", a = key, b = value})
end
local function addText(text, color)
for _, line in ipairs(wrap(text, W - 4)) do
table.insert(rows, {kind = "text", text = line, color = color})
end
end
local function addBlank()
table.insert(rows, {kind = "text", text = "", color = C.white})
end
local function buildCable()
rows = {}
if net == nil then
addText(T.noCable, C.light_gray)
return
end
addHead(plural(#cable, T.cableDevice, T.cableDevices), C.light_gray)
for _, d in ipairs(cable) do
addItem(d.icon, d.color, d.label, d.s1, d.where, d.name)
end
if #cable == 0 then
addText(T.noneCable, C.light_gray)
end
addBlank()
addHead(plural(#wired, T.cableComputer, T.cableComputers), C.white)
for _, c in ipairs(wired) do
addItem(I_PC, C.white, who(c), num(c.distance or 0) .. T.blocks, "", nil)
end
if #wired == 0 then
addText(T.none, C.light_gray)
end
end
local function buildRadio()
rows = {}
if net == nil or os.tier() < 4 then
addText(T.noRadio, C.light_gray)
return
end
addHead(plural(#antennas, T.myAntenna, T.myAntennas), C.purple)
for _, d in ipairs(antennas) do
addItem(d.icon, d.color, d.label, d.s1, d.where, d.name)
end
if #antennas == 0 then
addText(T.noneAntenna, C.light_gray)
end
addBlank()
addHead(plural(#networks, T.network, T.networks), C.magenta)
for _, n in ipairs(networks) do
local place = num(n.distance) .. T.blocks
if n.own then
place = T.yours
end
addItem(I_WIFI, C.magenta, n.ssid, plural(n.routers, T.router, T.routers), place, nil)
end
if #networks == 0 then
addText(T.noneNetwork, C.light_gray)
end
addBlank()
addHead(plural(#remote, T.inReach, T.inReachs), C.white)
for _, c in ipairs(remote) do
local via = c.via
if via == "wifi" then
via = "Wi-Fi"
end
addItem(I_PC, C.white, who(c), via, num(c.distance or 0) .. T.blocks, nil)
end
if #remote == 0 then
addText(T.none, C.light_gray)
end
end
local function byCount(a, b)
return counts[a] > counts[b]
end
-- The detail page: every reading of the device, what it holds, its functions
local function buildDetail()
rows = {}
local p = peripheral.wrap(target)
if p == nil then
addHead(target, C.gray)
addText(T.gone, C.light_gray)
return
end
local d = describe(target, p)
addHead(d.label, d.color)
addPair(T.name, target)
if SIDE_NAME[target] then
local text = redstone(rs.get(target), rs.get_output(target))
if text == "" then
text = "0"
end
addPair("Redstone", text)
end
addText('p = peripheral.wrap("' .. target .. '")', C.lime)
addBlank()
addHead(T.readings, C.gray)
local before = #rows
for _, m in ipairs(READERS) do
if p[m] then
local r = pcall(p[m])
if r.ok and r.value ~= nil then
addPair(m .. "()", show(r.value))
end
end
end
if p.tanks then
for i, tank in ipairs(try(p, "tanks") or {}) do
local fluid = T.empty
if tank.fluid then
fluid = short(tank.fluid)
end
addPair(T.tank .. i, fluid .. " " .. num(tank.amount) .. " / " .. num(tank.capacity) .. " mB")
end
end
if #rows == before then
addText(T.noReading, C.light_gray)
end
if p.push then
-- the same items of several slots are counted together, the most first
counts = {}
local order = {}
for _, stack in pairs(try(p, "list") or {}) do
if counts[stack.display] == nil then
counts[stack.display] = 0
table.insert(order, stack.display)
end
counts[stack.display] = counts[stack.display] + stack.count
end
table.sort(order, byCount)
addBlank()
addHead(T.contents, C.gray)
for _, item in ipairs(order) do
addPair(item, num(counts[item]))
end
if #order == 0 then
addText(T.empty, C.light_gray)
end
end
local names = {}
for key, value in pairs(p) do
table.insert(names, key .. "()")
end
table.sort(names)
addBlank()
addHead(T.functions, C.gray)
addText(table.concat(names, " "), C.white)
end
-- Pixel of a character cell: a cell is 6 x 9 pixels
local function px(x)
return (x - 1) * 6 + 1
end
local function py(y)
return (y - 1) * 9 + 1
end
local function cellX(col)
return OX + (col - 1) * (CW + GX)
end
local function cellY(row)
return OY + (row - 1) * (CH + GY)
end
-- True when that part of the screen must change; drawing only changes avoids flicker
local function changed(key, sig)
if shown[key] == sig then
return false
end
shown[key] = sig
return true
end
-- A card: in colour, a coloured title band and border. One-colour screens light
-- every pixel drawn with the same phosphor: a filled band would hide its text.
local function card(c)
local x = cellX(c.col)
local y = cellY(c.row)
table.insert(hits, {x1 = x, y1 = y, x2 = x + CW - 1, y2 = y + CH - 1, link = c.link, page = c.page})
if not changed("card" .. c.col .. c.row, tostring(c.color) .. c.head .. table.concat(c.lines, "|")) then
return
end
local left = px(x)
local top = py(y)
gfx.rect(left, top, CW * 6, CH * 9, "none", true)
if COLOR then
gfx.rect(left, top, CW * 6, 9, c.color, true)
gfx.rect(left, top, CW * 6, CH * 9, c.color)
else
gfx.rect(left, top, CW * 6, CH * 9, "white")
gfx.line(left, top + 9, left + CW * 6 - 1, top + 9, "white")
end
put(x + 1, y, fit(c.head, CW - 2), c.ink or C.black)
for i = 1, CH - 1 do
put(x + 1, y + i, fit(c.lines[i] or "", CW - 2), c.colors[i] or C.white)
end
if c.icon then
gfx.image(left + 2, top + 9, c.icon) -- under the first column of the first line
end
end
-- The line from the computer to a face, in the colour of what is there
local function connect(g)
local f = faces[g.side]
local color = "none"
if f.device then
color = f.device.color
elseif f.power > 0 or f.output > 0 then
color = C.red
elseif COLOR then
color = C.gray
end
if not changed("line" .. g.side, tostring(color)) then
return
end
local pl = px(cellX(2))
local pt = py(cellY(2))
local pr = pl + CW * 6 - 1
local pb = pt + CH * 9 - 1
local fl = px(cellX(g.col))
local ft = py(cellY(g.row))
local fr = fl + CW * 6 - 1
local fb = ft + CH * 9 - 1
local mx = (pl + pr) // 2
local my = (pt + pb) // 2
if g.side == "left" then
gfx.line(fr + 1, my, pl - 2, my, color, 2)
elseif g.side == "right" then
gfx.line(pr + 1, my, fl - 2, my, color, 2)
elseif GY == 0 then
return -- no room between the rows
elseif g.side == "top" then
gfx.line(mx, fb + 1, mx, pt - 2, color, 2)
elseif g.side == "bottom" then
gfx.line(mx, pb + 1, mx, ft - 2, color, 2)
elseif g.side == "back" then
gfx.line(pr + 1, pt - 2, fl - 2, fb + 1, color, 2)
else
gfx.line(pl - 2, pb + 1, fr + 1, ft - 2, color, 2)
end
end
local function faceCard(g)
local f = faces[g.side]
local d = f.device
local signal = redstone(f.power, f.output)
local head = SIDE_NAME[g.side]
if string.lower(head) ~= g.side and #head + 1 + #g.side <= CW - 2 then
head = fit(head, CW - 3 - #g.side) .. " " .. g.side -- the name for programs
end
local c = {col = g.col, row = g.row, head = head, color = C.gray, ink = C.light_gray}
local a = T.empty
local b = ""
local e = ""
local first = C.gray
if d then
c.color = d.color
c.ink = C.black
c.icon = d.icon
c.link = g.side
a = " " .. d.label
b = d.s1
e = d.s2
first = d.color
elseif signal ~= "" then
c.color = C.red
c.ink = C.black
c.icon = I_DUST
a = " Redstone"
b = signal
signal = ""
first = C.red
end
c.colors = {first, C.white, C.white, C.red}
if CH >= 5 then
c.lines = {a, b, e, signal}
elseif signal == "" then
c.lines = {a, b, e}
elseif CH == 4 then
c.lines = {a, b, signal}
c.colors[3] = C.red
else
c.lines = {a, signal}
c.colors[2] = C.red
end
card(c)
end
local function computerCard()
local lines = {" " .. (os.label() or T.noLabel), plural(found, T.device, T.devices), ""}
if net then
lines[3] = plural(#wired + #remote, T.computer, T.computers)
end
card({col = 2, row = 2, head = T.this .. num(os.id()), color = C.white, icon = I_PC, lines = lines,
colors = {C.white, C.white, C.white}})
end
local function cableCard()
local c = {col = 1, row = 1, head = T.cable, color = C.light_gray, icon = I_CABLE, page = CABLE,
colors = {C.white, C.white, C.white, C.light_gray}}
if net == nil then
c.lines = T.needMini
c.icon = nil
c.colors = {C.light_gray, C.light_gray, C.light_gray}
else
c.lines = {" " .. plural(#cable, T.device, T.devices), plural(#wired, T.computer, T.computers), "", T.more}
end
card(c)
end
local function radioCard()
local c = {col = 3, row = 3, head = T.radio, color = C.purple, page = RADIO,
colors = {C.light_gray, C.light_gray, C.light_gray}}
if net == nil or os.tier() < 4 then
c.lines = T.needPc
elseif #antennas == 0 then
c.lines = T.noAntenna
else
c.icon = I_WIFI
c.lines = {" " .. plural(#antennas, T.antenna, T.antennas), T.wifi .. #networks,
plural(#remote, T.computer, T.computers), T.more}
c.colors = {C.white, C.white, C.white, C.light_gray}
end
card(c)
end
local function drawFaces()
hits = {}
for _, g in ipairs(GRID) do
connect(g)
end
computerCard()
for _, g in ipairs(GRID) do
faceCard(g)
end
cableCard()
radioCard()
end
-- One line of a list; icons are drawn under the two spaces left for them
local function drawRow(y, r)
local sig = ""
if r then
sig = r.kind .. "|" .. (r.text or "") .. "|" .. (r.a or "") .. "|" .. (r.b or "") .. "|" .. (r.c or "") .. "|" .. tostring(r.color)
end
if not changed("row" .. y, sig) then
return
end
gfx.rect(1, py(y), W * 6, 9, "none", true)
if r == nil then
put(1, y, fit("", W), C.white)
elseif r.kind == "head" and COLOR then
gfx.rect(1, py(y), W * 6, 9, r.color, true)
put(1, y, fit(" " .. r.text, W), C.black)
elseif r.kind == "head" then
put(1, y, fit("-- " .. r.text .. " --", W), C.white)
elseif r.kind == "item" then
put(1, y, " " .. fit(r.a, A) .. " ", r.color)
put(6 + A, y, fit(r.b, B) .. " ", C.white)
put(7 + A + B, y, right(r.c, W - 6 - A - B), C.light_gray)
gfx.image(px(2) + 2, py(y), r.icon)
elseif r.kind == "pair" then
put(1, y, fit(" " .. r.a, KEY + 2), C.light_gray)
put(KEY + 3, y, fit(r.b, W - KEY - 2), C.white)
else
put(1, y, fit(" " .. r.text, W), r.color)
end
end
local function drawRows()
local view = H - 2
scroll = math.max(0, math.min(scroll, #rows - view))
hits = {}
for i = 1, view do
local r = rows[scroll + i]
drawRow(i + 1, r)
if r and r.link then
table.insert(hits, {x1 = 1, y1 = i + 1, x2 = W, y2 = i + 1, link = r.link})
end
end
end
local function drawTabs()
local active = page
if page == DETAIL then
active = back
end
if not changed("tabs", page .. "/" .. active) then
return
end
if COLOR then
gfx.rect(1, 1, W * 6, 9, "blue", true)
end
local x = W + 1
for i = 1, 3 do
x = x - #TABS[i] - 2
end
local title = " " .. T.title
if page == DETAIL then
title = " " .. T.back
end
put(1, 1, fit(title, x - 1), C.white)
for i = 1, 3 do
local label = " " .. TABS[i] .. " "
local ink = C.white
if i == active then
ink = C.black
if COLOR then
gfx.rect(px(x), 1, #label * 6, 9, "white", true)
else
label = "[" .. TABS[i] .. "]"
end
end
put(x, 1, label, ink)
tabs[i] = {x1 = x, x2 = x + #label - 1}
x = x + #label
end
end
local function draw()
drawTabs()
if page == FACES then
drawFaces()
else
drawRows()
end
local hint = T.hintFaces
if page == DETAIL then
hint = T.hintDetail
elseif page ~= FACES then
hint = T.hintList
end
if changed("footer", hint) then
put(1, H, fit(" " .. hint, W), C.light_gray)
end
end
local function refresh()
if page == DETAIL then
buildDetail()
else
scan()
if page == CABLE then
buildCable()
elseif page == RADIO then
buildRadio()
end
end
scanned = os.clock()
draw()
end
local function setPage(p)
page = p
scroll = 0
shown = {}
term.set_bg(C.black)
term.clear()
gfx.clear()
refresh()
end
local function open(name)
if page ~= DETAIL then
back = page
end
target = name
setPage(DETAIL)
end
-- e.x, e.y: the character clicked, in the terminal or on a monitor
local function onClick(e)
if e.y == 1 then
for i = 1, 3 do
if e.x >= tabs[i].x1 and e.x <= tabs[i].x2 then
setPage(i)
return
end
end
if page == DETAIL then
setPage(back)
end
return
end
for _, h in ipairs(hits) do
if e.x >= h.x1 and e.x <= h.x2 and e.y >= h.y1 and e.y <= h.y2 then
if h.link then
open(h.link)
return
elseif h.page then
setPage(h.page)
return
end
end
end
if page == DETAIL then
setPage(back)
end
end
local function onKey(k)
if page == DETAIL and (k == "backspace" or k == "left" or k == "right" or k == "tab") then
setPage(back)
elseif k == "tab" or k == "right" then
setPage(page % 3 + 1)
elseif k == "left" then
setPage((page + 1) % 3 + 1)
elseif page ~= FACES and k == "up" then
scroll = scroll - 1
drawRows()
elseif page ~= FACES and k == "down" then
scroll = scroll + 1
drawRows()
end
end
setPage(FACES)
local timer = os.start_timer(2)
while running do
local e = os.pull_event()
if e.name == "timer" and e.id == timer then
refresh() -- readings change: every 2 s, every second in detail
if page == DETAIL then
timer = os.start_timer(1)
else
timer = os.start_timer(2)
end
elseif e.name == "redstone" and os.clock() - scanned >= 0.5 then
refresh()
elseif e.name == "click" then
onClick(e)
elseif e.name == "key" then
onKey(e.key)
elseif e.name == "char" and string.lower(e.char) == "q" then
running = false
elseif e.name == "char" and tonumber(e.char) and tonumber(e.char) >= 1 and tonumber(e.char) <= 3 then
setPage(tonumber(e.char))
end
end
term.set_bg(C.black)
term.set_fg(C.white)
term.clear()
gfx.clear()
term.set_cursor(1, 1)