This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
masterdisasterHD's profile picture

Simple Menu Api

Started by masterdisasterHD, 08 March 2013 - 07:58 AM
masterdisasterHD #1
Posted 08 March 2013 - 08:58 AM
Completely rewritten!

Hello guys, sorry it took so long to update, i have been busy.
But here it is!
The new, improved menu api!

Pastebin:
pastebin get 8SiYazW5 menuApi

Usage:
SpoilerMenu layout:

menu_table = {
[1] = { text = "Lua", handler = function() shell.run("lua") end},
[2] = { text = "Hello", handler = function() shell.run("hello") sleep(1) end},
[3] = { text = "Adventure", handler = function() shell.run("adventure") end},
[4] = { text = "Exit", handler = function() stopMenu() end}
}

menuApi.runMenu(menu_table)
runs the menu_table menu

menuApi.stopMenu()
Stops the running menu

menuApi.listMethods()
Lists all api methods

menuApi.clear()
Clears screen

menuApi.setBackgroundColour(color) / menuApi.setBackgroundColor(color)
Sets the menu's background colour

menuApi.setBarColour(color) / menuApi.setBarColor(color)
Sets the menu's bar colour


menuApi.setTextColour(color) / menuApi.setTextColor(color)
Sets the menu's text colour


menuApi.setBarTextColour(color) / menuApi.setBarTextColor(color)
Sets the menu's bar's text colour

Code (a lot less code):
SpoilerApi:
Spoiler

os.loadAPI("/rom/apis/colors")

--[[ Variables ]]--
local offset = 0
local sel = 1
local printThanks = false

local bgColour, textColour, selectedBgColour, selectedTextColour

if term.isColour() then
bgColour = colors.white
textColour = colors.gray
selectedBgColour = colors.lightBlue
selectedTextColour = colors.white
else
bgColour = colors.white
textColour = colors.black
selectedBgColour = colors.black
selectedTextColour = colors.white
end

local maxX, maxY = term.getSize()
local running

--[[ Functions ]]--

clear = function()
term.clear()
term.setCursorPos(1,1)
end

