Advertisement
Jishh_

CC Menu

Feb 19th, 2025 (edited)
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.41 KB | None | 0 0
  1. ------------------------------------------------------------
  2. -- ComputerCraft: Tweaked – Group Navigator on Monitor with Touch,
  3. -- Customizable Title, Horizontal & Vertical Scrolling with Always-Visible Vertical Nav Buttons
  4. --
  5. -- Global Configuration Variables
  6. ------------------------------------------------------------
  7. MAIN_SCALE = 0.5 -- Main UI text scale (for title, navbar, and items)
  8.  
  9. -- Title customization (displayed above the navbar)
  10. TITLE_TEXT = "Bizuni Bay Cafe"         -- Title content
  11. TITLE_HEIGHT = 3                       -- Number of lines reserved for the title area
  12. TITLE_TEXT_COLOR = colors.white        -- Title text color
  13. TITLE_BACKGROUND_COLOR = colors.purple -- Title background color
  14.  
  15. -- Navbar arrow (scroll buttons) colors (for horizontal scrolling)
  16. ARROW_BACKGROUND_COLOR = colors.lightGray
  17. ARROW_TEXT_COLOR = colors.black
  18.  
  19. -- Vertical scrollbar arrow colors when enabled vs. disabled
  20. DISABLED_VERTICAL_ARROW_BACKGROUND = colors.gray
  21. DISABLED_VERTICAL_ARROW_TEXT = colors.black
  22.  
  23. -- Scroll steps:
  24. navbarScrollStep = 5  -- horizontal (navbar) scroll step
  25. itemsScrollStep = 1   -- vertical (items) scroll step
  26.  
  27. -- Vertical scrollbar colors (for the handle and background)
  28. SCROLLBAR_BACKGROUND_COLOR = colors.gray
  29. SCROLLBAR_HANDLE_COLOR = colors.white
  30.  
  31. -- Update controls
  32. UPDATE_INTERVAL = 10  -- seconds between updates
  33.  
  34. ------------------------------------------------------------
  35. -- Wrap the monitor peripheral.
  36. ------------------------------------------------------------
  37. local mon = peripheral.find("monitor")
  38. if not mon then
  39.   error("No monitor peripheral found!")
  40. end
  41.  
  42. mon.setTextScale(MAIN_SCALE)
  43.  
  44. rednet.open("back")
  45.  
  46. ------------------------------------------------------------
  47. -- Global Variables for the UI
  48. ------------------------------------------------------------
  49. local groups = {}           -- Mapping groupName => { itemIDs }
  50. local groupNames = {}       -- Ordered list of group names (with "all" first)
  51. local items = {}            -- Mapping itemID => { name, inStock }
  52. local selectedGroupIndex = 1  -- Index in groupNames for the currently selected group
  53. local navbarOffset = 0        -- Horizontal scroll offset (in characters)
  54. local itemsOffset = 0         -- Vertical scroll offset (in items)
  55. local updateTimer = os.startTimer(UPDATE_INTERVAL)
  56.  
  57. ------------------------------------------------------------
  58. -- Function: loadGroups
  59. -- Loads groups from a Lua file (e.g., "groups.lua") that returns a table
  60. -- where each key is a group name and its value is a list of item IDs.
  61. -- Also creates an "all" group by concatenating all items.
  62. ------------------------------------------------------------
  63. local function loadGroups(filename)
  64.   local fileGroups = dofile(filename)
  65.   groups = {}  -- Clear any previous data
  66.  
  67.   -- Copy groups from file (assume file does NOT include "all")
  68.   for groupName, groupItems in pairs(fileGroups) do
  69.     groups[groupName] = groupItems
  70.   end
  71.  
  72.   -- Build the "all" group by concatenating items from every group.
  73.   local allItems = {}
  74.   for _, groupItems in pairs(groups) do
  75.     for _, item in ipairs(groupItems) do
  76.       table.insert(allItems, item)
  77.     end
  78.   end
  79.   groups["All"] = allItems
  80.  
  81.   -- Build the ordered groupNames list.
  82.   groupNames = {}
  83.   for groupName, _ in pairs(groups) do
  84.     if groupName ~= "All" then
  85.       table.insert(groupNames, groupName)
  86.     end
  87.   end
  88.   table.sort(groupNames)  -- sort alphabetically if desired
  89.   table.insert(groupNames, 1, "All")
  90. end
  91.  
  92. ------------------------------------------------------------
  93. --- Function: getItem
  94. --- Returns the item data for the given itemID. If the item
  95. --- is not found, a placeholder is returned with the itemID
  96. --- as the name and inStock set to false.
  97. ------------------------------------------------------------
  98. local function getItem(itemID)
  99.   local item = items[itemID]
  100.   if not item then
  101.     return {
  102.       name = itemID,
  103.       inStock = false
  104.     }
  105.   end
  106.   return items[itemID]
  107. end
  108.  
  109. ------------------------------------------------------------
  110. -- Function: buildNavbarButtonsData
  111. -- Creates an array of navbar button data with text and their full widths.
  112. ------------------------------------------------------------
  113. local function buildNavbarButtonsData()
  114.   local data = {}
  115.   local totalLength = 0
  116.   for i, groupName in ipairs(groupNames) do
  117.     local btnText = " " .. groupName .. " "
  118.     local length = string.len(btnText)
  119.     local buttonData = {
  120.       group = groupName,
  121.       text = btnText,
  122.       length = length,
  123.       index = i,
  124.       startPos = totalLength + 1,
  125.       endPos = totalLength + length
  126.     }
  127.     totalLength = totalLength + length
  128.     table.insert(data, buttonData)
  129.   end
  130.   return data, totalLength
  131. end
  132.  
  133. ------------------------------------------------------------
  134. -- Function: drawTitle
  135. -- Clears the title area and writes the title on a single, central line.
  136. -- If TITLE_HEIGHT is odd, the title is written on the middle line;
  137. -- if even, it is written on math.ceil(TITLE_HEIGHT/2), which is near the middle.
  138. ------------------------------------------------------------
  139. local function drawTitle()
  140.   mon.setTextScale(MAIN_SCALE)
  141.   local titleWidth, _ = mon.getSize()
  142.   -- Clear all lines in the title area.
  143.   for i = 1, TITLE_HEIGHT do
  144.     mon.setCursorPos(1, i)
  145.     mon.setBackgroundColor(TITLE_BACKGROUND_COLOR)
  146.     mon.setTextColor(TITLE_TEXT_COLOR)
  147.     mon.clearLine()
  148.   end
  149.   -- Determine the middle line.
  150.   local middleLine = math.ceil(TITLE_HEIGHT / 2)
  151.   local text = TITLE_TEXT
  152.   local startX = math.floor((titleWidth - string.len(text)) / 2) + 1
  153.   mon.setCursorPos(startX, middleLine)
  154.   mon.write(text)
  155. end
  156.  
  157. ------------------------------------------------------------
  158. -- Function: drawNavbar
  159. -- Draws the horizontal navbar (with scroll arrows if needed) on a given row.
  160. -- uiOffsetY is the number of lines used by the title.
  161. -- The navbar is drawn on row (uiOffsetY + 1).
  162. ------------------------------------------------------------
  163. local function drawNavbar(uiOffsetY)
  164.   local mainWidth, _ = mon.getSize()
  165.   local availableWidth = mainWidth  -- will adjust if scrolling is active
  166.   local navbarData, totalLength = buildNavbarButtonsData()
  167.   local useScrolling = totalLength > mainWidth
  168.  
  169.   local leftMargin
  170.   if useScrolling then
  171.     leftMargin = 2                 -- Reserve column 1 for the left arrow.
  172.     availableWidth = mainWidth - 2 -- Reserve the rightmost column for the right arrow.
  173.     if navbarOffset > totalLength - availableWidth then
  174.       navbarOffset = totalLength - availableWidth
  175.     end
  176.     if navbarOffset < 0 then navbarOffset = 0 end
  177.   else
  178.     leftMargin = 1
  179.     availableWidth = mainWidth
  180.     navbarOffset = 0
  181.   end
  182.  
  183.   -- Clear the navbar line (row uiOffsetY + 1)
  184.   mon.setCursorPos(1, uiOffsetY + 1)
  185.   mon.setBackgroundColor(colors.black)
  186.   mon.setTextColor(colors.white)
  187.   mon.clearLine()
  188.  
  189.   local clickableButtons = {}
  190.  
  191.   -- Draw left arrow if needed.
  192.   if useScrolling and navbarOffset > 0 then
  193.     mon.setCursorPos(1, uiOffsetY + 1)
  194.     mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
  195.     mon.setTextColor(ARROW_TEXT_COLOR)
  196.     mon.write("<")
  197.     clickableButtons.leftArrow = { x1 = 1, x2 = 1, y = uiOffsetY + 1 }
  198.   else
  199.     clickableButtons.leftArrow = nil
  200.   end
  201.  
  202.   -- Draw right arrow if needed.
  203.   if useScrolling and (navbarOffset + availableWidth) < totalLength then
  204.     mon.setCursorPos(mainWidth, uiOffsetY + 1)
  205.     mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
  206.     mon.setTextColor(ARROW_TEXT_COLOR)
  207.     mon.write(">")
  208.     clickableButtons.rightArrow = { x1 = mainWidth, x2 = mainWidth, y = uiOffsetY + 1 }
  209.   else
  210.     clickableButtons.rightArrow = nil
  211.   end
  212.  
  213.   -- Draw each navbar button (or its visible portion).
  214.   for _, btn in ipairs(navbarData) do
  215.     local btnStartFull = btn.startPos
  216.     local btnEndFull = btn.endPos
  217.     local visibleStartFull = math.max(btnStartFull, navbarOffset + 1)
  218.     local visibleEndFull = math.min(btnEndFull, navbarOffset + availableWidth)
  219.     if visibleStartFull <= visibleEndFull then
  220.       local visibleTextStart = visibleStartFull - btnStartFull + 1
  221.       local visibleTextEnd = visibleEndFull - btnStartFull + 1
  222.       local visibleText = string.sub(btn.text, visibleTextStart, visibleTextEnd)
  223.       local onScreenX = leftMargin + (visibleStartFull - (navbarOffset + 1))
  224.       mon.setCursorPos(onScreenX, uiOffsetY + 1)
  225.       -- Highlight the selected group button.
  226.       if btn.index == selectedGroupIndex then
  227.         mon.setBackgroundColor(colors.white)
  228.         mon.setTextColor(colors.black)
  229.       else
  230.         mon.setBackgroundColor(colors.black)
  231.         mon.setTextColor(colors.white)
  232.       end
  233.       mon.write(visibleText)
  234.       local buttonClickable = {
  235.         group = btn.group,
  236.         index = btn.index,
  237.         x1 = onScreenX,
  238.         x2 = onScreenX + string.len(visibleText) - 1,
  239.         y = uiOffsetY + 1
  240.       }
  241.       table.insert(clickableButtons, buttonClickable)
  242.     end
  243.   end
  244.  
  245.   return clickableButtons, totalLength, availableWidth
  246. end
  247.  
  248. ------------------------------------------------------------
  249. -- Function: drawItems
  250. -- Draws the vertical list of items for the selected group with vertical
  251. -- scrolling. A blank line separates the navbar from the items (row uiOffsetY+2).
  252. -- The items area occupies rows from uiOffsetY+3 to the bottom, with the
  253. -- rightmost column reserved for a vertical scrollbar.
  254. ------------------------------------------------------------
  255. local function drawItems(uiOffsetY)
  256.   local mainWidth, mainHeight = mon.getSize()
  257.   local visibleStartRow = uiOffsetY + 3
  258.   local visibleRows = mainHeight - (uiOffsetY + 2)  -- number of rows for items
  259.  
  260.   local selectedGroup = groupNames[selectedGroupIndex]
  261.   local groupItems = groups[selectedGroup] or {}
  262.   local totalItems = #groupItems
  263.  
  264.   -- Clear the items area (rows visibleStartRow to mainHeight)
  265.   for y = visibleStartRow, mainHeight do
  266.     mon.setCursorPos(1, y)
  267.     mon.setBackgroundColor(colors.black)
  268.     mon.clearLine()
  269.   end
  270.  
  271.   -- Draw visible items (only using columns 1 to mainWidth-1)
  272.   for displayRow = 0, visibleRows - 1 do
  273.     local itemIndex = itemsOffset + displayRow + 1
  274.     if itemIndex > totalItems then break end
  275.     local itemID = groupItems[itemIndex]
  276.     local item = getItem(itemID)
  277.     mon.setCursorPos(1, visibleStartRow + displayRow)
  278.     if item.inStock then
  279.       mon.setTextColor(colors.white)
  280.     else
  281.       mon.setTextColor(colors.gray)
  282.     end
  283.     local displayText = string.sub(item.name, 1, mainWidth - 1)
  284.     mon.write(displayText)
  285.   end
  286.  
  287.   -- Always draw vertical scrollbar in the rightmost column.
  288.   -- We reserve the top and bottom rows of the items area for up/down arrows.
  289.   local maxOffset = math.max(0, totalItems - visibleRows)
  290.   local scrollAreaRows = (visibleRows >= 3) and (visibleRows - 2) or 0
  291.  
  292.   local handleHeight, handleStartRow
  293.   if totalItems > visibleRows and scrollAreaRows > 0 then
  294.     handleHeight = math.max(1, math.floor((scrollAreaRows * visibleRows) / totalItems))
  295.     handleStartRow = visibleStartRow + 1 + math.floor((itemsOffset / maxOffset) * (scrollAreaRows - handleHeight))
  296.   else
  297.     handleHeight = scrollAreaRows
  298.     handleStartRow = visibleStartRow + 1
  299.   end
  300.  
  301.   -- Draw each row in the scrollbar column (rightmost column).
  302.   for row = 0, visibleRows - 1 do
  303.     local currentRow = visibleStartRow + row
  304.     mon.setCursorPos(mainWidth, currentRow)
  305.     if row == 0 then
  306.       -- Top arrow always drawn.
  307.       if itemsOffset > 0 then
  308.         mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
  309.         mon.setTextColor(ARROW_TEXT_COLOR)
  310.       else
  311.         mon.setBackgroundColor(DISABLED_VERTICAL_ARROW_BACKGROUND)
  312.         mon.setTextColor(DISABLED_VERTICAL_ARROW_TEXT)
  313.       end
  314.       mon.write("^")
  315.     elseif row == visibleRows - 1 then
  316.       -- Bottom arrow always drawn.
  317.       if itemsOffset < maxOffset then
  318.         mon.setBackgroundColor(ARROW_BACKGROUND_COLOR)
  319.         mon.setTextColor(ARROW_TEXT_COLOR)
  320.       else
  321.         mon.setBackgroundColor(DISABLED_VERTICAL_ARROW_BACKGROUND)
  322.         mon.setTextColor(DISABLED_VERTICAL_ARROW_TEXT)
  323.       end
  324.       mon.write("v")
  325.     else
  326.       -- Middle rows: draw scrollbar handle or background.
  327.       if row >= (handleStartRow - visibleStartRow) and row < (handleStartRow - visibleStartRow + handleHeight) then
  328.         mon.setBackgroundColor(SCROLLBAR_HANDLE_COLOR)
  329.       else
  330.         mon.setBackgroundColor(SCROLLBAR_BACKGROUND_COLOR)
  331.       end
  332.       mon.write(" ")
  333.     end
  334.   end
  335. end
  336.  
  337. ------------------------------------------------------------
  338. -- Function: drawUI
  339. -- Clears the monitor and redraws the title, navbar (with horizontal scroll),
  340. -- a blank separator line, and the items list (with vertical scroll).
  341. ------------------------------------------------------------
  342. local function drawUI()
  343.   mon.clear()
  344.   mon.setTextScale(MAIN_SCALE)
  345.   drawTitle()
  346.   local uiOffsetY = TITLE_HEIGHT  -- Title occupies the first TITLE_HEIGHT lines.
  347.   local clickableButtons, totalLength, availableWidth = drawNavbar(uiOffsetY)
  348.  
  349.   -- Draw a blank separator line between the navbar and items.
  350.   mon.setCursorPos(1, uiOffsetY + 2)
  351.   mon.setBackgroundColor(colors.black)
  352.   mon.clearLine()
  353.  
  354.   drawItems(uiOffsetY)
  355.   return clickableButtons, totalLength, availableWidth, uiOffsetY
  356. end
  357.  
  358. ------------------------------------------------------------
  359. -- Main Event Loop – Process monitor touch events.
  360. -- Navbar touches (row uiOffsetY+1) are processed for horizontal scrolling
  361. -- and group selection. Touches in the reserved scrollbar column (rightmost column)
  362. -- on the top or bottom rows scroll items vertically.
  363. ------------------------------------------------------------
  364. local function main()
  365.   mon.setTextScale(MAIN_SCALE)
  366.   mon.clear()
  367.   drawTitle()
  368.  
  369.   loadGroups("items.lua")
  370.   rednet.broadcast(groups["All"], "items")
  371.   print("Sent 'items' message, listening for itemMetadata responses...")
  372.  
  373.   local clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
  374.   local mainWidth, mainHeight = mon.getSize()
  375.   local visibleStartRow = uiOffsetY + 3
  376.   local visibleRows = mainHeight - (uiOffsetY + 2)
  377.  
  378.   while true do
  379.     local event, param1, param2, param3, param4, param5 = os.pullEvent()
  380.     -- For monitor_touch events: param1 = side, param2 = x, param3 = y, param4 = button, param5 = player
  381.     if event == "monitor_touch" then
  382.       mainWidth, mainHeight = mon.getSize()
  383.       visibleStartRow = uiOffsetY + 3
  384.       visibleRows = mainHeight - (uiOffsetY + 2)
  385.  
  386.       if param3 == uiOffsetY + 1 then
  387.         -- Process horizontal (navbar) touches.
  388.         if clickableButtons.leftArrow and param2 >= clickableButtons.leftArrow.x1 and param2 <= clickableButtons.leftArrow.x2 then
  389.           navbarOffset = math.max(navbarOffset - navbarScrollStep, 0)
  390.           clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
  391.         elseif clickableButtons.rightArrow and param2 >= clickableButtons.rightArrow.x1 and param2 <= clickableButtons.rightArrow.x2 then
  392.           navbarOffset = math.min(navbarOffset + navbarScrollStep, totalLength - availableWidth)
  393.           clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
  394.         else
  395.           for _, btn in ipairs(clickableButtons) do
  396.             if btn.y == uiOffsetY + 1 and param2 >= btn.x1 and param2 <= btn.x2 then
  397.               if btn.index ~= selectedGroupIndex then
  398.                 selectedGroupIndex = btn.index
  399.                 itemsOffset = 0  -- Reset vertical scroll when switching groups.
  400.                 clickableButtons, totalLength, availableWidth, uiOffsetY = drawUI()
  401.               end
  402.               break
  403.             end
  404.           end
  405.         end
  406.       elseif param2 == mainWidth and param3 >= visibleStartRow and param3 < visibleStartRow + visibleRows then
  407.         -- Process vertical (items) scrollbar touches.
  408.         local selectedGroup = groupNames[selectedGroupIndex]
  409.         local groupItems = groups[selectedGroup] or {}
  410.         local totalItems = #groupItems
  411.         local maxOffset = math.max(0, totalItems - visibleRows)
  412.         if totalItems > visibleRows then
  413.           if param3 == visibleStartRow and itemsOffset > 0 then
  414.             itemsOffset = math.max(itemsOffset - itemsScrollStep, 0)
  415.             drawItems(uiOffsetY)
  416.           elseif param3 == visibleStartRow + visibleRows - 1 and itemsOffset < maxOffset then
  417.             itemsOffset = math.min(itemsOffset + itemsScrollStep, maxOffset)
  418.             drawItems(uiOffsetY)
  419.           end
  420.         else
  421.           drawItems(uiOffsetY)
  422.         end
  423.       end
  424.  
  425.     elseif event == "timer" then
  426.       if param1 == updateTimer then
  427.         -- Re-draw the items list to refresh in-stock statuses.
  428.         drawItems(uiOffsetY)
  429.         updateTimer = os.startTimer(UPDATE_INTERVAL)
  430.       end
  431.     elseif event == "rednet_message" then
  432.       local sender, message, protocol = param1, param2, param3
  433.       if protocol == "itemMetadata" then
  434.         if type(message) ~= "table" then
  435.           print("Received invalid itemMetadata response: " .. tostring(param3))
  436.           goto continue
  437.         end
  438.  
  439.         local itemID = message.item
  440.         local metadata = message.metadata
  441.         items[itemID] = metadata
  442.         print("Received itemMetadata from " .. sender .. " for " .. itemID .. ":")
  443.         print(textutils.serialize(metadata))
  444.  
  445.         drawItems(uiOffsetY)
  446.       end
  447.     end
  448.       ::continue::
  449.   end
  450. end
  451.  
  452. ------------------------------------------------------------
  453. -- Run the Program
  454. ------------------------------------------------------------
  455. main()
  456.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement