Advertisement
civilwargeeky

menuAPI 1.0.1

Jul 2nd, 2014
419
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.47 KB | None | 0 0
  1. --Menu API made by Civilwargeeky
  2. --Version 1.0.1
  3.  
  4. function titleize(text)
  5. local x = term.getSize()
  6. if #text+2 > x then return text:upper() end
  7. text = " "..string.upper(text).." "
  8. for i=1, math.floor((x-#text)/2) do
  9.   text = "="..text.."="
  10. end
  11. return text
  12. end
  13. function sentenceCaseTable(tab) --Expects a prepared table or sequentially numbered
  14.   local toRet = {} --So we don't go modifying people's tables
  15.   for a, b in pairs(tab) do
  16.     if type(b) == "table" then
  17.       local toMod = (b.value or b.text) --Priority to value so you can have pretty yet functional tables
  18.       table.insert(toRet[a], toMod:sub(1,1):upper()..toMod(2))
  19.     elseif type(b) == "string" then
  20.       toRet[a] = b:sub(1,1):upper()..b:sub(2)
  21.     end
  22.   end
  23.   return toRet
  24.  end
  25.      
  26. function prepareTable(tab)
  27.   local toRet = {}
  28.   for a,b in pairs(tab) do
  29.     table.insert(toRet, {key = a, value = b})
  30.   end
  31.   return toRet
  32. end
  33.  
  34. function menu(title, description, textTable, isNumbered, titleAlign, textAlign, prefixCharacter, suffixCharacter, spaceCharacter, incrementFunction)
  35. local x, y = term.getSize() --Screen size
  36. local currIndex, scroll = 1, 0 --currIndex is the item from the table it is on, scroll is how many down it should go.
  37. local titleLines, descriptionLines = 0,0 --How many lines the title and description take up
  38. local alignments = { left = "left", center = "center", right = "right" } --Used for checking if alignment is valid
  39. if not (title and textTable) then error("Requires title and menu list",2) end
  40. if not type(textTable) == "table" and #textTable >= 1 then error("Menu list must be a table with values",2) end
  41. if isNumbered == nil then isNumbered = true end --Setting isNumbered default
  42. titleAlign = alignments[titleAlign] or alignments.center --Default title alignment
  43. textAlign = alignments[textAlign] or alignments.left --Default options alignment
  44. prefixCharacter = prefixCharacter or "["
  45. suffixCharacter = suffixCharacter or "]"
  46. spaceCharacter = spaceCharacter or "."
  47. for i=1, #textTable do
  48.   if type(textTable[i]) ~= "table" then
  49.     textTable[i] = {text = textTable[i]} --If it is given without key and value pairs
  50.   end
  51.   textTable[i].text = textTable[i].text or textTable[i].value --This allows you to have tables of text, function pairs. So my function returns 1, and you call input[1].func()
  52.   textTable[i].key = textTable[i].key or i
  53. end
  54. local function align(text, alignment) --Used to align text to a certain direction
  55.   if alignment == "left" then return 1 end
  56.   if alignment == "center" then return (x/2)-(#text/2) end
  57.   if alignment == "right" then return x - #text+1 end
  58.   error("Invalid Alignment",3) --Three because is only called by output
  59. end
  60. local function seperateLines(text) --Separates multi-line text into a table
  61.   if type(text) ~= "string" then error("Separate Lines expects string, got "..type(text),2) end
  62.   local toRet = {}
  63.   local originalText = text --I do this because it may break the gsub if I modify while iterating
  64.   while true do
  65.     local count = 0
  66.     text = originalText
  67.     if #toRet >= 1 and not text:match("[^ ]") then --If there are no non-space characters, you are done
  68.       toRet[#toRet] = toRet[#toRet]..text:match(" *$") --Get buffer spaces at end
  69.       return toRet, #toRet
  70.     end
  71.     table.insert(toRet, "")
  72.     if #toRet == 1 then --We want to add in buffer spaces at the beginning
  73.       toRet[1] = toRet[1]..text:match("^ *")
  74.       count = #toRet[1]
  75.       originalText = originalText:sub(#toRet[1]+1)
  76.     end
  77.     for word in text:gmatch("[^ ]+ *") do --Non space characters with an optional space(s) at the end
  78.       local toBreak
  79.       local found = word:find("\n")--This makes newLines actually work (hopefully)
  80.       if found then
  81.         word = word:sub(1,found-1)
  82.         originalText = originalText:sub(1,found-1)..originalText:sub(found+1) --Cut out the newline
  83.         toBreak = true --If this line should be cut off
  84.       end
  85.       count = count + #word --Counts characters so we don't go over limit
  86.       if count <= x or #word > x then  --The second is for emergencies, if the word is longer than a line, put it here anyways
  87.         toRet[#toRet] = toRet[#toRet]..word
  88.         originalText = originalText:sub(#word+1) --Sub out the beginning
  89.         if toBreak then break end
  90.       else
  91.         break --Go to next line
  92.       end
  93.     end
  94.   end
  95. end
  96.  
  97. local function output(text,y, alignment, assumeSingle) --My own term.write with more control
  98.   local originalAlignment, printTab, lines = alignment --Setting locals
  99.   if type(text) == "table" then --Assuming this is from seperateLines
  100.     printTab, lines = text, #text
  101.   elseif assumeSingle then --Saves from doing seperateLines on all the menu options
  102.     printTab, lines = {text}, 1
  103.   else
  104.     printTab, lines = seperateLines(text)
  105.   end
  106.   for i=1, lines do
  107.     local x = align(printTab[i], alignment)
  108.     term.setCursorPos(x,y+i-1) ---1 because it will always be at least +1
  109.     term.clearLine()
  110.     term.write(printTab[i])
  111.     --term.write(" Writing to "..tostring(x)..","..tostring(y+i-1).." lines "..tostring(lines)) --Debug
  112.     --os.pullEvent("char")
  113.   end
  114. end
  115.  
  116. title, titleLines = seperateLines(title)
  117. if description then  --descriptionLines is how many lines the description takes up
  118.   description, descriptionLines = seperateLines(description)
  119. end
  120. local upperLines = descriptionLines + titleLines --The title line, descriptions, plus extra line
  121. if upperLines > y-3 then error("Top takes up too many lines",2) end --So at least two options are on screen
  122. local top, bottom = 1, (y-upperLines) --These two are used to determine what options are on the screen right now (through scroll)
  123. while true do
  124.   while currIndex <= top and top > 1 do --If index is at top, scroll up
  125.     scroll = scroll - 1
  126.     top, bottom = top - 1, bottom - 1
  127.   end
  128.   while currIndex >= bottom and bottom < #textTable do --If at bottom scroll down. Change to > instead of >= to only do on bottom line. Same for above
  129.     scroll = scroll + 1
  130.     top, bottom = top + 1, bottom + 1
  131.   end
  132.   term.clear()
  133.   output(title,1, titleAlign) --Print title
  134.   if descriptionLines >= 1 then --Not an else because we don't want to print nothing
  135.     output(description,titleLines+1, titleAlign)
  136.   end
  137.   for i = 1, math.min(y - upperLines,#textTable) do --The min because may be fewer table entries than the screen is big
  138.     local prefix, suffix = "", "" --Stuff like spaces and numbers
  139.     if isNumbered then prefix = tostring(textTable[i+scroll].key)..spaceCharacter.." " end --Attaches a number to the front
  140.     if i + scroll == currIndex then prefix = prefixCharacter.." "..prefix; suffix = suffix.." "..suffixCharacter  --Puts brackets on the one highlighted
  141.       elseif textAlign == "left" then for i=1, #prefixCharacter+1 do prefix = " "..prefix end --This helps alignment
  142.       elseif textAlign == "right" then for i=1, #suffixCharacter+1 do suffix  = suffix.." " end --Same as above
  143.     end
  144.     local toPrint = prefix..textTable[i+scroll].text..suffix
  145.     if #toPrint > x then term.clear(); term.setCursorPos(1,1); error("Menu item "..tostring(i+scroll).." is longer than one line. Cannot Print",2) end
  146.     output(toPrint, i + upperLines, textAlign, true)
  147.   end
  148.   if type(incrementFunction) ~= "function" then --This allows you to have your own custom logic for how to shift up and down and press enter.
  149.     incrementFunction = defaultMenuKeyHandler--e.g. You could use redstone on left to increment, right to decrement, front to press enter.
  150.   end
  151.   action = incrementFunction(currIndex, #textTable)
  152.   if type(action) == number or tonumber(action) then
  153.     local num = tonumber(action)
  154.     if num <= #textTable and num > 0 then
  155.       currIndex = num
  156.     end
  157.   elseif action == "up" and currIndex > 1 then
  158.     currIndex = currIndex - 1
  159.   elseif action == "down" and currIndex < #textTable then
  160.     currIndex = currIndex + 1
  161.   elseif action == "enter" then
  162.     return textTable[currIndex].text, textTable[currIndex].key, currIndex, textTable[currIndex].value
  163.   end
  164. end
  165. end
  166.  
  167. function defaultMenuKeyHandler(index, maxIndex)
  168.   while true do --So it doesn't redraw every time button pressed
  169.     _, key = os.pullEvent("key")
  170.     if key == 200 then
  171.       if index == 1 then return maxIndex end --Go to bottom if at top
  172.       return "up"
  173.     elseif key == 208 then
  174.       if index == maxIndex then return 1 end --Go to top if at bottom
  175.       return "down"
  176.     elseif key == 28 then return "enter"
  177.     elseif key >= 2 and key <= 11 then return key-1 --This is for quickly selected a menu option
  178.     end
  179.   end
  180. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement