Advertisement
Guest User

Untitled

a guest
Feb 20th, 2019
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 38.75 KB | None | 0 0
  1. --[[
  2.         NPaintPro
  3.         By NitrogenFingers
  4. ]]--
  5.  
  6. --The screen size
  7. local w,h = term.getSize()
  8. --Whether or not the program is currently waiting on user input
  9. local inMenu = false
  10. --Whether or not animation tools are enabled (use -a to turn them on)
  11. local animated = false
  12. --The tool/mode npaintpro is currently in. Default is "paint"
  13. --For a list of modes, check out the help file
  14. local state = "paint"
  15. --Whether or not the program is presently running
  16. local isRunning = true
  17.  
  18. --The list of every frame, containing every image in the picture/animation
  19. --Note: nfp files always have the picture at frame 1
  20. local frames = { }
  21. --How many frames are currently in the given animation.
  22. local frameCount = 1
  23. --The Colour Picker column
  24. local column = {}
  25. --The currently selected left and right colours
  26. local lSel,rSel = colours.white,nil
  27. --The amount of scrolling on the X and Y axis
  28. local sx,sy = 0,0
  29. --The alpha channel colour
  30. --Change this to change default canvas colour
  31. local alphaC = colours.cyan
  32. --The currently selected frame. Default is 1
  33. local sFrame = 1
  34. --The contents of the image buffer- contains contents, width and height
  35. local buffer = nil
  36. --The position, width and height of the selection rectangle
  37. local selectrect = nil
  38.  
  39. --The animation state of the selection rectangle and image buffer
  40. local rectblink = 0
  41. --The ID for the timer
  42. local recttimer = nil
  43. --The radius of the brush tool
  44. local brushsize = 3
  45. --Whether or not "record" mode is activated (animation mode only)
  46. local record = false
  47. --The time between each frame when in play mode (animation mode only)
  48. local animtime = 0.3
  49.  
  50. --A list of hexidecimal conversions from numbers to hex digits
  51. local hexnums = {[10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f"}
  52. --The NPaintPro logo (divine, isn't it?)
  53. local logo = {
  54. "fcc              3   339";
  55. " fcc          9333    33";
  56. "  fcc        933 333  33";
  57. "   fcc       933  33  33";
  58. "    fcc      933   33 33";
  59. "     c88     333   93333";
  60. "     888     333    9333";
  61. "      333 3  333     939";
  62. }
  63. --The available menu options in the ctrl menu
  64. local mChoices = {"Save","Exit"}
  65. --The available modes from the dropdown menu- tables indicate submenus (include a name!)
  66. local ddModes = { "paint", "brush", "pippette", "flood", "move", "select", { "save", "exit", name = "file" }, name = "modes" }
  67. --The available modes from the selection right-click menu
  68. local srModes = { "cut", "copy", "paste", "clear", "hide", name = "selection" }
  69. --The list of available help topics for each mode 127
  70. local helpTopics = {
  71.     [1] = {
  72.         name = "Paint Mode",
  73.         key = nil,
  74.         animonly = false,
  75.         message = "The default mode for NPaintPro, for painting pixels."
  76.         .." Controls here that are not overridden will apply for all other modes. Leaving a mode by selecting that mode "
  77.         .." again will always send the user back to paint mode.",
  78.         controls = {
  79.             { "Arrow keys", "Scroll the canvas" },
  80.             { "Left Click", "Paint/select left colour" },
  81.             { "Right Click", "Paint/select right colour" },
  82.             { "Z Key", "Clear image on screen" },
  83.             { "Tab Key", "Hide selection rectangle if visible" },
  84.             { "Q Key", "Set alpha mask to left colour" },
  85.             { "W Key", "Set alpha mask to right colour" },
  86.             { "Number Keys", "Swich between frames 1-9" },
  87.             { "</> keys", "Move to the next/last frame" },
  88.             { "R Key", "Removes every frame after the current frame"}
  89.         }
  90.     },
  91.     [2] = {
  92.         name = "Brush Mode",
  93.         key = "b",
  94.         animonly = false,
  95.         message = "Brush mode allows painting a circular area of variable diameter rather than a single pixel, working in "..
  96.         "the exact same way as paint mode in all other regards.",
  97.         controls = {
  98.             { "Left Click", "Paints a brush blob with the left colour" },
  99.             { "Right Click", "Paints a brush blob with the right colour" },
  100.             { "Number Keys", "Changes the radius of the brush blob from 2-9" }
  101.         }
  102.     },
  103.     [3] = {
  104.         name = "Pippette Mode",
  105.         key = "p",
  106.         animonly = false,
  107.         message = "Pippette mode allows the user to click the canvas and set the colour clicked to the left or right "..
  108.         "selected colour, for later painting.",
  109.         controls = {
  110.             { "Left Click", "Sets clicked colour to the left selected colour" },
  111.             { "Right Click", "Sets clicked colour to the right selected colour" }
  112.         }
  113.     },
  114.     [4] = {
  115.         name = "Move Mode",
  116.         key = "m",
  117.         animonly = false,
  118.         message = "Mode mode allows the moving of the entire image on the screen. This is especially useful for justifying"..
  119.         " the image to the top-left for animations or game assets.",
  120.         controls = {
  121.             { "Left/Right Click", "Moves top-left corner of image to selected square" },
  122.             { "Arrow keys", "Moves image one pixel in any direction" }
  123.         }
  124.     },
  125.     [5] = {
  126.         name = "Flood Mode (NYI)",
  127.         key = "f",
  128.         animonly = false,
  129.         message = "Flood mode allows the changing of an area of a given colour to that of the selected colour. "..
  130.         "The tool uses a flood4 algorithm and will not fill diagonally. Transparency cannot be flood filled.",
  131.         controls = {
  132.             { "Left Click", "Flood fills selected area to left colour" },
  133.             { "Right Click", "Flood fills selected area to right colour" }
  134.         }
  135.     },
  136.     [6] = {
  137.         name = "Select Mode",
  138.         key = "s",
  139.         animonly = false,
  140.         message = "Select mode allows the creation and use of the selection rectangle, to highlight specific areas on "..
  141.         "the screen and perform operations on the selected area of the image. The selection rectangle can contain an "..
  142.         "image on the clipboard- if it does, the image will flash inside the rectangle, and the rectangle edges will "..
  143.         "be light grey instead of dark grey.",
  144.         controls = {
  145.             { "C Key", "Copy: Moves selection into the clipboard" },
  146.             { "X Key", "Cut: Clears canvas under the rectangle, and moves it into the clipboard" },
  147.             { "V Key", "Paste: Copys clipboard to the canvas" },
  148.             { "Z Key", "Clears clipboard" },
  149.             { "Left Click", "Moves top-left corner of rectangle to selected pixel" },
  150.             { "Right Click", "Opens selection menu" },
  151.             { "Arrow Keys", "Moves rectangle one pixel in any direction" }
  152.         }
  153.     },
  154.     [7] = {
  155.         name = "Corner Select Mode",
  156.         key = nil,
  157.         animonly = false,
  158.         message = "If a selection rectangle isn't visible, this mode is selected automatically. It allows the "..
  159.         "defining of the corners of the rectangle- one the top-left and bottom-right corners have been defined, "..
  160.         "NPaintPro switches to selection mode. Note rectangle must be at least 2 pixels wide and high.",
  161.         controls = {
  162.             { "Left/Right Click", "Defines a corner of the selection rectangle" }
  163.         }
  164.     },
  165.     [8] = {
  166.         name = "Play Mode",
  167.         key = "space",
  168.         animonly = true,
  169.         message = "Play mode will loop through each frame in your animation at a constant rate. Editing tools are "..
  170.         "locked in this mode, and the coordinate display will turn green to indicate it is on.",
  171.         controls = {
  172.             { "</> Keys", "Increases/Decreases speed of the animation" },
  173.             { "Space Bar", "Returns to paint mode" }
  174.         }
  175.     },
  176.     [9] = {
  177.         name = "Record Mode",
  178.         key = "\\",
  179.         animonly = true,
  180.         message = "Record mode is not a true mode, but influences how other modes work. Changes made that modify the "..
  181.         "canvas in record mode will affect ALL frames in the animation. The coordinates will turn red to indicate that "..
  182.         "record mode is on.",
  183.         controls = {
  184.             { "", "Affects:" },
  185.             { "- Paint Mode", "" },
  186.             { "- Brush Mode", "" },
  187.             { "- Cut and Paste in Select Mode", ""},
  188.             { "- Move Mode", ""}
  189.         }
  190.     },
  191.     [10] = {
  192.         name = "File Mode",
  193.         keys = nil,
  194.         animonly = false,
  195.         message = "Clicking on the mode display at the bottom of the screen will open the options menu. Here you can"..
  196.         " activate all of the modes in the program with a simple mouse click."
  197.         controls = { }
  198.     }
  199. }
  200. --The "bounds" of the image- the first/last point on both axes where a pixel appears
  201. local toplim,botlim,leflim,riglim = nil,nil,nil,nil
  202. --The selected path
  203. local sPath = nil
  204.  
  205. --[[  
  206.             Section:  Helpers      
  207. ]]--
  208.  
  209. --[[Converts a colour parameter into a single-digit hex coordinate for the colour
  210.     Params: colour:int = The colour to be converted
  211.     Returns:string A string conversion of the colour
  212. ]]--
  213. local function getHexOf(colour)
  214.     if not colour or not tonumber(colour) then
  215.         return " "
  216.     end
  217.     local value = math.log(colour)/math.log(2)
  218.     if value > 9 then
  219.         value = hexnums[value]
  220.     end
  221.     return value
  222. end
  223.  
  224. --[[Converts a hex digit into a colour value
  225.     Params: hex:?string = the hex digit to be converted
  226.     Returns:string A colour value corresponding to the hex, or nil if the character is invalid
  227. ]]--
  228. local function getColourOf(hex)
  229.     local value = tonumber(hex, 16)
  230.     if not value then return nil end
  231.     value = math.pow(2,value)
  232.     return value
  233. end
  234.  
  235. --[[Finds the biggest and smallest bounds of the image- the outside points beyond which pixels do not appear
  236.     These values are assigned to the "lim" parameters for access by other methods
  237.     Params: forAllFrames:bool = True if all frames should be used to find bounds, otherwise false or nil
  238.     Returns:nil
  239. ]]--
  240. local function updateImageLims(forAllFrames)
  241.     local f,l = sFrame,sFrame
  242.     if forAllFrames == true then f,l = 1,framecount end
  243.    
  244.     toplim,botlim,leflim,riglim = nil,nil,nil,nil
  245.     for locf = f,l do
  246.         for y,_ in pairs(frames[locf]) do
  247.             for x,_ in pairs(frames[locf][y]) do
  248.                 if frames[locf][y][x] ~= nil then
  249.                     if leflim == nil or x < leflim then leflim = x end
  250.                     if toplim == nil or y < toplim then toplim = y end
  251.                     if riglim == nil or x > riglim then riglim = x end
  252.                     if botlim == nil or y > botlim then botlim = y end
  253.                 end
  254.             end
  255.         end
  256.     end
  257. end
  258.  
  259. --[[Updates the rectangle blink timer. Should be called anywhere events are captured, along with a timer capture.
  260.     Params: nil
  261.     Returns:nil
  262. ]]--
  263. local function updateTimer(id)
  264.     if id == recttimer then
  265.         recttimer = os.startTimer(0.5)
  266.         rectblink = (rectblink % 2) + 1
  267.     end
  268. end
  269.  
  270. --[[  
  271.             Section: Loading  
  272. ]]--
  273.  
  274. --[[Loads a non-animted paint file into the program
  275.     Params: path:string = The path in which the file is located
  276.     Returns:nil
  277. ]]--
  278. local function loadNFP(path)
  279.     sFrame = 1
  280.     frames[sFrame] = { }
  281.     if fs.exists(path) then
  282.         local file = io.open(path, "r" )
  283.         local sLine = file:read()
  284.         local num = 1
  285.         while sLine do
  286.             table.insert(frames[sFrame], num, {})
  287.             for i=1,#sLine do
  288.                 frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
  289.             end
  290.             num = num+1
  291.             sLine = file:read()
  292.         end
  293.         file:close()
  294.     end
  295. end
  296.  
  297. --[[Saves a non-animated paint file to the specified path
  298.     Params: path:string = The path to save the file to
  299.     Returns:nil
  300. ]]--
  301. local function saveNFP(path)
  302.     local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
  303.     if not fs.exists(sDir) then
  304.         fs.makeDir(sDir)
  305.     end
  306.  
  307.     local file = io.open(path, "w")
  308.     updateImageLims(false)
  309.     if not toplim then
  310.         file:close()
  311.         return
  312.     end
  313.     for y=1,botlim do
  314.         local line = ""
  315.         if frames[sFrame][y] then
  316.             for x=1,riglim do
  317.                 line = line..getHexOf(frames[sFrame][y][x])
  318.             end
  319.         end
  320.         file:write(line.."\n")
  321.     end
  322.     file:close()
  323. end
  324.  
  325. --[[Loads an animated paint file into the program
  326.     Params: path:string = The path in which the file is located
  327.     Returns:nil
  328. ]]--
  329. local function loadNFA(path)
  330.     frames[sFrame] = { }
  331.     if fs.exists(path) then
  332.         local file = io.open(path, "r" )
  333.         local sLine = file:read()
  334.         local num = 1
  335.         while sLine do
  336.             table.insert(frames[sFrame], num, {})
  337.             if sLine == "~" then
  338.                 sFrame = sFrame + 1
  339.                 frames[sFrame] = { }
  340.                 num = 1
  341.             else
  342.                 for i=1,#sLine do
  343.                     frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
  344.                 end
  345.                 num = num+1
  346.             end
  347.             sLine = file:read()
  348.         end
  349.         file:close()
  350.     end
  351.     framecount = sFrame
  352.     sFrame = 1
  353. end
  354.  
  355. --[[Saves a animated paint file to the specified path
  356.     Params: path:string = The path to save the file to
  357.     Returns:nil
  358. ]]--
  359. local function saveNFA(path)
  360.     local file = io.open(path, "w")
  361.     updateImageLims(true)
  362.     if not toplim then
  363.         file:close()
  364.         return
  365.     end
  366.     for i=1,#frames do
  367.         for y=1,botlim do
  368.             local line = ""
  369.             if frames[i][y] then
  370.                 for x=1,riglim do
  371.                     line = line..getHexOf(frames[i][y][x])
  372.                 end
  373.             end
  374.             file:write(line.."\n")
  375.         end
  376.         if i < #frames then file:write("~\n") end
  377.     end
  378.     file:close()
  379. end
  380.  
  381. --[[Initializes the program, by loading in the paint file. Called at the start of each program.
  382.     Params: none
  383.     Returns:nil
  384. ]]--
  385. local function init()
  386.     if animated then
  387.         loadNFA(sPath)
  388.         table.insert(ddModes, { "record", "play", name = "anim" })
  389.         table.insert(ddModes, { "go to", "remove", name = "frames"})
  390.     else loadNFP(sPath) end
  391.  
  392.     for i=0,15 do
  393.         table.insert(column, math.pow(2,i))
  394.     end
  395. end
  396.  
  397. --[[  
  398.             Section: Drawing  
  399. ]]--
  400.  
  401. --[[Draws the rather superflous logo. Takes about 1 second, before user is able to move to the
  402.     actual program.
  403.     Params: none
  404.     Returns:nil
  405. ]]--
  406. local function drawLogo()
  407.     term.setBackgroundColour(colours.white)
  408.     term.clear()
  409.     for y=1,#logo do
  410.         for x=1,#logo[y] do
  411.             term.setCursorPos(w/2 - #logo[y]/2 + x, h/2 - #logo/2 + y)
  412.             local col = getColourOf(string.sub(logo[y], x, x))
  413.             if not col then col = 1 end
  414.             term.setBackgroundColour(col)
  415.             term.write(" ")
  416.         end
  417.     end
  418.     sleep(1)
  419.     term.setBackgroundColour(colours.white)
  420.     term.setTextColour(colours.black)
  421.     local msg = "NPaintPro"
  422.     term.setCursorPos(w/2 - #msg/2, h-3)
  423.     term.write(msg)
  424.     msg = "By NitrogenFingers"
  425.     term.setCursorPos(w/2 - #msg/2, h-2)
  426.     term.write(msg)
  427.    
  428.     os.pullEvent()
  429. end
  430.  
  431. --[[Clears the display to the alpha channel colour, draws the canvas, the image buffer and the selection
  432.     rectanlge if any of these things are present.
  433.     Params: none
  434.     Returns:nil
  435. ]]--
  436. local function drawCanvas()
  437.     term.setBackgroundColor(alphaC)
  438.     --term.clear()
  439.     --Picture first
  440.     for y=sy+1,sy+h-1 do
  441.         if frames[sFrame][y] then
  442.             for x=sx+1,sx+w-2 do
  443.                 if frames[sFrame][y][x] then
  444.                     term.setBackgroundColour(frames[sFrame][y][x])
  445.                 else
  446.                     term.setBackgroundColour(alphaC)
  447.                 end
  448.                 term.setCursorPos(x-sx,y-sy)
  449.                 term.write(" ")
  450.             end
  451.         else
  452.             for x=sx+1,sx+w-2 do
  453.                 term.setBackgroundColour(alphaC)
  454.                 term.setCursorPos(x-sx,y-sy)
  455.                 term.write(" ")
  456.             end
  457.         end
  458.     end
  459.    
  460.     --Then the buffer
  461.     if selectrect then
  462.         if buffer and rectblink == 1 then
  463.         for y=selectrect.y1, math.min(selectrect.y2, selectrect.y1 + buffer.height-1) do
  464.             for x=selectrect.x1, math.min(selectrect.x2, selectrect.x1 + buffer.width-1) do
  465.                 if buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1] then
  466.                     term.setCursorPos(x+sx,y+sy)
  467.                     term.setBackgroundColour(buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1])
  468.                     term.write(" ")
  469.                 end
  470.             end
  471.         end
  472.         end
  473.    
  474.         --This draws the "selection" box
  475.         local add = nil
  476.         if buffer then
  477.             term.setBackgroundColour(colours.lightGrey)
  478.         else
  479.             term.setBackgroundColour(colours.grey)
  480.         end
  481.         for i=selectrect.x1, selectrect.x2 do
  482.             add = (i + selectrect.y1 + rectblink) % 2 == 0
  483.             term.setCursorPos(i-sx,selectrect.y1-sy)
  484.             if add then term.write(" ") end
  485.             add = (i + selectrect.y2 + rectblink) % 2 == 0
  486.             term.setCursorPos(i-sx,selectrect.y2-sy)
  487.             if add then term.write(" ") end
  488.         end
  489.         for i=selectrect.y1 + 1, selectrect.y2 - 1 do
  490.             add = (i + selectrect.x1 + rectblink) % 2 == 0
  491.             term.setCursorPos(selectrect.x1-sx,i-sy)
  492.             if add then term.write(" ") end
  493.             add = (i + selectrect.x2 + rectblink) % 2 == 0
  494.             term.setCursorPos(selectrect.x2-sx,i-sy)
  495.             if add then term.write(" ") end
  496.         end
  497.     end
  498. end
  499.  
  500. --[[Draws the colour picker on the right side of the screen, the colour pallette and the footer with any
  501.     messages currently being displayed
  502.     Params: none
  503.     Returns:nil
  504. ]]--
  505. local function drawInterface()
  506.     --Picker
  507.     for i=1,#column do
  508.         term.setCursorPos(w-1, i)
  509.         term.setBackgroundColour(column[i])
  510.         term.write("  ")
  511.     end
  512.     term.setCursorPos(w-1,#column+1)
  513.     term.setBackgroundColour(colours.black)
  514.     term.setTextColour(colours.red)
  515.     term.write("XX")
  516.     --Pallette
  517.     term.setCursorPos(w-1,h-1)
  518.     if not lSel then
  519.         term.setBackgroundColour(colours.black)
  520.         term.setTextColour(colours.red)
  521.         term.write("X")
  522.     else
  523.         term.setBackgroundColour(lSel)
  524.         term.setTextColour(lSel)
  525.         term.write(" ")
  526.     end
  527.     if not rSel then
  528.         term.setBackgroundColour(colours.black)
  529.         term.setTextColour(colours.red)
  530.         term.write("X")
  531.     else
  532.         term.setBackgroundColour(rSel)
  533.         term.setTextColour(rSel)
  534.         term.write(" ")
  535.     end
  536.     --Footer
  537.     if inMenu then return end
  538.    
  539.     term.setCursorPos(1, h)
  540.     term.setBackgroundColour(colours.lightGrey)
  541.     term.setTextColour(colours.grey)
  542.     term.clearLine()
  543.     term.write(string.upper(string.sub(state, 1, 1))..string.sub(state, 2, #state).." mode")
  544.     if state == "brush" then term.write(", size="..brushsize) end
  545.     term.write(" (H for help)")
  546.    
  547.     local coords="X:"..sx.." Y:"..sy
  548.     if animated then coords = coords.." Frame:"..sFrame.."/"..framecount.."   " end
  549.     term.setCursorPos(w-#coords+1,h)
  550.     if state == "play" then term.setBackgroundColour(colours.lime)
  551.     elseif record then term.setBackgroundColour(colours.red) end
  552.     term.write(coords)
  553.    
  554.     if animated then
  555.         term.setCursorPos(w-1,h)
  556.         term.setBackgroundColour(colours.grey)
  557.         term.setTextColour(colours.lightGrey)
  558.         term.write("<>")
  559.     end
  560. end
  561.  
  562. --[[Runs an interface where users can select topics of help. Will return once the user quits the help screen.
  563.     Params: none
  564.     Returns:nil
  565. ]]--
  566. local function drawHelpScreen()
  567.     local selectedHelp = nil
  568.     while true do
  569.         term.setBackgroundColour(colours.lightGrey)
  570.         term.clear()
  571.         if not selectedHelp then
  572.             term.setCursorPos(4, 1)
  573.             term.setTextColour(colours.brown)
  574.             term.write("Available modes (click for info):")
  575.             for i=1,#helpTopics do
  576.                 term.setCursorPos(2, 2 + i)
  577.                 term.setTextColour(colours.black)
  578.                 term.write(helpTopics[i].name)
  579.                 if helpTopics[i].key then
  580.                     term.setTextColour(colours.red)
  581.                     term.write(" ("..helpTopics[i].key..")")
  582.                 end
  583.             end
  584.             term.setCursorPos(4,h)
  585.             term.setTextColour(colours.black)
  586.             term.write("Press any key to exit")
  587.         else
  588.             term.setCursorPos(4,1)
  589.             term.setTextColour(colours.brown)
  590.             term.write(helpTopics[selectedHelp].name)
  591.             if helpTopics[selectedHelp].key then
  592.                 term.setTextColour(colours.red)
  593.                 term.write(" ("..helpTopics[selectedHelp].key..")")
  594.             end
  595.             term.setCursorPos(1,3)
  596.             term.setTextColour(colours.black)
  597.             print(helpTopics[selectedHelp].message.."\n")
  598.             for i=1,#helpTopics[selectedHelp].controls do
  599.                 term.setTextColour(colours.brown)
  600.                 term.write(helpTopics[selectedHelp].controls[i][1].." ")
  601.                 term.setTextColour(colours.black)
  602.                 print(helpTopics[selectedHelp].controls[i][2])
  603.             end
  604.         end
  605.        
  606.         local id,p1,p2,p3 = os.pullEvent()
  607.        
  608.         if id == "timer" then updateTimer(p1)
  609.         elseif id == "key" then
  610.             if selectedHelp then selectedHelp = nil
  611.             else break end
  612.         elseif id == "mouse_click" then
  613.             if not selectedHelp then
  614.                 if p3 >=3 and p3 <= 2+#helpTopics then
  615.                     selectedHelp = p3-2
  616.                 else break end
  617.             else
  618.                 selectedHelp = nil
  619.             end
  620.         end
  621.     end
  622. end
  623.  
  624. --[[Draws a message in the footer bar. A helper for DrawInterface, but can be called for custom messages, if the
  625.     inMenu paramter is set to true while this is being done (remember to set it back when done!)
  626.     Params: message:string = The message to be drawn
  627.     Returns:nil
  628. ]]--
  629. local function drawMessage(message)
  630.     term.setCursorPos(1,h)
  631.     term.setBackgroundColour(colours.lightGrey)
  632.     term.setTextColour(colours.grey)
  633.     term.clearLine()
  634.     term.write(message)
  635. end
  636.  
  637. --[[  Image tools ]]--
  638.  
  639. --[[Copies all pixels beneath the selection rectangle into the image buffer. Empty buffers are converted to nil.
  640.     Params: removeImage:bool = true if the image is to be erased after copying, false otherwise
  641.     Returns:nil
  642. ]]--
  643. local function copyToBuffer(removeImage)
  644.     buffer = { width = selectrect.x2 - selectrect.x1 + 1, height = selectrect.y2 - selectrect.y1 + 1, contents = { } }
  645.    
  646.     local containsSomething = false
  647.     for y=1,buffer.height do
  648.         buffer.contents[y] = { }
  649.         local f,l = sFrame,sFrame
  650.         if record then f,l = 1, framecount end
  651.        
  652.         for fra = f,l do
  653.             if frames[fra][selectrect.y1 + y - 1] then
  654.                 for x=1,buffer.width do
  655.                     buffer.contents[y][x] = frames[sFrame][selectrect.y1 + y - 1][selectrect.x1 + x - 1]
  656.                     if removeImage then frames[fra][selectrect.y1 + y - 1][selectrect.x1 + x - 1] = nil end
  657.                     if buffer.contents[y][x] then containsSomething = true end
  658.                 end
  659.             end
  660.         end
  661.     end
  662.     --I don't classify an empty buffer as a real buffer- confusing to the user.
  663.     if not containsSomething then buffer = nil end
  664. end
  665.  
  666. --[[Replaces all pixels under the selection rectangle with the image buffer (or what can be seen of it). Record-dependent.
  667.     Params: removeBuffer:bool = true if the buffer is to be emptied after copying, false otherwise
  668.     Returns:nil
  669. ]]--
  670. local function copyFromBuffer(removeBuffer)
  671.     if not buffer then return end
  672.  
  673.     for y = 1, math.min(buffer.height,selectrect.y2-selectrect.y1) do
  674.         local f,l = sFrame, sFrame
  675.         if record then f,l = 1, framecount end
  676.        
  677.         for fra = f,l do
  678.             if not frames[fra][selectrect.y1+y-1] then frames[fra][selectrect.y1+y-1] = { } end
  679.             for x = 1, math.min(buffer.width,selectrect.x2-selectrect.x1) do
  680.                 frames[fra][selectrect.y1+y-1][selectrect.x1+x-1] = buffer.contents[y][x]
  681.             end
  682.         end
  683.     end
  684.    
  685.     if removeBuffer then buffer = nil end
  686. end
  687.  
  688. --[[Moves the entire image (or entire animation) to the specified coordinates. Record-dependent.
  689.     Params: newx:int = the X coordinate to move the image to
  690.             newy:int = the Y coordinate to move the image to
  691.     Returns:nil
  692. ]]--
  693. local function moveImage(newx,newy)
  694.     if not leflim or not toplim then return end
  695.     if newx <=0 or newy <=0 then return end
  696.     local f,l = sFrame,sFrame
  697.     if record then f,l = 1,framecount end
  698.    
  699.     for i=f,l do
  700.         local newlines = { }
  701.         for y,line in pairs(frames[i]) do
  702.             newlines[y-toplim+newy] = { }
  703.             for x,char in pairs(line) do
  704.                 newlines[y-toplim+newy][x-leflim+newx] = char
  705.             end
  706.         end
  707.         frames[i] = newlines
  708.     end
  709. end
  710.  
  711. --[[Prompts the user to clear the current frame or all frames. Record-dependent.,
  712.     Params: none
  713.     Returns:nil
  714. ]]--
  715. local function clearImage()
  716.     inMenu = true
  717.     if not animated then
  718.         drawMessage("Clear image? Y/N: ")
  719.     elseif record then
  720.         drawMessage("Clear ALL frames? Y/N: ")
  721.     else
  722.         drawMessage("Clear current frame? Y/N :")
  723.     end
  724.     if string.find(string.upper(readInput(1)), "Y") then
  725.         local f,l = sFrame,sFrame
  726.         if record then f,l = 1,framecount end
  727.        
  728.         for i=f,l do
  729.             frames[i] = { }
  730.         end
  731.     end
  732.     inMenu = false
  733. end
  734.  
  735. --[[A recursively called method (watch out for big calls!) in which every pixel of a set colour is
  736.     changed to another colour. Does not work on the nil colour, for obvious reasons.
  737.     Params: x:int = The X coordinate of the colour to flood-fill
  738.             y:int = The Y coordinate of the colour to flood-fill
  739.             targetColour:colour = the colour that is being flood-filled
  740.             newColour:colour = the colour with which to replace the target colour
  741.     Returns:nil
  742. ]]--
  743. local function floodFill(x, y, targetColour, newColour)
  744.     if not frames[sFrame][y] or frames[sFrame][y][x] ~= targetColour or targetColour == newColour then return end
  745.    
  746.     frames[sFrame][y][x] = newColour
  747.     if x > 1 then floodFill(x-1, y, targetColour, newColour) end
  748.     if y > 1 then floodFill(x, y-1, targetColour, newColour) end
  749.     floodFill(x+1, y, targetColour, newColour)
  750.     floodFill(x, y+1, targetColour, newColour)
  751. end
  752.  
  753. --[[  Animation Tools  ]]--
  754.  
  755. --[[Enters play mode, allowing the animation to play through. Interface is restricted to allow this,
  756.     and method only leaves once the player leaves play mode.
  757.     Params: none
  758.     Returns:nil
  759. ]]--
  760. local function playAnimation()
  761.     state = "play"
  762.     selectedrect = nil
  763.    
  764.     os.pullEvent("timer")
  765.     os.startTimer(animtime)
  766.     repeat
  767.         drawCanvas()
  768.         drawInterface()
  769.        
  770.         local id,key,_,y = os.pullEvent()
  771.        
  772.         if id=="timer" then
  773.             os.startTimer(animtime)
  774.             sFrame = (sFrame % framecount) + 1
  775.         elseif id=="key" then
  776.             if key == keys.comma and animtime > 0.1 then animtime = animtime - 0.05
  777.             elseif key == keys.period and animtime < 0.5 then animtime = animtime + 0.05
  778.             elseif key == keys.space then state = "paint" end
  779.         elseif id=="mouse_click" and y == h then
  780.             state = "paint"
  781.         end
  782.     until state ~= "play"
  783.     os.startTimer(0.5)
  784. end
  785.  
  786. --[[Changes the selected frame (sFrame) to the chosen frame. If this frame is above the framecount,
  787.     additional frames are created with a copy of the image on the selected frame.
  788.     Params: newframe:int = the new frame to move to
  789.     Returns:nil
  790. ]]--
  791. local function changeFrame(newframe)
  792.     inMenu = true
  793.     if not tonumber(newframe) then
  794.         term.setCursorPos(1,h)
  795.         term.setBackgroundColour(colours.lightGrey)
  796.         term.setTextColour(colours.grey)
  797.         term.clearLine()
  798.    
  799.         term.write("Go to frame: ")
  800.         newframe = tonumber(readInput(2))
  801.         if not newframe or newframe <= 0 then
  802.             inMenu = false
  803.             return
  804.         end
  805.     elseif newframe <= 0 then return end
  806.    
  807.     if newframe > framecount then
  808.         for i=framecount+1,newframe do
  809.             frames[i] = {}
  810.             for y,line in pairs(frames[sFrame]) do
  811.                 frames[i][y] = { }
  812.                 for x,v in pairs(line) do
  813.                     frames[i][y][x] = v
  814.                 end
  815.             end
  816.         end
  817.         framecount = newframe
  818.     end
  819.     sFrame = newframe
  820.     inMenu = false
  821. end
  822.  
  823. --[[Removes every frame leading after the frame passed in
  824.     Params: frame:int the non-inclusive lower bounds of the delete
  825.     Returns:nil
  826. ]]--
  827. local function removeFramesAfter(frame)
  828.     inMenu = true
  829.     if frame==framecount then return end
  830.     drawMessage("Remove frames "..(frame+1).."/"..framecount.."? Y/N :")
  831.     local answer = string.upper(readInput(1))
  832.    
  833.     if string.find(answer, string.upper("Y")) ~= 1 then
  834.         inMenu = false
  835.         return
  836.     end
  837.    
  838.     for i=frame+1, framecount do
  839.         frames[i] = nil
  840.     end
  841.     framecount = frame
  842.     inMenu = false
  843. end
  844.  
  845. --[[  
  846.             Section: Interface  
  847. ]]--
  848.  
  849. --[[This function changes the current paint program to another tool or mode, depending on user input. Handles
  850.     any necessary changes in logic involved in that.
  851.     Params: mode:string = the name of the mode to change to
  852.     Returns:nil
  853. ]]--
  854. local function performSelection(mode)
  855.     if not mode or mode == "" then return
  856.    
  857.     elseif mode == "go to" then
  858.         changeFrame()
  859.    
  860.     elseif mode == "remove" then
  861.         removeFramesAfter(sFrame)
  862.    
  863.     elseif mode == "play" then
  864.         playAnimation()
  865.        
  866.     elseif mode == "copy" and selectrect and selectrect.x1 ~= selectrect.x2 then
  867.         copyToBuffer(false)
  868.    
  869.     elseif mode == "cut" and selectrect and selectrect.x1 ~= selectrect.x2 then
  870.         copyToBuffer(true)
  871.        
  872.     elseif mode == "paste" and selectrect and selectrect.x1 ~= selectrect.x2 then
  873.         copyFromBuffer(false)
  874.        
  875.     elseif mode == "hide" then
  876.         selectrect = nil
  877.         if state == "select" then state = "corner select" end
  878.        
  879.     elseif mode == "record" then
  880.         record = not record
  881.        
  882.     elseif mode == "clear" then
  883.         if state=="select" then buffer = nil
  884.         else clearImage() end
  885.    
  886.     elseif mode == "select" then
  887.         if state=="corner select" or state=="select" then
  888.             state = "paint"
  889.         elseif selectrect and selectrect.x1 ~= selectrect.x2 then
  890.             state = "select"
  891.         else
  892.             state = "corner select"
  893.         end
  894.        
  895.     elseif mode == "save" then
  896.         if animated then saveNFA(sPath)
  897.         else saveNFP(sPath) end
  898.        
  899.     elseif mode == "exit" then
  900.         isRunning = false
  901.    
  902.     elseif mode ~= state then state = mode
  903.     else state = "paint"
  904.     end
  905. end
  906.  
  907. --[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
  908.     of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
  909.     Params: x:int = the x position the menu should be displayed at
  910.             y:int = the y position the menu should be displayed at
  911.             options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
  912.     Returns:string the selected menu option.
  913. ]]--
  914. local function displayDropDown(x, y, options)
  915.     --Figures out the dimensions of our thing
  916.     local longestX = #options.name
  917.     for i=1,#options do
  918.         local currVal = options[i]
  919.         if type(currVal) == "table" then currVal = currVal.name end
  920.        
  921.         longestX = math.max(longestX, #currVal)
  922.     end
  923.     local xOffset = math.max(0, longestX - ((w-2) - x))
  924.     local yOffset = math.max(0, #options - ((h-1) - y))
  925.    
  926.     local clickTimes = 0
  927.     local tid = nil
  928.     local selection = nil
  929.     while clickTimes < 2 do
  930.         drawCanvas()
  931.         drawInterface()
  932.        
  933.         term.setCursorPos(x-xOffset,y-yOffset)
  934.         term.setBackgroundColour(colours.grey)
  935.         term.setTextColour(colours.lightGrey)
  936.         term.write(options.name..string.rep(" ", longestX-#options.name + 1))
  937.    
  938.         for i=1,#options do
  939.             term.setCursorPos(x-xOffset, y-yOffset+i)
  940.             if i==selection and clickTimes % 2 == 0 then
  941.                 term.setBackgroundColour(colours.grey)
  942.                 term.setTextColour(colours.lightGrey)
  943.             else
  944.                 term.setBackgroundColour(colours.lightGrey)
  945.                 term.setTextColour(colours.grey)
  946.             end
  947.             local currVal = options[i]
  948.             if type(currVal) == "table" then currVal = currVal.name end
  949.        
  950.             term.write(currVal..string.rep(" ", longestX-#currVal + 1))
  951.         end
  952.        
  953.         local id, p1, p2, p3 = os.pullEvent()
  954.         if id == "timer" then
  955.             if p1 == tid then
  956.                 clickTimes = clickTimes + 1
  957.                 if clickTimes > 2 then
  958.                     break
  959.                 else
  960.                     tid = os.startTimer(0.1)
  961.                 end
  962.             else
  963.                 updateTimer(p1)
  964.                 drawCanvas()
  965.                 drawInterface()
  966.             end
  967.         elseif id == "mouse_click" then
  968.             if p2 >=x-xOffset and p2 <= x-xOffset + longestX and p3 >= y-yOffset+1 and p3 <= y-yOffset+#options+1 then
  969.                 selection = p3-(y-yOffset)
  970.                 tid = os.startTimer(0.1)
  971.             else
  972.                 selection = ""
  973.                 break
  974.             end
  975.         end
  976.     end
  977.    
  978.     if type(selection) == "number" then
  979.         selection = options[selection]
  980.     end
  981.    
  982.     if type(selection) == "string" then
  983.         return selection
  984.     elseif type(selection) == "table" then
  985.         return displayDropDown(x, y, selection)
  986.     end
  987. end
  988.  
  989. --[[A custom io.read() function with a few differences- it limits the number of characters being printed,
  990.     waits a 1/100th of a second so any keys still in the event library are removed before input is read and
  991.     the timer for the selectionrectangle is continuously updated during the process.
  992.     Params: lim:int = the number of characters input is allowed
  993.     Returns:string the inputted string, trimmed of leading and tailing whitespace
  994. ]]--
  995. function readInput(lim)
  996.     term.setCursorBlink(true)
  997.  
  998.     local inputString = ""
  999.     if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end
  1000.     local ox,oy = term.getCursorPos()
  1001.     --We only get input from the footer, so this is safe. Change if recycling
  1002.     term.setBackgroundColour(colours.lightGrey)
  1003.     term.setTextColour(colours.grey)
  1004.     term.write(string.rep(" ", lim))
  1005.     term.setCursorPos(ox, oy)
  1006.     --As events queue immediately, we may get an unwanted key... this will solve that problem
  1007.     local inputTimer = os.startTimer(0.01)
  1008.     local keysAllowed = false
  1009.    
  1010.     while true do
  1011.         local id,key = os.pullEvent()
  1012.        
  1013.         if keysAllowed then
  1014.             if id == "key" and key == 14 and #inputString > 0 then
  1015.                 inputString = string.sub(inputString, 1, #inputString-1)
  1016.                 term.setCursorPos(ox + #inputString,oy)
  1017.                 term.write(" ")
  1018.             elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then
  1019.                 break
  1020.             elseif id == "key" and key == keys.leftCtrl then
  1021.                 return ""
  1022.             elseif id == "char" and #inputString < lim then
  1023.                 inputString = inputString..key
  1024.             end
  1025.         end
  1026.        
  1027.         if id == "timer" then
  1028.             if key == inputTimer then
  1029.                 keysAllowed = true
  1030.             else
  1031.                 updateTimer(key)
  1032.                 drawCanvas()
  1033.                 drawInterface()
  1034.                 term.setBackgroundColour(colours.lightGrey)
  1035.                 term.setTextColour(colours.grey)
  1036.             end
  1037.         end
  1038.         term.setCursorPos(ox,oy)
  1039.         term.write(inputString)
  1040.         term.setCursorPos(ox + #inputString, oy)
  1041.     end
  1042.    
  1043.     while string.sub(inputString, 1, 1) == " " do
  1044.         inputString = string.sub(inputString, 2, #inputString)
  1045.     end
  1046.     while string.sub(inputString, #inputString, #inputString) == " " do
  1047.         inputString = string.sub(inputString, 1, #inputString-1)
  1048.     end
  1049.     term.setCursorBlink(false)
  1050.    
  1051.     return inputString
  1052. end
  1053.  
  1054. --[[The main function of the program, reads and handles all events and updates them accordingly. Mode changes,
  1055.     painting to the canvas and general selections are done here.
  1056.     Params: none
  1057.     Returns:nil
  1058. ]]--
  1059. local function handleEvents()
  1060.     recttimer = os.startTimer(0.5)
  1061.     while isRunning do
  1062.         drawCanvas()
  1063.         drawInterface()
  1064.         local id,p1,p2,p3 = os.pullEvent()
  1065.         if id=="timer" then
  1066.             updateTimer(p1)
  1067.         elseif id=="mouse_click" or id=="mouse_drag" then
  1068.             if p2 >=w-1 and p3 < #column+1 then
  1069.                 if p1==1 then lSel = column[p3]
  1070.                 else rSel = column[p3] end
  1071.             elseif p2 >=w-1 and p3==#column+1 then
  1072.                 if p1==1 then lSel = nil
  1073.                 else rSel = nil end
  1074.             elseif p2==w-1 and p3==h and animated then
  1075.                 changeFrame(sFrame-1)
  1076.             elseif p2==w and p3==h and animated then
  1077.                 changeFrame(sFrame+1)
  1078.             elseif p2 < w-10 and p3==h then
  1079.                 inMenu = true
  1080.                 drawMessage("")
  1081.                 local sel = displayDropDown(1, h-1, ddModes)
  1082.                 inMenu = false
  1083.                 performSelection(sel)
  1084.             elseif p2 < w-1 and p3 <= h-1 then
  1085.                 if state=="pippette" then
  1086.                     if p1==1 then
  1087.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1088.                             lSel = frames[sFrame][p3+sy][p2+sx]
  1089.                         end
  1090.                     elseif p1==2 then
  1091.                         if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1092.                             rSel = frames[sFrame][p3+sy][p2+sx]
  1093.                         end
  1094.                     end
  1095.                 elseif state=="move" then
  1096.                     updateImageLims(record)
  1097.                     moveImage(p2,p3)
  1098.                 elseif state=="flood" then
  1099.                     if p1 == 1 and lSel and frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1100.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],lSel)
  1101.                     elseif p1 == 2 and rSel and frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
  1102.                         floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],rSel)
  1103.                     end
  1104.                 elseif state=="corner select" then
  1105.                     if not selectrect then
  1106.                         selectrect = { x1=p2, x2=p2, y1=p3, y2=p3 }
  1107.                     elseif selectrect.x1 ~= p2 and selectrect.y1 ~= p3 then
  1108.                         if p2<selectrect.x1 then selectrect.x1 = p2
  1109.                         else selectrect.x2 = p2 end
  1110.                        
  1111.                         if p3<selectrect.y1 then selectrect.y1 = p3
  1112.                         else selectrect.y2 = p3 end
  1113.                        
  1114.                         state = "select"
  1115.                     end
  1116.                 elseif state=="select" then
  1117.                     if p1 == 1 then
  1118.                         local swidth = selectrect.x2 - selectrect.x1
  1119.                         local sheight = selectrect.y2 - selectrect.y1
  1120.                    
  1121.                         selectrect.x1 = p2
  1122.                         selectrect.y1 = p3
  1123.                         selectrect.x2 = p2 + swidth
  1124.                         selectrect.y2 = p3 + sheight
  1125.                     elseif p1 == 2 and p2 < w-2 and p3 < h-1 then
  1126.                         drawMessage("")
  1127.                         performSelection(displayDropDown(p2, p3, srModes))
  1128.                         inMenu = false
  1129.                     end
  1130.                 else
  1131.                     local f,l = sFrame,sFrame
  1132.                     if record then f,l = 1,framecount end
  1133.                     local bwidth = 0
  1134.                     if state == "brush" then bwidth = brushsize-1 end
  1135.                
  1136.                     for i=f,l do
  1137.                         for x = math.max(1,p2+sx-bwidth),p2+sx+bwidth do
  1138.                             for y = math.max(1,p3+sy-bwidth), p3+sy+bwidth do
  1139.                                 if math.abs(x - (p2+sx)) + math.abs(y - (p3+sy)) <= bwidth then
  1140.                                     if not frames[i][y] then frames[i][y] = {} end
  1141.                                     if p1==1 then frames[i][y][x] = lSel
  1142.                                     else frames[i][y][x] = rSel end
  1143.                                 end
  1144.                             end
  1145.                         end
  1146.                     end
  1147.                 end
  1148.             end
  1149.         elseif id=="key" then
  1150.             if p1==keys.h then drawHelpScreen()
  1151.             elseif p1==keys.x then
  1152.                 performSelection("cut")
  1153.             elseif p1==keys.c then
  1154.                 performSelection("copy")
  1155.             elseif p1==keys.v then
  1156.                 performSelection("paste")
  1157.             elseif p1==keys.z then
  1158.                 performSelection("clear")
  1159.             elseif p1==keys.s then
  1160.                 performSelection("select")
  1161.             elseif p1==keys.tab then
  1162.                 performSelection("hide")
  1163.             elseif p1==keys.q then
  1164.                 if lSel then alphaC = lSel end
  1165.             elseif p1==keys.w then
  1166.                 if rSel then alphaC = rSel end
  1167.             elseif p1==keys.f then
  1168.                 performSelection("flood")
  1169.             elseif p1==keys.b then
  1170.                 performSelection("brush")
  1171.             elseif p1==keys.m then
  1172.                 performSelection("move")
  1173.             elseif p1==keys.backslash and animated then
  1174.                 performSelection("record")
  1175.             elseif p1==keys.p then
  1176.                 performSelection("pippette")
  1177.             elseif p1==keys.g and animated then
  1178.                 performSelection("changeframe")
  1179.             elseif p1==keys.period and animated then
  1180.                 changeFrame(sFrame+1)
  1181.             elseif p1==keys.comma and animated then
  1182.                 changeFrame(sFrame-1)
  1183.             elseif p1==keys.r and animated then
  1184.                 performSelection("removeframes")
  1185.             elseif p1==keys.space and animated then
  1186.                 performSelection("play")
  1187.             elseif p1==keys.left then
  1188.                 if state == "move" then
  1189.                     updateImageLims(record)
  1190.                     moveImage(leflim-1,toplim)
  1191.                 elseif state=="select" and selectrect.x1 > 1 then
  1192.                     selectrect.x1 = selectrect.x1-1
  1193.                     selectrect.x2 = selectrect.x2-1
  1194.                 elseif sx > 0 then sx=sx-1 end
  1195.             elseif p1==keys.right then
  1196.                 if state == "move" then
  1197.                     updateImageLims(record)
  1198.                     moveImage(leflim+1,toplim)
  1199.                 elseif state=="select" then
  1200.                     selectrect.x1 = selectrect.x1+1
  1201.                     selectrect.x2 = selectrect.x2+1
  1202.                 else sx=sx+1 end
  1203.             elseif p1==keys.up then
  1204.                 if state == "move" then
  1205.                     updateImageLims(record)
  1206.                     moveImage(leflim,toplim-1)
  1207.                 elseif state=="select" and selectrect.y1 > 1 then
  1208.                     selectrect.y1 = selectrect.y1-1
  1209.                     selectrect.y2 = selectrect.y2-1
  1210.                 elseif sy > 0 then sy=sy-1 end
  1211.             elseif p1==keys.down then
  1212.                 if state == "move" then
  1213.                     updateImageLims(record)
  1214.                     moveImage(leflim,toplim+1)
  1215.                 elseif state=="select" then
  1216.                     selectrect.y1 = selectrect.y1+1
  1217.                     selectrect.y2 = selectrect.y2+1
  1218.                 else sy=sy+1 end
  1219.             end
  1220.         elseif id=="char" and tonumber(p1) then
  1221.             if state=="brush" and tonumber(p1) > 1 then
  1222.                 brushsize = tonumber(p1)
  1223.             elseif tonumber(p1) > 0 then
  1224.                 changeFrame(tonumber(p1))
  1225.             end
  1226.         end
  1227.     end
  1228. end
  1229.  
  1230. --[[  
  1231.             Section: Main  
  1232. ]]--
  1233.  
  1234. --Taken almost directly from edit (for consistency)
  1235. local tArgs = {...}
  1236.  
  1237. local ca = 1
  1238.  
  1239. if tArgs[ca] == "-a" then
  1240.     animated = true
  1241.     ca = ca + 1
  1242. end
  1243.  
  1244. if #tArgs < ca then
  1245.     print("Usage: npaintpro [-a] <path>")
  1246.     return
  1247. end
  1248.  
  1249. sPath = shell.resolve(tArgs[ca])
  1250. local bReadOnly = fs.isReadOnly(sPath)
  1251. if fs.exists(sPath) then
  1252.     if fs.isDir(sPath) then
  1253.         print("Cannot edit a directory.")
  1254.         return
  1255.     elseif string.find(sPath, ".nfp") ~= #sPath-3 and string.find(sPath, ".nfa") ~= #sPath-3 then
  1256.         print("Can only edit .nfp and nfa files:",string.find(sPath, ".nfp"),#sPath-3)
  1257.         return
  1258.     end
  1259.    
  1260.     if string.find(sPath, ".nfa") == #sPath-3 then
  1261.         animated = true
  1262.     end
  1263.    
  1264.     if string.find(sPath, ".nfp") == #sPath-3 and animated then
  1265.         print("Convert to nfa? Y/N")
  1266.         if string.find(string.lower(io.read()), "y") then
  1267.             local nsPath = string.sub(sPath, 1, #sPath-1).."a"
  1268.             fs.move(sPath, nsPath)
  1269.             sPath = nsPath
  1270.         else
  1271.             animated = false
  1272.         end
  1273.     end
  1274. else
  1275.     if not animated and string.find(sPath, ".nfp") ~= #sPath-3 then
  1276.         sPath = sPath..".nfp"
  1277.     elseif animated and string.find(sPath, ".nfa") ~= #sPath-3 then
  1278.         sPath = sPath..".nfa"
  1279.     end
  1280. end
  1281.  
  1282. if not term.isColour() then
  1283.     print("For colour computers only")
  1284.     return
  1285. end
  1286.  
  1287. drawLogo()
  1288. init()
  1289. handleEvents()
  1290.  
  1291. term.setBackgroundColour(colours.black)
  1292. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement