-- Paint created by nitrogenfingers (edited by dan200) -- http://www.youtube.com/user/NitrogenFingers ------------ -- Fields -- ------------ -- The width and height of the terminal local w, h = term.getSize() -- The selected colours on the left and right mouse button, and the colour of the canvas local leftColour, rightColour = colours.white, nil local canvasColour = colours.black -- The values stored in the canvas local canvas = {} -- The menu options local mChoices = { "Save", "Exit" } -- The message displayed in the footer bar local fMessage = "Press Ctrl or click here to access menu" ------------------------- -- Initialisation -- ------------------------- -- Determine if we can even run this if not term.isColour() then print("Requires an Advanced Computer") return end -- Determines if the file exists, and can be edited on this computer local tArgs = { ... } if #tArgs == 0 then local programName = arg[0] or fs.getName(shell.getRunningProgram()) print("Usage: " .. programName .. " ") return end local sPath = shell.resolve(tArgs[1]) local bReadOnly = fs.isReadOnly(sPath) if fs.exists(sPath) and fs.isDir(sPath) then print("Cannot edit a directory.") return end -- Create .nfp files by default if not fs.exists(sPath) and not string.find(sPath, "%.") then local sExtension = settings.get("paint.default_extension") if sExtension ~= "" and type(sExtension) == "string" then sPath = sPath .. "." .. sExtension end end --------------- -- Functions -- --------------- local function getCanvasPixel(x, y) if canvas[y] then return canvas[y][x] end return nil end --[[ Converts a colour value to a text character params: colour = the number to convert to a hex value returns: a string representing the chosen colour ]] local function getCharOf(colour) -- Incorrect values always convert to nil if type(colour) == "number" then local value = math.floor(math.log(colour) / math.log(2)) + 1 if value >= 1 and value <= 16 then return string.sub("0123456789abcdef", value, value) end end return " " end --[[ Converts a text character to colour value params: char = the char (from string.byte) to convert to number returns: the colour number of the hex value ]] local tColourLookup = {} for n = 1, 16 do tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) end local function getColourOf(char) -- Values not in the hex table are transparent (canvas coloured) return tColourLookup[char] end --[[ Loads the file into the canvas params: path = the path of the file to open returns: nil ]] local function load(path) -- Load the file if fs.exists(path) then local file = fs.open(sPath, "r") local sLine = file.readLine() while sLine do local line = {} for x = 1, w - 2 do line[x] = getColourOf(string.byte(sLine, x, x)) end table.insert(canvas, line) sLine = file.readLine() end file.close() end end --[[ Saves the current canvas to file params: path = the path of the file to save returns: true if save was successful, false otherwise ]] local function save(path) -- Open file local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath)) if not fs.exists(sDir) then fs.makeDir(sDir) end local file, err = fs.open(path, "w") if not file then return false, err end -- Encode (and trim) local tLines = {} local nLastLine = 0 for y = 1, h - 1 do local sLine = "" local nLastChar = 0 for x = 1, w - 2 do local c = getCharOf(getCanvasPixel(x, y)) sLine = sLine .. c if c ~= " " then nLastChar = x end end sLine = string.sub(sLine, 1, nLastChar) tLines[y] = sLine if #sLine > 0 then nLastLine = y end end -- Save out for n = 1, nLastLine do file.writeLine(tLines[n]) end file.close() return true end --[[ Draws colour picker sidebar, the pallette and the footer returns: nil ]] local function drawInterface() -- Footer term.setCursorPos(1, h) term.setBackgroundColour(colours.black) term.setTextColour(colours.yellow) term.clearLine() term.write(fMessage) -- Colour Picker for i = 1, 16 do term.setCursorPos(w - 1, i) term.setBackgroundColour(2 ^ (i - 1)) term.write(" ") end term.setCursorPos(w - 1, 17) term.setBackgroundColour(canvasColour) term.setTextColour(colours.grey) term.write("\127\127") -- Left and Right Selected Colours do term.setCursorPos(w - 1, 18) if leftColour ~= nil then term.setBackgroundColour(leftColour) term.write(" ") else term.setBackgroundColour(canvasColour) term.setTextColour(colours.grey) term.write("\127") end if rightColour ~= nil then term.setBackgroundColour(rightColour) term.write(" ") else term.setBackgroundColour(canvasColour) term.setTextColour(colours.grey) term.write("\127") end end -- Padding term.setBackgroundColour(canvasColour) for i = 20, h - 1 do term.setCursorPos(w - 1, i) term.write(" ") end end --[[ Converts a single pixel of a single line of the canvas and draws it returns: nil ]] local function drawCanvasPixel(x, y) local pixel = getCanvasPixel(x, y) if pixel then term.setBackgroundColour(pixel or canvasColour) term.setCursorPos(x, y) term.write(" ") else term.setBackgroundColour(canvasColour) term.setTextColour(colours.grey) term.setCursorPos(x, y) term.write("\127") end end local color_hex_lookup = {} for i = 0, 15 do color_hex_lookup[2 ^ i] = string.format("%x", i) end --[[ Converts each colour in a single line of the canvas and draws it returns: nil ]] local function drawCanvasLine(y) local text, fg, bg = "", "", "" for x = 1, w - 2 do local pixel = getCanvasPixel(x, y) if pixel then text = text .. " " fg = fg .. "0" bg = bg .. color_hex_lookup[pixel or canvasColour] else text = text .. "\127" fg = fg .. color_hex_lookup[colours.grey] bg = bg .. color_hex_lookup[canvasColour] end end term.setCursorPos(1, y) term.blit(text, fg, bg) end --[[ Converts each colour in the canvas and draws it returns: nil ]] local function drawCanvas() for y = 1, h - 1 do drawCanvasLine(y) end end local menu_choices = { Save = function() if bReadOnly then fMessage = "Access denied" return false end local success, err = save(sPath) if success then fMessage = "Saved to " .. sPath else if err then fMessage = "Error saving to " .. err else fMessage = "Error saving to " .. sPath end end return false end, Exit = function() return true end, } --[[ Draws menu options and handles input from within the menu. returns: true if the program is to be exited; false otherwise ]] local function accessMenu() -- Selected menu option local selection = 1 term.setBackgroundColour(colours.black) while true do -- Draw the menu term.setCursorPos(1, h) term.clearLine() term.setTextColour(colours.white) for k, v in pairs(mChoices) do if selection == k then term.setTextColour(colours.yellow) term.write("[") term.setTextColour(colours.white) term.write(v) term.setTextColour(colours.yellow) term.write("]") term.setTextColour(colours.white) else term.write(" " .. v .. " ") end end -- Handle input in the menu local id, param1, param2, param3 = os.pullEvent() if id == "key" then local key = param1 -- Handle menu shortcuts. for _, menu_item in ipairs(mChoices) do local k = keys[menu_item:sub(1, 1):lower()] if k and k == key then return menu_choices[menu_item]() end end if key == keys.right then -- Move right selection = selection + 1 if selection > #mChoices then selection = 1 end elseif key == keys.left and selection > 1 then -- Move left selection = selection - 1 if selection < 1 then selection = #mChoices end elseif key == keys.enter then -- Select an option return menu_choices[mChoices[selection]]() elseif key == keys.leftCtrl or keys == keys.rightCtrl then -- Cancel the menu return false end elseif id == "mouse_click" then local cx, cy = param2, param3 if cy ~= h then return false end -- Exit the menu local nMenuPosEnd = 1 local nMenuPosStart = 1 for _, sMenuItem in ipairs(mChoices) do nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1 if cx > nMenuPosStart and cx < nMenuPosEnd then return menu_choices[sMenuItem]() end nMenuPosEnd = nMenuPosEnd + 1 nMenuPosStart = nMenuPosEnd end end end end --[[ Runs the main thread of execution. Draws the canvas and interface, and handles mouse and key events. returns: nil ]] local function handleEvents() local programActive = true while programActive do local id, p1, p2, p3 = os.pullEvent() if id == "mouse_click" or id == "mouse_drag" then if p2 >= w - 1 and p3 >= 1 and p3 <= 17 then if id ~= "mouse_drag" then -- Selecting an items in the colour picker if p3 <= 16 then if p1 == 1 then leftColour = 2 ^ (p3 - 1) else rightColour = 2 ^ (p3 - 1) end else if p1 == 1 then leftColour = nil else rightColour = nil end end --drawCanvas() drawInterface() end elseif p2 < w - 1 and p3 <= h - 1 then -- Clicking on the canvas local paintColour = nil if p1 == 1 then paintColour = leftColour elseif p1 == 2 then paintColour = rightColour end if not canvas[p3] then canvas[p3] = {} end canvas[p3][p2] = paintColour drawCanvasPixel(p2, p3) elseif p3 == h and id == "mouse_click" then -- Open menu programActive = not accessMenu() drawInterface() end elseif id == "key" then if p1 == keys.leftCtrl or p1 == keys.rightCtrl then programActive = not accessMenu() drawInterface() end elseif id == "term_resize" then w, h = term.getSize() drawCanvas() drawInterface() end end end -- Init load(sPath) drawCanvas() drawInterface() -- Main loop handleEvents() -- Shutdown term.setBackgroundColour(colours.black) term.setTextColour(colours.white) term.clear() term.setCursorPos(1, 1)