217 lines
6.8 KiB
Lua
217 lines
6.8 KiB
Lua
-- lib/ui.lua — Shared UI helpers for manager & client
|
|
-- Usage: local ui = dofile("lib/ui.lua")
|
|
--
|
|
-- Set ui.draw to the current render target (window/monitor)
|
|
-- before calling any drawing functions.
|
|
|
|
local ui = {}
|
|
|
|
-------------------------------------------------
|
|
-- Drawing target — set this before drawing
|
|
-------------------------------------------------
|
|
|
|
ui.draw = nil
|
|
|
|
-------------------------------------------------
|
|
-- Drawing primitives
|
|
-------------------------------------------------
|
|
|
|
function ui.monWrite(x, y, text, fg, bg)
|
|
ui.draw.setCursorPos(x, y)
|
|
if fg then ui.draw.setTextColor(fg) end
|
|
if bg then ui.draw.setBackgroundColor(bg) end
|
|
ui.draw.write(text)
|
|
end
|
|
|
|
function ui.monFill(y, color)
|
|
local w, _ = ui.draw.getSize()
|
|
ui.draw.setCursorPos(1, y)
|
|
ui.draw.setBackgroundColor(color)
|
|
ui.draw.write(string.rep(" ", w))
|
|
end
|
|
|
|
function ui.monCenter(y, text, fg, bg)
|
|
local w, _ = ui.draw.getSize()
|
|
local x = math.floor((w - #text) / 2) + 1
|
|
ui.monWrite(x, y, text, fg, bg)
|
|
end
|
|
|
|
function ui.monBar(x, y, width, ratio, barColor, bgColor)
|
|
local filled = math.floor(ratio * width)
|
|
ui.draw.setCursorPos(x, y)
|
|
ui.draw.setBackgroundColor(barColor)
|
|
ui.draw.write(string.rep(" ", filled))
|
|
ui.draw.setBackgroundColor(bgColor)
|
|
ui.draw.write(string.rep(" ", width - filled))
|
|
end
|
|
|
|
function ui.drawButton(x, y, text, fg, bg, padLeft, padRight)
|
|
padLeft = padLeft or 1
|
|
padRight = padRight or 1
|
|
local full = string.rep(" ", padLeft) .. text .. string.rep(" ", padRight)
|
|
ui.monWrite(x, y, full, fg, bg)
|
|
return x, y, x + #full - 1, y
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Touch zone management
|
|
-------------------------------------------------
|
|
|
|
--- Append a clickable zone to a pending-zone list.
|
|
-- @param zones table — the pending zone array to append to
|
|
function ui.addZone(zones, x1, y1, x2, y2, action, data)
|
|
table.insert(zones, {
|
|
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
|
|
action = action, data = data,
|
|
})
|
|
end
|
|
|
|
--- Find which zone was hit.
|
|
-- @param zones table — the active zone array to search
|
|
function ui.hitTest(zones, x, y)
|
|
for _, zone in ipairs(zones) do
|
|
if x >= zone.x1 and x <= zone.x2 and y >= zone.y1 and y <= zone.y2 then
|
|
return zone.action, zone.data
|
|
end
|
|
end
|
|
return nil, nil
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Monitor setup
|
|
-------------------------------------------------
|
|
|
|
--- Wrap and configure the main inventory monitor.
|
|
-- @param side string — preferred peripheral side/name
|
|
-- @param excludeSide string — side to skip (e.g. smelter monitor)
|
|
-- @return mon, monName or nil, nil
|
|
function ui.setupMonitor(side, excludeSide)
|
|
local mon = peripheral.wrap(side)
|
|
local monName
|
|
if mon and mon.setTextScale then
|
|
monName = side
|
|
else
|
|
mon = nil
|
|
end
|
|
if not mon then
|
|
for _, name in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(name) == "monitor" and name ~= excludeSide then
|
|
mon = peripheral.wrap(name)
|
|
monName = name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not mon then return nil, nil end
|
|
mon.setTextScale(0.5)
|
|
mon.clear()
|
|
return mon, monName
|
|
end
|
|
|
|
--- Wrap and configure the smelter monitor.
|
|
-- @param side string — preferred peripheral side/name
|
|
-- @param excludeName string — main monitor name to skip
|
|
-- @return smelterMon, smelterMonName or nil, nil
|
|
function ui.setupSmelterMonitor(side, excludeName)
|
|
local smelterMon = peripheral.wrap(side)
|
|
local smelterMonName
|
|
if smelterMon and smelterMon.setTextScale then
|
|
smelterMonName = side
|
|
else
|
|
smelterMon = nil
|
|
end
|
|
if not smelterMon then
|
|
for _, name in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(name) == "monitor" and name ~= excludeName then
|
|
smelterMon = peripheral.wrap(name)
|
|
smelterMonName = name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not smelterMon then return nil, nil end
|
|
smelterMon.setTextScale(0.5)
|
|
smelterMon.clear()
|
|
return smelterMon, smelterMonName
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Item filtering
|
|
-------------------------------------------------
|
|
|
|
--- Filter an item list by search query.
|
|
-- @param itemList table — array of { name, total }
|
|
-- @param searchQuery string — filter text (case-insensitive, plain match)
|
|
-- @return filtered table — matching subset
|
|
function ui.getFilteredItems(itemList, searchQuery)
|
|
local filtered = {}
|
|
for _, item in ipairs(itemList) do
|
|
if searchQuery == "" then
|
|
table.insert(filtered, item)
|
|
else
|
|
local lower = item.name:lower():gsub("minecraft:", ""):gsub("_", " ")
|
|
if lower:find(searchQuery:lower(), 1, true) then
|
|
table.insert(filtered, item)
|
|
end
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Crafting recipe helpers
|
|
-------------------------------------------------
|
|
|
|
--- Count ingredients needed for one craft.
|
|
function ui.getRecipeIngredients(recipe)
|
|
local ingredients = {}
|
|
for _, item in ipairs(recipe.grid) do
|
|
if item then
|
|
ingredients[item] = (ingredients[item] or 0) + 1
|
|
end
|
|
end
|
|
return ingredients
|
|
end
|
|
|
|
--- Check if a recipe can be crafted with current stock.
|
|
-- @param recipe table — recipe with .grid
|
|
-- @param getTotal function(itemName) -> number — returns stock count
|
|
function ui.canCraftRecipe(recipe, getTotal)
|
|
local ingredients = ui.getRecipeIngredients(recipe)
|
|
for itemName, needed in pairs(ingredients) do
|
|
if (getTotal(itemName) or 0) < needed then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- How many batches can be crafted.
|
|
-- @param recipe table — recipe with .grid
|
|
-- @param getTotal function(itemName) -> number
|
|
function ui.maxCraftBatches(recipe, getTotal)
|
|
local ingredients = ui.getRecipeIngredients(recipe)
|
|
local minBatches = math.huge
|
|
for itemName, needed in pairs(ingredients) do
|
|
local batches = math.floor((getTotal(itemName) or 0) / needed)
|
|
if batches < minBatches then minBatches = batches end
|
|
end
|
|
if minBatches == math.huge then return 0 end
|
|
return minBatches
|
|
end
|
|
|
|
--- Get list of ingredients where stock < needed.
|
|
-- @param recipe table — recipe with .grid
|
|
-- @param getTotal function(itemName) -> number
|
|
function ui.getMissingIngredients(recipe, getTotal)
|
|
local ingredients = ui.getRecipeIngredients(recipe)
|
|
local missing = {}
|
|
for itemName, needed in pairs(ingredients) do
|
|
local have = getTotal(itemName) or 0
|
|
if have < needed then
|
|
table.insert(missing, { name = itemName, have = have, need = needed })
|
|
end
|
|
end
|
|
return missing
|
|
end
|
|
|
|
return ui
|