Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- 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")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement