Advertisement
Aadvert

Menu API

Mar 30th, 2012
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.71 KB | None | 0 0
  1. -- (C) 2012 Sandre S. (firstname at gmail)
  2. --
  3. -- Licensed under the Apache License, Version 2.0 (the "License");
  4. -- you may not use this file except in compliance with the License.
  5. -- You may obtain a copy of the License at
  6. --
  7. --     http://www.apache.org/licenses/LICENSE-2.0
  8. --
  9. -- Unless required by applicable law or agreed to in writing, software
  10. -- distributed under the License is distributed on an "AS IS" BASIS,
  11. -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. -- See the License for the specific language governing permissions and
  13. -- limitations under the License.
  14. --
  15.  
  16. local arg; -- fu idlua
  17. local stringx = {} -- Some string utilities
  18. --- Pad a string.
  19. -- Does NOT trim.
  20. -- @param str The string to pad
  21. -- @param dir The direction to pad in: 1 = end, 2 = start, 3 = both
  22. -- @param tosize The end size
  23. -- @param ... Pairs of [string, num]. If num is nil, assumes infinite.
  24. function stringx.pad(str, dir, tosize, ...) -- TODO: #extremelybored redo this with math instead of loop
  25.     local tArg = {...}
  26.     local tConcat = {}
  27.     local tlen = #str
  28.     local _tosize = tosize
  29.     if dir == 3 then
  30.         tosize = math.ceil(#str + ((tosize - #str) /2))
  31.     end
  32.     for i = 1, #tArg, 2 do
  33.         local padchar, num = tArg[i], tArg[i+1] or math.huge
  34.         local len = #padchar
  35.         for i = 1, num do
  36.             if len + tlen > tosize or tlen == tosize then
  37.                 break
  38.             end
  39.             tlen = tlen + len
  40.             table.insert(tConcat, padchar)
  41.         end
  42.     end
  43.     if dir > 1 then
  44.         -- This is a load of pain :(
  45.         local rtConcat = {}
  46.         for i = #tConcat, 1, -1 do -- go through tConcat in reverse D:
  47.             table.insert(rtConcat, tConcat[i])
  48.         end
  49.         str = table.concat(rtConcat) .. str
  50.         return stringx.pad(str, 1, _tosize, ...)
  51.     else
  52.         return str .. table.concat(tConcat)
  53.     end
  54. end
  55.  
  56.  
  57.  
  58.  
  59. --- Menu prototype
  60. Menu = {}
  61. local Menu = Menu; -- slight optimization
  62.  
  63. --- Metatable for the Menu prototype
  64. MenuMT = {}
  65. local MenuMT = MenuMT;
  66. MenuMT.__index = Menu
  67.  
  68.  
  69. -- standard stuff
  70. Menu.title = "Menu"
  71.  
  72. --- Create a new menu.
  73. -- If using ... param, you'll need to skip the first argument or pass a table to it.
  74. -- @param base Optional table to turn into a menu
  75. -- @param ... Passed to base.__init
  76. --
  77. function Menu:create(base, ...)
  78.     base = base or {}
  79.     setmetatable(base, MenuMT)
  80.     if base.__init then
  81.         base:__init(...)
  82.     end
  83.     return base
  84. end
  85.  
  86. --- Make sure the item table exists.
  87. -- @return The item table
  88. function Menu:createItemTable()
  89.     if not self.items then
  90.         self.items = {}
  91.     end
  92.     return self.items
  93. end
  94.  
  95. --- Add a menu item.
  96. -- @param name The name of the menu - Can also be a function; the function will be called to update it's name.
  97. -- @param func The function to be called when the menu item is selected
  98. -- @return self
  99. function Menu:addItem(name, func)
  100.     local items = self.items or self:createItemTable()
  101.     items[name] = func
  102.     table.insert(items, name)
  103.     return self
  104. end
  105.  
  106.  
  107. --- Resolves an index to a key/name
  108. -- @param index The index to retrieve the key/name for
  109. -- @return key The key you've been looking for.
  110. function Menu:resolveIndex(key)
  111.     local items = self.items or self:createItemTable()
  112.     for i, k in ipairs(items) do
  113.         if k == key then
  114.             return i
  115.         end
  116.     end
  117. end
  118.  
  119. --- Retrieve a menu item by index or name.
  120. -- @param key
  121. -- @return item_name, item, item_num
  122. function Menu:getItem(v)
  123.     local items = self.items or self:createItemTable()
  124.     local t = type(v)
  125.     local key, index, value;
  126.  
  127.     if t == "string" and items[v] then
  128.         key, index, value = v, self:resolveItem(v), items[v]
  129.     elseif t == "number" then
  130.         key, index, value = items[v], v, items[items[v]]
  131.     else
  132.         error("Menu:removeItem(): Bad argument #1: Not a string or number")
  133.     end
  134.  
  135.     return key, value, index
  136. end
  137.  
  138. --- Remove a menu item by index or name.
  139. -- @param key The item/key
  140. -- @return self
  141. function Menu:removeItem(v)
  142.     local items = self.items
  143.     local key, value, index = self:getItem(v)
  144.     if items[key] then
  145.         items[key] = nil
  146.         table.remove(items, index)
  147.     end
  148.     return self
  149. end
  150.  
  151.  
  152. --- Returns a iterator function for use by for loops.
  153. -- @return iterator function that returns item_name, item_function, item_num
  154. function Menu:ipairs()
  155.     local n = 0
  156.     return function()
  157.         n = n + 1
  158.         return self:getItem(n)
  159.     end
  160. end
  161.  
  162. --- Render the menu on the screen.
  163. --
  164. -- @param sel The current selection
  165. -- @return nil
  166. function Menu:render(sel)
  167.     local items = self.items or self:createItemTable()
  168.     if sel > #items or sel < 1 or type(sel) ~= "number" then
  169.         error("Menu:render(): Bad argument #1; out of bounds or bad type:" .. type(sel) .. ": " .. sel .. ";" .. #items)
  170.     end
  171.  
  172.     self:cleanScreen()
  173.     local x, y = term.getSize()
  174.     if x < 20 or y < 4 then
  175.         error("Menu:render(): Screen too small. Minimum 20x4")
  176.     end
  177.     local banner = stringx.pad(self.title, 3, x, " ", 3, "-")
  178.     term.write(banner)
  179.     term.setCursorPos(1, 2)
  180.     local display = y - 2 -- Number of entries we can display
  181.     local hdisplay =  math.floor(display/2) -- Round it down so we can have the selection in the middle
  182.  
  183.     for i = 1, display do
  184.         term.setCursorPos(1, i + 1)
  185.         local n = (sel - hdisplay + i)
  186.         if not (n < 1 or n > #items) then
  187.             local key, value = self:getItem(n)
  188.             n = tostring(n)
  189.             if i == hdisplay then
  190.                 term.write(" >[" .. stringx.pad(n, 2, 3, "0") .. "]" .. key)
  191.             else
  192.                 term.write(string.sub("  [" .. stringx.pad(n, 2, 3, "0") .. "]" .. key,1 + math.abs( i - hdisplay)))
  193.             end
  194.         end
  195.     end
  196.     term.setCursorPos(1, y)
  197.     term.write(banner)
  198. end
  199.  
  200. --- TODO: fix this doc
  201. function Menu:receiveInput(sel)
  202.     for sEvent, key in os.pullEvent do
  203.         if sEvent == "key" then
  204.             if key == 200 or key == 17 then
  205.                 if sel == 1 then
  206.                     return #self:createItemTable()
  207.                 end
  208.                 return sel - 1
  209.             elseif key == 208 or key == 31 then
  210.                 if sel == #self:createItemTable() then
  211.                     return 1
  212.                 end
  213.                 return sel + 1
  214.             elseif key == 28 then
  215.                 self:cleanScreen()
  216.                 return true
  217.             end
  218.         end
  219.     end
  220. end
  221.  
  222. --- Cleans the screen.
  223. --TODO: fix this doc
  224. function Menu:cleanScreen()
  225.     term.clear()
  226.     term.setCursorPos(1, 1)
  227. end
  228. --- Open the menu and present it to the user.
  229. -- TODO: fix this doc
  230. function Menu:open(sel, bOpen, ...)
  231.     sel = sel or 1
  232.     if bOpen == nil then
  233.         bOpen = false
  234.     end
  235.  
  236.     local n = #self:createItemTable()
  237.     while true do
  238.         if not bOpen then
  239.             self:render(sel)
  240.             bOpen = self:receiveInput(sel)
  241.         end
  242.         if bOpen == true then
  243.             self:cleanScreen()
  244.             local res = {select(2, self:getItem(sel))(self, sel)}
  245.             if res[1] then
  246.                 return unpack(res, 2)
  247.             end
  248.             bOpen = false
  249.         else
  250.             sel = bOpen
  251.             bOpen = false
  252.         end
  253.     end
  254. end
  255.  
  256. --- TODO: fix this doc
  257. -- @param title
  258. --
  259. function Menu:setTitle(title)
  260.     self.title = title
  261. end
  262.  
  263. --- TODO: fix this doc
  264. -- @param obj
  265. --
  266. function isMenu(obj)
  267.     if getmetatable(obj) == MenuMT then
  268.         return true
  269.     end
  270.     return false
  271. end
  272.  
  273. function sleepWithPercent(n)
  274.     local startTime = os.clock() -- Get the starting time
  275.     local timer = os.startTimer(n) -- Create a timer so we know when to stop sleeping
  276.     local cursorx, cursory = term.getCursorPos() -- Save the cursor position to avoid spamming the terminal
  277.     local tick = os.startTimer(0) -- Create another timer so we can update the % shown on the terminal
  278.     for sEvent, t in os.pullEvent do -- Loop, with the results of os.pullEvent as sEvent and t
  279.         if sEvent == "timer" and t == timer then -- Check for the exit timer
  280.             term.setCursorPos(cursorx, cursory) -- Clean the screen
  281.             term.write("    ")
  282.             term.setCursorPos(cursorx, cursory)
  283.             break -- Break out of the loop
  284.         elseif sEvent == "timer" and t == tick then -- Check for the update timer
  285.             term.setCursorPos(cursorx, cursory)
  286.             tick = os.startTimer(0.1) -- Re-start the timer since timers only run once
  287.             local p = tostring(math.floor(((os.clock() - startTime) / n) * 100)) -- Calculate the %age
  288.             p = #p == 3 and p or (#p == 2 and ("0" .. p) or "00" .. p) -- Pad with 0s infront if it's not long enough
  289.             term.write(p .. "%")
  290.         end
  291.     end
  292. end
  293.  
  294. local MainMenu = Menu:create()
  295.  
  296.  
  297. --- Helper function so we can repeat less code
  298. -- @param name
  299. -- @param ... Bundle of [targetname, targetside, targetcolor, pumpduration]
  300. --
  301. local function createTankMenu(name, ...)
  302.     local menu = Menu:create()
  303.     menu:setTitle(name)
  304.     for i = 1, #arg, 4 do
  305.         local target, side, color, duration = arg[i], arg[i+1], arg[i+2], arg[i+3]
  306.         if not target or not side or not color or not duration then
  307.             break
  308.         end
  309.         menu:addItem(target, function()
  310.             term.write("Pumping from " .. name .. " to " .. target .. " [")
  311.             local x, y = term.getCursorPos()
  312.             term.write("####]")
  313.             term.setCursorPos(x, y)
  314.             rs.setBundledOutput(side, colors.combine(rs.getBundledOutput(side), color))
  315.             sleepWithPercent(duration)
  316.             rs.setBundledOutput(side, colors.subtract(rs.getBundledOutput(side), color))
  317.         end)
  318.     end
  319.     menu:addItem("Go back", function() return true end)
  320.     return menu
  321. end
  322.  
  323. --- Wraps a menu
  324. -- @param menu
  325. --
  326. local function wrapMenu(menu)
  327.     return function() menu:open() end
  328. end
  329.  
  330. MainMenu:addItem("Pump From Main Tank", wrapMenu(createTankMenu(
  331.     "Main Pump",
  332.     "Tank #1", "top", colors.blue, 20,
  333.     "Tank #2", "top", colors.lightBlue, 20,
  334.     "Tank #3", "top", colors.purple, 20,
  335.     "Tank #4", "top", colors.cyan, 20
  336. )))
  337. MainMenu:addItem("Pump To Main Tank", wrapMenu(createTankMenu(
  338.     "Pump To Main",
  339.     "Tank #1", "top", colors.white, 20,
  340.     "Tank #2", "top", colors.lightGray, 20,
  341.     "Tank #3", "top", colors.gray, 20,
  342.     "Tank #4", "top", colors.black, 20
  343. )))
  344. MainMenu:addItem("Exit", function() return true end)
  345.  
  346. MainMenu:open()
  347.  
  348. --TODO: rearrange functions
  349.  
  350. --[[ Idea:
  351. -- Allow tables as addItem targets;table would have some funcitons to return information to the render fucntion, so it can be basically like this:
  352. ------ menu --------
  353. [1] a   | selected
  354. [2] b   | description
  355. [3] c   | etc
  356.  
  357. --]]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement