Add shared UI helpers for inventory management and client interaction
This commit is contained in:
216
lib/ui.lua
Normal file
216
lib/ui.lua
Normal file
@@ -0,0 +1,216 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user