diff options
Diffstat (limited to 'programs/npaintpro.lua')
| -rw-r--r-- | programs/npaintpro.lua | 2827 |
1 files changed, 0 insertions, 2827 deletions
diff --git a/programs/npaintpro.lua b/programs/npaintpro.lua deleted file mode 100644 index 56f7ef0..0000000 --- a/programs/npaintpro.lua +++ /dev/null @@ -1,2827 +0,0 @@ ---[[ - NPaintPro - By NitrogenFingers -]]-- - ---The screen size -local w,h = term.getSize() ---Whether or not the program is currently waiting on user input -local inMenu = false ---Whether or not a drop down menu is active -local inDropDown = false ---Whether or not animation tools are enabled (use -a to turn them on) -local animated = false ---Whether or not the text tools are enabled (use -t to turn them on) -local textual = false ---Whether or not "blueprint" display mode is on -local blueprint = false ---Whether or not the "layer" display is on -local layerDisplay = false ---Whether or not the interface is presently hidden -local interfaceHidden = false ---Whether or not the "direction" display is on -local printDirection = false ---The tool/mode npaintpro is currently in. Default is "paint" ---For a list of modes, check out the help file -local state = "paint" ---Whether or not the program is presently running -local isRunning = true ---The rednet address of the 3D printer, if one has been attached -local printer = nil - ---The list of every frame, containing every image in the picture/animation ---Note: nfp files always have the picture at frame 1 -local frames = { } ---How many frames are currently in the given animation. -local frameCount = 1 ---The Colour Picker column -local column = {} ---The offset of visible colours in the picker column, if the screen cannot fit all 16 -local columnoffset = 0 ---The currently selected left and right colours -local lSel,rSel = colours.white,nil ---The amount of scrolling on the X and Y axis -local sx,sy = 0,0 ---The alpha channel colour ---Change this to change default canvas colour -local alphaC = colours.black ---The currently selected frame. Default is 1 -local sFrame = 1 ---The contents of the image buffer- contains contents, width and height -local buffer = nil ---The position, width and height of the selection rectangle -local selectrect = nil - ---Whether or not text tools are enabled for this document -local textEnabled = false ---The X and Y positions of the text cursor -local textCurX, textCurY = 1,1 - ---The currently calculated required materials -local requiredMaterials = {} ---Whether or not required materials are being displayed in the pallette -local requirementsDisplayed = false ---A list of the rednet ID's all in-range printers located -local printerList = { } ---A list of the names of all in-range printers located. Same as the printerList in reference -local printerNames = { } ---The selected printer -local selectedPrinter = 1 ---The X,Y,Z and facing of the printer -local px,py,pz,pfx,pfz = 0,0,0,0,0 ---The form of layering used -local layering = "up" - ---The animation state of the selection rectangle and image buffer -local rectblink = 0 ---The ID for the timer -local recttimer = nil ---The radius of the brush tool -local brushsize = 3 ---Whether or not "record" mode is activated (animation mode only) -local record = false ---The time between each frame when in play mode (animation mode only) -local animtime = 0.3 - ---The current "cursor position" in text mode -local cursorTexX,cursorTexY = 1,1 - ---A list of hexidecimal conversions from numbers to hex digits -local hexnums = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f" } ---The NPaintPro logo (divine, isn't it?) -local logo = { -"fcc 3 339"; -" fcc 9333 33"; -" fcc 933 333 33"; -" fcc 933 33 33"; -" fcc 933 33 33"; -" c88 333 93333"; -" 888 333 9333"; -" 333 3 333 939"; -} ---The Layer Up and Layer Forward printing icons -local layerUpIcon = { - "0000000"; - "0088880"; - "0888870"; - "07777f0"; - "0ffff00"; - "0000000"; -} -local layerForwardIcon = { - "0000000"; - "000fff0"; - "00777f0"; - "0888700"; - "0888000"; - "0000000"; -} ---The available menu options in the ctrl menu -local mChoices = {"Save","Exit"} ---The available modes from the dropdown menu- tables indicate submenus (include a name!) -local ddModes = { { "paint", "brush", "pippette", "flood", "move", "clear", "select", name = "painting" }, { "alpha to left", "alpha to right", "hide interface", name = "display" }, "help", { "print", "save", "exit", name = "file" }, name = "menu" } ---The available modes from the selection right-click menu -local srModes = { "cut", "copy", "paste", "clear", "hide", name = "selection" } ---The list of available help topics for each mode 127 -local helpTopics = { - [1] = { - name = "Paint Mode", - key = nil, - animonly = false, - textonly = false, - message = "The default mode for NPaintPro, for painting pixels." - .." Controls here that are not overridden will apply for all other modes. Leaving a mode by selecting that mode " - .." again will always send the user back to paint mode.", - controls = { - { "Arrow keys", "Scroll the canvas" }, - { "Left Click", "Paint/select left colour" }, - { "Right Click", "Paint/select right colour" }, - { "Z Key", "Clear image on screen" }, - { "Tab Key", "Hide selection rectangle if visible" }, - { "Q Key", "Set alpha mask to left colour" }, - { "W Key", "Set alpha mask to right colour" }, - { "Number Keys", "Swich between frames 1-9" }, - { "</> keys", "Move to the next/last frame" }, - { "R Key", "Removes every frame after the current frame"} - } - }, - [2] = { - name = "Brush Mode", - key = "b", - animonly = false, - textonly = false, - message = "Brush mode allows painting a circular area of variable diameter rather than a single pixel, working in ".. - "the exact same way as paint mode in all other regards.", - controls = { - { "Left Click", "Paints a brush blob with the left colour" }, - { "Right Click", "Paints a brush blob with the right colour" }, - { "Number Keys", "Changes the radius of the brush blob from 2-9" } - } - }, - [3] = { - name = "Pippette Mode", - key = "p", - animonly = false, - textonly = false, - message = "Pippette mode allows the user to click the canvas and set the colour clicked to the left or right ".. - "selected colour, for later painting.", - controls = { - { "Left Click", "Sets clicked colour to the left selected colour" }, - { "Right Click", "Sets clicked colour to the right selected colour" } - } - }, - [4] = { - name = "Move Mode", - key = "m", - animonly = false, - textonly = false, - message = "Mode mode allows the moving of the entire image on the screen. This is especially useful for justifying".. - " the image to the top-left for animations or game assets.", - controls = { - { "Left/Right Click", "Moves top-left corner of image to selected square" }, - { "Arrow keys", "Moves image one pixel in any direction" } - } - }, - [5] = { - name = "Flood Mode", - key = "f", - animonly = false, - textonly = false, - message = "Flood mode allows the changing of an area of a given colour to that of the selected colour. ".. - "The tool uses a flood4 algorithm and will not fill diagonally. Transparency cannot be flood filled.", - controls = { - { "Left Click", "Flood fills selected area to left colour" }, - { "Right Click", "Flood fills selected area to right colour" } - } - }, - [6] = { - name = "Select Mode", - key = "s", - animonly = false, - textonly = false, - message = "Select mode allows the creation and use of the selection rectangle, to highlight specific areas on ".. - "the screen and perform operations on the selected area of the image. The selection rectangle can contain an ".. - "image on the clipboard- if it does, the image will flash inside the rectangle, and the rectangle edges will ".. - "be light grey instead of dark grey.", - controls = { - { "C Key", "Copy: Moves selection into the clipboard" }, - { "X Key", "Cut: Clears canvas under the rectangle, and moves it into the clipboard" }, - { "V Key", "Paste: Copys clipboard to the canvas" }, - { "Z Key", "Clears clipboard" }, - { "Left Click", "Moves top-left corner of rectangle to selected pixel" }, - { "Right Click", "Opens selection menu" }, - { "Arrow Keys", "Moves rectangle one pixel in any direction" } - } - }, - [7] = { - name = "Corner Select Mode", - key = nil, - animonly = false, - textonly = false, - message = "If a selection rectangle isn't visible, this mode is selected automatically. It allows the ".. - "defining of the corners of the rectangle- one the top-left and bottom-right corners have been defined, ".. - "NPaintPro switches to selection mode. Note rectangle must be at least 2 pixels wide and high.", - controls = { - { "Left/Right Click", "Defines a corner of the selection rectangle" } - } - }, - [8] = { - name = "Play Mode", - key = "space", - animonly = true, - textonly = false, - message = "Play mode will loop through each frame in your animation at a constant rate. Editing tools are ".. - "locked in this mode, and the coordinate display will turn green to indicate it is on.", - controls = { - { "</> Keys", "Increases/Decreases speed of the animation" }, - { "Space Bar", "Returns to paint mode" } - } - }, - [9] = { - name = "Record Mode", - key = "\\", - animonly = true, - textonly = false, - message = "Record mode is not a true mode, but influences how other modes work. Changes made that modify the ".. - "canvas in record mode will affect ALL frames in the animation. The coordinates will turn red to indicate that ".. - "record mode is on.", - controls = { - { "", "Affects:" }, - { "- Paint Mode", "" }, - { "- Brush Mode", "" }, - { "- Cut and Paste in Select Mode", ""}, - { "- Move Mode", ""} - } - }, - [10] = { - name = "Hide Interface", - key = "~", - animonly = false, - textonly = false, - message = "Hides the sidebar and colour picker so only the image is visible.".. - " The program can be started with the interface hidden using the -d command line option.".. - " When hidden, if a file is animated it will automatically go to play mode.\n".. - "Note that all other input is locked until the display is revealed again in this".. - " mode.", - controls = { - { "</> Keys", "Increases/Decreases speed of the animation" }, - { "~ Key", "Shows interface"} - } - }, - [11] = { - name = "Help Mode", - key = "h", - animonly = false, - textonly = false, - message = "Displays this help screen. Clicking on options will display help on that topic. Clicking out of the screen".. - " will leave this mode.", - controls = { - { "Left/Right Click", "Displays a topic/Leaves the mode" } - } - }, - [12] = { - name = "File Mode", - key = nil, - animonly = false, - textonly = false, - message = "Clicking on the mode display at the bottom of the screen will open the options menu. Here you can".. - " activate all of the modes in the program with a simple mouse click. Pressing left control will open up the".. - " file menu automatically.", - controls = { - { "leftCtrl", "Opens the file menu" }, - { "leftAlt", "Opens the paint menu" } - } - }, - [13] = { - name = "Text Mode", - key = "t", - animonly = false, - textonly = true, - message = "In this mode, the user is able to type letters onto the document for display. The left colour ".. - "pallette value determines what colour the text will be, and the right determines what colour the background ".. - "will be (set either to nil to keep the same colours as already there).", - controls = { - { "Backspace", "Deletes the character on the previous line" }, - { "Arrow Keys", "Moves the cursor in any direction" }, - { "Left Click", "Moves the cursor to beneath the mouse cursor" } - } - }, - [14] = { - name = "Textpaint Mode", - key = "y", - animonly = false, - textonly = true, - message = "Allows the user to paint any text on screen to the desired colour with the mouse. If affects the text colour".. - " values rather than the background values, but operates identically to paint mode in all other regards.", - controls = { - { "Left Click", "Paints the text with the left colour" }, - { "Right Click", "Paints the text with the right colour" } - } - }, - [15] = { - name = "About NPaintPro", - keys = nil, - animonly = false, - textonly = false, - message = "NPaintPro: The feature-bloated paint program for ComputerCraft by Nitrogen Fingers.", - controls = { - { "Testers:", " "}, - { " ", "Faubiguy"}, - { " ", "TheOriginalBIT"} - } - } -} ---The "bounds" of the image- the first/last point on both axes where a pixel appears -local toplim,botlim,leflim,riglim = nil,nil,nil,nil ---The selected path -local sPath = nil - - ---Screen Size Parameters- decided dynamically further down the program ---Whether or not the help screen is available -local helpAvailable = true ---Whether or not the main menu is available -local mainAvailable = true ---Whether or not selection box dropdowns are available -local boxdropAvailable = true ---Whether or not a manual file descriptor option is available (part of the title) -local filemakerAvailable = true - ---[[ - Section: Helpers -]]-- - ---[[Converts a colour parameter into a single-digit hex coordinate for the colour - Params: colour:int = The colour to be converted - Returns:string A string conversion of the colour -]]-- -local function getHexOf(colour) - if not colour or not tonumber(colour) then - return " " - end - local value = math.log(colour)/math.log(2) - if value > 9 then - value = hexnums[value] - end - return value -end - ---[[Converts a hex digit into a colour value - Params: hex:?string = the hex digit to be converted - Returns:string A colour value corresponding to the hex, or nil if the character is invalid -]]-- -local function getColourOf(hex) - local value = tonumber(hex, 16) - if not value then return nil end - value = math.pow(2,value) - return value -end - ---[[Finds the largest width and height of the text in a given menu. Should conform to the format - of all standard menus (number indexed values and a 'name' field). - This is done recursively. It's just easier that way. - Params: menu:table = the table being tested for the max width and height - Returns:number,number = the max width and height of the text or names of any menu or submenu. -]]-- -local function findMaxWH(menu) - local wmax,hmax = #menu.name, #menu - for _,entry in pairs(menu) do - if type(entry) == "table" then - local nw,nh = findMaxWH(entry) - wmax = math.max(wmax,nw) - hmax = math.max(hmax,nh) - else - wmax = math.max(wmax,#entry) - end - end - return wmax,hmax -end - ---[[Determines what services are available depending on the size of the screen. Certain features - may be disabled with screen real estate does not allow for it. - Params: none - Returns:nil -]]-- -local function determineAvailableServices() - --Help files were designed to fit a 'standard' CC screen, of 51 x 19. The height of the screen - --needs to match the number of available options plus white space, but for consistency with - --the files themselves, a natural size of 51 is required for the screen width as well. - if w < 51 or h < #helpTopics+3 then helpAvailable = false end - if not helpAvailable then table.remove(ddModes,3) end - --These hard-coded values mirror the drawLogo values, with extra consideration for the - --additional menu options - if h < 14 or w < 24 then filemakerAvailable = false end - - --Menus can't cover the picker and need 2 spaces for branches. 4 whitespace on X total. - --Menus need a title and can't eclipse the footer. 2 whitespace on Y total. - local wmin,hmin = findMaxWH(ddModes) - if w < wmin+4 or h < hmin+2 then mainAvailable = false end - wmin,hmin = findMaxWH(srModes) - if w < wmin+4 or h < hmin+2 then boxdropAvailable = false end -end - ---[[Finds the biggest and smallest bounds of the image- the outside points beyond which pixels do not appear - These values are assigned to the "lim" parameters for access by other methods - Params: forAllFrames:bool = True if all frames should be used to find bounds, otherwise false or nil - Returns:nil -]]-- -local function updateImageLims(forAllFrames) - local f,l = sFrame,sFrame - if forAllFrames == true then f,l = 1,framecount end - - toplim,botlim,leflim,riglim = nil,nil,nil,nil - for locf = f,l do - for y,_ in pairs(frames[locf]) do - if type(y) == "number" then - for x,_ in pairs(frames[locf][y]) do - if frames[locf][y][x] ~= nil then - if leflim == nil or x < leflim then leflim = x end - if toplim == nil or y < toplim then toplim = y end - if riglim == nil or x > riglim then riglim = x end - if botlim == nil or y > botlim then botlim = y end - end - end - end - end - end - - --There is just... no easier way to do this. It's horrible, but necessary - if textEnabled then - for locf = f,l do - for y,_ in pairs(frames[locf].text) do - for x,_ in pairs(frames[locf].text[y]) do - if frames[locf].text[y][x] ~= nil then - if leflim == nil or x < leflim then leflim = x end - if toplim == nil or y < toplim then toplim = y end - if riglim == nil or x > riglim then riglim = x end - if botlim == nil or y > botlim then botlim = y end - end - end - end - for y,_ in pairs(frames[locf].textcol) do - for x,_ in pairs(frames[locf].textcol[y]) do - if frames[locf].textcol[y][x] ~= nil then - if leflim == nil or x < leflim then leflim = x end - if toplim == nil or y < toplim then toplim = y end - if riglim == nil or x > riglim then riglim = x end - if botlim == nil or y > botlim then botlim = y end - end - end - end - end - end -end - ---[[Determines how much of each material is required for a print. Done each time printing is called. - Params: none - Returns:table A complete list of how much of each material is required. -]]-- -function calculateMaterials() - updateImageLims(animated) - requiredMaterials = {} - for i=1,16 do - requiredMaterials[i] = 0 - end - - if not toplim then return end - - for i=1,#frames do - for y = toplim, botlim do - for x = leflim, riglim do - if type(frames[i][y][x]) == "number" then - requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] = - requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] + 1 - end - end - end - end -end - - ---[[Updates the rectangle blink timer. Should be called anywhere events are captured, along with a timer capture. - Params: nil - Returns:nil -]]-- -local function updateTimer(id) - if id == recttimer then - recttimer = os.startTimer(0.5) - rectblink = (rectblink % 2) + 1 - end -end - ---[[Constructs a message based on the state currently selected - Params: nil - Returns:string A message regarding the state of the application -]]-- -local function getStateMessage() - local msg = " "..string.upper(string.sub(state, 1, 1))..string.sub(state, 2, #state).." mode" - if state == "brush" then msg = msg..", size="..brushsize end - return msg -end - ---[[Calls the rednet_message event, but also looks for timer events to keep then - system timer ticking. - Params: timeout:number how long before the event times out - Returns:number the id of the sender - :number the message send -]]-- -local function rsTimeReceive(timeout) - local timerID - if timeout then timerID = os.startTimer(timeout) end - - local id,key,msg = nil,nil - while true do - id,key,msg = os.pullEvent() - - if id == "timer" then - if key == timerID then return - else updateTimer(key) end - end - if id == "rednet_message" then - return key,msg - end - end -end - ---[[Draws a picture, in paint table format on the screen - Params: image:table = the image to display - xinit:number = the x position of the top-left corner of the image - yinit:number = the y position of the top-left corner of the image - alpha:number = the color to display for the alpha channel. Default is white. - Returns:nil -]]-- -local function drawPictureTable(image, xinit, yinit, alpha) - if not alpha then alpha = 1 end - for y=1,#image do - for x=1,#image[y] do - term.setCursorPos(xinit + x-1, yinit + y-1) - local col = getColourOf(string.sub(image[y], x, x)) - if not col then col = alpha end - term.setBackgroundColour(col) - term.write(" ") - end - end -end - ---[[ - Section: Loading -]]-- - ---[[Loads a non-animted paint file into the program - Params: path:string = The path in which the file is located - Returns:nil -]]-- -local function loadNFP(path) - sFrame = 1 - frames[sFrame] = { } - if fs.exists(path) then - local file = io.open(path, "r" ) - local sLine = file:read() - local num = 1 - while sLine do - table.insert(frames[sFrame], num, {}) - for i=1,#sLine do - frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i)) - end - num = num+1 - sLine = file:read() - end - file:close() - end -end - ---[[Loads a text-paint file into the program - Params: path:string = The path in which the file is located - Returns:nil -]]-- -local function loadNFT(path) - sFrame = 1 - frames[sFrame] = { } - frames[sFrame].text = { } - frames[sFrame].textcol = { } - - if fs.exists(path) then - local file = io.open(path, "r") - local sLine = file:read() - local num = 1 - while sLine do - table.insert(frames[sFrame], num, {}) - table.insert(frames[sFrame].text, num, {}) - table.insert(frames[sFrame].textcol, num, {}) - - --As we're no longer 1-1, we keep track of what index to write to - local writeIndex = 1 - --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour - local bgNext, fgNext = false, false - --The current background and foreground colours - local currBG, currFG = nil,nil - term.setCursorPos(1,1) - for i=1,#sLine do - local nextChar = string.sub(sLine, i, i) - if nextChar:byte() == 30 then - bgNext = true - elseif nextChar:byte() == 31 then - fgNext = true - elseif bgNext then - currBG = getColourOf(nextChar) - bgNext = false - elseif fgNext then - currFG = getColourOf(nextChar) - fgNext = false - else - if nextChar ~= " " and currFG == nil then - currFG = colours.white - end - frames[sFrame][num][writeIndex] = currBG - frames[sFrame].textcol[num][writeIndex] = currFG - frames[sFrame].text[num][writeIndex] = nextChar - writeIndex = writeIndex + 1 - end - end - num = num+1 - sLine = file:read() - end - file:close() - end -end - ---[[Loads an animated paint file into the program - Params: path:string = The path in which the file is located - Returns:nil -]]-- -local function loadNFA(path) - frames[sFrame] = { } - if fs.exists(path) then - local file = io.open(path, "r" ) - local sLine = file:read() - local num = 1 - while sLine do - table.insert(frames[sFrame], num, {}) - if sLine == "~" then - sFrame = sFrame + 1 - frames[sFrame] = { } - num = 1 - else - for i=1,#sLine do - frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i)) - end - num = num+1 - end - sLine = file:read() - end - file:close() - end - framecount = sFrame - sFrame = 1 -end - ---[[Saves a non-animated paint file to the specified path - Params: path:string = The path to save the file to - Returns:nil -]]-- -local function saveNFP(path) - local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath)) - if not fs.exists(sDir) then - fs.makeDir(sDir) - end - - local file = io.open(path, "w") - updateImageLims(false) - if not toplim then - file:close() - return - end - for y=1,botlim do - local line = "" - if frames[sFrame][y] then - for x=1,riglim do - line = line..getHexOf(frames[sFrame][y][x]) - end - end - file:write(line.."\n") - end - file:close() -end - ---[[Saves a text-paint file to the specified path - Params: path:string = The path to save the file to - Returns:nil -]]-- -local function saveNFT(path) - local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath)) - if not fs.exists(sDir) then - fs.makeDir(sDir) - end - - local file = io.open(path, "w") - updateImageLims(false) - if not toplim then - file:close() - return - end - for y=1,botlim do - local line = "" - local currBG, currFG = nil,nil - for x=1,riglim do - if frames[sFrame][y] and frames[sFrame][y][x] ~= currBG then - line = line..string.char(30)..getHexOf(frames[sFrame][y][x]) - currBG = frames[sFrame][y][x] - end - if frames[sFrame].textcol[y] and frames[sFrame].textcol[y][x] ~= currFG then - line = line..string.char(31)..getHexOf(frames[sFrame].textcol[y][x]) - currFG = frames[sFrame].textcol[y][x] - end - if frames[sFrame].text[y] then - local char = frames[sFrame].text[y][x] - if not char then char = " " end - line = line..char - end - end - file:write(line.."\n") - end - file:close() -end - ---[[Saves a animated paint file to the specified path - Params: path:string = The path to save the file to - Returns:nil -]]-- -local function saveNFA(path) - local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath)) - if not fs.exists(sDir) then - fs.makeDir(sDir) - end - - local file = io.open(path, "w") - updateImageLims(true) - if not toplim then - file:close() - return - end - for i=1,#frames do - for y=1,botlim do - local line = "" - if frames[i][y] then - for x=1,riglim do - line = line..getHexOf(frames[i][y][x]) - end - end - file:write(line.."\n") - end - if i < #frames then file:write("~\n") end - end - file:close() -end - ---[[Runs a special pre-program dialogue to determine the filename and filepath. Done if - there's room, and a file name hasn't been specified - Params: none - Returns:bool= true if file is created; false otherwise -]]-- -local function runFileMaker() - local newFName = "" - local fileType = 1 - if animated then fileType = 2 - elseif textEnabled then fileType = 3 end - - local tlx,tly = math.floor(w/2 - #logo[1]/2), math.floor(h/2 + #logo/2 + 1) - - --This is done on top of the logo, so it backpedals a bit. - term.setCursorPos(tlx, tly) - term.clearLine() - term.write("Name: ") - term.setCursorPos(tlx, tly + 1) - term.clearLine() - term.write("Filetype: Sprite") - term.setCursorPos(tlx + 12, tly + 2) - term.write("Animation") - term.setCursorPos(tlx + 12, tly + 3) - term.write("Text") - - while true do - term.setCursorPos(tlx + 6, tly) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.write(newFName..string.rep(" ", 15-#newFName)) - term.setBackgroundColour(colours.white) - term.setTextColour(colours.black) - local extension = ".nfp" - if fileType == 2 then extension = ".nfa" - elseif fileType == 3 then extension = ".nft" end - term.write(extension) - - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - for i=1,3 do - term.setCursorPos(tlx + 24, tly + i) - if i==fileType then term.write("X") - else term.write(" ") end - end - - local fPath = shell.resolve(newFName..extension) - term.setCursorPos(tlx, tly + 3) - local fileValid = true - if (fs.exists(fPath) and fs.isDir(fPath)) or newFName == "" then - term.setBackgroundColour(colours.white) - term.setTextColour(colours.red) - term.write("Invalid ") - fileValid = false - elseif fs.exists(fPath) then - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(" Edit ") - else - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(" Create ") - end - - term.setTextColour(colours.grey) - term.setCursorPos(tlx + 6 + #newFName, tly) - term.setCursorBlink(true) - - local id,p1,p2,p3 = os.pullEvent() - if id == "key" then - if p1 == keys.backspace and #newFName > 0 then - newFName = string.sub(newFName, 1, #newFName-1) - elseif p1 == keys.enter and fileValid then - sPath = fPath - return true - end - elseif id == "char" and p1 ~= "." and p1 ~= " " and #newFName < 15 then - newFName = newFName..p1 - elseif id == "mouse_click" then - --The option boxes. Man, hardcoding is ugly... - if p2 == tlx + 24 then - for i=1,3 do - if p3 == tly+i then fileType = i end - end - end - if p3 == tly + 3 and p2 >= tlx and p2 <= tlx + 8 and fileValid then - sPath = fPath - return true - end - end - end -end - ---[[Initializes the program, by loading in the paint file. Called at the start of each program. - Params: none - Returns:nil -]]-- -local function init() - if textEnabled then - loadNFT(sPath) - table.insert(ddModes, 2, { "text", "textpaint", name = "text"}) - elseif animated then - loadNFA(sPath) - table.insert(ddModes, #ddModes, { "record", "play", name = "anim" }) - table.insert(ddModes, #ddModes, { "go to", "remove", name = "frames"}) - table.insert(ddModes[2], #ddModes[2], "blueprint on") - table.insert(ddModes[2], #ddModes[2], "layers on") - else - loadNFP(sPath) - table.insert(ddModes[2], #ddModes[2], "blueprint on") - end - - for i=0,15 do - table.insert(column, math.pow(2,i)) - end -end - ---[[ - Section: Drawing -]]-- - - ---[[Draws the rather superflous logo. Takes about 1 second, before user is able to move to the - actual program. - Params: none - Returns:bool= true if the file select ran successfully; false otherwise. -]]-- -local function drawLogo() - term.setBackgroundColour(colours.white) - term.clear() - if h >= 12 and w >= 24 then - drawPictureTable(logo, w/2 - #logo[1]/2, h/2 - #logo/2, colours.white) - term.setBackgroundColour(colours.white) - term.setTextColour(colours.black) - local msg = "NPaintPro" - term.setCursorPos(w/2 - #msg/2, h/2 + #logo/2 + 1) - term.write(msg) - msg = "By NitrogenFingers" - term.setCursorPos(w/2 - #msg/2, h/2 + #logo/2 + 2) - term.write(msg) - elseif w >= 15 then - local msg = "NPaintPro" - term.setCursorPos(math.ceil(w/2 - #msg/2), h/2) - term.setTextColour(colours.cyan) - term.write(msg) - msg = "NitrogenFingers" - term.setCursorPos(math.ceil(w/2 - #msg/2), h/2 + 1) - term.setTextColour(colours.black) - term.write(msg) - else - local msg = "NPP" - term.setCursorPos(math.ceil(w/2 - #msg/2), math.floor(h/2)) - term.setTextColour(colours.cyan) - term.write(msg) - msg = "By NF" - term.setCursorPos(math.ceil(w/2 - #msg/2), math.ceil(h/2)) - term.setTextColour(colours.black) - term.write(msg) - end - os.pullEvent() -end - ---[[Clears the display to the alpha channel colour, draws the canvas, the image buffer and the selection - rectanlge if any of these things are present. - Params: none - Returns:nil -]]-- -local function drawCanvas() - --We have to readjust the position of the canvas if we're printing - turtlechar = "@" - if state == "active print" then - if layering == "up" then - if py >= 1 and py <= #frames then - sFrame = py - end - if pz < sy then sy = pz - elseif pz > sy + h - 1 then sy = pz + h - 1 end - if px < sx then sx = px - elseif px > sx + w - 2 then sx = px + w - 2 end - else - if pz >= 1 and pz <= #frames then - sFrame = pz - end - - if py < sy then sy = py - elseif py > sy + h - 1 then sy = py + h - 1 end - if px < sx then sx = px - elseif px > sx + w - 2 then sx = px + w - 2 end - end - - if pfx == 1 then turtlechar = ">" - elseif pfx == -1 then turtlechar = "<" - elseif pfz == 1 then turtlechar = "V" - elseif pfz == -1 then turtlechar = "^" - end - end - - --Picture next - local topLayer, botLayer - if layerDisplay then - topLayer = sFrame - botLayer = 1 - else - topLayer,botLayer = sFrame,sFrame - end - - --How far the canvas draws. If the interface is visible, it stops short of that. - local wlim,hlim = 0,0 - if not interfaceHidden then - wlim = 2 - hlim = 1 - end - - for currframe = botLayer,topLayer,1 do - for y=sy+1,sy+h-hlim do - if frames[currframe][y] then - for x=sx+1,sx+w-wlim do - term.setCursorPos(x-sx,y-sy) - if frames[currframe][y][x] then - term.setBackgroundColour(frames[currframe][y][x]) - if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then - term.setTextColour(frames[currframe].textcol[y][x]) - term.write(frames[currframe].text[y][x]) - else - term.write(" ") - end - else - tileExists = false - for i=currframe-1,botLayer,-1 do - if frames[i][y][x] then - tileExists = true - break - end - end - - if not tileExists then - if blueprint then - term.setBackgroundColour(colours.blue) - term.setTextColour(colours.white) - if x == sx+1 and y % 4 == 1 then - term.write(""..((y/4) % 10)) - elseif y == sy + 1 and x % 4 == 1 then - term.write(""..((x/4) % 10)) - elseif x % 2 == 1 and y % 2 == 1 then - term.write("+") - elseif x % 2 == 1 then - term.write("|") - elseif y % 2 == 1 then - term.write("-") - else - term.write(" ") - end - else - term.setBackgroundColour(alphaC) - if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then - term.setTextColour(frames[currframe].textcol[y][x]) - term.write(frames[currframe].text[y][x]) - else - term.write(" ") - end - end - end - end - end - else - for x=sx+1,sx+w-wlim do - term.setCursorPos(x-sx,y-sy) - - tileExists = false - for i=currframe-1,botLayer,-1 do - if frames[i][y] and frames[i][y][x] then - tileExists = true - break - end - end - - if not tileExists then - if blueprint then - term.setBackgroundColour(colours.blue) - term.setTextColour(colours.white) - if x == sx+1 and y % 4 == 1 then - term.write(""..((y/4) % 10)) - elseif y == sy + 1 and x % 4 == 1 then - term.write(""..((x/4) % 10)) - elseif x % 2 == 1 and y % 2 == 1 then - term.write("+") - elseif x % 2 == 1 then - term.write("|") - elseif y % 2 == 1 then - term.write("-") - else - term.write(" ") - end - else - term.setBackgroundColour(alphaC) - term.write(" ") - end - end - end - end - end - end - - --Then the printer, if he's on - if state == "active print" then - local bgColour = alphaC - if layering == "up" then - term.setCursorPos(px-sx,pz-sy) - if frames[sFrame] and frames[sFrame][pz-sy] and frames[sFrame][pz-sy][px-sx] then - bgColour = frames[sFrame][pz-sy][px-sx] - elseif blueprint then bgColour = colours.blue end - else - term.setCursorPos(px-sx,py-sy) - if frames[sFrame] and frames[sFrame][py-sy] and frames[sFrame][py-sy][px-sx] then - bgColour = frames[sFrame][py-sy][px-sx] - elseif blueprint then bgColour = colours.blue end - end - - term.setBackgroundColour(bgColour) - if bgColour == colours.black then term.setTextColour(colours.white) - else term.setTextColour(colours.black) end - - term.write(turtlechar) - end - - --Then the buffer - if selectrect then - if buffer and rectblink == 1 then - for y=selectrect.y1, math.min(selectrect.y2, selectrect.y1 + buffer.height-1) do - for x=selectrect.x1, math.min(selectrect.x2, selectrect.x1 + buffer.width-1) do - if buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1] then - term.setCursorPos(x+sx,y+sy) - term.setBackgroundColour(buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1]) - term.write(" ") - end - end - end - end - - --This draws the "selection" box - local add = nil - if buffer then - term.setBackgroundColour(colours.lightGrey) - else - term.setBackgroundColour(colours.grey) - end - for i=selectrect.x1, selectrect.x2 do - add = (i + selectrect.y1 + rectblink) % 2 == 0 - term.setCursorPos(i-sx,selectrect.y1-sy) - if add then term.write(" ") end - add = (i + selectrect.y2 + rectblink) % 2 == 0 - term.setCursorPos(i-sx,selectrect.y2-sy) - if add then term.write(" ") end - end - for i=selectrect.y1 + 1, selectrect.y2 - 1 do - add = (i + selectrect.x1 + rectblink) % 2 == 0 - term.setCursorPos(selectrect.x1-sx,i-sy) - if add then term.write(" ") end - add = (i + selectrect.x2 + rectblink) % 2 == 0 - term.setCursorPos(selectrect.x2-sx,i-sy) - if add then term.write(" ") end - end - end -end - ---[[Draws the colour picker on the right side of the screen, the colour pallette and the footer with any - messages currently being displayed - Params: none - Returns:nil -]]-- -local function drawInterface() - --Picker - local coffset,ioffset = 0,0 - local maxcsize = h-2 - if h < #column + 2 then - maxcsize = h-4 - coffset = columnoffset - ioffset = 1 - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.setCursorPos(w-1,1) - term.write("^^") - term.setCursorPos(w-1,h-2) - term.write("VV") - end - for i=1,math.min(#column+1,maxcsize) do - term.setCursorPos(w-1, i + ioffset) - local ci = i+coffset - if ci == #column+1 then - term.setBackgroundColour(colours.black) - term.setTextColour(colours.red) - term.write("XX") - elseif state == "print" then - term.setBackgroundColour(column[ci]) - if column[ci] == colours.black then - term.setTextColour(colours.white) - else term.setTextColour(colours.black) end - - if requirementsDisplayed then - if requiredMaterials[i] < 10 then term.write(" ") end - term.setCursorPos(w-#tostring(requiredMaterials[i])+1, i) - term.write(requiredMaterials[i]) - else - if i+coffset < 10 then term.write(" ") end - term.write(i+coffset) - end - else - term.setBackgroundColour(column[ci]) - term.write(" ") - end - end - --Filling the whitespace with... 'greyspace' *shudder* - if h > #column+3 then - term.setTextColour(colours.grey) - term.setBackgroundColour(colours.lightGrey) - for y=#column+2,h-2 do - term.setCursorPos(w-1,y) - term.write("| ") - end - end - --Pallette - term.setCursorPos(w-1,h-1) - if not lSel then - term.setBackgroundColour(colours.black) - term.setTextColour(colours.red) - term.write("X") - else - term.setBackgroundColour(lSel) - term.setTextColour(lSel) - term.write(" ") - end - if not rSel then - term.setBackgroundColour(colours.black) - term.setTextColour(colours.red) - term.write("X") - else - term.setBackgroundColour(rSel) - term.setTextColour(rSel) - term.write(" ") - end - --Footer - if inMenu then return end - - term.setCursorPos(1, h) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.clearLine() - if mainAvailable then - if inDropDown then - term.write(string.rep(" ", #ddModes.name + 2)) - else - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(ddModes.name.." ") - end - end - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.write(getStateMessage()) - - local coords="X:"..sx.." Y:"..sy - if animated then coords = coords.." Frame:"..sFrame.."/"..framecount.." " end - term.setCursorPos(w-#coords+1,h) - if state == "play" then term.setBackgroundColour(colours.lime) - elseif record then term.setBackgroundColour(colours.red) end - term.write(coords) - - if animated then - term.setCursorPos(w-1,h) - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write("<>") - end -end - ---[[Runs an interface where users can select topics of help. Will return once the user quits the help screen. - Params: none - Returns:nil -]]-- -local function drawHelpScreen() - local selectedHelp = nil - while true do - term.setBackgroundColour(colours.lightGrey) - term.clear() - if not selectedHelp then - term.setCursorPos(4, 1) - term.setTextColour(colours.brown) - term.write("Available modes (click for info):") - for i=1,#helpTopics do - term.setCursorPos(2, 2 + i) - term.setTextColour(colours.black) - term.write(helpTopics[i].name) - if helpTopics[i].key then - term.setTextColour(colours.red) - term.write(" ("..helpTopics[i].key..")") - end - end - term.setCursorPos(4,h) - term.setTextColour(colours.black) - term.write("Press any key to exit") - else - term.setCursorPos(4,1) - term.setTextColour(colours.brown) - term.write(helpTopics[selectedHelp].name) - if helpTopics[selectedHelp].key then - term.setTextColour(colours.red) - term.write(" ("..helpTopics[selectedHelp].key..")") - end - term.setCursorPos(1,3) - term.setTextColour(colours.black) - print(helpTopics[selectedHelp].message.."\n") - for i=1,#helpTopics[selectedHelp].controls do - term.setTextColour(colours.brown) - term.write(helpTopics[selectedHelp].controls[i][1].." ") - term.setTextColour(colours.black) - print(helpTopics[selectedHelp].controls[i][2]) - end - end - - local id,p1,p2,p3 = os.pullEvent() - - if id == "timer" then updateTimer(p1) - elseif id == "key" then - if selectedHelp then selectedHelp = nil - else break end - elseif id == "mouse_click" then - if not selectedHelp then - if p3 >=3 and p3 <= 2+#helpTopics then - selectedHelp = p3-2 - else break end - else - selectedHelp = nil - end - end - end -end - ---[[Draws a message in the footer bar. A helper for DrawInterface, but can be called for custom messages, if the - inMenu paramter is set to true while this is being done (remember to set it back when done!) - Params: message:string = The message to be drawn - Returns:nil -]]-- -local function drawMessage(message) - term.setCursorPos(1,h) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.clearLine() - term.write(message) -end - ---[[ - Section: Generic Interfaces -]]-- - - ---[[One of my generic text printing methods, printing a message at a specified position with width and offset. - No colour materials included. - Params: msg:string = The message to print off-center - height:number = The starting height of the message - width:number = The limit as to how many characters long each line may be - offset:number = The starting width offset of the message - Returns:number the number of lines used in printing the message -]]-- -local function wprintOffCenter(msg, height, width, offset) - local inc = 0 - local ops = 1 - while #msg - ops > width do - local nextspace = 0 - while string.find(msg, " ", ops + nextspace) and - string.find(msg, " ", ops + nextspace) - ops < width do - nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops - end - local ox,oy = term.getCursorPos() - term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc) - inc = inc + 1 - term.write(string.sub(msg, ops, nextspace + ops - 1)) - ops = ops + nextspace - end - term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc) - term.write(string.sub(msg, ops)) - - return inc + 1 -end - ---[[Draws a message that must be clicked on or a key struck to be cleared. No options, so used for displaying - generic information. - Params: ctitle:string = The title of the confirm dialogue - msg:string = The message displayed in the dialogue - Returns:nil -]]-- -local function displayConfirmDialogue(ctitle, msg) - local dialogoffset = 8 - --We actually print twice- once to get the lines, second time to print proper. Easier this way. - local lines = wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2) - - term.setCursorPos(dialogoffset, 3) - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(string.rep(" ", w - dialogoffset * 2)) - term.setCursorPos(dialogoffset + (w - dialogoffset * 2)/2 - #ctitle/2, 3) - term.write(ctitle) - term.setTextColour(colours.grey) - term.setBackgroundColour(colours.lightGrey) - term.setCursorPos(dialogoffset, 4) - term.write(string.rep(" ", w - dialogoffset * 2)) - for i=5,5+lines do - term.setCursorPos(dialogoffset, i) - term.write(" "..string.rep(" ", w - (dialogoffset) * 2 - 2).." ") - end - wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2) - - --In the event of a message, the player hits anything to continue - while true do - local id,key = os.pullEvent() - if id == "timer" then updateTimer(key); - elseif id == "key" or id == "mouse_click" or id == "mouse_drag" then break end - end -end - ---[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position - of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing - Params: x:int = the x position the menu should be displayed at - y:int = the y position the menu should be displayed at - options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter) - Returns:string the selected menu option. -]]-- -local function displayDropDown(x, y, options) - inDropDown = true - --Figures out the dimensions of our thing - local longestX = #options.name - for i=1,#options do - local currVal = options[i] - if type(currVal) == "table" then currVal = currVal.name end - - longestX = math.max(longestX, #currVal) - end - local xOffset = math.max(0, longestX - ((w-2) - x) + 1) - local yOffset = math.max(0, #options - ((h-1) - y)) - - local clickTimes = 0 - local tid = nil - local selection = nil - while clickTimes < 2 do - drawCanvas() - drawInterface() - - term.setCursorPos(x-xOffset,y-yOffset) - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(options.name..string.rep(" ", longestX-#options.name + 2)) - - for i=1,#options do - term.setCursorPos(x-xOffset, y-yOffset+i) - if i==selection and clickTimes % 2 == 0 then - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - else - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - end - local currVal = options[i] - if type(currVal) == "table" then - term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1)) - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.write(">") - else - term.write(currVal..string.rep(" ", longestX-#currVal + 2)) - end - end - - local id, p1, p2, p3 = os.pullEvent() - if id == "timer" then - if p1 == tid then - clickTimes = clickTimes + 1 - if clickTimes > 2 then - break - else - tid = os.startTimer(0.1) - end - else - updateTimer(p1) - drawCanvas() - drawInterface() - end - elseif id == "mouse_click" then - if p2 >=x-xOffset and p2 <= x-xOffset + longestX + 1 and p3 >= y-yOffset+1 and p3 <= y-yOffset+#options then - selection = p3-(y-yOffset) - tid = os.startTimer(0.1) - else - selection = "" - break - end - end - end - - if type(selection) == "number" then - selection = options[selection] - end - - if type(selection) == "string" then - inDropDown = false - return selection - elseif type(selection) == "table" then - return displayDropDown(x, y, selection) - end -end - ---[[A custom io.read() function with a few differences- it limits the number of characters being printed, - waits a 1/100th of a second so any keys still in the event library are removed before input is read and - the timer for the selectionrectangle is continuously updated during the process. - Params: lim:int = the number of characters input is allowed - Returns:string the inputted string, trimmed of leading and tailing whitespace -]]-- -local function readInput(lim) - term.setCursorBlink(true) - - local inputString = "" - if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end - local ox,oy = term.getCursorPos() - --We only get input from the footer, so this is safe. Change if recycling - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.write(string.rep(" ", lim)) - term.setCursorPos(ox, oy) - --As events queue immediately, we may get an unwanted key... this will solve that problem - local inputTimer = os.startTimer(0.01) - local keysAllowed = false - - while true do - local id,key = os.pullEvent() - - if keysAllowed then - if id == "key" and key == 14 and #inputString > 0 then - inputString = string.sub(inputString, 1, #inputString-1) - term.setCursorPos(ox + #inputString,oy) - term.write(" ") - elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then - break - elseif id == "key" and key == keys.leftCtrl then - return "" - elseif id == "char" and #inputString < lim then - inputString = inputString..key - end - end - - if id == "timer" then - if key == inputTimer then - keysAllowed = true - else - updateTimer(key) - drawCanvas() - drawInterface() - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - end - end - term.setCursorPos(ox,oy) - term.write(inputString) - term.setCursorPos(ox + #inputString, oy) - end - - while string.sub(inputString, 1, 1) == " " do - inputString = string.sub(inputString, 2, #inputString) - end - while string.sub(inputString, #inputString, #inputString) == " " do - inputString = string.sub(inputString, 1, #inputString-1) - end - term.setCursorBlink(false) - - return inputString -end - ---[[ - Section: Image tools -]]-- - - ---[[Copies all pixels beneath the selection rectangle into the image buffer. Empty buffers are converted to nil. - Params: removeImage:bool = true if the image is to be erased after copying, false otherwise - Returns:nil -]]-- -local function copyToBuffer(removeImage) - buffer = { width = selectrect.x2 - selectrect.x1 + 1, height = selectrect.y2 - selectrect.y1 + 1, contents = { } } - - local containsSomething = false - for y=1,buffer.height do - buffer.contents[y] = { } - local f,l = sFrame,sFrame - if record then f,l = 1, framecount end - - for fra = f,l do - if frames[fra][selectrect.y1 + y - 1] then - for x=1,buffer.width do - buffer.contents[y][x] = frames[sFrame][selectrect.y1 + y - 1][selectrect.x1 + x - 1] - if removeImage then frames[fra][selectrect.y1 + y - 1][selectrect.x1 + x - 1] = nil end - if buffer.contents[y][x] then containsSomething = true end - end - end - end - end - --I don't classify an empty buffer as a real buffer- confusing to the user. - if not containsSomething then buffer = nil end -end - ---[[Replaces all pixels under the selection rectangle with the image buffer (or what can be seen of it). Record-dependent. - Params: removeBuffer:bool = true if the buffer is to be emptied after copying, false otherwise - Returns:nil -]]-- -local function copyFromBuffer(removeBuffer) - if not buffer then return end - - for y = 1, math.min(buffer.height,selectrect.y2-selectrect.y1+1) do - local f,l = sFrame, sFrame - if record then f,l = 1, framecount end - - for fra = f,l do - if not frames[fra][selectrect.y1+y-1] then frames[fra][selectrect.y1+y-1] = { } end - for x = 1, math.min(buffer.width,selectrect.x2-selectrect.x1+1) do - frames[fra][selectrect.y1+y-1][selectrect.x1+x-1] = buffer.contents[y][x] - end - end - end - - if removeBuffer then buffer = nil end -end - ---[[Moves the entire image (or entire animation) to the specified coordinates. Record-dependent. - Params: newx:int = the X coordinate to move the image to - newy:int = the Y coordinate to move the image to - Returns:nil -]]-- -local function moveImage(newx,newy) - if not leflim or not toplim then return end - if newx <=0 or newy <=0 then return end - local f,l = sFrame,sFrame - if record then f,l = 1,framecount end - - for i=f,l do - local newlines = { } - for y=toplim,botlim do - local line = frames[i][y] - if line then - newlines[y-toplim+newy] = { } - for x,char in pairs(line) do - newlines[y-toplim+newy][x-leflim+newx] = char - end - end - end - --Exceptions that allow us to move the text as well - if textEnabled then - newlines.text = { } - for y=toplim,botlim do - local line = frames[i].text[y] - if line then - newlines.text[y-toplim+newy] = { } - for x,char in pairs(line) do - newlines.text[y-toplim+newy][x-leflim+newx] = char - end - end - end - - newlines.textcol = { } - for y=toplim,botlim do - local line = frames[i].textcol[y] - if line then - newlines.textcol[y-toplim+newy] = { } - for x,char in pairs(line) do - newlines.textcol[y-toplim+newy][x-leflim+newx] = char - end - end - end - end - - frames[i] = newlines - end -end - ---[[Prompts the user to clear the current frame or all frames. Record-dependent., - Params: none - Returns:nil -]]-- -local function clearImage() - inMenu = true - if not animated then - drawMessage("Clear image? Y/N: ") - elseif record then - drawMessage("Clear ALL frames? Y/N: ") - else - drawMessage("Clear current frame? Y/N :") - end - if string.find(string.upper(readInput(1)), "Y") then - local f,l = sFrame,sFrame - if record then f,l = 1,framecount end - - for i=f,l do - frames[i] = { } - end - end - inMenu = false -end - ---[[A recursively called method (watch out for big calls!) in which every pixel of a set colour is - changed to another colour. Does not work on the nil colour, for obvious reasons. - Params: x:int = The X coordinate of the colour to flood-fill - y:int = The Y coordinate of the colour to flood-fill - targetColour:colour = the colour that is being flood-filled - newColour:colour = the colour with which to replace the target colour - Returns:nil -]]-- -local function floodFill(x, y, targetColour, newColour) - if not newColour or not targetColour then return end - local nodeList = { } - - table.insert(nodeList, {x = x, y = y}) - - while #nodeList > 0 do - local node = nodeList[1] - if frames[sFrame][node.y] and frames[sFrame][node.y][node.x] == targetColour then - frames[sFrame][node.y][node.x] = newColour - table.insert(nodeList, { x = node.x + 1, y = node.y}) - table.insert(nodeList, { x = node.x, y = node.y + 1}) - if x > 1 then table.insert(nodeList, { x = node.x - 1, y = node.y}) end - if y > 1 then table.insert(nodeList, { x = node.x, y = node.y - 1}) end - end - table.remove(nodeList, 1) - end -end - ---[[ - Section: Animation Tools -]]-- - ---[[Enters play mode, allowing the animation to play through. Interface is restricted to allow this, - and method only leaves once the player leaves play mode. - Params: none - Returns:nil -]]-- -local function playAnimation() - state = "play" - selectedrect = nil - - local animt = os.startTimer(animtime) - repeat - drawCanvas() - drawInterface() - - local id,key,_,y = os.pullEvent() - - if id=="timer" then - if key == animt then - animt = os.startTimer(animtime) - sFrame = (sFrame % framecount) + 1 - else - updateTimer(key) - end - elseif id=="key" then - if key == keys.comma and animtime > 0.1 then animtime = animtime - 0.05 - elseif key == keys.period and animtime < 0.5 then animtime = animtime + 0.05 - elseif key == keys.space then state = "paint" end - elseif id=="mouse_click" and y == h then - state = "paint" - end - until state ~= "play" - os.startTimer(0.5) -end - ---[[Changes the selected frame (sFrame) to the chosen frame. If this frame is above the framecount, - additional frames are created with a copy of the image on the selected frame. - Params: newframe:int = the new frame to move to - Returns:nil -]]-- -local function changeFrame(newframe) - inMenu = true - if not tonumber(newframe) then - term.setCursorPos(1,h) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.clearLine() - - term.write("Go to frame: ") - newframe = tonumber(readInput(2)) - if not newframe or newframe <= 0 then - inMenu = false - return - end - elseif newframe <= 0 then return end - - if newframe > framecount then - for i=framecount+1,newframe do - frames[i] = {} - for y,line in pairs(frames[sFrame]) do - frames[i][y] = { } - for x,v in pairs(line) do - frames[i][y][x] = v - end - end - end - framecount = newframe - end - sFrame = newframe - inMenu = false -end - ---[[Removes every frame leading after the frame passed in - Params: frame:int the non-inclusive lower bounds of the delete - Returns:nil -]]-- -local function removeFramesAfter(frame) - inMenu = true - if frame==framecount then return end - drawMessage("Remove frames "..(frame+1).."/"..framecount.."? Y/N :") - local answer = string.upper(readInput(1)) - - if string.find(answer, string.upper("Y")) ~= 1 then - inMenu = false - return - end - - for i=frame+1, framecount do - frames[i] = nil - end - framecount = frame - inMenu = false -end - ---[[ - Section: Printing Tools -]]-- - ---[[Constructs a new facing to the left of the current facing - Params: curx:number = The facing on the X axis - curz:number = The facing on the Z axis - hand:string = The hand of the axis ("right" or "left") - Returns:number,number = the new facing on the X and Z axis after a left turn -]]-- -local function getLeft(curx, curz) - local hand = "left" - if layering == "up" then hand = "right" end - - if hand == "right" then - if curx == 1 then return 0,-1 end - if curx == -1 then return 0,1 end - if curz == 1 then return 1,0 end - if curz == -1 then return -1,0 end - else - if curx == 1 then return 0,1 end - if curx == -1 then return 0,-1 end - if curz == 1 then return -1,0 end - if curz == -1 then return 1,0 end - end -end - ---[[Constructs a new facing to the right of the current facing - Params: curx:number = The facing on the X axis - curz:number = The facing on the Z axis - hand:string = The hand of the axis ("right" or "left") - Returns:number,number = the new facing on the X and Z axis after a right turn -]]-- -local function getRight(curx, curz) - local hand = "left" - if layering == "up" then hand = "right" end - - if hand == "right" then - if curx == 1 then return 0,1 end - if curx == -1 then return 0,-1 end - if curz == 1 then return -1,0 end - if curz == -1 then return 1,0 end - else - if curx == 1 then return 0,-1 end - if curx == -1 then return 0,1 end - if curz == 1 then return 1,0 end - if curz == -1 then return -1,0 end - end -end - - ---[[Sends out a rednet signal requesting local printers, and will listen for any responses. Printers found are added to the - printerList (for ID's) and printerNames (for names) - Params: nil - Returns:nil -]]-- -local function locatePrinters() - printerList = { } - printerNames = { name = "Printers" } - local oldState = state - state = "Locating printers, please wait... " - drawCanvas() - drawInterface() - state = oldState - - local modemOpened = false - for k,v in pairs(rs.getSides()) do - if peripheral.isPresent(v) and peripheral.getType(v) == "modem" then - rednet.open(v) - modemOpened = true - break - end - end - - if not modemOpened then - displayConfirmDialogue("Modem not found!", "No modem peripheral. Must have network modem to locate printers.") - return false - end - - rednet.broadcast("$3DPRINT IDENTIFY") - - while true do - local id, msg = rsTimeReceive(1) - - if not id then break end - if string.find(msg, "$3DPRINT IDACK") == 1 then - msg = string.gsub(msg, "$3DPRINT IDACK ", "") - table.insert(printerList, id) - table.insert(printerNames, msg) - end - end - - if #printerList == 0 then - displayConfirmDialogue("Printers not found!", "No active printers found in proximity of this computer.") - return false - else - return true - end -end - ---[[Sends a request to the printer. Waits on a response and updates the state of the application accordingly. - Params: command:string the command to send - param:string a parameter to send, if any - Returns:nil -]]-- -local function sendPC(command,param) - local msg = "$PC "..command - if param then msg = msg.." "..param end - rednet.send(printerList[selectedPrinter], msg) - - while true do - local id,key = rsTimeReceive() - if id == printerList[selectedPrinter] then - if key == "$3DPRINT ACK" then - break - elseif key == "$3DPRINT DEP" then - displayConfirmDialogue("Printer Empty", "The printer has exhasted a material. Please refill slot "..param.. - ", and click this message when ready to continue.") - rednet.send(printerList[selectedPrinter], msg) - elseif key == "$3DPRINT OOF" then - displayConfirmDialogue("Printer Out of Fuel", "The printer has no fuel. Please replace the material ".. - "in slot 1 with a fuel source, then click this message.") - rednet.send(printerList[selectedPrinter], "$PC SS 1") - id,key = rsTimeReceive() - rednet.send(printerList[selectedPrinter], "$PC RF") - id,key = rsTimeReceive() - rednet.send(printerList[selectedPrinter], msg) - end - end - end - - --Changes to position are handled after the event has been successfully completed - if command == "FW" then - px = px + pfx - pz = pz + pfz - elseif command == "BK" then - px = px - pfx - pz = pz - pfz - elseif command == "UP" then - if layering == "up" then - py = py + 1 - else - py = py - 1 - end - elseif command == "DW" then - if layering == "up" then - py = py - 1 - else - py = py + 1 - end - elseif command == "TL" then - pfx,pfz = getLeft(pfx,pfz) - elseif command == "TR" then - pfx,pfz = getRight(pfx,pfz) - elseif command == "TU" then - pfx = -pfx - pfz = -pfz - end - - drawCanvas() - drawInterface() -end - ---[[A printing function that commands the printer to turn to face the desired direction, if it is not already doing so - Params: desx:number = the normalized x direction to face - desz:number = the normalized z direction to face - Returns:nil -]]-- -local function turnToFace(desx,desz) - if desx ~= 0 then - if pfx ~= desx then - local temppfx,_ = getLeft(pfx,pfz) - if temppfx == desx then - sendPC("TL") - elseif temppfx == -desx then - sendPC("TR") - else - sendPC("TU") - end - end - else - print("on the z axis") - if pfz ~= desz then - local _,temppfz = getLeft(pfx,pfz) - if temppfz == desz then - sendPC("TL") - elseif temppfz == -desz then - sendPC("TR") - else - sendPC("TU") - end - end - end -end - ---[[Performs the print - Params: nil - Returns:nil -]]-- -local function performPrint() - state = "active print" - if layering == "up" then - --An up layering starts our builder bot on the bottom left corner of our build - px,py,pz = leflim, 0, botlim + 1 - pfx,pfz = 0,-1 - - --We move him forward and up a bit from his original position. - sendPC("FW") - sendPC("UP") - --For each layer that needs to be completed, we go up by one each time - for layers=1,#frames do - --We first decide if we're going forwards or back, depending on what side we're on - local rowbot,rowtop,rowinc = nil,nil,nil - if pz == botlim then - rowbot,rowtop,rowinc = botlim,toplim,-1 - else - rowbot,rowtop,rowinc = toplim,botlim,1 - end - - for rows = rowbot,rowtop,rowinc do - --Then we decide if we're going left or right, depending on what side we're on - local linebot,linetop,lineinc = nil,nil,nil - if px == leflim then - --Facing from the left side has to be easterly- it's changed here - turnToFace(1,0) - linebot,linetop,lineinc = leflim,riglim,1 - else - --Facing from the right side has to be westerly- it's changed here - turnToFace(-1,0) - linebot,linetop,lineinc = riglim,leflim,-1 - end - - for lines = linebot,linetop,lineinc do - --We move our turtle forward, placing the right material at each step - local material = frames[py][pz][px] - if material then - material = math.log10(frames[py][pz][px])/math.log10(2) + 1 - sendPC("SS", material) - sendPC("PD") - end - if lines ~= linetop then - sendPC("FW") - end - end - - --The printer then has to do a U-turn, depending on which way he's facing and - --which way he needs to go - local temppfx,temppfz = getLeft(pfx,pfz) - if temppfz == rowinc and rows ~= rowtop then - sendPC("TL") - sendPC("FW") - sendPC("TL") - elseif temppfz == -rowinc and rows ~= rowtop then - sendPC("TR") - sendPC("FW") - sendPC("TR") - end - end - --Now at the end of a run he does a 180 and moves up to begin the next part of the print - sendPC("TU") - if layers ~= #frames then - sendPC("UP") - end - end - --All done- now we head back to where we started. - if px ~= leflim then - turnToFace(-1,0) - while px ~= leflim do - sendPC("FW") - end - end - if pz ~= botlim then - turnToFace(0,-1) - while pz ~= botlim do - sendPC("BK") - end - end - turnToFace(0,-1) - sendPC("BK") - while py > 0 do - sendPC("DW") - end - else - --The front facing is at the top-left corner, facing south not north - px,py,pz = leflim, botlim, 1 - pfx,pfz = 0,1 - --We move the printer to the last layer- he prints from the back forwards - while pz < #frames do - sendPC("FW") - end - - --For each layer in the frame we build our wall, the move back - for layers = 1,#frames do - --We first decide if we're going left or right based on our position - local rowbot,rowtop,rowinc = nil,nil,nil - if px == leflim then - rowbot,rowtop,rowinc = leflim,riglim,1 - else - rowbot,rowtop,rowinc = riglim,leflim,-1 - end - - for rows = rowbot,rowtop,rowinc do - --Then we decide if we're going up or down, depending on our given altitude - local linebot,linetop,lineinc = nil,nil,nil - if py == botlim then - linebot,linetop,lineinc = botlim,toplim,-1 - else - linebot,linetop,lineinc = toplim,botlim,1 - end - - for lines = linebot,linetop,lineinc do - --We move our turtle up/down, placing the right material at each step - local material = frames[pz][py][px] - if material then - material = math.log10(frames[pz][py][px])/math.log10(2) + 1 - sendPC("SS", material) - sendPC("PF") - end - if lines ~= linetop then - if lineinc == 1 then sendPC("DW") - else sendPC("UP") end - end - end - - if rows ~= rowtop then - turnToFace(rowinc,0) - sendPC("FW") - turnToFace(0,1) - end - end - - if layers ~= #frames then - sendPC("TU") - sendPC("FW") - sendPC("TU") - end - end - --He's easy to reset - while px ~= leflim do - turnToFace(-1,0) - sendPC("FW") - end - turnToFace(0,1) - end - - sendPC("DE") - - displayConfirmDialogue("Print complete", "The 3D print was successful.") -end - ---[[ - Section: Interface -]]-- - ---[[Runs the printing interface. Allows users to find/select a printer, the style of printing to perform and to begin the operation - Params: none - Returns:boolean true if printing was started, false otherwse -]]-- -local function runPrintInterface() - calculateMaterials() - --There's nothing on canvas yet! - if not botlim then - displayConfirmDialogue("Cannot Print Empty Canvas", "There is nothing on canvas that ".. - "can be printed, and the operation cannot be completed.") - return false - end - --No printers nearby - if not locatePrinters() then - return false - end - - layering = "up" - requirementsDisplayed = false - selectedPrinter = 1 - while true do - drawCanvas() - term.setBackgroundColour(colours.lightGrey) - for i=1,10 do - term.setCursorPos(1,i) - term.clearLine() - end - drawInterface() - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.black) - - local msg = "3D Printing" - term.setCursorPos(w/2-#msg/2 - 2, 1) - term.write(msg) - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - if(requirementsDisplayed) then - msg = "Count:" - else - msg = " Slot:" - end - term.setCursorPos(w-3-#msg, 1) - term.write(msg) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.black) - - term.setCursorPos(7, 2) - term.write("Layering") - drawPictureTable(layerUpIcon, 3, 3, colours.white) - drawPictureTable(layerForwardIcon, 12, 3, colours.white) - if layering == "up" then - term.setBackgroundColour(colours.red) - else - term.setBackgroundColour(colours.lightGrey) - end - term.setCursorPos(3, 9) - term.write("Upwards") - if layering == "forward" then - term.setBackgroundColour(colours.red) - else - term.setBackgroundColour(colours.lightGrey) - end - term.setCursorPos(12, 9) - term.write("Forward") - - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.black) - term.setCursorPos(31, 2) - term.write("Printer ID") - term.setCursorPos(33, 3) - if #printerList > 1 then - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - else - term.setTextColour(colours.red) - end - term.write(" "..printerNames[selectedPrinter].." ") - - term.setBackgroundColour(colours.grey) - term.setTextColour(colours.lightGrey) - term.setCursorPos(25, 10) - term.write(" Cancel ") - term.setCursorPos(40, 10) - term.write(" Print ") - - local id, p1, p2, p3 = os.pullEvent() - - if id == "timer" then - updateTimer(p1) - elseif id == "mouse_click" then - --Layering Buttons - if p2 >= 3 and p2 <= 9 and p3 >= 3 and p3 <= 9 then - layering = "up" - elseif p2 >= 12 and p2 <= 18 and p3 >= 3 and p3 <= 9 then - layering = "forward" - --Count/Slot - elseif p2 >= w - #msg - 3 and p2 <= w - 3 and p3 == 1 then - requirementsDisplayed = not requirementsDisplayed - --Printer ID - elseif p2 >= 33 and p2 <= 33 + #printerNames[selectedPrinter] and p3 == 3 and #printerList > 1 then - local chosenName = displayDropDown(33, 3, printerNames) - for i=1,#printerNames do - if printerNames[i] == chosenName then - selectedPrinter = i - break; - end - end - --Print and Cancel - elseif p2 >= 25 and p2 <= 32 and p3 == 10 then - break - elseif p2 >= 40 and p2 <= 46 and p3 == 10 then - rednet.send(printerList[selectedPrinter], "$3DPRINT ACTIVATE") - ready = false - while true do - local id,msg = rsTimeReceive(10) - - if id == printerList[selectedPrinter] and msg == "$3DPRINT ACTACK" then - ready = true - break - end - end - if ready then - performPrint() - break - else - displayConfirmDialogue("Printer Didn't Respond", "The printer didn't respond to the activation command. Check to see if it's online") - end - end - end - end - state = "paint" -end - ---[[Performs a legacy save. When the dropdown menu is unavailable, it requests the user to save - or exit using keyboard shortcuts rather than selecting a menu option from the dropdown. - Pressing the control key again will cancel the save operation. - Params: none - Returns:string = the selection made -]]-- -local function performLegacySaveExit() - local saveMsg = "(S)ave/(E)xit?" - if w < #saveMsg then saveMsg = "S/E?" end - - term.setCursorPos(1,h) - term.setBackgroundColour(colours.lightGrey) - term.setTextColour(colours.grey) - term.clearLine() - term.write(saveMsg) - - while true do - local id,val = os.pullEvent() - if id == "timer" then updateTimer(val) - elseif id == "key" then - if val == keys.s then return "save" - elseif val == keys.e then - --Get rid of the extra event - os.pullEvent("char") - return "exit" - elseif val == keys.leftCtrl then return nil - end - end - end -end - ---[[This function changes the current paint program to another tool or mode, depending on user input. Handles - any necessary changes in logic involved in that. - Params: mode:string = the name of the mode to change to - Returns:nil -]]-- -local function performSelection(mode) - if not mode or mode == "" then return - - elseif mode == "help" and helpAvailable then - drawHelpScreen() - - elseif mode == "blueprint on" then - blueprint = true - for i=1,#ddModes[2] do if ddModes[2][i] == "blueprint on" then - ddModes[2][i] = "blueprint off" - end end - - elseif mode == "blueprint off" then - blueprint = false - for i=1,#ddModes[2] do if ddModes[2][i] == "blueprint off" then - ddModes[2][i] = "blueprint on" - end end - - elseif mode == "layers on" then - layerDisplay = true - for i=1,#ddModes[2] do if ddModes[2][i] == "layers on" then - ddModes[2][i] = "layers off" - end end - - elseif mode == "layers off" then - layerDisplay = false - for i=1,#ddModes[2] do if ddModes[2][i] == "layers off" then - ddModes[2][i] = "layers on" - end end - - elseif mode == "direction on" then - printDirection = true - for i=1,#ddModes[2] do if ddModes[2][i] == "direction on" then - ddModes[2][i] = "direction off" - end end - - elseif mode == "direction off" then - printDirection = false - for i=1,#ddModes[2] do if ddModes[2][i] == "direction off" then - ddModes[2][i] = "direction on" - end end - - elseif mode == "hide interface" then - interfaceHidden = true - - elseif mode == "show interface" then - interfaceHidden = false - - elseif mode == "go to" then - changeFrame() - - elseif mode == "remove" then - removeFramesAfter(sFrame) - - elseif mode == "play" then - playAnimation() - - elseif mode == "copy" then - if selectrect and selectrect.x1 ~= selectrect.x2 then - copyToBuffer(false) - end - - elseif mode == "cut" then - if selectrect and selectrect.x1 ~= selectrect.x2 then - copyToBuffer(true) - end - - elseif mode == "paste" then - if selectrect and selectrect.x1 ~= selectrect.x2 then - copyFromBuffer(false) - end - - elseif mode == "hide" then - selectrect = nil - if state == "select" then state = "corner select" end - - elseif mode == "alpha to left" then - if lSel then alphaC = lSel end - - elseif mode == "alpha to right" then - if rSel then alphaC = rSel end - - elseif mode == "record" then - record = not record - - elseif mode == "clear" then - if state=="select" then buffer = nil - else clearImage() end - - elseif mode == "select" then - if state=="corner select" or state=="select" then - state = "paint" - elseif selectrect and selectrect.x1 ~= selectrect.x2 then - state = "select" - else - state = "corner select" - end - - elseif mode == "print" then - state = "print" - runPrintInterface() - state = "paint" - - elseif mode == "save" then - if animated then saveNFA(sPath) - elseif textEnabled then saveNFT(sPath) - else saveNFP(sPath) end - - elseif mode == "exit" then - isRunning = false - - elseif mode ~= state then state = mode - else state = "paint" - end -end - ---[[The main function of the program, reads and handles all events and updates them accordingly. Mode changes, - painting to the canvas and general selections are done here. - Params: none - Returns:nil -]]-- -local function handleEvents() - recttimer = os.startTimer(0.5) - while isRunning do - drawCanvas() - if not interfaceHidden then drawInterface() end - - if state == "text" then - term.setCursorPos(textCurX - sx, textCurY - sy) - term.setCursorBlink(true) - end - - local id,p1,p2,p3 = os.pullEvent() - term.setCursorBlink(false) - if id=="timer" then - updateTimer(p1) - elseif (id=="mouse_click" or id=="mouse_drag") and not interfaceHidden then - if p2 >=w-1 and p3 < #column+1 then - local off = 0 - local cansel = true - if h < #column + 2 then - if p3 == 1 then - if columnoffset > 0 then columnoffset = columnoffset-1 end - cansel = false - elseif p3 == h-2 then - if columnoffset < #column-(h-4)+1 then columnoffset = columnoffset+1 end - cansel = false - else - off = columnoffset - 1 - end - end - --This rather handily accounts for the nil case (p3+off=#column+1) - if p1==1 and cansel then lSel = column[p3+off] - elseif p1==2 and cansel then rSel = column[p3+off] end - elseif p2 >=w-1 and p3==#column+1 then - if p1==1 then lSel = nil - else rSel = nil end - elseif p2==w-1 and p3==h and animated then - changeFrame(sFrame-1) - elseif p2==w and p3==h and animated then - changeFrame(sFrame+1) - elseif p2 <= #ddModes.name + 2 and p3==h and mainAvailable then - local sel = displayDropDown(1, h-1, ddModes) - performSelection(sel) - elseif p2 < w-1 and p3 <= h-1 then - if state=="pippette" then - if p1==1 then - if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then - lSel = frames[sFrame][p3+sy][p2+sx] - end - elseif p1==2 then - if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then - rSel = frames[sFrame][p3+sy][p2+sx] - end - end - elseif state=="move" then - updateImageLims(record) - moveImage(p2,p3) - elseif state=="flood" then - if p1 == 1 and lSel and frames[sFrame][p3+sy] then - floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],lSel) - elseif p1 == 2 and rSel and frames[sFrame][p3+sy] then - floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],rSel) - end - elseif state=="corner select" then - if not selectrect then - selectrect = { x1=p2+sx, x2=p2+sx, y1=p3+sy, y2=p3+sy } - elseif selectrect.x1 ~= p2+sx and selectrect.y1 ~= p3+sy then - if p2+sx<selectrect.x1 then selectrect.x1 = p2+sx - else selectrect.x2 = p2+sx end - - if p3+sy<selectrect.y1 then selectrect.y1 = p3+sy - else selectrect.y2 = p3+sy end - - state = "select" - end - elseif state=="textpaint" then - local paintCol = lSel - if p1 == 2 then paintCol = rSel end - if frames[sFrame].textcol[p3+sy] then - frames[sFrame].textcol[p3+sy][p2+sx] = paintCol - end - elseif state=="text" then - textCurX = p2 + sx - textCurY = p3 + sy - elseif state=="select" then - if p1 == 1 then - local swidth = selectrect.x2 - selectrect.x1 - local sheight = selectrect.y2 - selectrect.y1 - - selectrect.x1 = p2 + sx - selectrect.y1 = p3 + sy - selectrect.x2 = p2 + swidth + sx - selectrect.y2 = p3 + sheight + sy - elseif p1 == 2 and p2 < w-2 and p3 < h-1 and boxdropAvailable then - inMenu = true - local sel = displayDropDown(p2, p3, srModes) - inMenu = false - performSelection(sel) - end - else - local f,l = sFrame,sFrame - if record then f,l = 1,framecount end - local bwidth = 0 - if state == "brush" then bwidth = brushsize-1 end - - for i=f,l do - for x = math.max(1,p2+sx-bwidth),p2+sx+bwidth do - for y = math.max(1,p3+sy-bwidth), p3+sy+bwidth do - if math.abs(x - (p2+sx)) + math.abs(y - (p3+sy)) <= bwidth then - if not frames[i][y] then frames[i][y] = {} end - if p1==1 then frames[i][y][x] = lSel - else frames[i][y][x] = rSel end - - if textEnabled then - if not frames[i].text[y] then frames[i].text[y] = { } end - if not frames[i].textcol[y] then frames[i].textcol[y] = { } end - end - end - end - end - end - end - end - elseif id=="char" then - if state=="text" then - if not frames[sFrame][textCurY] then frames[sFrame][textCurY] = { } end - if not frames[sFrame].text[textCurY] then frames[sFrame].text[textCurY] = { } end - if not frames[sFrame].textcol[textCurY] then frames[sFrame].textcol[textCurY] = { } end - - if rSel then frames[sFrame][textCurY][textCurX] = rSel end - if lSel then - frames[sFrame].text[textCurY][textCurX] = p1 - frames[sFrame].textcol[textCurY][textCurX] = lSel - else - frames[sFrame].text[textCurY][textCurX] = " " - frames[sFrame].textcol[textCurY][textCurX] = rSel - end - - textCurX = textCurX+1 - if textCurX > w + sx - 2 then sx = textCurX - w + 2 end - elseif tonumber(p1) then - if state=="brush" and tonumber(p1) > 1 then - brushsize = tonumber(p1) - elseif animated and tonumber(p1) > 0 then - changeFrame(tonumber(p1)) - end - end - elseif id=="key" then - --All standard interface methods are locked when the interface is hidden - if interfaceHidden then - if p1==keys.grave then - performSelection("show interface") - end - --Text needs special handlers (all other keyboard shortcuts are of course reserved for typing) - elseif state=="text" then - if p1==keys.backspace and textCurX > 1 then - textCurX = textCurX-1 - if frames[sFrame].text[textCurY] then - frames[sFrame].text[textCurY][textCurX] = nil - frames[sFrame].textcol[textCurY][textCurX] = nil - end - if textCurX < sx then sx = textCurX end - elseif p1==keys.left and textCurX > 1 then - textCurX = textCurX-1 - if textCurX-1 < sx then sx = textCurX-1 end - elseif p1==keys.right then - textCurX = textCurX+1 - if textCurX > w + sx - 2 then sx = textCurX - w + 2 end - elseif p1==keys.up and textCurY > 1 then - textCurY = textCurY-1 - if textCurY-1 < sy then sy = textCurY-1 end - elseif p1==keys.down then - textCurY = textCurY+1 - if textCurY > h + sy - 1 then sy = textCurY - h + 1 end - end - - elseif p1==keys.leftCtrl then - local sel = nil - if mainAvailable then - sel = displayDropDown(1, h-1, ddModes[#ddModes]) - else sel = performLegacySaveExit() end - performSelection(sel) - elseif p1==keys.leftAlt then - local sel = displayDropDown(1, h-1, ddModes[1]) - performSelection(sel) - elseif p1==keys.h then - performSelection("help") - elseif p1==keys.x then - performSelection("cut") - elseif p1==keys.c then - performSelection("copy") - elseif p1==keys.v then - performSelection("paste") - elseif p1==keys.z then - performSelection("clear") - elseif p1==keys.s then - performSelection("select") - elseif p1==keys.tab then - performSelection("hide") - elseif p1==keys.q then - performSelection("alpha to left") - elseif p1==keys.w then - performSelection("alpha to right") - elseif p1==keys.f then - performSelection("flood") - elseif p1==keys.b then - performSelection("brush") - elseif p1==keys.m then - performSelection("move") - elseif p1==keys.backslash and animated then - performSelection("record") - elseif p1==keys.p then - performSelection("pippette") - elseif p1==keys.g and animated then - performSelection("go to") - elseif p1==keys.grave then - performSelection("hide interface") - elseif p1==keys.period and animated then - changeFrame(sFrame+1) - elseif p1==keys.comma and animated then - changeFrame(sFrame-1) - elseif p1==keys.r and animated then - performSelection("remove") - elseif p1==keys.space and animated then - performSelection("play") - elseif p1==keys.t and textEnabled then - performSelection("text") - sleep(0.01) - elseif p1==keys.y and textEnabled then - performSelection("textpaint") - elseif p1==keys.left then - if state == "move" and toplim then - updateImageLims(record) - if toplim and leflim then - moveImage(leflim-1,toplim) - end - elseif state=="select" and selectrect.x1 > 1 then - selectrect.x1 = selectrect.x1-1 - selectrect.x2 = selectrect.x2-1 - elseif sx > 0 then sx=sx-1 end - elseif p1==keys.right then - if state == "move" then - updateImageLims(record) - if toplim and leflim then - moveImage(leflim+1,toplim) - end - elseif state=="select" then - selectrect.x1 = selectrect.x1+1 - selectrect.x2 = selectrect.x2+1 - else sx=sx+1 end - elseif p1==keys.up then - if state == "move" then - updateImageLims(record) - if toplim and leflim then - moveImage(leflim,toplim-1) - end - elseif state=="select" and selectrect.y1 > 1 then - selectrect.y1 = selectrect.y1-1 - selectrect.y2 = selectrect.y2-1 - elseif sy > 0 then sy=sy-1 end - elseif p1==keys.down then - if state == "move" then - updateImageLims(record) - if toplim and leflim then - moveImage(leflim,toplim+1) - end - elseif state=="select" then - selectrect.y1 = selectrect.y1+1 - selectrect.y2 = selectrect.y2+1 - else sy=sy+1 end - end - end - end -end - ---[[ - Section: Main -]]-- - ---The first thing done is deciding what features we actually have, given the screen size -if w < 7 or h < 4 then - --NPaintPro simply doesn't work at certain configurations - shell.run("clear") - print("Screen too small") - os.pullEvent("key") - return -end ---And reduces the number of features in others. -determineAvailableServices() - ---There is no b&w support for NPP. -if not term.isColour() then - shell.run("clear") - print("NPaintPro\nBy NitrogenFingers\n\nNPaintPro can only be run on advanced ".. - "computers. Please reinstall on an advanced computer.") - return -end - ---Taken almost directly from edit (for consistency) -local tArgs = {...} - ---Command line options can appear before the file path to specify the file format -local ca = 1 -while ca <= #tArgs do - if tArgs[ca] == "-a" then animated = true - elseif tArgs[ca] == "-t" then textEnabled = true - elseif tArgs[ca] == "-d" then interfaceHidden = true - elseif string.sub(tArgs[ca], 1, 1) == "-" then - print("Unrecognized option: "..tArgs[ca]) - return - else break end - ca = ca + 1 -end - ---Presently, animations and text files are not supported -if animated and textEnabled then - print("No support for animated text files- cannot have both -a and -t") - return -end - ---Filepaths must be added if the screen is too small -if #tArgs < ca then - if not filemakerAvailable then - print("Usage: npaintpro [-a,-t,-d] <path>") - return - else - --Otherwise do the logo draw early, to determine the file. - drawLogo() - if not runFileMaker() then return end - end -else - if not interfaceHidden then drawLogo() end - sPath = shell.resolve(tArgs[ca]) -end - -if fs.exists(sPath) then - if fs.isDir(sPath) then - print("Cannot edit a directory.") - return - elseif string.find(sPath, ".nfp") ~= #sPath-3 and string.find(sPath, ".nfa") ~= #sPath-3 and - string.find(sPath, ".nft") ~= #sPath-3 then - print("Can only edit .nfp, .nft and .nfa files:",string.find(sPath, ".nfp"),#sPath-3) - return - end - - if string.find(sPath, ".nfa") == #sPath-3 then - animated = true - end - - if string.find(sPath, ".nft") == #sPath-3 then - textEnabled = true - end - - if string.find(sPath, ".nfp") == #sPath-3 and animated then - print("Convert to nfa? Y/N") - if string.find(string.lower(io.read()), "y") then - local nsPath = string.sub(sPath, 1, #sPath-1).."a" - fs.move(sPath, nsPath) - sPath = nsPath - else - animated = false - end - end - - --Again this is possible, I just haven't done it. Maybe I will? - if textEnabled and (string.find(sPath, ".nfp") == #sPath-3 or string.find(sPath, ".nfa") == #sPath-3) then - print("Cannot convert to nft") - end -else - if not animated and not textEnabled and string.find(sPath, ".nfp") ~= #sPath-3 then - sPath = sPath..".nfp" - elseif animated and string.find(sPath, ".nfa") ~= #sPath-3 then - sPath = sPath..".nfa" - elseif textEnabled and string.find(sPath, ".nft") ~= #sPath-3 then - sPath = sPath..".nft" - end -end - -init() -handleEvents() - -term.setBackgroundColour(colours.black) -shell.run("clear") |