local function centerWrite(txt)
local x, y = term.getCursorPos()
term.setCursorPos(math.floor(maxX/2-#tostring(txt)/2),y)
write(txt)
end

local function redraw(tbl, sel, offset)
term.setBackgroundColour(bgColour)
clear()
for i=1, maxY do
  if tbl[i] ~= nil then
   term.setCursorPos(1, i)
   if (i+offset) == sel then
	term.setBackgroundColour(selectedBgColour)
	term.clearLine()
	term.setTextColour(selectedTextColour)
	centerWrite("[ "..tbl[i + offset].text.." ]")
   else
	term.setBackgroundColour(bgColour)
	term.clearLine()
	term.setTextColour(textColour)
	centerWrite(tbl[i + offset].text)
   end
  end
end
end

local function checkTable(tbl)
for i,v in ipairs(tbl) do
  if v.handler == nil or type(v.handler) ~= "function" then
   if term.isColour() then
	term.setTextColour(colors.red)
   end
   print("Menu item \""..i.."\" has no valid handler!")
   local txt = textutils.serialize(tostring(v.handler))
   error("handler = "..txt, 0)
  elseif v.text == nil then
   if term.isColour() then
	term.setTextColour(colors.red)
   end
   print("Menu item \""..i.."\" has no text!")
   local txt = textutils.serialize(tostring(v.text))
   error("text = "..txt, 0)
  end
end
end

runMenu = function(tbl)
if type(tbl) ~= "table" then
  error("Invalid arguments!\nUsage: menuApi.runMenu(menu_table)", 0)
elseif #tbl < 2 then
  error("Not enough items in menu!\nAt least 2 items are required!", 0)
end

checkTable(tbl)

running = true
while running do
  term.setCursorBlink(false)
  os.queueEvent("")
  os.pullEvent()
  redraw(tbl, sel, offset)
  local ev = {os.pullEvent()}
  if ev[1] == "key" then
   if ev[2] == keys.up then
	if sel > 1 then sel = sel - 1 end
	if offset > 0 then offset = offset - 1 end
   elseif ev[2] == keys.down then
	if sel < #tbl then sel = sel + 1 end
	if offset < math.max(#tbl - maxY, 0) then offset = offset + 1 end
   elseif ev[2] == keys.enter then
	term.setBackgroundColour(colors.black)
	term.setTextColour(colors.white)
	clear()
	tbl[sel].handler()  
   end
  elseif ev[1] == "mouse_scroll" then
   if ev[2] == -1 then
	if sel > 1 then sel = sel - 1 end
	if offset > 0 then offset = offset - 1 end
   elseif ev[2] == 1 then
	if sel < #tbl then sel = sel + 1 end
	if offset < math.max(#tbl - maxY, 0) then offset = offset + 1 end
   end
  elseif ev[1] == "mouse_click" then
   if tbl[(ev[4] + offset)] ~= nil then
	sel = ev[4] + offset
	redraw(tbl, sel, offset)
	sleep(.1)
	term.setBackgroundColour(colors.black)
	term.setTextColour(colors.white)
	clear()
	tbl[(ev[4] + offset)].handler()
   end
  end
end
end

stopMenu = function()
running = false
term.setBackgroundColour(colors.black)
if printThanks == true then
  if term.isColour() == true then
   term.setTextColour(colors.yellow)
  end
  clear()
  centerWrite("Thank you for using HD's menu api.")
  print("")
else
  clear()
end
end

listMethods = function()
local tmp = {}
for i,v in pairs(menuApi) do
  table.insert(tmp, i.."()")
end
textutils.pagedTabulate(tmp)
local tmp = nil
end

local function isColour(color)
if term.isColour() then
  if type(color) == "string" then
   if colors[color] ~= nil then
	return {true, colors[color]}
   else
	return false
   end
  elseif type(color) == "number" then
   if color >= 1 and color <= colors.black then
	return {true, color}
   else
	return false
   end
  else
   return false
  end
else
  return false
end
end

setBackgroundColour = function(color)
local tmp = isColour(color)
if tmp[1] then
  bgColour = tmp[2]
end
end

setBarColour = function(color)
local tmp = isColour(color)
if tmp[1] then
  selectedBgColour = tmp[2]
end
end

setTextColour = function(color)
local tmp = isColour(color)
if tmp[1] then
  textColour = tmp[2]
end
end

setBarTextColour = function(color)
local tmp = isColour(color)
if tmp[1] then
  selectedTextColour = tmp[2]
end
end

setBarTextColor = setBarTextColour
setTextColor = setTextColour
setBarColor = setBarColour
setBackgroundColor = setBackgroundColour

Example program:
Spoiler

os.loadAPI("menuApi")

local menu = {
[1] = { text = "Lua", handler = function() shell.run("lua") end},
[2] = { text = "Hello", handler = function() shell.run("hello") sleep(1) end},
[3] = { text = "Adventure", handler = function() shell.run("adventure") end},
[4] = { text = "Exit", handler = function() stopMenu() end}
}

runMenu(menu)

Changelog:
SpoilerBeta 3.0:
+Added colour changing!
+Added listMethods() function!

Beta 2.0:
+Better menu checking!
*Renamed functions to fit api name!

Beta 1.0:
+Initial release!
+Mouse support!
+Advanced / non-Advaced pc compatibility!


Old version:
SpoilerHello i made a menu API feel free to use.

To use shell.run as function do it like this


'os.run( {}, "rom/programs/etc")'
ENTER THE FULL PROGRAM PATH


Changelog:
Spoilerv4:
String functions!

V3:
Color support!

V2:
Lots of bugfixes

V1:
Initial release

PLEASE REPORT BUGS

Pastebin: http://pastebin.com/aPpQ5mYR
Computer: pastebin get aPpQ5mYR menuAPI

Submenu's will be added in the future.

API Code:
Spoiler


function stopMenu()
  repeating = false
  running = false
term.clear()
term.setCursorPos(1,1)
end

function clear()
  term.clear()
  term.setCursorPos(1,1)
end

function runMenu( menu, repeating, selectedItem, tColor )
running = true
--[[ Printing Methods ]]--

function printMenu( menu, D )
  for i=1,#menu do
	if i == selectedItem then
	  if term.isColour() then
		term.setTextColour(D) write("&amp;gt;&amp;gt; ") term.setTextColour(colors.white) write(menu[i].text) term.setTextColour(D) write(" &amp;lt;&amp;lt;") term.setTextColour(colors.white) print("")
	else
	  print("&amp;gt;&amp;gt; "..menu[i].text.." &amp;lt;&amp;lt;")
	end
  else
	print("   "..menu[i].text.."   ")
  end
end

--[[ Handler Methods ]]--

function onKeyPressed( key, menu )
  if key == keys.enter then
		onItemSelected(menu)
  elseif key == keys.up then
		if selectedItem &amp;gt; 1 then
		  selectedItem = selectedItem - 1
		end
  elseif key == keys.down then
		if selectedItem &amp;lt; #menu then
		  selectedItem = selectedItem + 1
		end
	 end	
  end
end

function onItemSelected( menu )
  func = loadstring(menu[selectedItem].handler)
  setfenv(func, getfenv())
  func()
end

function onClick( button, xPos, yPos, menu, D )
if button == 1 then
   if yPos &amp;lt;= #menu then
	 selectedItem = yPos
	 func2 = (menu[selectedItem].handler)
	 setfenv(func2, getfenv())
	 func2()
   else
	 func3 = loadstring(menu[#menu].handler)
	 setfenv(func3, getfenv())
	 func3()
   end
elseif button == 2 then
	if yPos &amp;gt; #menu then
	  yPos = #menu
	end
	selectedItem = yPos
	clear()
	printMenu(menu,D)
end
end

function loadMenu( menu, repeating, D )
  while repeating and running do
   clear()
	printMenu(menu, D)
	term.setCursorPos(50, 1)
	if term.isColour() then
	term.setTextColour(colors.yellow)
	print(selectedItem)
	term.setTextColour(colors.white)
	else
	print(selectedItem)
	end
	event, arg1, arg2, arg3 = os.pullEvent()
	if event=="mouse_click" then
	  onClick( arg1, arg2, arg3, menu, D )
	elseif event == "key" then
	  onKeyPressed( arg1, menu )
	end
  end
end


tColor = string.lower(tColor)
if tColor == "white" then
   D = 1
elseif tColor == "orange" then
   D = 2
elseif tColor == "magenta" then
   D = 4
elseif tColor == "lightblue" then
   D = 8
elseif tColor == "yellow" then
   D = 16
elseif tColor == "lime" then
   D = 32
elseif tColor == "pink" then
   D = 64
elseif tColor == "gray" then
   D = 128
elseif tColor == "lightgray" then
   D = 256
elseif tColor == "cyan" then
   D = 512
elseif tColor == "purple" then
   D = 1024
elseif tColor == "blue" then
   D = 2048
elseif tColor == "brown" then
   D = 4096
elseif tColor == "green" then
   D = 8192
elseif tColor == "red" then
   D = 16384
elseif tColor == "black" then
   D = 32768
elseif tColor ~= "white" or "orange" or "magenta" or "lightblue" or "yellow" or "lime" or "pink" or "gray" or "lightgray" or "cyan" or "purple" or "blue" or "brown" or "green" or "red" or "black" then
  D = 1
end

loadMenu(menu, repeating, D)

end

function setSelectedItem( newSelItem )
  selectedItem = newSelItem
  clear()
end

function subMenu(newMenu, oldMenu, D)
  returnMenu = oldMenu
  stopMenu()
  runMenu(newMenu, true, 1, D)
end

function returnToMenu()
  stopMenu()
  runMenu(returnMenu, true, 1, "lime")
end
[color=#222222]

example program:
Spoiler


os.loadAPI("menuAPI")

mainMenu = {
[1] = { text = "Reboot", handler = 'os.reboot()' },
[2] = { text = "Shutdown", handler = 'os.shutdown()' },
[3] = { text = "Lua", handler = 'os.run({}, "rom/programs/lua")'},
[4] = { text = "Stop menu", handler = 'menuAPI.stopMenu()' }
}

menuAPI.runMenu(mainMenu, true, 1, "lime")

Usage:
in order to make a menu work you need to do the following:

1. make sure the API is loaded

os.loadAPI("menuAPI")

2. make a menu like this:

name_of_menu{
[1] = { text = "item1", handler = 'function1()' },
[2] = { text = "item2", handler = 'function2()' },
[3] = { text = "Lua", handler = 'os.run({}, "rom/programs/lua")'},
[4] = { text = "Stop menu", handler = 'menuAPI.stopMenu()' }
}

3. run the menu like this:

menuAPI.runMenu(name_of_menu, true, 1, "white") --or any color

1 means the selected menu item
true means its running (don't change it )
name_of_menu means the name of the menu you made
"white" is the color of the arrows of the selected item

the handler is a function you make or already exists

Guide:
Spoiler

menuAPI.runMenu( name_of_menu, true, 1, "color")
name_of_menu is the name if the menu you want to run
true means it is running (don't change)
1 means the selected menu item
"color" is the color of the arrows on the selected item


menuAPI.stopMenu()
This stops the menu


menuAPI.clear()
clears the screen


menuAPI.setSelectedItem( newSelItem )
Sets the selected item


menuAPI.subMenu(newMenu, oldMenu, "color")
newMenu is the submenu you want to load
oldMenu should be the menu for return
"color" is the color of the arrows on the selected item
Edited on 16 January 2014 - 06:12 AM
masterdisasterHD #2
Posted 09 March 2013 - 06:03 AM
I fixed most of the crashes :D/>
NonStopGamer #3
Posted 09 March 2013 - 01:44 PM
Can you show us all of the API functions?
masterdisasterHD #4
Posted 09 March 2013 - 09:04 PM
I did but you are not suppose to use the functions inside the runMenu function
masterdisasterHD #5
Posted 14 March 2013 - 05:50 AM
Currently submenu's can be added like this:

function subMenu(menu, repeating, selectedItem, color)
menuAPI.stopMenu()
menuAPI.runMenu(menu, repeating, selectedItem, color)
end
masterdisasterHD #6
Posted 23 April 2013 - 10:25 AM
Custom color support yay!
masterdisasterHD #7
Posted 22 June 2013 - 04:39 AM
v4 released!

Changelog:
Spoilerv4:
String functions!

V3:
Color support!

V2:
Lots of bugfixes

V1:
Initial release
Lyqyd #8
Posted 23 June 2013 - 04:03 PM
Moved to APIs and Utilities.
MrEmielH #9
Posted 30 June 2013 - 08:59 AM
thanks :)/>, i love this :)/>
masterdisasterHD #10
Posted 17 November 2013 - 05:42 AM
Rewrite coming soon!
skwerlman #11
Posted 03 December 2013 - 02:34 AM
Great job! This looks really useful.

Suggestion:
I think it'd be pretty neat if the API handled the execution of the program by itself. If you added a 'type' to the menu format, you could have everything from programs to events all run from the menu, without needing the user to code a handler. For example, the menu format might look like this:

name_of_menu{
[1] = { text = "item1", type = 'function', item = 'function1()' }, -- "because it's a function, we'd use the method you've already written into onClick()"
[2] = { text = "item2", type = 'function', item = 'function2()' },
[3] = { text = "Lua", type = 'program', item = 'lua' }, -- "because it's a program, we'd call shell.run()"
[4] = { text = "Receive a fake message from rednet", type = 'event', item = 'rednet_message', param = { 3, "Hi", 17 } } -- "because it's an event, we check for one extra argument, param, and then call os.queueEvent()"
[5] = { text = "Stop menu", item = 'menuAPI.stopMenu()', type = 'function' }
}
By adding this functionality, you can greatly increase the flexibility and power of the API.

I think I'm gonna write a version with this added, and I'll get it to you tomorrow, if you want it.
Edited on 03 December 2013 - 01:36 AM
masterdisasterHD #12
Posted 03 December 2013 - 02:06 PM
Great job! This looks really useful.

Suggestion:
I think it'd be pretty neat if the API handled the execution of the program by itself. If you added a 'type' to the menu format, you could have everything from programs to events all run from the menu, without needing the user to code a handler. For example, the menu format might look like this:

name_of_menu{
[1] = { text = "item1", type = 'function', item = 'function1()' }, -- "because it's a function, we'd use the method you've already written into onClick()"
[2] = { text = "item2", type = 'function', item = 'function2()' },
[3] = { text = "Lua", type = 'program', item = 'lua' }, -- "because it's a program, we'd call shell.run()"
[4] = { text = "Receive a fake message from rednet", type = 'event', item = 'rednet_message', param = { 3, "Hi", 17 } } -- "because it's an event, we check for one extra argument, param, and then call os.queueEvent()"
[5] = { text = "Stop menu", item = 'menuAPI.stopMenu()', type = 'function' }
}
By adding this functionality, you can greatly increase the flexibility and power of the API.

I think I'm gonna write a version with this added, and I'll get it to you tomorrow, if you want it.

sure :P/> but this is the old version :P/>
masterdisasterHD #13
Posted 15 January 2014 - 02:41 PM
Beta 2.0 is now released!
Changelog:
+Better menu checking!
*Renamed functions to fit api name!
Edited on 16 January 2014 - 05:32 AM
masterdisasterHD #14
Posted 16 January 2014 - 07:11 AM
Beta 3.0 now available!
Goof #15
Posted 16 January 2014 - 10:05 AM
Could you please stop bumping your post?

On topic:
looks awesome :D/>


keep the work!