Advertisement
theTANCO

ContextMenu.lua

Jun 27th, 2022 (edited)
1,005
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.00 KB | None | 0 0
  1. -- Custom context menu creator for ComputerCraft.
  2. -- Run the following commands before using this file:
  3. --   pastebin get Rac6Jxjg "/API/LibAppend.lua"
  4. --   pastebin get KA2dK07y "/API/Events.lua"
  5. --   pastebin get t2TvSiSU "/API/Class.lua"
  6. --   pastebin get 4hpwn3mn "/API/ContextMenu.lua"
  7. -- Documentation and the changelog for this class can be found below.
  8. require("/API/Events")
  9.  
  10. ContextMenu = Class(function()
  11. local events = Events() -- Primarily for processing user input.
  12. local pressed = "" -- The name of the menu button currently being pressed.
  13. local buttons = {} -- Positions of buttons.
  14. local disabled = {} -- Functions that check if buttons are disabled.
  15. local dir = {left = {}, right = {}, up = {}, down = {}} -- Names of adjacent buttons.
  16. local pos = vector.new() -- Position of the menu.
  17. local size = vector.new() -- Size of the menu.
  18. local color = {} -- All color values.
  19. local alt = "" -- The button that is selected when 'alt' is pressed.
  20. local win = {} -- The window for the menu.
  21. local ro = {} -- All of the readonly methods/properties for the menu object.
  22.  
  23. -- Processes the graphics for a button being unpressed.
  24. local unpress = function()
  25.     local button = pressed
  26.     if button ~= "" then
  27.         pressed = ""
  28.         ro.drawButton(button)
  29.     end
  30. end
  31.  
  32. -- Processes the graphics for a button being pressed.
  33. local press = function(button)
  34.     unpress(pressed)
  35.     if button ~= "" then
  36.         pressed = button
  37.         ro.drawButton(button)
  38.     end
  39. end
  40.  
  41. -- Checks if a click is within the hitbox of a button.
  42. local checkButtonPress = function(menuPos)
  43.     for a, b in pairs(buttons) do
  44.         local buttonPos = vector.new(menuPos.x-b.x+1, menuPos.y-b.y+1)
  45.         if isBetween(buttonPos.x, 1, a:len(), true) and buttonPos.y == 1 and not disabled[a]() then
  46.             press(a)
  47.             return
  48.         end
  49.     end
  50.     unpress()
  51. end
  52.  
  53. -- Returns the position of the menu.
  54. ro.getPos = function()
  55.     return pos.x, pos.y
  56. end
  57.  
  58. -- Returns the size of the menu.
  59. ro.getSize = function()
  60.     return size.x, size.y
  61. end
  62.  
  63. -- Redraws the specified button.
  64. ro.drawButton = function(button)
  65.     if disabled[button]() then
  66.         win.setBackgroundColor(color.disabled[1])
  67.         win.setTextColor(color.disabled[2])
  68.     elseif pressed == button then
  69.         win.setBackgroundColor(color.pressed[1])
  70.         win.setTextColor(color.pressed[2])
  71.     else
  72.         win.setBackgroundColor(color.default[1])
  73.         win.setTextColor(color.default[2])
  74.     end
  75.     if button:sub(1, 10) == "HSeparator" then
  76.         win.setCursorPos(1, buttons[button].y)
  77.         win.write(string.rep("\140", size.x))
  78.     elseif button:sub(1, 10) == "VSeparator" then
  79.         for y = 1, size.y do
  80.             win.setCursorPos(buttons[button].x, y)
  81.             win.write("\149")
  82.         end
  83.     else
  84.         win.setCursorPos(buttons[button].x, buttons[button].y)
  85.         win.write(button)
  86.     end
  87. end
  88.  
  89. -- Redraws the whole menu.
  90. ro.drawMenu = function()
  91.     win.setBackgroundColor(color.default[1])
  92.     win.setTextColor(color.default[2])
  93.     win.clear()
  94.     for a, b in pairs(buttons) do
  95.         ro.drawButton(a)
  96.     end
  97. end
  98.  
  99. -- Processes user input for this menu.
  100. ro.ui = function()
  101.     ro.drawMenu()
  102.     while true do
  103.         local event = events.getEvent()
  104.         if OR(event[1], "mouse_click", "mouse_drag", "mouse_up") and event[2] == 1 then
  105.             local menuPos = vector.new(event[3]-pos.x+1, event[4]-pos.y+1)
  106.             if isBetween(menuPos.x, 1, size.x, true) and isBetween(menuPos.y, 1, size.y, true) then
  107.                 checkButtonPress(menuPos)
  108.                 if event[1] == "mouse_up" and not(pressed == "" or ro[pressed] == nil or disabled[pressed]()) then
  109.                     ro[pressed]()
  110.                 end
  111.             else
  112.                 if event[1] == "mouse_click" then
  113.                     events.queueEvent(table.unpack(event))
  114.                     unpress()
  115.                     return
  116.                 end
  117.             end
  118.             if event[1] == "mouse_up" then
  119.                 unpress()
  120.                 return
  121.             end
  122.  
  123.         elseif event[1] == "key" then
  124.             if OR(event[2], keys.leftAlt, keys.rightAlt) and alt then
  125.                 if pressed == "" then
  126.                     press(alt)
  127.                 else
  128.                     unpress()
  129.                     os.pullEvent("key_up")
  130.                     return
  131.                 end
  132.  
  133.             elseif OR(event[2], keys.right, keys.left, keys.up, keys.down) then
  134.                 if dir[keys.getName(event[2])][pressed] ~= nil then
  135.                     press(dir[keys.getName(event[2])][pressed])
  136.                 end
  137.                 if pressed == "" then press(alt) end
  138.  
  139.             elseif OR(event[2], keys.enter, keys.numPadEnter) then
  140.                 if not(ro[pressed] == nil or disabled[pressed]()) then
  141.                     ro[pressed]()
  142.                 end
  143.                 unpress()
  144.                 break
  145.             end
  146.         end
  147.     end
  148. end
  149.  
  150. return {
  151.     ctor = function(data)
  152.         pos, size = data.pos, data.size
  153.         win = window.create(term.current(), pos.x, pos.y, size.x, size.y)
  154.         alt = data.alt
  155.         color = data.color or {}
  156.         color.default = color.default or {colors.lightGray, colors.black}
  157.         color.pressed = color.pressed or {colors.gray, colors.black}
  158.         color.disabled = colors.disabled or {colors.lightGray, colors.gray}
  159.  
  160.         for a, b in pairs(data.buttons) do
  161.             if ro[a] == nil then ro[a] = b.main
  162.             else error("Cannot use the menu button '" .. a .. "' because it would overwrite an already existing property in this menu.", 2)
  163.             end
  164.             buttons[a] = b.pos
  165.             dir.left[a] = b.left
  166.             dir.right[a] = b.right
  167.             dir.up[a] = b.up
  168.             dir.down[a] = b.down
  169.             if a:sub(2, 10) == "Separator" then disabled[a] = function() return true end
  170.             elseif b.disable == nil then disabled[a] = function() return false end
  171.             else disabled[a] = b.disable end
  172.         end
  173.     end,
  174.     protected = true,
  175.     readOnly = ro
  176. }
  177. end)
  178.  
  179. --[[ Documentation
  180. local exampleMenu = ContextMenu{
  181. • This is how you create a context menu. You can also put the curly brackets in
  182.   parentheses but this way is less cluttery.
  183.  
  184.     pos = vector.new(<x position>, <y position>),
  185.     • This is the coordinates on the screen the menu will be drawn.
  186.     • This is required.
  187.  
  188.     size = vector.new(<width>, <height>),
  189.     • This is the fixed width and height of the menu.
  190.     • This is required.
  191.  
  192.     alt = <button name>,
  193.     • This is the name of the menu button that will be selected first when the
  194.       menu is opened.
  195.     • This is required.
  196.  
  197.     color = {
  198.     • These are the colors the menu and buttons will be depending on if a button
  199.       is pressed or disabled or neither.
  200.     • First value is the background color.
  201.     • Second value is the text color.
  202.     • All colors have a default value so none are required.
  203.  
  204.         default = {<color>, <color>},
  205.         • These are the colors of the whole menu. They are also the colors of
  206.           buttons that are not disabled or pressed.
  207.         • Default background color is 'colors.lightGray'.
  208.         • Default text color is 'colors.black'.
  209.  
  210.         pressed = {<color>, <color>},
  211.         • These are the colors of buttons that are pressed and not disabled.
  212.         • Default background color is 'colors.gray'.
  213.         • Default text color is 'colors.black'.
  214.  
  215.         disabled = {<color>, <color>}
  216.         • These are the colors of buttons that are disabled. These buttons can't
  217.           be pressed.
  218.         • Default background color is 'colors.lightGray'.
  219.         • Default text color is 'colors.gray'.
  220.  
  221.     },
  222.     buttons = {
  223.     • These are the definitions of each button in the menu.
  224.     • This table is required.
  225.     • Technically no buttons are required, but not having at least 1 would make
  226.       the whole menu redundant.
  227.  
  228.         [<button 1 name>] = {
  229.         • The button name is the key/index of the table element.
  230.         • There are some names that your buttons can't have. See below for more
  231.           info.
  232.  
  233.             pos = vector.new(<x>, <y>),
  234.             • This is the coordinates relative to the menu where the button will
  235.               be drawn.
  236.             • This is required.
  237.  
  238.             main = function() ... end,
  239.             • This is the function that runs when the button is pressed.
  240.             • This is not required for separators.
  241.             • This is technically not required for anything else, but not having
  242.               this would make the button redundant.
  243.  
  244.             disable = function() ... end,
  245.             • This function determines if the button should be disabled.
  246.             • This function must return a boolean saying if the button is
  247.               disabled or not.
  248.             • This is NOT required. By default this will return false.
  249.  
  250.             up = <button 2 name>,
  251.             • This is the name of the button that will be selected when the up
  252.               key is pressed.
  253.             • This is NOT required.
  254.  
  255.             down = <button 3 name>,
  256.             • This is the name of the button that will be selected when the down
  257.               key is pressed.
  258.             • This is NOT required.
  259.  
  260.             left = <button 4 name>,
  261.             • This is the name of the button that will be selected when the left
  262.               key is pressed.
  263.             • This is NOT required.
  264.  
  265.             right = <button 5 name>,
  266.             • This is the name of the button that will be selected when the
  267.               right key is pressed.
  268.             • This is NOT required.
  269.  
  270.         },
  271.         [<button 2 name>] = {...},
  272.         • Additional buttons follow the same format as the first one.
  273.  
  274.         ["HSeparator"] = {...},
  275.         • This will add a horizontal line that stretch's from the left to the
  276.           right of the whole menu.
  277.         • This button will always be disabled and will ignore the 'main'
  278.           function.
  279.         • Make sure your separator's name always starts with "HSeparator". The
  280.           name can have any text after that.
  281.         • If you have more than one separator, each one has to have a different
  282.           name, like "HSeparator1", "HSeparator2", etc.
  283.  
  284.         [<button 3 name>] = {...},
  285.         [<button 4 name>] = {...},
  286.         ["VSeparator"] = {...},
  287.         • This follows the same rules as "HSeparator", but it draws a vertical
  288.           line from the top to the bottom of the whole menu instead of a
  289.           horizontal one.
  290.  
  291.         [<button 5 name>] = {...}
  292.     }
  293. }
  294.  
  295. The following methods are used in the object and therefore are protected. Avoid
  296. naming your buttons the same as any of these methods. Methods are case sensitive
  297. so your button may be the same name as long as capitalization is different.
  298.  
  299. exampleMenu.getPos()
  300. • This returns the x and y coordinates of the menu.
  301.  
  302. exampleMenu.getSize()
  303. • This returns the width and height of the menu.
  304.  
  305. exampleMenu.drawMenu()
  306. • This redraws the whole menu.
  307.  
  308. exampleMenu.drawButton()
  309. • This redraws a specific menu button.
  310.  
  311. exampleMenu.ui()
  312. • This enables the menu UI.
  313. ]]
  314.  
  315. --[[ Changelog
  316. 2024/01/02:
  317. • Changed all instances of 'vector2' to 'vector' to comply with the changes made
  318.   to 'LibAppend.lua'.
  319. • Removed some commented debugging code.
  320. • Fixed some typos in the documentation.
  321. • Rewrote some parts of the documentation to be more descriptive.
  322. • Changed the color parameter to use a basic array instead of a vector (should
  323.   reduce memory usage).
  324. • Rewrote the 'drawButton' method to flow better. Fixed the vertical separator
  325.   not displaying correctly. Fixed the separators not starting at the top/left
  326.   edges of the menu.
  327. • Removed the 'disable' table because it was unused.
  328. • Replaced/added various comments with more descriptive text.
  329.  
  330. 2023/03/09:
  331. • Completely rewrote this program to comply with the changes made to 'Class.lua'
  332.   and 'Events.lua'.
  333. • Removed the automatic download of required API files. The files must be
  334.   downloaded manually.
  335. • Paste exposure has been returned to public.
  336.  
  337. 2023/02/23:
  338. • Changed require directories from "/API/Raiu/" to "/API/".
  339. • Renamed vector to vector2 to reflect the changes to "/API/LibAppend.lua".
  340. • Reformatted the changelog so it's a bit cleaner than before.
  341. • Changed to unlisted until I can fix the changes made to "/API/events.lua".
  342.  
  343. 2022/07/20:
  344. • Added an example for how to use the menuClass.
  345.  
  346. 2022/07/15:
  347. • Added parameters for custom menu colors.
  348.  
  349. 2022/07/13:
  350. • Added require functions for required APIs so the main program doesn't have to.
  351.  
  352. 2022/06/29:
  353. • Updated eventListener() to events.listen().
  354.  
  355. 2022/06/28:
  356. • Completely rewrote the ui and graphical code (twice). Old code was a complete
  357.   mess and unintuitive.
  358. • With the new code, everything is handled within the context menu class.
  359. • Now works with both mouse clicks and key presses.
  360. ]]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement