2017-05-01 13:32:39 +00:00
|
|
|
-- Paint created by nitrogenfingers (edited by dan200)
|
|
|
|
-- http://www.youtube.com/user/NitrogenFingers
|
|
|
|
|
|
|
|
------------
|
|
|
|
-- Fields --
|
|
|
|
------------
|
|
|
|
|
|
|
|
-- The width and height of the terminal
|
2019-12-07 10:33:47 +00:00
|
|
|
local w, h = term.getSize()
|
2017-05-01 13:32:39 +00:00
|
|
|
|
|
|
|
-- 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
|
2019-12-07 10:33:47 +00:00
|
|
|
local mChoices = { "Save", "Exit" }
|
2017-05-01 13:32:39 +00:00
|
|
|
|
|
|
|
-- The message displayed in the footer bar
|
|
|
|
local fMessage = "Press Ctrl to access menu"
|
|
|
|
|
|
|
|
-------------------------
|
|
|
|
-- Initialisation --
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
-- Determine if we can even run this
|
|
|
|
if not term.isColour() then
|
2017-05-17 18:32:21 +00:00
|
|
|
print("Requires an Advanced Computer")
|
|
|
|
return
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Determines if the file exists, and can be edited on this computer
|
|
|
|
local tArgs = {...}
|
|
|
|
if #tArgs == 0 then
|
2017-05-17 18:32:21 +00:00
|
|
|
print("Usage: paint <path>")
|
|
|
|
return
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
local sPath = shell.resolve(tArgs[1])
|
|
|
|
local bReadOnly = fs.isReadOnly(sPath)
|
|
|
|
if fs.exists(sPath) and fs.isDir(sPath) then
|
2017-05-17 18:32:21 +00:00
|
|
|
print("Cannot edit a directory.")
|
|
|
|
return
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2017-05-29 13:05:19 +00:00
|
|
|
-- Create .nfp files by default
|
|
|
|
if not fs.exists( sPath ) and not string.find( sPath, "%." ) then
|
|
|
|
local sExtension = settings.get("paint.default_extension", "" )
|
2017-11-02 19:14:34 +00:00
|
|
|
if sExtension ~= "" and type( sExtension ) == "string" then
|
2017-05-29 13:05:19 +00:00
|
|
|
sPath = sPath .. "." .. sExtension
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
---------------
|
|
|
|
-- Functions --
|
|
|
|
---------------
|
|
|
|
|
|
|
|
local function getCanvasPixel( x, y )
|
|
|
|
if canvas[y] then
|
|
|
|
return canvas[y][x]
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
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
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function getCharOf( colour )
|
2017-05-17 18:32:21 +00:00
|
|
|
-- 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 " "
|
2018-12-17 17:22:15 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
|
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
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
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local tColourLookup = {}
|
2019-12-07 10:33:47 +00:00
|
|
|
for n = 1, 16 do
|
|
|
|
tColourLookup[ string.byte( "0123456789abcdef", n, n ) ] = 2 ^ (n - 1)
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
local function getColourOf( char )
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Values not in the hex table are transparent (canvas coloured)
|
|
|
|
return tColourLookup[char]
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Loads the file into the canvas
|
|
|
|
params: path = the path of the file to open
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function load(path)
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Load the file
|
|
|
|
if fs.exists(path) then
|
|
|
|
local file = fs.open(sPath, "r")
|
|
|
|
local sLine = file.readLine()
|
|
|
|
while sLine do
|
|
|
|
local line = {}
|
2019-12-07 10:33:47 +00:00
|
|
|
for x = 1, w - 2 do
|
|
|
|
line[x] = getColourOf( string.byte(sLine, x, x) )
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
|
|
|
table.insert( canvas, line )
|
|
|
|
sLine = file.readLine()
|
|
|
|
end
|
|
|
|
file.close()
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
|
|
|
Saves the current canvas to file
|
2017-05-17 18:32:21 +00:00
|
|
|
params: path = the path of the file to save
|
|
|
|
returns: true if save was successful, false otherwise
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function save(path)
|
|
|
|
-- Open file
|
2017-05-17 18:32:21 +00:00
|
|
|
local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
|
|
|
|
if not fs.exists(sDir) then
|
|
|
|
fs.makeDir(sDir)
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2017-07-28 13:36:33 +00:00
|
|
|
local file, err = fs.open( path, "w" )
|
2017-05-17 18:32:21 +00:00
|
|
|
if not file then
|
2017-07-28 13:36:33 +00:00
|
|
|
return false, err
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
|
|
|
|
-- Encode (and trim)
|
2017-05-17 18:32:21 +00:00
|
|
|
local tLines = {}
|
|
|
|
local nLastLine = 0
|
2019-12-07 10:33:47 +00:00
|
|
|
for y = 1, h - 1 do
|
2017-05-17 18:32:21 +00:00
|
|
|
local sLine = ""
|
|
|
|
local nLastChar = 0
|
2019-12-07 10:33:47 +00:00
|
|
|
for x = 1, w - 2 do
|
2017-05-17 18:32:21 +00:00
|
|
|
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 string.len( sLine ) > 0 then
|
|
|
|
nLastLine = y
|
|
|
|
end
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
|
|
|
|
-- Save out
|
2019-12-07 10:33:47 +00:00
|
|
|
for n = 1, nLastLine do
|
2017-05-17 18:32:21 +00:00
|
|
|
file.writeLine( tLines[ n ] )
|
|
|
|
end
|
|
|
|
file.close()
|
|
|
|
return true
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Draws colour picker sidebar, the pallette and the footer
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function drawInterface()
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Footer
|
|
|
|
term.setCursorPos(1, h)
|
|
|
|
term.setBackgroundColour(colours.black)
|
|
|
|
term.setTextColour(colours.yellow)
|
|
|
|
term.clearLine()
|
|
|
|
term.write(fMessage)
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Colour Picker
|
2019-12-07 10:33:47 +00:00
|
|
|
for i = 1, 16 do
|
|
|
|
term.setCursorPos(w - 1, i)
|
|
|
|
term.setBackgroundColour( 2 ^ (i - 1) )
|
2017-05-17 18:32:21 +00:00
|
|
|
term.write(" ")
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2019-12-07 10:33:47 +00:00
|
|
|
term.setCursorPos(w - 1, 17)
|
2017-05-17 18:32:21 +00:00
|
|
|
term.setBackgroundColour( canvasColour )
|
|
|
|
term.setTextColour( colours.grey )
|
|
|
|
term.write("\127\127")
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Left and Right Selected Colours
|
2019-12-03 23:26:13 +00:00
|
|
|
do
|
2019-12-07 10:33:47 +00:00
|
|
|
term.setCursorPos(w - 1, 18)
|
2017-05-17 18:32:21 +00:00
|
|
|
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
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Padding
|
|
|
|
term.setBackgroundColour( canvasColour )
|
2019-12-07 10:33:47 +00:00
|
|
|
for i = 20, h - 1 do
|
|
|
|
term.setCursorPos(w - 1, i)
|
2017-05-17 18:32:21 +00:00
|
|
|
term.write(" ")
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Converts a single pixel of a single line of the canvas and draws it
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function drawCanvasPixel( x, y )
|
2017-05-17 18:32:21 +00:00
|
|
|
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)
|
2017-05-01 13:32:39 +00:00
|
|
|
term.write("\127")
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Converts each colour in a single line of the canvas and draws it
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function drawCanvasLine( y )
|
2019-12-07 10:33:47 +00:00
|
|
|
for x = 1, w - 2 do
|
2017-05-17 18:32:21 +00:00
|
|
|
drawCanvasPixel( x, y )
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Converts each colour in the canvas and draws it
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function drawCanvas()
|
2019-12-07 10:33:47 +00:00
|
|
|
for y = 1, h - 1 do
|
2017-05-17 18:32:21 +00:00
|
|
|
drawCanvasLine( y )
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Draws menu options and handles input from within the menu.
|
|
|
|
returns: true if the program is to be exited; false otherwise
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function accessMenu()
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Selected menu option
|
|
|
|
local selection = 1
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
term.setBackgroundColour(colours.black)
|
|
|
|
while true do
|
|
|
|
-- Draw the menu
|
2019-12-07 10:33:47 +00:00
|
|
|
term.setCursorPos(1, h)
|
2017-05-17 18:32:21 +00:00
|
|
|
term.clearLine()
|
|
|
|
term.setTextColour(colours.white)
|
2019-12-07 10:33:47 +00:00
|
|
|
for k, v in pairs(mChoices) do
|
|
|
|
if selection == k then
|
2017-05-17 18:32:21 +00:00
|
|
|
term.setTextColour(colours.yellow)
|
2019-12-03 23:26:13 +00:00
|
|
|
local ox = term.getCursorPos()
|
2019-12-07 10:33:47 +00:00
|
|
|
term.write("[" .. string.rep(" ", #v) .. "]")
|
|
|
|
term.setCursorPos(ox + 1, h)
|
2017-05-17 18:32:21 +00:00
|
|
|
term.setTextColour(colours.white)
|
|
|
|
term.write(v)
|
2019-12-07 10:33:47 +00:00
|
|
|
term.setCursorPos(term.getCursorPos() + 1, h)
|
2017-05-17 18:32:21 +00:00
|
|
|
else
|
2019-12-07 10:33:47 +00:00
|
|
|
term.write(" " .. v .. " ")
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Handle input in the menu
|
2019-12-07 10:33:47 +00:00
|
|
|
local id, key = os.pullEvent("key")
|
2017-05-17 18:32:21 +00:00
|
|
|
if id == "key" then
|
|
|
|
-- S and E are shortcuts
|
|
|
|
if key == keys.s then
|
|
|
|
selection = 1
|
|
|
|
key = keys.enter
|
|
|
|
elseif key == keys.e then
|
|
|
|
selection = 2
|
|
|
|
key = keys.enter
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
if key == keys.right then
|
|
|
|
-- Move right
|
|
|
|
selection = selection + 1
|
|
|
|
if selection > #mChoices then
|
|
|
|
selection = 1
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
elseif key == keys.left and selection > 1 then
|
|
|
|
-- Move left
|
|
|
|
selection = selection - 1
|
|
|
|
if selection < 1 then
|
|
|
|
selection = #mChoices
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
elseif key == keys.enter then
|
|
|
|
-- Select an option
|
2019-12-07 10:33:47 +00:00
|
|
|
if mChoices[selection] == "Save" then
|
2018-12-17 17:22:15 +00:00
|
|
|
if bReadOnly then
|
2017-06-13 18:33:42 +00:00
|
|
|
fMessage = "Access denied"
|
2017-05-17 18:32:21 +00:00
|
|
|
return false
|
|
|
|
end
|
2017-07-28 13:36:33 +00:00
|
|
|
local success, err = save(sPath)
|
2017-05-17 18:32:21 +00:00
|
|
|
if success then
|
2019-12-07 10:33:47 +00:00
|
|
|
fMessage = "Saved to " .. sPath
|
2017-05-17 18:32:21 +00:00
|
|
|
else
|
2017-07-28 13:36:33 +00:00
|
|
|
if err then
|
2019-12-07 10:33:47 +00:00
|
|
|
fMessage = "Error saving to " .. err
|
2017-07-28 13:36:33 +00:00
|
|
|
else
|
2019-12-07 10:33:47 +00:00
|
|
|
fMessage = "Error saving to " .. sPath
|
2017-07-28 13:36:33 +00:00
|
|
|
end
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
|
|
|
return false
|
2019-12-07 10:33:47 +00:00
|
|
|
elseif mChoices[selection] == "Exit" then
|
2017-05-17 18:32:21 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
elseif key == keys.leftCtrl or keys == keys.rightCtrl then
|
|
|
|
-- Cancel the menu
|
2018-12-17 17:22:15 +00:00
|
|
|
return false
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
--[[
|
2017-05-17 18:32:21 +00:00
|
|
|
Runs the main thread of execution. Draws the canvas and interface, and handles
|
|
|
|
mouse and key events.
|
|
|
|
returns: nil
|
2017-05-01 13:32:39 +00:00
|
|
|
]]
|
|
|
|
local function handleEvents()
|
2017-05-17 18:32:21 +00:00
|
|
|
local programActive = true
|
|
|
|
while programActive do
|
2019-12-07 10:33:47 +00:00
|
|
|
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
|
2017-05-17 18:32:21 +00:00
|
|
|
if id ~= "mouse_drag" then
|
|
|
|
-- Selecting an items in the colour picker
|
|
|
|
if p3 <= 16 then
|
2019-12-07 10:33:47 +00:00
|
|
|
if p1 == 1 then
|
|
|
|
leftColour = 2 ^ (p3 - 1)
|
2017-05-17 18:32:21 +00:00
|
|
|
else
|
2019-12-07 10:33:47 +00:00
|
|
|
rightColour = 2 ^ (p3 - 1)
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
|
|
|
else
|
2019-12-07 10:33:47 +00:00
|
|
|
if p1 == 1 then
|
2017-05-17 18:32:21 +00:00
|
|
|
leftColour = nil
|
|
|
|
else
|
|
|
|
rightColour = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
--drawCanvas()
|
|
|
|
drawInterface()
|
|
|
|
end
|
2019-12-07 10:33:47 +00:00
|
|
|
elseif p2 < w - 1 and p3 <= h - 1 then
|
2017-05-17 18:32:21 +00:00
|
|
|
-- Clicking on the canvas
|
|
|
|
local paintColour = nil
|
2019-12-07 10:33:47 +00:00
|
|
|
if p1 == 1 then
|
2017-05-17 18:32:21 +00:00
|
|
|
paintColour = leftColour
|
2019-12-07 10:33:47 +00:00
|
|
|
elseif p1 == 2 then
|
2017-05-17 18:32:21 +00:00
|
|
|
paintColour = rightColour
|
|
|
|
end
|
|
|
|
if not canvas[p3] then
|
2017-05-01 13:32:39 +00:00
|
|
|
canvas[p3] = {}
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
canvas[p3][p2] = paintColour
|
|
|
|
|
2017-05-17 18:32:21 +00:00
|
|
|
drawCanvasPixel( p2, p3 )
|
|
|
|
end
|
2019-12-07 10:33:47 +00:00
|
|
|
elseif id == "key" then
|
|
|
|
if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
|
2017-05-17 18:32:21 +00:00
|
|
|
programActive = not accessMenu()
|
|
|
|
drawInterface()
|
|
|
|
end
|
2019-12-07 10:33:47 +00:00
|
|
|
elseif id == "term_resize" then
|
|
|
|
w, h = term.getSize()
|
2017-05-01 13:32:39 +00:00
|
|
|
drawCanvas()
|
|
|
|
drawInterface()
|
|
|
|
end
|
2017-05-17 18:32:21 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Init
|
|
|
|
load(sPath)
|
|
|
|
drawCanvas()
|
|
|
|
drawInterface()
|
|
|
|
|
|
|
|
-- Main loop
|
|
|
|
handleEvents()
|
|
|
|
|
|
|
|
-- Shutdown
|
|
|
|
term.setBackgroundColour(colours.black)
|
|
|
|
term.setTextColour(colours.white)
|
|
|
|
term.clear()
|
2019-12-07 10:33:47 +00:00
|
|
|
term.setCursorPos(1, 1)
|