452 lines
12 KiB
Text
452 lines
12 KiB
Text
-- 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 .. " <path>")
|
|
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()
|
|
sleep(0) -- Super janky, but consumes stray "char" events from pressing Ctrl then E separately.
|
|
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 or key == keys.numPadEnter 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)
|