Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ------------------------------------------------------------
- -- ComputerCraft: Tweaked – Group Navigator on Monitor with Touch,
- -- Customizable Title, Horizontal & Vertical Scrolling with Always-Visible Vertical Nav Buttons
- --
- -- Global Configuration Variables
- ------------------------------------------------------------
- MAIN_SCALE = 0.5 -- Main UI text scale (for title, navbar, and items)
- -- Title customization (displayed above the navbar)
- TITLE_TEXT = "Bizuni Bay Cafe" -- Title content
- TITLE_HEIGHT = 3 -- Number of lines reserved for the title area
- TITLE_TEXT_COLOR = colors.white -- Title text color
- TITLE_BACKGROUND_COLOR = colors.purple -- Title background color
- -- Navbar arrow (scroll buttons) colors (for horizontal scrolling)
- ARROW_BACKGROUND_COLOR = colors.lightGray
- ARROW_TEXT_COLOR = colors.black
- -- Vertical scrollbar arrow colors when enabled vs. disabled
- DISABLED_VERTICAL_ARROW_BACKGROUND = colors.gray
- DISABLED_VERTICAL_ARROW_TEXT = colors.black
- -- Scroll steps:
- navbarScrollStep = 5 -- horizontal (navbar) scroll step
- itemsScrollStep = 1 -- vertical (items) scroll step
- -- Vertical scrollbar colors (for the handle and background)
- SCROLLBAR_BACKGROUND_COLOR = colors.gray
- SCROLLBAR_HANDLE_COLOR = colors.white
- -- Update controls
- UPDATE_INTERVAL = 10 -- seconds between updates
- ------------------------------------------------------------
- -- Wrap the monitor peripheral.
- ------------------------------------------------------------
- local mon = peripheral.find("monitor")
- if not mon then
- error("No monitor peripheral found!")
- end
- mon.setTextScale(MAIN_SCALE)
- rednet.open("back")
- ------------------------------------------------------------
- -- Global Variables for the UI
- ------------------------------------------------------------
- local groups = {} -- Mapping groupName => { itemIDs }
- local groupNames = {} -- Ordered list of group names (with "all" first)
- local items = {} -- Mapping itemID => { name, inStock }
- local selectedGroupIndex = 1 -- Index in groupNames for the currently selected group
- local navbarOffset = 0 -- Horizontal scroll offset (in characters)
- local itemsOffset = 0 -- Vertical scroll offset (in items)
- local updateTimer = os.startTimer(UPDATE_INTERVAL)
- ------------------------------------------------------------
- -- Function: loadGroups
- -- Loads groups from a Lua file (e.g., "groups.lua") that returns a table
- -- where each key is a group name and its value is a list of item IDs.
- -- Also creates an "all" group by concatenating all items.
- ------------------------------------------------------------
- local function loadGroups(filename)
- local fileGroups = dofile(filename)
- groups = {} -- Clear any previous data
- -- Copy groups from file (assume file does NOT include "all")
- for groupName, groupItems in pairs(fileGroups) do
- groups[groupName] = groupItems
- end
- -- Build the "all" group by concatenating items from every group.
- local allItems = {}
- for _, groupItems in pairs(groups) do
- for _, item in ipairs(groupItems) do
- table.insert(allItems, item)
- end
- end
- groups["All"] = allItems
- -- Build the ordered groupNames list.
- groupNames = {}
- for groupName, _ in pairs(groups) do
- if groupName ~= "All" then
- table.insert(groupNames, groupName)
- end
- end
- table.sort(groupNames) -- sort alphabetically if desired
- table.insert(groupNames, 1, "All")
- end
- ------------------------------------------------------------
- --- Function: getItem
- --- Returns the item data for the given itemID. If the item
- --- is not found, a placeholder is returned with the itemID
- --- as the name and inStock set to false.
- ------------------------------------------------------------
- local function getItem(itemID)
- local item = items[itemID]
- if not item then
- return {
- name = itemID,
- inStock = false
- }
- end
- return items[itemID]
- end
- ------------------------------------------------------------
- -- Function: buildNavbarButtonsData
- -- Creates an array of navbar button data with text and their full widths.
- ------------------------------------------------------------
- local function buildNavbarButtonsData()
- local data = {}
- local totalLength = 0
- for i, groupName in ipairs(groupNames) do
- local btnText = " " .. groupName .. " "
- local length = string.len(btnText)
- local buttonData = {
- group = groupName,
- text = btnText,
- length = length,
- index = i,
- startPos = totalLength + 1,
- endPos = totalLength + length
- }
- totalLength = totalLength + length
- table.insert(data, buttonData)
- end
- return data, totalLength
- end
- ------------------------------------------------------------
- -- Function: drawTitle
- -- Clears the title area and writes the title on a single, central line.
- -- If TITLE_HEIGHT is odd, the title is written on the middle line;
- -- if even, it is written on math.ceil(TITLE_HEIGHT/2), which is near the middle.
- ------------------------------------------------------------
- local function drawTitle()
- mon.setTextScale(MAIN_SCALE)
- local titleWidth, _ = mon.getSize()
- -- Clear all lines in the title area.
- for i = 1, TITLE_HEIGHT do
- mon.setCursorPos(1, i)
- mon.setBackgroundColor(TITLE_BACKGROUND_COLOR)
- mon.setTextColor(TITLE_TEXT_COLOR)
- mon.clearLine()
- end
- -- Determine the middle line.
- local middleLine = math.ceil(TITLE_HEIGHT / 2)
- local text = TITLE_TEXT
- local startX = math.floor((titleWidth - string.len(text)) / 2) + 1
- mon.setCursorPos(startX, middleLine)
- mon.write(text)
- end
- ------------------------------------------------------------
- -- Function: drawNavbar
- -- Draws the horizontal navbar (with scroll arrows if needed) on a given row.
- -- uiOffsetY is the number of lines used by the title.
- -- The navbar is drawn on row (uiOffsetY + 1).
- ------------------------------------------------------------
- local function drawNavbar(uiOffsetY)
- local mainWidth, _ = mon.getSize()
- local availableWidth = mainWidth -- will adjust if scrolling is active
- local navbarData, totalLength = buildNavbarButtonsData()
- local useScrolling = totalLength > mainWidth
- local leftMargin
- if useScrolling then
- leftMargin = 2 -- Reserve column 1 for the left arrow.
- availableWidth = mainWidth - 2 -- Reserve the rightmost column for the right arrow.
- if navbarOffset > totalLength - availableWidth then
- navbarOffset = totalLength - availableWidth
- end
- if navbarOffset < 0 then navbarOffset = 0 end
- else
- leftMargin = 1
- availableWidth = mainWidth
- navbarOffset = 0
- end
- -- Clear the navbar line (row uiOffsetY + 1)
- mon.setCursorPos(1, uiOffsetY + 1)
- mon.setBackgroundColor(colors.black)
- mon.setTextColor(colors.white)
- mon.clearLine()
- local clickableButtons = {}
- -- Draw left arrow if needed.
- if useScrolling and navbarOffset > 0 then
- mon.setCursorPos(1, uiOffsetY + 1)
- mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
- mon.setTextColor(ARROW_TEXT_COLOR)
- mon.write("<")
- clickableButtons.leftArrow = { x1 = 1, x2 = 1, y = uiOffsetY + 1 }
- else
- clickableButtons.leftArrow = nil
- end
- -- Draw right arrow if needed.
- if useScrolling and (navbarOffset + availableWidth) < totalLength then
- mon.setCursorPos(mainWidth, uiOffsetY + 1)
- mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
- mon.setTextColor(ARROW_TEXT_COLOR)
- mon.write(">")
- clickableButtons.rightArrow = { x1 = mainWidth, x2 = mainWidth, y = uiOffsetY + 1 }
- else
- clickableButtons.rightArrow = nil
- end
- -- Draw each navbar button (or its visible portion).
- for _, btn in ipairs(navbarData) do
- local btnStartFull = btn.startPos
- local btnEndFull = btn.endPos
- local visibleStartFull = math.max(btnStartFull, navbarOffset + 1)
- local visibleEndFull = math.min(btnEndFull, navbarOffset + availableWidth)
- if visibleStartFull <= visibleEndFull then
- local visibleTextStart = visibleStartFull - btnStartFull + 1
- local visibleTextEnd = visibleEndFull - btnStartFull + 1
- local visibleText = string.sub(btn.text, visibleTextStart, visibleTextEnd)
- local onScreenX = leftMargin + (visibleStartFull - (navbarOffset + 1))
- mon.setCursorPos(onScreenX, uiOffsetY + 1)
- -- Highlight the selected group button.
- if btn.index == selectedGroupIndex then
- mon.setBackgroundColor(colors.white)
- mon.setTextColor(colors.black)
- else
- mon.setBackgroundColor(colors.black)
- mon.setTextColor(colors.white)
- end
- mon.write(visibleText)
- local buttonClickable = {
- group = btn.group,
- index = btn.index,
- x1 = onScreenX,
- x2 = onScreenX + string.len(visibleText) - 1,
- y = uiOffsetY + 1
- }
- table.insert(clickableButtons, buttonClickable)
- end
- end
- return clickableButtons, totalLength, availableWidth
- end
- ------------------------------------------------------------
- -- Function: drawItems
- -- Draws the vertical list of items for the selected group with vertical
- -- scrolling. A blank line separates the navbar from the items (row uiOffsetY+2).
- -- The items area occupies rows from uiOffsetY+3 to the bottom, with the
- -- rightmost column reserved for a vertical scrollbar.
- ------------------------------------------------------------
- local function drawItems(uiOffsetY)
- local mainWidth, mainHeight = mon.getSize()
- local visibleStartRow = uiOffsetY + 3
- local visibleRows = mainHeight - (uiOffsetY + 2) -- number of rows for items
- local selectedGroup = groupNames[selectedGroupIndex]
- local groupItems = groups[selectedGroup] or {}
- local totalItems = #groupItems
- -- Clear the items area (rows visibleStartRow to mainHeight)
- for y = visibleStartRow, mainHeight do
- mon.setCursorPos(1, y)
- mon.setBackgroundColor(colors.black)
- mon.clearLine()
- end
- -- Draw visible items (only using columns 1 to mainWidth-1)
- for displayRow = 0, visibleRows - 1 do
- local itemIndex = itemsOffset + displayRow + 1
- if itemIndex > totalItems then break end
- local itemID = groupItems[itemIndex]
- local item = getItem(itemID)
- mon.setCursorPos(1, visibleStartRow + displayRow)
- if item.inStock then
- mon.setTextColor(colors.white)
- else
- mon.setTextColor(colors.gray)
- end
- local displayText = string.sub(item.name, 1, mainWidth - 1)
- mon.write(displayText)
- end
- -- Always draw vertical scrollbar in the rightmost column.
- -- We reserve the top and bottom rows of the items area for up/down arrows.
- local maxOffset = math.max(0, totalItems - visibleRows)
- local scrollAreaRows = (visibleRows >= 3) and (visibleRows - 2) or 0
- local handleHeight, handleStartRow
- if totalItems > visibleRows and scrollAreaRows > 0 then
- handleHeight = math.max(1, math.floor((scrollAreaRows * visibleRows) / totalItems))
- handleStartRow = visibleStartRow + 1 + math.floor((itemsOffset / maxOffset) * (scrollAreaRows - handleHeight))
- else
- handleHeight = scrollAreaRows
- handleStartRow = visibleStartRow + 1
- end
- -- Draw each row in the scrollbar column (rightmost column).
- for row = 0, visibleRows - 1 do
- local currentRow = visibleStartRow + row
- mon.setCursorPos(mainWidth, currentRow)
- if row == 0 then
- -- Top arrow always drawn.
- if itemsOffset > 0 then
- mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
- mon.setTextColor(ARROW_TEXT_COLOR)
- else
- mon.setBackgroundColor(DISABLED_VERTICAL_ARROW_BACKGROUND)
- mon.setTextColor(DISABLED_VERTICAL_ARROW_TEXT)
- end
- mon.write("^")
- elseif row == visibleRows - 1 then
- -- Bottom arrow always drawn.
- if itemsOffset < maxOffset then
- mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
- mon.setTextColor(ARROW_TEXT_COLOR)
- else
- mon.setBackgroundColor(DISABLED_VERTICAL_ARROW_BACKGROUND)
- mon.setTextColor(DISABLED_VERTICAL_ARROW_TEXT)
- end
- mon.write("v")
- else
- -- Middle rows: draw scrollbar handle or background.
- if row >= (handleStartRow - visibleStartRow) and row < (handleStartRow - visibleStartRow + handleHeight) then
- mon.setBackgroundColor(SCROLLBAR_HANDLE_COLOR)
- else
- mon.setBackgroundColor(SCROLLBAR_BACKGROUND_COLOR)
- end
- mon.write(" ")
- end
- end
- end
- ------------------------------------------------------------
- -- Function: drawUI
- -- Clears the monitor and redraws the title, navbar (with horizontal scroll),
- -- a blank separator line, and the items list (with vertical scroll).
- ------------------------------------------------------------
- local function drawUI()
- mon.clear()
- mon.setTextScale(MAIN_SCALE)
- drawTitle()
- local uiOffsetY = TITLE_HEIGHT -- Title occupies the first TITLE_HEIGHT lines.
- local clickableButtons, totalLength, availableWidth = drawNavbar(uiOffsetY)
- -- Draw a blank separator line between the navbar and items.
- mon.setCursorPos(1, uiOffsetY + 2)
- mon.setBackgroundColor(colors.black)
- mon.clearLine()
- drawItems(uiOffsetY)
- return clickableButtons, totalLength, availableWidth, uiOffsetY
- end
- ------------------------------------------------------------
- -- Main Event Loop – Process monitor touch events.
- -- Navbar touches (row uiOffsetY+1) are processed for horizontal scrolling
- -- and group selection. Touches in the reserved scrollbar column (rightmost column)
- -- on the top or bottom rows scroll items vertically.
- ------------------------------------------------------------
- local function main()
- mon.setTextScale(MAIN_SCALE)
- mon.clear()
- drawTitle()
- loadGroups("items.lua")
- rednet.broadcast(groups["All"], "items")
- print("Sent 'items' message, listening for itemMetadata responses...")
- local clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
- local mainWidth, mainHeight = mon.getSize()
- local visibleStartRow = uiOffsetY + 3
- local visibleRows = mainHeight - (uiOffsetY + 2)
- while true do
- local event, param1, param2, param3, param4, param5 = os.pullEvent()
- -- For monitor_touch events: param1 = side, param2 = x, param3 = y, param4 = button, param5 = player
- if event == "monitor_touch" then
- mainWidth, mainHeight = mon.getSize()
- visibleStartRow = uiOffsetY + 3
- visibleRows = mainHeight - (uiOffsetY + 2)
- if param3 == uiOffsetY + 1 then
- -- Process horizontal (navbar) touches.
- if clickableButtons.leftArrow and param2 >= clickableButtons.leftArrow.x1 and param2 <= clickableButtons.leftArrow.x2 then
- navbarOffset = math.max(navbarOffset - navbarScrollStep, 0)
- clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
- elseif clickableButtons.rightArrow and param2 >= clickableButtons.rightArrow.x1 and param2 <= clickableButtons.rightArrow.x2 then
- navbarOffset = math.min(navbarOffset + navbarScrollStep, totalLength - availableWidth)
- clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
- else
- for _, btn in ipairs(clickableButtons) do
- if btn.y == uiOffsetY + 1 and param2 >= btn.x1 and param2 <= btn.x2 then
- if btn.index ~= selectedGroupIndex then
- selectedGroupIndex = btn.index
- itemsOffset = 0 -- Reset vertical scroll when switching groups.
- clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
- end
- break
- end
- end
- end
- elseif param2 == mainWidth and param3 >= visibleStartRow and param3 < visibleStartRow + visibleRows then
- -- Process vertical (items) scrollbar touches.
- local selectedGroup = groupNames[selectedGroupIndex]
- local groupItems = groups[selectedGroup] or {}
- local totalItems = #groupItems
- local maxOffset = math.max(0, totalItems - visibleRows)
- if totalItems > visibleRows then
- if param3 == visibleStartRow and itemsOffset > 0 then
- itemsOffset = math.max(itemsOffset - itemsScrollStep, 0)
- drawItems(uiOffsetY)
- elseif param3 == visibleStartRow + visibleRows - 1 and itemsOffset < maxOffset then
- itemsOffset = math.min(itemsOffset + itemsScrollStep, maxOffset)
- drawItems(uiOffsetY)
- end
- else
- drawItems(uiOffsetY)
- end
- end
- elseif event == "timer" then
- if param1 == updateTimer then
- -- Re-draw the items list to refresh in-stock statuses.
- drawItems(uiOffsetY)
- updateTimer = os.startTimer(UPDATE_INTERVAL)
- end
- elseif event == "rednet_message" then
- local sender, message, protocol = param1, param2, param3
- if protocol == "itemMetadata" then
- if type(message) ~= "table" then
- print("Received invalid itemMetadata response: " .. tostring(param3))
- goto continue
- end
- local itemID = message.item
- local metadata = message.metadata
- items[itemID] = metadata
- print("Received itemMetadata from " .. sender .. " for " .. itemID .. ":")
- print(textutils.serialize(metadata))
- drawItems(uiOffsetY)
- end
- end
- ::continue::
- end
- end
- ------------------------------------------------------------
- -- Run the Program
- ------------------------------------------------------------
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement