Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- (C) 2012 Sandre S. (firstname at gmail)
- --
- -- Licensed under the Apache License, Version 2.0 (the "License");
- -- you may not use this file except in compliance with the License.
- -- You may obtain a copy of the License at
- --
- -- http://www.apache.org/licenses/LICENSE-2.0
- --
- -- Unless required by applicable law or agreed to in writing, software
- -- distributed under the License is distributed on an "AS IS" BASIS,
- -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- -- See the License for the specific language governing permissions and
- -- limitations under the License.
- --
- local arg; -- fu idlua
- local stringx = {} -- Some string utilities
- --- Pad a string.
- -- Does NOT trim.
- -- @param str The string to pad
- -- @param dir The direction to pad in: 1 = end, 2 = start, 3 = both
- -- @param tosize The end size
- -- @param ... Pairs of [string, num]. If num is nil, assumes infinite.
- function stringx.pad(str, dir, tosize, ...) -- TODO: #extremelybored redo this with math instead of loop
- local tArg = {...}
- local tConcat = {}
- local tlen = #str
- local _tosize = tosize
- if dir == 3 then
- tosize = math.ceil(#str + ((tosize - #str) /2))
- end
- for i = 1, #tArg, 2 do
- local padchar, num = tArg[i], tArg[i+1] or math.huge
- local len = #padchar
- for i = 1, num do
- if len + tlen > tosize or tlen == tosize then
- break
- end
- tlen = tlen + len
- table.insert(tConcat, padchar)
- end
- end
- if dir > 1 then
- -- This is a load of pain :(
- local rtConcat = {}
- for i = #tConcat, 1, -1 do -- go through tConcat in reverse D:
- table.insert(rtConcat, tConcat[i])
- end
- str = table.concat(rtConcat) .. str
- return stringx.pad(str, 1, _tosize, ...)
- else
- return str .. table.concat(tConcat)
- end
- end
- --- Menu prototype
- Menu = {}
- local Menu = Menu; -- slight optimization
- --- Metatable for the Menu prototype
- MenuMT = {}
- local MenuMT = MenuMT;
- MenuMT.__index = Menu
- -- standard stuff
- Menu.title = "Menu"
- --- Create a new menu.
- -- If using ... param, you'll need to skip the first argument or pass a table to it.
- -- @param base Optional table to turn into a menu
- -- @param ... Passed to base.__init
- --
- function Menu:create(base, ...)
- base = base or {}
- setmetatable(base, MenuMT)
- if base.__init then
- base:__init(...)
- end
- return base
- end
- --- Make sure the item table exists.
- -- @return The item table
- function Menu:createItemTable()
- if not self.items then
- self.items = {}
- end
- return self.items
- end
- --- Add a menu item.
- -- @param name The name of the menu - Can also be a function; the function will be called to update it's name.
- -- @param func The function to be called when the menu item is selected
- -- @return self
- function Menu:addItem(name, func)
- local items = self.items or self:createItemTable()
- items[name] = func
- table.insert(items, name)
- return self
- end
- --- Resolves an index to a key/name
- -- @param index The index to retrieve the key/name for
- -- @return key The key you've been looking for.
- function Menu:resolveIndex(key)
- local items = self.items or self:createItemTable()
- for i, k in ipairs(items) do
- if k == key then
- return i
- end
- end
- end
- --- Retrieve a menu item by index or name.
- -- @param key
- -- @return item_name, item, item_num
- function Menu:getItem(v)
- local items = self.items or self:createItemTable()
- local t = type(v)
- local key, index, value;
- if t == "string" and items[v] then
- key, index, value = v, self:resolveItem(v), items[v]
- elseif t == "number" then
- key, index, value = items[v], v, items[items[v]]
- else
- error("Menu:removeItem(): Bad argument #1: Not a string or number")
- end
- return key, value, index
- end
- --- Remove a menu item by index or name.
- -- @param key The item/key
- -- @return self
- function Menu:removeItem(v)
- local items = self.items
- local key, value, index = self:getItem(v)
- if items[key] then
- items[key] = nil
- table.remove(items, index)
- end
- return self
- end
- --- Returns a iterator function for use by for loops.
- -- @return iterator function that returns item_name, item_function, item_num
- function Menu:ipairs()
- local n = 0
- return function()
- n = n + 1
- return self:getItem(n)
- end
- end
- --- Render the menu on the screen.
- --
- -- @param sel The current selection
- -- @return nil
- function Menu:render(sel)
- local items = self.items or self:createItemTable()
- if sel > #items or sel < 1 or type(sel) ~= "number" then
- error("Menu:render(): Bad argument #1; out of bounds or bad type:" .. type(sel) .. ": " .. sel .. ";" .. #items)
- end
- self:cleanScreen()
- local x, y = term.getSize()
- if x < 20 or y < 4 then
- error("Menu:render(): Screen too small. Minimum 20x4")
- end
- local banner = stringx.pad(self.title, 3, x, " ", 3, "-")
- term.write(banner)
- term.setCursorPos(1, 2)
- local display = y - 2 -- Number of entries we can display
- local hdisplay = math.floor(display/2) -- Round it down so we can have the selection in the middle
- for i = 1, display do
- term.setCursorPos(1, i + 1)
- local n = (sel - hdisplay + i)
- if not (n < 1 or n > #items) then
- local key, value = self:getItem(n)
- n = tostring(n)
- if i == hdisplay then
- term.write(" >[" .. stringx.pad(n, 2, 3, "0") .. "]" .. key)
- else
- term.write(string.sub(" [" .. stringx.pad(n, 2, 3, "0") .. "]" .. key,1 + math.abs( i - hdisplay)))
- end
- end
- end
- term.setCursorPos(1, y)
- term.write(banner)
- end
- --- TODO: fix this doc
- function Menu:receiveInput(sel)
- for sEvent, key in os.pullEvent do
- if sEvent == "key" then
- if key == 200 or key == 17 then
- if sel == 1 then
- return #self:createItemTable()
- end
- return sel - 1
- elseif key == 208 or key == 31 then
- if sel == #self:createItemTable() then
- return 1
- end
- return sel + 1
- elseif key == 28 then
- self:cleanScreen()
- return true
- end
- end
- end
- end
- --- Cleans the screen.
- --TODO: fix this doc
- function Menu:cleanScreen()
- term.clear()
- term.setCursorPos(1, 1)
- end
- --- Open the menu and present it to the user.
- -- TODO: fix this doc
- function Menu:open(sel, bOpen, ...)
- sel = sel or 1
- if bOpen == nil then
- bOpen = false
- end
- local n = #self:createItemTable()
- while true do
- if not bOpen then
- self:render(sel)
- bOpen = self:receiveInput(sel)
- end
- if bOpen == true then
- self:cleanScreen()
- local res = {select(2, self:getItem(sel))(self, sel)}
- if res[1] then
- return unpack(res, 2)
- end
- bOpen = false
- else
- sel = bOpen
- bOpen = false
- end
- end
- end
- --- TODO: fix this doc
- -- @param title
- --
- function Menu:setTitle(title)
- self.title = title
- end
- --- TODO: fix this doc
- -- @param obj
- --
- function isMenu(obj)
- if getmetatable(obj) == MenuMT then
- return true
- end
- return false
- end
- function sleepWithPercent(n)
- local startTime = os.clock() -- Get the starting time
- local timer = os.startTimer(n) -- Create a timer so we know when to stop sleeping
- local cursorx, cursory = term.getCursorPos() -- Save the cursor position to avoid spamming the terminal
- local tick = os.startTimer(0) -- Create another timer so we can update the % shown on the terminal
- for sEvent, t in os.pullEvent do -- Loop, with the results of os.pullEvent as sEvent and t
- if sEvent == "timer" and t == timer then -- Check for the exit timer
- term.setCursorPos(cursorx, cursory) -- Clean the screen
- term.write(" ")
- term.setCursorPos(cursorx, cursory)
- break -- Break out of the loop
- elseif sEvent == "timer" and t == tick then -- Check for the update timer
- term.setCursorPos(cursorx, cursory)
- tick = os.startTimer(0.1) -- Re-start the timer since timers only run once
- local p = tostring(math.floor(((os.clock() - startTime) / n) * 100)) -- Calculate the %age
- p = #p == 3 and p or (#p == 2 and ("0" .. p) or "00" .. p) -- Pad with 0s infront if it's not long enough
- term.write(p .. "%")
- end
- end
- end
- local MainMenu = Menu:create()
- --- Helper function so we can repeat less code
- -- @param name
- -- @param ... Bundle of [targetname, targetside, targetcolor, pumpduration]
- --
- local function createTankMenu(name, ...)
- local menu = Menu:create()
- menu:setTitle(name)
- for i = 1, #arg, 4 do
- local target, side, color, duration = arg[i], arg[i+1], arg[i+2], arg[i+3]
- if not target or not side or not color or not duration then
- break
- end
- menu:addItem(target, function()
- term.write("Pumping from " .. name .. " to " .. target .. " [")
- local x, y = term.getCursorPos()
- term.write("####]")
- term.setCursorPos(x, y)
- rs.setBundledOutput(side, colors.combine(rs.getBundledOutput(side), color))
- sleepWithPercent(duration)
- rs.setBundledOutput(side, colors.subtract(rs.getBundledOutput(side), color))
- end)
- end
- menu:addItem("Go back", function() return true end)
- return menu
- end
- --- Wraps a menu
- -- @param menu
- --
- local function wrapMenu(menu)
- return function() menu:open() end
- end
- MainMenu:addItem("Pump From Main Tank", wrapMenu(createTankMenu(
- "Main Pump",
- "Tank #1", "top", colors.blue, 20,
- "Tank #2", "top", colors.lightBlue, 20,
- "Tank #3", "top", colors.purple, 20,
- "Tank #4", "top", colors.cyan, 20
- )))
- MainMenu:addItem("Pump To Main Tank", wrapMenu(createTankMenu(
- "Pump To Main",
- "Tank #1", "top", colors.white, 20,
- "Tank #2", "top", colors.lightGray, 20,
- "Tank #3", "top", colors.gray, 20,
- "Tank #4", "top", colors.black, 20
- )))
- MainMenu:addItem("Exit", function() return true end)
- MainMenu:open()
- --TODO: rearrange functions
- --[[ Idea:
- -- Allow tables as addItem targets;table would have some funcitons to return information to the render fucntion, so it can be basically like this:
- ------ menu --------
- [1] a | selected
- [2] b | description
- [3] c | etc
- --]]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement