mirror of
https://github.com/LDDestroier/CC/
synced 2024-12-15 04:30:28 +00:00
3485 lines
95 KiB
Lua
3485 lines
95 KiB
Lua
--[[
|
||
PAIN image editor for ComputerCraft
|
||
Get it with
|
||
wget https://raw.githubusercontent.com/LDDestroier/CC/master/pain.lua pain
|
||
pastebin get wJQ7jav0 pain
|
||
std ld pain pain
|
||
--]]
|
||
local askToSerialize = false
|
||
local defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name
|
||
--[[
|
||
defaultSaveFormat possible parameters:
|
||
1. NFP (paint)
|
||
2. NFT (npaintpro)
|
||
3. BLT (blittle)
|
||
4. Native PAIN
|
||
5. GIF
|
||
6. UCG
|
||
--]]
|
||
|
||
local progname = fs.getName(shell.getRunningProgram())
|
||
local apipath = ".painapi"
|
||
|
||
local painconfig = {
|
||
undoBufferSize = 8, -- amount of times undo will save your neck
|
||
readNonImageAsNFP = true, -- reads non-image files as NFP images
|
||
useFlattenGIF = true, -- will flatten compressed GIFs
|
||
gridBleedThrough = false, -- will draw grid instead of character value of dots
|
||
doFillDiagonal = false, -- checks for diagonal dots when using fill tool
|
||
doFillAnimation = false, -- whether or not to animate the fill tool
|
||
}
|
||
|
||
local saveConfig = function()
|
||
local file = fs.open(fs.combine(apipath,"painconfig"), "w")
|
||
file.write(textutils.serialize(painconfig))
|
||
file.close()
|
||
end
|
||
|
||
local loadConfig = function()
|
||
if fs.exists(fs.combine(apipath,"painconfig")) then
|
||
local file = fs.open(fs.combine(apipath,"painconfig"), "r")
|
||
painconfig = textutils.unserialize(file.readAll())
|
||
file.close()
|
||
end
|
||
end
|
||
|
||
loadConfig()
|
||
saveConfig()
|
||
|
||
local displayHelp = function()
|
||
print(progname)
|
||
print(progname.." <filename>")
|
||
print(progname.." [-h/--help]")
|
||
print("Press F1 in program for more.")
|
||
end
|
||
|
||
local tsv = term.current().setVisible
|
||
local undoBuffer
|
||
local undoPos = 1
|
||
local pMode = 0
|
||
local scr_x, scr_y = term.getSize()
|
||
screenEdges = {
|
||
scr_x,
|
||
scr_y,
|
||
}
|
||
|
||
local tArg = {...}
|
||
if (tArg[1] == "--help" or tArg[1] == "-h") and shell then
|
||
return displayHelp()
|
||
end
|
||
|
||
if tArg[2] == "view" then
|
||
pMode = 1
|
||
elseif (tArg[2] == "moo") and (not fs.exists("moo")) then
|
||
return print("This PAIN does not have Super Cow Powers.")
|
||
end
|
||
|
||
local fileName
|
||
if (not term.isColor()) and (pMode ~= 1) then
|
||
error("PAIN only works with Advanced Computers at the moment.")
|
||
end
|
||
local barmsg = "Press F1 for help."
|
||
local tse = textutils.serialise
|
||
local tun = textutils.unserialise
|
||
local paintEncoded
|
||
local lastPaintEncoded
|
||
local frame = 1
|
||
local doRender = false
|
||
local metaHistory = {}
|
||
local bepimode = false -- this is a family-friendly program! now stand still while I murder you
|
||
local evenDrawGrid = true -- will you evenDraw(the)Grid ?
|
||
local renderBlittle = false -- whether or not to render all in blittle
|
||
local firstTerm, blittleTerm = term.current()
|
||
local firstBG = term.getBackgroundColor()
|
||
local firstTX = term.getTextColor()
|
||
local changedImage = false
|
||
local isCurrentlyFilling = false
|
||
local theClipboard = {}
|
||
|
||
local _
|
||
local tableconcat = table.concat
|
||
|
||
local rendback = {
|
||
b = colors.black,
|
||
t = colors.gray,
|
||
}
|
||
|
||
local grid
|
||
|
||
local yield = function()
|
||
os.queueEvent("yield")
|
||
os.pullEvent("yield")
|
||
end
|
||
|
||
local paint = {
|
||
scrollX = 0,
|
||
scrollY = 0,
|
||
t = colors.gray,
|
||
b = colors.white,
|
||
m = 1, -- in case you want to use PAIN as a level editor or something
|
||
c = " ",
|
||
doGray = false,
|
||
}
|
||
local boxchar = {topLeft = true, topRight = true, left = true, right = true, bottomLeft = true, bottomRight = true}
|
||
local swapColors = false -- swaps background and text colors, for use with those tricky box characters
|
||
local scrollX, scrollY = 0, 0
|
||
|
||
local keysDown = {}
|
||
local miceDown = {}
|
||
|
||
local doRenderBar = 1 -- Not true or false
|
||
|
||
local fixstr = function(str)
|
||
return str:gsub("\\(%d%d%d)",string.char)
|
||
end
|
||
|
||
local choice = function(input,breakkeys,returnNumber)
|
||
local fpos = 0
|
||
repeat
|
||
event, key = os.pullEvent("key")
|
||
if type(key) == "number" then key = keys.getName(key) end
|
||
if key == nil then key = " " end
|
||
if type(breakkeys) == "table" then
|
||
for a = 1, #breakkeys do
|
||
if key == breakkeys[a] then
|
||
return ""
|
||
end
|
||
end
|
||
end
|
||
fpos = string.find(input, key)
|
||
until fpos
|
||
return returnNumber and fpos or key
|
||
end
|
||
local explode = function(div,str)
|
||
if (div=='') then return false end
|
||
local pos,arr = 0,{}
|
||
for st,sp in function() return string.find(str,div,pos,true) end do
|
||
arr[#arr+1] = str:sub(pos,st-1)
|
||
pos = sp + 1
|
||
end
|
||
arr[#arr+1] = str:sub(pos)
|
||
return arr
|
||
end
|
||
|
||
local cutString = function(max_line_length, str) -- from stack overflow
|
||
local lines = {}
|
||
local line
|
||
str:gsub('(%s*)(%S+)',
|
||
function(spc, word)
|
||
if not line or #line + #spc + #word > max_line_length then
|
||
lines[#lines+1] = line
|
||
line = word
|
||
else
|
||
line = line..spc..word
|
||
end
|
||
end
|
||
)
|
||
lines[#lines+1] = line
|
||
return lines
|
||
end
|
||
|
||
local getDrawingCharacter = function(topLeft, topRight, left, right, bottomLeft, bottomRight) -- thank you oli414
|
||
local data = 128
|
||
if not bottomRight then
|
||
data = data + (topLeft and 1 or 0)
|
||
data = data + (topRight and 2 or 0)
|
||
data = data + (left and 4 or 0)
|
||
data = data + (right and 8 or 0)
|
||
data = data + (bottomLeft and 16 or 0)
|
||
else
|
||
data = data + (topLeft and 0 or 1)
|
||
data = data + (topRight and 0 or 2)
|
||
data = data + (left and 0 or 4)
|
||
data = data + (right and 0 or 8)
|
||
data = data + (bottomLeft and 0 or 16)
|
||
end
|
||
return {char = string.char(data), inverted = bottomRight}
|
||
end
|
||
|
||
local cutUp = function(len,tbl)
|
||
local output = {}
|
||
local e = 0
|
||
local s
|
||
for a = 1, #tbl do
|
||
if #(tbl[a]:gsub(" ","")) == 0 then
|
||
s = {""}
|
||
else
|
||
s = cutString(len,tbl[a])
|
||
end
|
||
for b = 1, #s do
|
||
output[#output+1] = s[b]
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local getEvents = function(...)
|
||
local arg, output = table.pack(...)
|
||
while true do
|
||
output = {os.pullEvent()}
|
||
for a = 1, #arg do
|
||
if type(arg[a]) == "boolean" then
|
||
if doRender == arg[a] then
|
||
return {}
|
||
end
|
||
elseif output[1] == arg[a] then
|
||
return unpack(output)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
|
||
|
||
local sanitize = function(sani,tize)
|
||
local _,x = string.find(sani,tize)
|
||
if x then
|
||
return sani:sub(x+1)
|
||
else
|
||
return sani
|
||
end
|
||
end
|
||
local ro = function(input, max)
|
||
return math.floor(input % max)
|
||
end
|
||
|
||
local guiHelp = function(inputText)
|
||
term.redirect(firstTerm)
|
||
scr_x, scr_y = term.current().getSize()
|
||
local _helpText = inputText or [[
|
||
|
||
'PAIN' super-verbose help page
|
||
Programmed by LDDestroier
|
||
|
||
(use UP/DOWN or scrollwheel, exit with Q)
|
||
If you wish to use PAIN to its fullest, read everything here.
|
||
You'll be image-editing like a pro in no time flat.
|
||
|
||
Syntax:
|
||
>pain <filename> [view] [x] [y]
|
||
>pain [-n]
|
||
>pain [-h/--help]
|
||
|
||
[view]: renders the image once (optionally scrolling with [x] and [y])
|
||
"-n" or no arguments: Create new document, declare name upon saving
|
||
"-h" or "--help": Display short syntax help
|
||
|
||
You can see what colors are selected based on the word "PAIN" on the hotbar.
|
||
|
||
Hotkeys:
|
||
left/right ctrl: Toggle the menu
|
||
|
||
left click:
|
||
+left shift = Drag and let go to draw a line
|
||
-alone = Place a dot
|
||
|
||
Right Click: delete pixel
|
||
|
||
Middle Click, or "T": Place text down with current colors; cancel with X
|
||
|
||
"Z":
|
||
+LeftAlt = Redo
|
||
-alone = Undo
|
||
|
||
"P": Pick colors from position onscreen; cancel with X
|
||
|
||
"N":
|
||
+LeftShift = Change character to that of a special character
|
||
-alone = Change box character for drawing
|
||
(cancel with CTRL, N, or by clicking outside)
|
||
|
||
"[" or mouse scroll down:
|
||
+LeftShift = Change to previous text color
|
||
-alone = Change to previous background color
|
||
|
||
"]" or mouse scroll up:
|
||
+LeftShift = Change to next text color
|
||
-alone = Change to next background color
|
||
|
||
"F1":
|
||
-alone = Access help screen
|
||
|
||
"F3:"
|
||
-alone = View all connected monitors
|
||
|
||
Spacebar:
|
||
+LeftShift = Toggle background grid
|
||
-alone = Toggle bar visibility
|
||
|
||
Arrow keys:
|
||
+LeftShift = Displaces the entire frame
|
||
+Tab = Moves canvas one pixel at a time
|
||
-alone = Looks around the canvas smoothly
|
||
|
||
"+" (or equals):
|
||
+LeftAlt = Swap the current frame with the next frame
|
||
+LeftShift = Merge the current frame atop the next frame
|
||
+RightShift = If you are making a new frame, duplicates the last frame
|
||
-alone = Change to next frame
|
||
|
||
"-":
|
||
+LeftAlt = Swap the current frame with the previous frame
|
||
+LeftShift = Merge the current frame atop the previous frame
|
||
-alone = Change to previous frame
|
||
|
||
(oh good, you're actually reading this stuff)
|
||
|
||
"A": Set the coordinates to 0,0
|
||
|
||
"N": Open block character selection
|
||
|
||
"B": Toggle redirect to blittle, to preview in teletext characters
|
||
|
||
"c":
|
||
+LeftAlt = Select region to copy to specified clipboard
|
||
-alone = Input coordinates to scroll over to
|
||
|
||
"LeftAlt + X": Select region to cut to specified clipboard
|
||
|
||
"LeftAlt + X": Pastes from specified clipboard
|
||
|
||
"G": toggle grayscale mode.
|
||
Everything is in shades of gray.
|
||
If you Save, it saves in grayscale.
|
||
|
||
"F":
|
||
+LeftShift = fill all empty pixels with background color and selected box character
|
||
-alone = activate fill tool - click anywhere to fill with color
|
||
|
||
"M": set metadata for pixels (for game makers, otherwise please ignore)
|
||
|
||
==================================
|
||
Thy Menu (accessible with CTRL):
|
||
==================================
|
||
|
||
Left click on a menu item to select it.
|
||
If you click on the menubar, release on an option to select it.
|
||
|
||
"File > Save"
|
||
Saves all frames to a specially formatted PAIN paint file. The format PAIN uses is very inefficient despite my best efforts, so Export if you don't use text or multiple frame.
|
||
|
||
"File > Save As"
|
||
Same as "File > Save", but you change the filename.
|
||
|
||
"File > Export"
|
||
Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format.
|
||
|
||
"File > Open"
|
||
Opens up a file picker for you to change the image currently being edited.
|
||
|
||
"Edit > Delete Frame"
|
||
Deletes the current frame. Tells you off if you try to delete the only frame.
|
||
|
||
"Edit > Clear"
|
||
Deletes all pixels on the current frame.
|
||
|
||
"Edit > Crop Frame"
|
||
Deletes all pixels that are outside of the screen.
|
||
|
||
"Edit > Change Box Character"
|
||
Opens the block character selection. Used for making those delicious subpixel pictures.
|
||
|
||
"Edit > Change Special Character"
|
||
Opens the special character selector, which lets you change the paint character to that of byte 0 to 255.
|
||
|
||
"Edit > BLittle Shrink"
|
||
Shrinks the current frame using the BLittle API. Very lossy, and unreversable without Undo.
|
||
|
||
"Edit > Copy"
|
||
Drag to select a region of the screen, and save it in a clipboard of a specified name.
|
||
|
||
"Edit > Cut"
|
||
Same as Copy, but deletes the selected region on the screen.
|
||
|
||
"Edit > Paste"
|
||
Takes the contents of the specified clipboard, and plops it on the canvas where the mouse is.
|
||
(The mouse will indicate the top-left corner of the pasted selection)
|
||
|
||
"Set > ..."
|
||
Each option will toggle a config option (or set it's value to something else).
|
||
Changing a value is saved automatically, and effective immediately.
|
||
|
||
"Window > Set Screen Size"
|
||
Sets the sizes of the screen border references displayed on the canvas.
|
||
|
||
"Window > Set Grid Colors"
|
||
Sets the backdrop colors to your currently selected color configuration.
|
||
|
||
"About > PAIN"
|
||
Tells you about PAIN and its developer.
|
||
|
||
"About > File Formats"
|
||
Tells you the ins and outs of the file formats, and a brief description of their creators.
|
||
|
||
"About > Help"
|
||
Opens up this help page.
|
||
|
||
"Exit"
|
||
Durr I dunno, I think it exits.
|
||
|
||
|
||
I hope my PAIN causes you joy.
|
||
]]
|
||
_helpText = explode("\n",_helpText)
|
||
helpText = cutUp(scr_x,_helpText)
|
||
local helpscroll = 0
|
||
term.setBackgroundColor(colors.gray)
|
||
term.setTextColor(colors.white)
|
||
term.clear()
|
||
local evt, key
|
||
while true do
|
||
term.clear()
|
||
for a = 1, scr_y do
|
||
term.setCursorPos(1,a)
|
||
term.clearLine()
|
||
write(helpText[a-helpscroll] or "")
|
||
end
|
||
repeat
|
||
evt,key = os.pullEvent()
|
||
until evt == "key" or evt == "mouse_scroll"
|
||
if evt == "key" then
|
||
if key == keys.up then
|
||
helpscroll = helpscroll + 1
|
||
elseif key == keys.down then
|
||
helpscroll = helpscroll - 1
|
||
elseif key == keys.pageUp then
|
||
helpscroll = helpscroll + scr_y
|
||
elseif key == keys.pageDown then
|
||
helpscroll = helpscroll - scr_y
|
||
elseif (key == keys.q) or (key == keys.space) then
|
||
doRender = true
|
||
if renderBlittle then term.redirect(blittleTerm) end
|
||
scr_x, scr_y = term.current().getSize()
|
||
return
|
||
end
|
||
elseif evt == "mouse_scroll" then
|
||
helpscroll = helpscroll - key
|
||
end
|
||
if helpscroll > 0 then
|
||
helpscroll = 0
|
||
elseif helpscroll < -(#helpText-(scr_y-3)) then
|
||
helpscroll = -(#helpText-(scr_y-3))
|
||
end
|
||
end
|
||
end
|
||
|
||
local tableRemfind = function(tbl, str)
|
||
local out = tbl
|
||
for a = 1, #tbl do
|
||
if tbl[a] == str then
|
||
table.remove(out,a)
|
||
return out,a
|
||
end
|
||
end
|
||
return {}
|
||
end
|
||
|
||
local stringShift = function(str,amt)
|
||
return str:sub(ro(amt-1,#str)+1)..str:sub(1,ro(amt-1,#str))
|
||
end
|
||
|
||
local deepCopy
|
||
deepCopy = function(obj)
|
||
if type(obj) ~= 'table' then return obj end
|
||
local res = {}
|
||
for k, v in pairs(obj) do res[deepCopy(k)] = deepCopy(v) end
|
||
return res
|
||
end
|
||
|
||
local clearLines = function(y1, y2)
|
||
local cx,cy = term.getCursorPos()
|
||
for y = y1, y2 do
|
||
term.setCursorPos(1,y)
|
||
term.clearLine()
|
||
end
|
||
term.setCursorPos(cx,cy)
|
||
end
|
||
|
||
local renderBottomBar = function(txt,extraClearY)
|
||
term.setCursorPos(1,scr_y - math.floor(#txt/scr_x))
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.setTextColor(colors.black)
|
||
clearLines(scr_y - (math.floor(#txt/scr_x) - (extraClearY or 0)), scr_y)
|
||
return write(txt)
|
||
end
|
||
|
||
local bottomPrompt = function(txt,history,cho,breakkeys,returnNumber,writeIndent)
|
||
local writeIndent = renderBottomBar(txt,writeIndent)
|
||
local out
|
||
sleep(0)
|
||
if cho then
|
||
out = choice(cho,breakkeys,returnNumber)
|
||
else
|
||
out = read(_,history)
|
||
end
|
||
return out, writeIndent
|
||
end
|
||
|
||
local makeSubMenu = function(x,y,options)
|
||
local longestLen = 0
|
||
for a = 1, #options do
|
||
if #options[a] > longestLen then
|
||
longestLen = #options[a]
|
||
end
|
||
end
|
||
longestLen = longestLen + 1
|
||
term.setTextColor(colors.black)
|
||
local sel = 1
|
||
local rend = function()
|
||
for a = #options, 1, -1 do
|
||
term.setCursorPos(x or 1, ((y or (scr_y-1)) - (#options-1)) + (a - 1))
|
||
term.setBackgroundColor(a == sel and colors.white or colors.lightGray)
|
||
term.write(options[a])
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.write((" "):rep(longestLen-#options[a]))
|
||
end
|
||
end
|
||
local usingMouse = false
|
||
while true do
|
||
rend()
|
||
local evt, key, mx, my = os.pullEvent()
|
||
if evt == "key" then
|
||
if key == keys.up then
|
||
sel = sel - 1
|
||
elseif key == keys.down then
|
||
sel = sel + 1
|
||
elseif (key == keys.enter) or (key == keys.right) then
|
||
return sel, longestLen
|
||
elseif (key == keys.leftCtrl) or (key == keys.rightCtrl) or (key == keys.backspace) or (key == keys.left) then
|
||
return false, longestLen
|
||
end
|
||
elseif evt == "mouse_drag" or evt == "mouse_click" then
|
||
if (mx >= x) and (mx < x+longestLen) and (my <= y and my > y-#options) then
|
||
sel = math.min(#options,math.max(1,(my+#options) - y))
|
||
usingMouse = true
|
||
else
|
||
usingMouse = false
|
||
if evt == "mouse_click" then
|
||
return false, longestLen
|
||
end
|
||
end
|
||
elseif evt == "mouse_up" then
|
||
if usingMouse then
|
||
return sel, longestLen
|
||
end
|
||
end
|
||
if sel > #options then sel = 1 elseif sel < 1 then sel = #options end
|
||
end
|
||
end
|
||
|
||
local getDotsInLine = function( startX, startY, endX, endY ) -- stolen from the paintutils API...nwehehehe
|
||
local out = {}
|
||
startX = math.floor(startX)
|
||
startY = math.floor(startY)
|
||
endX = math.floor(endX)
|
||
endY = math.floor(endY)
|
||
if startX == endX and startY == endY then
|
||
out = {{x=startX,y=startY}}
|
||
return out
|
||
end
|
||
local minX = math.min( startX, endX )
|
||
if minX == startX then
|
||
minY = startY
|
||
maxX = endX
|
||
maxY = endY
|
||
else
|
||
minY = endY
|
||
maxX = startX
|
||
maxY = startY
|
||
end
|
||
local xDiff = maxX - minX
|
||
local yDiff = maxY - minY
|
||
if xDiff > math.abs(yDiff) then
|
||
local y = minY
|
||
local dy = yDiff / xDiff
|
||
for x=minX,maxX do
|
||
out[#out+1] = {x=x,y=math.floor(y+0.5)}
|
||
y = y + dy
|
||
end
|
||
else
|
||
local x = minX
|
||
local dx = xDiff / yDiff
|
||
if maxY >= minY then
|
||
for y=minY,maxY do
|
||
out[#out+1] = {x=math.floor(x+0.5),y=y}
|
||
x = x + dx
|
||
end
|
||
else
|
||
for y=minY,maxY,-1 do
|
||
out[#out+1] = {x=math.floor(x+0.5),y=y}
|
||
x = x - dx
|
||
end
|
||
end
|
||
end
|
||
return out
|
||
end
|
||
|
||
local movePaintEncoded = function(pe,xdiff,ydiff)
|
||
local outpootis = deepCopy(pe)
|
||
for a = 1, #outpootis do
|
||
outpootis[a].x = outpootis[a].x+xdiff
|
||
outpootis[a].y = outpootis[a].y+ydiff
|
||
end
|
||
return outpootis
|
||
end
|
||
|
||
local clearRedundant = function(dots)
|
||
local input = {}
|
||
local pheight = 0
|
||
local pwidth = 0
|
||
local minX, minY = 0, 0
|
||
for a = 1, #dots do
|
||
pheight = math.max(pheight, dots[a].y)
|
||
pwidth = math.max(pwidth, dots[a].x)
|
||
minX = math.min(minX, dots[a].x)
|
||
minY = math.min(minY, dots[a].y)
|
||
end
|
||
for a = 1, #dots do
|
||
if not input[dots[a].y] then input[dots[a].y] = {} end
|
||
input[dots[a].y][dots[a].x] = dots[a]
|
||
end
|
||
local output = {}
|
||
local frame = 0
|
||
for y = minY, pheight do
|
||
for x = minX, pwidth do
|
||
if input[y] then
|
||
if input[y][x] then
|
||
output[#output+1] = input[y][x]
|
||
end
|
||
end
|
||
if frame >= 50 then
|
||
-- yield()
|
||
frame = 0
|
||
end
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local grayOut = function(color)
|
||
local c = deepCopy(_G.colors)
|
||
local grays = {
|
||
[c.white] = c.white,
|
||
[c.orange] = c.lightGray,
|
||
[c.magenta] = c.lightGray,
|
||
[c.lightBlue] = c.lightGray,
|
||
[c.yellow] = c.white,
|
||
[c.lime] = c.lightGray,
|
||
[c.pink] = c.lightGray,
|
||
[c.gray] = c.gray,
|
||
[c.lightGray] = c.lightGray,
|
||
[c.cyan] = c.lightGray,
|
||
[c.purple] = c.gray,
|
||
[c.blue] = c.gray,
|
||
[c.brown] = c.gray,
|
||
[c.green] = c.lightGray,
|
||
[c.red] = c.gray,
|
||
[c.black] = c.black,
|
||
}
|
||
if (not color) or (color == " ") then return color end
|
||
local newColor = grays[color] or 1
|
||
return newColor
|
||
end
|
||
|
||
local getOnscreenCoords = function(tbl,_x,_y)
|
||
local screenTbl = {}
|
||
for a = 1, #tbl do
|
||
if tbl[a].x+paint.scrollX > 0 and tbl[a].x+paint.scrollX <= scr_x then
|
||
if tbl[a].y+paint.scrollY > 0 and tbl[a].y+paint.scrollY <= scr_y then
|
||
screenTbl[#screenTbl+1] = {tbl[a].x+paint.scrollX,tbl[a].y+paint.scrollY}
|
||
end
|
||
end
|
||
end
|
||
if not _x and _y then
|
||
return screenTbl
|
||
else
|
||
for a = 1, #screenTbl do
|
||
if screenTbl[a][1] == _x and screenTbl[a][2] == _y then
|
||
return true
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
end
|
||
|
||
local clearAllRedundant = function(info)
|
||
local output = {}
|
||
for a = 1, #info do
|
||
output[a] = clearRedundant(info[a])
|
||
if a % 4 == 0 then yield() end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local saveFile = function(path,info)
|
||
local output = clearAllRedundant(info)
|
||
local fileout = textutils.serialize(output):gsub(" ",""):gsub("\n",""):gsub(" = ","="):gsub(",}","}"):gsub("}},{{","}},\n{{")
|
||
if #fileout >= fs.getFreeSpace(fs.getDir(path)) then
|
||
barmsg = "Not enough space."
|
||
return
|
||
end
|
||
local file = fs.open(path,"w")
|
||
file.write(fileout)
|
||
file.close()
|
||
end
|
||
local renderBar = function(msg,dontSetVisible)
|
||
if (doRenderBar == 0) or renderBlittle then return end
|
||
if tsv and (not dontSetVisible) then tsv(false) end
|
||
term.setCursorPos(1,scr_y)
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.setTextColor(colors.black)
|
||
term.clearLine()
|
||
term.setBackgroundColor(paint.b or rendback.b)
|
||
term.setTextColor(paint.t or rendback.t)
|
||
term.setCursorPos(2,scr_y)
|
||
term.write("PAIN")
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.setTextColor(colors.black)
|
||
local fmsg = tableconcat({"Fr:",frame,"/",#paintEncoded," (",paint.scrollX,",",paint.scrollY,")"})
|
||
term.setCursorPos(7,scr_y)
|
||
term.write(msg)
|
||
term.setCursorPos(scr_x-(#fmsg),scr_y)
|
||
term.write(fmsg)
|
||
if tsv and (not dontSetVisible) then tsv(true) end
|
||
end
|
||
|
||
local tableFormatPE = function(input)
|
||
local doot = {}
|
||
local pwidths = {}
|
||
local pheight = 0
|
||
for k, dot in pairs(input) do
|
||
pwidths[dot.y] = math.max((pwidths[dot.y] or 0), dot.x)
|
||
pheight = math.max(pheight, dot.y)
|
||
doot[dot.y] = doot[dot.y] or {}
|
||
doot[dot.y][dot.x] = {
|
||
char = dot.c,
|
||
text = CTB(dot.t),
|
||
back = CTB(dot.b)
|
||
}
|
||
end
|
||
for y = 1, pheight do
|
||
pwidths[y] = pwidths[y] or 0
|
||
if doot[y] then
|
||
for x = 1, pwidths[y] do
|
||
doot[y][x] = doot[y][x] or {
|
||
text = " ",
|
||
back = " ",
|
||
char = " ",
|
||
}
|
||
end
|
||
else
|
||
doot[y] = false
|
||
end
|
||
end
|
||
return doot, pheight, pwidths
|
||
end
|
||
|
||
CTB = function(_color) --Color To Blit
|
||
local blitcolors = {
|
||
[0] = " ",
|
||
[colors.white] = "0",
|
||
[colors.orange] = "1",
|
||
[colors.magenta] = "2",
|
||
[colors.lightBlue] = "3",
|
||
[colors.yellow] = "4",
|
||
[colors.lime] = "5",
|
||
[colors.pink] = "6",
|
||
[colors.gray] = "7",
|
||
[colors.lightGray] = "8",
|
||
[colors.cyan] = "9",
|
||
[colors.purple] = "a",
|
||
[colors.blue] = "b",
|
||
[colors.brown] = "c",
|
||
[colors.green] = "d",
|
||
[colors.red] = "e",
|
||
[colors.black] = "f",
|
||
}
|
||
if _color == nil then return nil end
|
||
return blitcolors[_color] or "f"
|
||
end
|
||
|
||
BTC = function(_color,allowZero) --Blit To Color
|
||
local blitcolors = {
|
||
[" "] = allowZero and 0 or nil,
|
||
["0"] = colors.white,
|
||
["1"] = colors.orange,
|
||
["2"] = colors.magenta,
|
||
["3"] = colors.lightBlue,
|
||
["4"] = colors.yellow,
|
||
["5"] = colors.lime,
|
||
["6"] = colors.pink,
|
||
["7"] = colors.gray,
|
||
["8"] = colors.lightGray,
|
||
["9"] = colors.cyan,
|
||
["a"] = colors.purple,
|
||
["b"] = colors.blue,
|
||
["c"] = colors.brown,
|
||
["d"] = colors.green,
|
||
["e"] = colors.red,
|
||
["f"] = colors.black,
|
||
}
|
||
if _color == nil then return nil end
|
||
return blitcolors[_color]
|
||
end
|
||
|
||
local renderPainyThings = function(xscroll,yscroll,doGrid)
|
||
local yadjust = (renderBlittle and 0 or doRenderBar)
|
||
if bepimode then
|
||
grid = {
|
||
"Bepis",
|
||
"episB",
|
||
"pisBe",
|
||
"isBep",
|
||
"sBepi",
|
||
}
|
||
else
|
||
grid = {
|
||
"%%..",
|
||
"%%..",
|
||
"%%..",
|
||
"..%%",
|
||
"..%%",
|
||
"..%%",
|
||
}
|
||
end
|
||
term.setBackgroundColor(rendback.b)
|
||
term.setTextColor(rendback.t)
|
||
local badchar = "/"
|
||
local blittlelabel = "blittle max"
|
||
local screenlabel = "screen max"
|
||
|
||
local dotBuffChar, dotBuffBack = "", "" --only used if gridBleedThrough is true
|
||
local doot
|
||
if doGrid then
|
||
for y = 1, scr_y - yadjust do
|
||
term.setCursorPos(1,y)
|
||
-- the single most convoluted line I've ever written that works, and I love it
|
||
term.write(stringShift(grid[ro(y+(yscroll+2),#grid)+1],xscroll+1):rep(math.ceil(scr_x/#grid[ro(y+(yscroll+2),#grid)+1])):sub(1,scr_x))
|
||
term.setCursorPos((xscroll <= 0) and (1-xscroll) or 0,y)
|
||
if ((screenEdges[2]+1)-yscroll) == y then --regular limit
|
||
term.write( (string.rep("@", math.max(0,( (screenEdges[1]) ) - (#screenlabel+1) )) ..screenlabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]) )
|
||
elseif (((screenEdges[2]*3)+1)-yscroll) == y then --blittle limit
|
||
term.write( (string.rep("@", math.max(0,( ((screenEdges[1]*2)) ) - (#blittlelabel+1) ))..blittlelabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]*2) )
|
||
end
|
||
-- Stupid easter eggs, ho! --
|
||
if 1000-yscroll == y then
|
||
term.setCursorPos(1000-xscroll,y)
|
||
term.write(" What ARE you doing? Stop messing around! ")
|
||
end
|
||
if 2016-yscroll == y then
|
||
term.setCursorPos(200-xscroll,y)
|
||
term.write(" Lines don't like to be intersected, you know. ")
|
||
end
|
||
if 2017-yscroll == y then
|
||
term.setCursorPos(200-xscroll,y)
|
||
term.write(" It makes them very crossed. ")
|
||
end
|
||
if 800-yscroll == y then
|
||
term.setCursorPos(1700-xscroll,y)
|
||
term.write(" You stare deeply into the void. ")
|
||
end
|
||
if 801-yscroll == y then
|
||
term.setCursorPos(1704-xscroll,y)
|
||
term.write(" And the void ")
|
||
end
|
||
if 802-yscroll == y then
|
||
term.setCursorPos(1704-xscroll,y)
|
||
term.write(" stares back. ")
|
||
end
|
||
--Is this the end?--
|
||
if (xscroll > ((screenEdges[1]*2)-scr_x)) then
|
||
for y = 1, scr_y do
|
||
if y+yscroll <= (screenEdges[2]*3) then
|
||
if not (y == scr_y and doRenderBar == 1) then
|
||
term.setCursorPos((screenEdges[1]+1)-(xscroll-screenEdges[1]),y)
|
||
term.write("@")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if (xscroll > (screenEdges[1]-scr_x)) then --regular limit
|
||
for y = 1, scr_y do
|
||
if y+yscroll <= screenEdges[2] then
|
||
if not (y == scr_y and doRenderBar == 1) then
|
||
term.setCursorPos((screenEdges[1]+1)-xscroll,y)
|
||
term.write("@")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
--render areas that won't save
|
||
if xscroll < 0 then
|
||
for y = 1, scr_y do
|
||
if not (y == scr_y and doRenderBar == 1) then
|
||
term.setCursorPos(1,y)
|
||
term.write(badchar:rep(-xscroll))
|
||
end
|
||
end
|
||
end
|
||
if yscroll < 0 then
|
||
for y = 1, -yscroll do
|
||
if not (y == scr_y and doRenderBar == 1) then
|
||
term.setCursorPos(1,y)
|
||
term.write(badchar:rep(scr_x))
|
||
end
|
||
end
|
||
end
|
||
else
|
||
for y = 1, scr_y - yadjust do
|
||
term.setCursorPos(1,y)
|
||
term.clearLine()
|
||
end
|
||
end
|
||
end
|
||
|
||
importFromPaint = function(theInput)
|
||
local output = {}
|
||
local input
|
||
if type(theInput) == "string" then
|
||
input = explode("\n",theInput)
|
||
else
|
||
input = {}
|
||
for y = 1, #theInput do
|
||
input[y] = ""
|
||
for x = 1, #theInput[y] do
|
||
input[y] = input[y]..(CTB(theInput[y][x]) or " ")
|
||
end
|
||
end
|
||
end
|
||
for a = 1, #input do
|
||
line = input[a]
|
||
for b = 1, #line do
|
||
if (line:sub(b,b) ~= " ") and BTC(line:sub(b,b)) then
|
||
output[#output+1] = {
|
||
x = b,
|
||
y = a,
|
||
t = colors.white,
|
||
b = BTC(line:sub(b,b)) or colors.black,
|
||
c = " ",
|
||
}
|
||
end
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local lddfm = {
|
||
scroll = 0,
|
||
ypaths = {}
|
||
}
|
||
|
||
lddfm.scr_x, lddfm.scr_y = term.getSize()
|
||
|
||
lddfm.setPalate = function(_p)
|
||
if type(_p) ~= "table" then
|
||
_p = {}
|
||
end
|
||
lddfm.p = { --the DEFAULT color palate
|
||
bg = _p.bg or colors.gray, -- whole background color
|
||
d_txt = _p.d_txt or colors.yellow, -- directory text color
|
||
d_bg = _p.d_bg or colors.gray, -- directory bg color
|
||
f_txt = _p.f_txt or colors.white, -- file text color
|
||
f_bg = _p.f_bg or colors.gray, -- file bg color
|
||
p_txt = _p.p_txt or colors.black, -- path text color
|
||
p_bg = _p.p_bg or colors.lightGray, -- path bg color
|
||
close_txt = _p.close_txt or colors.gray, -- close button text color
|
||
close_bg = _p.close_bg or colors.lightGray,-- close button bg color
|
||
scr = _p.scr or colors.lightGray, -- scrollbar color
|
||
scrbar = _p.scrbar or colors.gray, -- scroll tab color
|
||
}
|
||
end
|
||
|
||
lddfm.setPalate()
|
||
|
||
lddfm.foldersOnTop = function(floop,path)
|
||
local output = {}
|
||
for a = 1, #floop do
|
||
if fs.isDir(fs.combine(path,floop[a])) then
|
||
table.insert(output,1,floop[a])
|
||
else
|
||
table.insert(output,floop[a])
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden)
|
||
local output = {}
|
||
for a = 1, #list do
|
||
local entry = fs.combine(path,list[a])
|
||
if fs.isDir(entry) then
|
||
if entry == ".." then
|
||
if not (_noCD or _noFolders) then table.insert(output,list[a]) end
|
||
else
|
||
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
|
||
if not _noFolders then table.insert(output,list[a]) end
|
||
end
|
||
end
|
||
else
|
||
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
|
||
if not _noFiles then table.insert(output,list[a]) end
|
||
end
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
lddfm.isColor = function(col)
|
||
for k,v in pairs(colors) do
|
||
if v == col then
|
||
return true, k
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
lddfm.clearLine = function(x1,x2,_y,_bg,_char)
|
||
local cbg, bg = term.getBackgroundColor()
|
||
local x,y = term.getCursorPos()
|
||
local sx,sy = term.getSize()
|
||
if type(_char) == "string" then char = _char else char = " " end
|
||
if type(_bg) == "number" then
|
||
if lddfm.isColor(_bg) then bg = _bg
|
||
else bg = cbg end
|
||
else bg = cbg end
|
||
term.setCursorPos(x1 or 1, _y or y)
|
||
term.setBackgroundColor(bg)
|
||
if x2 then --it pains me to add an if statement to something as simple as this
|
||
term.write((char or " "):rep(x2-x1))
|
||
else
|
||
term.write((char or " "):rep(sx-(x1 or 0)))
|
||
end
|
||
term.setBackgroundColor(cbg)
|
||
term.setCursorPos(x,y)
|
||
end
|
||
|
||
lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY)
|
||
local tsv = term.current().setVisible
|
||
local px,py = term.getCursorPos()
|
||
if tsv then tsv(false) end
|
||
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
|
||
local rlist = _rlist or {"Invalid directory."}
|
||
local path = _path or "And that's terrible."
|
||
ypaths = {}
|
||
local rscroll = _rscroll or 0
|
||
for a = y1, y2 do
|
||
lddfm.clearLine(x1,x2,a,lddfm.p.bg)
|
||
end
|
||
term.setCursorPos(x1,y1)
|
||
term.setTextColor(lddfm.p.p_txt)
|
||
lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg)
|
||
term.setBackgroundColor(lddfm.p.p_bg)
|
||
term.write(("/"..path):sub(1,x2-x1))
|
||
for a = 1,(y2-y1) do
|
||
if rlist[a+rscroll] then
|
||
term.setCursorPos(x1,a+(y1))
|
||
if fs.isDir(fs.combine(path,rlist[a+rscroll])) then
|
||
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg)
|
||
term.setTextColor(lddfm.p.d_txt)
|
||
term.setBackgroundColor(lddfm.p.d_bg)
|
||
else
|
||
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg)
|
||
term.setTextColor(lddfm.p.f_txt)
|
||
term.setBackgroundColor(lddfm.p.f_bg)
|
||
end
|
||
term.write(rlist[a+rscroll]:sub(1,x2-x1))
|
||
ypaths[a+(y1)] = rlist[a+rscroll]
|
||
else
|
||
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg)
|
||
end
|
||
end
|
||
local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) )
|
||
for a = y1+1, y2 do
|
||
term.setCursorPos(x2,a)
|
||
if a == scrbarY then
|
||
term.setBackgroundColor(lddfm.p.scrbar)
|
||
else
|
||
term.setBackgroundColor(lddfm.p.scr)
|
||
end
|
||
term.write(" ")
|
||
end
|
||
if _canClose then
|
||
term.setCursorPos(x2-4,y1)
|
||
term.setTextColor(lddfm.p.close_txt)
|
||
term.setBackgroundColor(lddfm.p.close_bg)
|
||
term.write("close")
|
||
end
|
||
term.setCursorPos(px,py)
|
||
if tsv then tsv(true) end
|
||
return scrbarY
|
||
end
|
||
|
||
lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char)
|
||
local cx, cy = term.getCursorPos()
|
||
local bg, txt = term.getBackgroundColor(), term.getTextColor()
|
||
term.setTextColor(_txt or colors.white)
|
||
term.setBackgroundColor(_bg or colors.black)
|
||
local _uwah = 0
|
||
for y = y1, y2 do
|
||
for x = x1, x2 do
|
||
_uwah = _uwah + 1
|
||
term.setCursorPos(x,y)
|
||
term.write(char or " ")
|
||
if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0) _uwah = 0 end
|
||
end
|
||
end
|
||
term.setTextColor(txt)
|
||
term.setBackgroundColor(bg)
|
||
term.setCursorPos(cx,cy)
|
||
end
|
||
|
||
lddfm.scrollMenu = function(amount,list,y1,y2)
|
||
if #list >= y2-y1 then
|
||
lddfm.scroll = lddfm.scroll + amount
|
||
if lddfm.scroll < 0 then
|
||
lddfm.scroll = 0
|
||
end
|
||
if lddfm.scroll > #list-(y2-y1) then
|
||
lddfm.scroll = #list-(y2-y1)
|
||
end
|
||
end
|
||
end
|
||
|
||
--[[
|
||
a quick explanation of the arguments for lddfm.makeMenu:
|
||
|
||
x1 and y1: top-left corner coordinates of menu window. defaults to the top-left corner of the screen
|
||
x2 and y2: bottom-right corner coordinates of menu window. defaults to the bottom-right corner of the screen
|
||
_path: path to start viewing. defaults to "/"
|
||
_noFiles: whether or not to view files in the menu, mainly for picking a path for installing something. defaults to false
|
||
_noFolders: whether or not to view folders in the menu, mainly for choosing a file to run or whatever. defaults to false
|
||
_noCD: whether or not you can change the directory, mainly to limit choices to a single folder. defaults to false
|
||
_noSelectFolders: whether or not you can select folders to return. defaults to false
|
||
_doHidden: whether or not to hide hidden files (starts with "."). defaults to false
|
||
_p: the palate. has: bg, d_txt, d_bg, f_txt, t_bg, p_txt, p_bg, scr, scrbar. 'd' is for directory, 'f' is for file, 'p' is for path bar.
|
||
_canClose: whether or not you can click on the little top-right "Cancel" button.
|
||
--]]
|
||
|
||
lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose)
|
||
if _noFiles and _noFolders then
|
||
return false, "C'mon, man..."
|
||
end
|
||
if _x1 == true then
|
||
return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help
|
||
end
|
||
lddfm.setPalate(_p)
|
||
local path, list = _path or ""
|
||
lddfm.scroll = 0
|
||
local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor()
|
||
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
|
||
local keysDown = {}
|
||
local _barrY
|
||
while true do
|
||
list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path)
|
||
if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then
|
||
table.insert(list,1,"..")
|
||
end
|
||
_res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end)
|
||
if not _res then
|
||
local tsv = term.current().setVisible
|
||
if tsv then tsv(true) end
|
||
error(_barrY)
|
||
end
|
||
local evt = {os.pullEvent()}
|
||
if evt[1] == "mouse_scroll" then
|
||
lddfm.scrollMenu(evt[2],list,y1,y2)
|
||
elseif evt[1] == "mouse_click" then
|
||
local butt,mx,my = evt[2],evt[3],evt[4]
|
||
if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then
|
||
--lddfm.coolOutro(x1,y1,x2,y2)
|
||
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
|
||
return false
|
||
elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh
|
||
if fs.isDir(fs.combine(path,ypaths[my])) then
|
||
if _noCD or butt == 3 then
|
||
if not _noSelectFolders or _noFolders then
|
||
--lddfm.coolOutro(x1,y1,x2,y2)
|
||
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
|
||
return fs.combine(path,ypaths[my])
|
||
end
|
||
else
|
||
path = fs.combine(path,ypaths[my])
|
||
lddfm.scroll = 0
|
||
end
|
||
else
|
||
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
|
||
return fs.combine(path,ypaths[my])
|
||
end
|
||
end
|
||
elseif evt[1] == "key" then
|
||
keysDown[evt[2]] = true
|
||
if evt[2] == keys.enter and not (_noFolders or _noCD or _noSelectFolders) then --the logic for _noCD being you'd normally need to go back a directory to select the current directory.
|
||
--lddfm.coolOutro(x1,y1,x2,y2)
|
||
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
|
||
return path
|
||
end
|
||
if evt[2] == keys.up then
|
||
lddfm.scrollMenu(-1,list,y1,y2)
|
||
elseif evt[2] == keys.down then
|
||
lddfm.scrollMenu(1,list,y1,y2)
|
||
end
|
||
if evt[2] == keys.pageUp then
|
||
lddfm.scrollMenu(y1-y2,list,y1,y2)
|
||
elseif evt[2] == keys.pageDown then
|
||
lddfm.scrollMenu(y2-y1,list,y1,y2)
|
||
end
|
||
if evt[2] == keys.home then
|
||
lddfm.scroll = 0
|
||
elseif evt[2] == keys["end"] then
|
||
if #list > (y2-y1) then
|
||
lddfm.scroll = #list-(y2-y1)
|
||
end
|
||
end
|
||
if evt[2] == keys.h then
|
||
if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
|
||
_doHidden = not _doHidden
|
||
end
|
||
elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then
|
||
--lddfm.coolOutro(x1,y1,x2,y2)
|
||
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
|
||
return false
|
||
end
|
||
elseif evt[1] == "key_up" then
|
||
keysDown[evt[2]] = false
|
||
end
|
||
end
|
||
end
|
||
|
||
local getBlittle = function()
|
||
if not blittle then
|
||
if fs.exists(fs.combine(apipath,"blittle")) then
|
||
os.loadAPI(fs.combine(apipath,"blittle"))
|
||
if not blittleTerm then
|
||
blittleTerm = blittle.createWindow()
|
||
end
|
||
return blittleTerm, firstTerm
|
||
else
|
||
local geet = http.get("http://pastebin.com/raw/ujchRSnU")
|
||
if not geet then
|
||
return false
|
||
else
|
||
geet = geet.readAll()
|
||
local file = fs.open(fs.combine(apipath,"blittle"),"w")
|
||
file.write(geet)
|
||
file.close()
|
||
os.loadAPI(fs.combine(apipath,"blittle"))
|
||
--fs.delete(apipath)
|
||
if not blittleTerm then
|
||
blittleTerm = blittle.createWindow()
|
||
end
|
||
return blittleTerm, firstTerm
|
||
end
|
||
end
|
||
else
|
||
if not blittleTerm then
|
||
blittleTerm = blittle.createWindow()
|
||
end
|
||
return blittleTerm, firstTerm
|
||
end
|
||
end
|
||
|
||
local getUCG = function()
|
||
if not ucg then
|
||
if fs.exists(fs.combine(apipath,"ucg")) then
|
||
os.loadAPI(fs.combine(apipath,"ucg"))
|
||
return true
|
||
else
|
||
local geet = http.get("https://raw.githubusercontent.com/ardera/libucg/master/src/libucg")
|
||
if not geet then
|
||
return false
|
||
else
|
||
geet = geet.readAll()
|
||
local file = fs.open(fs.combine(apipath,"ucg"),"w")
|
||
file.write(geet)
|
||
file.close()
|
||
os.loadAPI(fs.combine(apipath,"ucg"))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local getBBPack = function()
|
||
if not bbpack then
|
||
if fs.exists(fs.combine(apipath,"bbpack")) then
|
||
os.loadAPI(fs.combine(apipath,"bbpack"))
|
||
return true
|
||
else
|
||
local geet = http.get("https://pastebin.com/raw/cUYTGbpb")
|
||
if not geet then
|
||
return false
|
||
else
|
||
geet = geet.readAll()
|
||
local file = fs.open(fs.combine(apipath,"bbpack"),"w")
|
||
file.write(geet)
|
||
file.close()
|
||
os.loadAPI(fs.combine(apipath,"bbpack"))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local getGIF = function()
|
||
getBBPack()
|
||
if not GIF then
|
||
if fs.exists(fs.combine(apipath,"GIF")) then
|
||
os.loadAPI(fs.combine(apipath,"GIF"))
|
||
return true
|
||
else
|
||
local geet = http.get("https://pastebin.com/raw/5uk9uRjC")
|
||
if not geet then
|
||
return false
|
||
else
|
||
geet = geet.readAll()
|
||
local file = fs.open(fs.combine(apipath,"GIF"),"w")
|
||
file.write(geet)
|
||
file.close()
|
||
os.loadAPI(fs.combine(apipath,"GIF"))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local NFPserializeImage = function(str)
|
||
local bepis = explode("\n",str)
|
||
local output = {}
|
||
for y = 1, #bepis do
|
||
output[y] = {}
|
||
for x = 1, #bepis[y] do
|
||
output[y][x] = BTC(bepis[y]:sub(x,x),true)
|
||
end
|
||
end
|
||
return textutils.unserialize(textutils.serialize(output):gsub("\n",""):gsub(" ",""):gsub(",}","}"))
|
||
end
|
||
|
||
local importFromGIF = function(filename,verbose)
|
||
getGIF()
|
||
local output = {}
|
||
local image
|
||
local rawGif = GIF.loadGIF(filename)
|
||
if painconfig.useFlattenGIF then
|
||
if verbose then
|
||
print("Flattening...")
|
||
end
|
||
rawGif = GIF.flattenGIF(rawGif)
|
||
sleep(0)
|
||
end
|
||
local cx, cy = term.getCursorPos()
|
||
for a = 1, #rawGif do
|
||
output[a] = importFromPaint(GIF.toPaintutils(rawGif[a]))
|
||
if verbose then
|
||
term.setCursorPos(cx,cy)
|
||
write("Did "..a.."/"..#rawGif.." ")
|
||
end
|
||
if a % 1 then sleep(0) end --used to be a % 2, might change later
|
||
end
|
||
return output
|
||
end
|
||
|
||
local exportToPaint
|
||
|
||
local exportToGIF = function(input)
|
||
getGIF()
|
||
local outGIF = {}
|
||
for a = 1, #paintEncoded do
|
||
outGIF[a] = NFPserializeImage(exportToPaint(paintEncoded[a]))
|
||
sleep(0)
|
||
end
|
||
if painconfig.useFlattenGIF then
|
||
return GIF.flattenGIF(GIF.buildGIF(table.unpack(outGIF)),true)
|
||
else
|
||
return GIF.buildGIF(table.unpack(outGIF))
|
||
end
|
||
end
|
||
|
||
local importFromUCG = function(filename)
|
||
getUCG()
|
||
return importFromPaint(ucg.readFile(filename))
|
||
end
|
||
|
||
local exportToUCG = function(filename, input)
|
||
getUCG()
|
||
ucg.writeFile(filename, NFPserializeImage(exportToPaint(input)))
|
||
end
|
||
|
||
renderPAIN = function(dots,xscroll,yscroll,doPain,dontRenderBar)
|
||
if tsv then tsv(false) end
|
||
local beforeTX,beforeBG = term.getTextColor(), term.getBackgroundColor()
|
||
local cx,cy = term.getCursorPos()
|
||
local FUCK, SHIT = pcall(function()
|
||
if doPain then
|
||
if (not renderBlittle) then
|
||
if not dontRenderBar then
|
||
renderBar(barmsg,true)
|
||
end
|
||
renderPainyThings(xscroll,yscroll,evenDrawGrid)
|
||
else
|
||
term.clear()
|
||
end
|
||
end
|
||
for a = 1, #dots do
|
||
local d = dots[a]
|
||
if doPain then
|
||
if not ((d.y-yscroll >= 1 and d.y-yscroll <= scr_y-(renderBlittle and 0 or doRenderBar)) and (d.x-xscroll >= 1 and d.x-xscroll <= scr_x)) then
|
||
d = nil
|
||
end
|
||
end
|
||
if d then
|
||
term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0))
|
||
term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b)
|
||
if painconfig.gridBleedThrough then
|
||
term.setTextColor(rendback.t)
|
||
term.write((d.x >= 1 and d.y >= 1) and grid[ ro( d.y+2, #grid)+1]:sub(1+ro(d.x+-1,#grid[1]), 1+ro(d.x+-1,#grid[1])) or "/")
|
||
else
|
||
term.setTextColor( (paint.doGray and grayOut(d.t) or d.t) or rendback.t)
|
||
term.write(d.c or " ")
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
term.setBackgroundColor(beforeBG or rendback.b)
|
||
term.setTextColor(beforeTX or rendback.t)
|
||
term.setCursorPos(cx,cy)
|
||
if tsv then tsv(true) end
|
||
if not FUCK then error(SHIT) end --GOD DAMN IT
|
||
end
|
||
|
||
renderPAINFS = function(filename,xscroll,yscroll,frameNo,doPain)
|
||
local tun, tse = textutils.unserialize, textutils.serialize
|
||
local file = fs.open(filename,"r")
|
||
local contents = file.readAll()
|
||
local amntFrames
|
||
file.close()
|
||
local tcontents = tun(contents)
|
||
if type(tcontents) ~= "table" then
|
||
tcontents = importFromPaint(contents)
|
||
else
|
||
amntFrames = #tcontents
|
||
tcontents = tcontents[frameNo or 1]
|
||
end
|
||
renderPAIN(tcontents,xscroll,yscroll,doPain)
|
||
return amntFrames
|
||
end
|
||
|
||
local putDotDown = function(dot) -- only 'x' and 'y' are required arguments
|
||
paintEncoded[frame][#paintEncoded[frame]+1] = {
|
||
x = dot.x + paint.scrollX,
|
||
y = dot.y + paint.scrollY,
|
||
c = dot.c or paint.c,
|
||
b = dot.b or (swapColors and paint.t or paint.b),
|
||
t = dot.t or (swapColors and paint.b or paint.t),
|
||
m = dot.m or paint.m,
|
||
}
|
||
end
|
||
|
||
local saveToUndoBuffer = function()
|
||
if undoPos < #undoBuffer then
|
||
for a = #undoBuffer, undoPos+1, -1 do
|
||
table.remove(undoBuffer,a)
|
||
end
|
||
end
|
||
if undoPos >= painconfig.undoBufferSize then
|
||
for a = 2, #undoBuffer do
|
||
undoBuffer[a-1] = undoBuffer[a]
|
||
end
|
||
undoBuffer[#undoBuffer] = deepCopy(paintEncoded)
|
||
else
|
||
undoPos = undoPos + 1
|
||
undoBuffer[undoPos] = deepCopy(paintEncoded)
|
||
end
|
||
end
|
||
|
||
local doUndo = function()
|
||
undoPos = math.max(1,undoPos-1)
|
||
paintEncoded = deepCopy(undoBuffer[undoPos])
|
||
if not paintEncoded[frame] then
|
||
frame = #paintEncoded
|
||
end
|
||
end
|
||
|
||
local doRedo = function()
|
||
undoPos = math.min(#undoBuffer,undoPos+1)
|
||
paintEncoded = deepCopy(undoBuffer[undoPos])
|
||
if not paintEncoded[frame] then
|
||
frame = #paintEncoded
|
||
end
|
||
end
|
||
|
||
local putDownText = function(x,y)
|
||
term.setCursorPos(x,y)
|
||
term.setTextColor((paint.doGray and grayOut(paint.t or rendback.t)) or (paint.t or rendback.t))
|
||
term.setBackgroundColor((paint.doGray and grayOut(paint.b or rendback.b)) or (paint.b or rendback.b))
|
||
local msg = read()
|
||
if #msg > 0 then
|
||
for a = 1, #msg do
|
||
putDotDown({x=(x+a)-1, y=y, c=msg:sub(a,a)})
|
||
end
|
||
end
|
||
saveToUndoBuffer()
|
||
end
|
||
|
||
local deleteDot = function(x,y) --deletes all dots at point x,y
|
||
local good = false
|
||
for a = #paintEncoded[frame],1,-1 do
|
||
local b = paintEncoded[frame][a]
|
||
if (x == b.x) and (y == b.y) then
|
||
table.remove(paintEncoded[frame],a)
|
||
good = true
|
||
end
|
||
end
|
||
return good
|
||
end
|
||
|
||
exportToPaint = function(input,noTransparent) --exports paintEncoded frame to regular paint format. input is expected to be paintEncoded[frame]
|
||
local doopTXT, doopTXCOL, doopBGCOL = {}, {}, {}
|
||
local p = input
|
||
local pheight = 0
|
||
local pwidth = 0
|
||
for a = 1, #p do
|
||
if p[a].y > pheight then
|
||
pheight = p[a].y
|
||
end
|
||
if p[a].x > pwidth then
|
||
pwidth = p[a].x
|
||
end
|
||
end
|
||
for k,v in pairs(p) do
|
||
if not doopBGCOL[v.y] then
|
||
doopBGCOL[v.y] = {}
|
||
doopTXCOL[v.y] = {}
|
||
doopTXT[v.y] = {}
|
||
end
|
||
doopBGCOL[v.y][v.x] = CTB(v.b)
|
||
doopTXCOL[v.y][v.x] = CTB(v.t)
|
||
doopTXT[v.y][v.x] = v.c
|
||
end
|
||
local nfpoutputTXT, nfpoutputTXCOL, nfpoutputBGCOL = "", "", ""
|
||
for y = 1, pheight do
|
||
if doopBGCOL[y] then
|
||
for x = 1, pwidth do
|
||
if doopBGCOL[y][x] then
|
||
nfpoutputBGCOL = nfpoutputBGCOL..doopBGCOL[y][x]
|
||
nfpoutputTXCOL = nfpoutputTXCOL..doopTXCOL[y][x]
|
||
nfpoutputTXT = nfpoutputTXT..(((doopTXT[y][x] == " " and noTransparent) and "\128" or doopTXT[y][x]) or " ")
|
||
else
|
||
nfpoutputBGCOL = nfpoutputBGCOL..(noTransparent and "0" or " ")
|
||
nfpoutputTXCOL = nfpoutputTXCOL..(noTransparent and "0" or " ")
|
||
nfpoutputTXT = nfpoutputTXT.." "
|
||
end
|
||
end
|
||
end
|
||
if y ~= pheight then
|
||
nfpoutputBGCOL = nfpoutputBGCOL.."\n"
|
||
nfpoutputTXCOL = nfpoutputTXCOL.."\n"
|
||
nfpoutputTXT = nfpoutputTXT.."\n"
|
||
end
|
||
end
|
||
return nfpoutputBGCOL, pheight, pwidth
|
||
end
|
||
|
||
local exportToNFT = function(input)
|
||
|
||
local bgcode, txcode = "\30", "\31"
|
||
local output = ""
|
||
local text, back
|
||
|
||
local doot, pheight, pwidths = tableFormatPE(input)
|
||
|
||
for y = 1, pheight do
|
||
|
||
text, back = "0", "f"
|
||
if doot[y] then
|
||
for x = 1, pwidths[y] do
|
||
|
||
if doot[y][x] then
|
||
if doot[y][x].back ~= back then
|
||
back = doot[y][x].back
|
||
output = output .. bgcode .. back
|
||
end
|
||
if doot[y][x].text ~= text then
|
||
text = doot[y][x].text
|
||
output = output .. txcode .. text
|
||
end
|
||
output = output .. doot[y][x].char
|
||
else
|
||
output = output .. " "
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
if y < pheight then
|
||
output = output .. "\n"
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local importFromNFT = function(input) --imports NFT formatted string image to paintEncoded[frame] formatted table image. please return a paintEncoded[frame] formatted table.
|
||
local tinput = explode("\n",input)
|
||
local tcol,bcol
|
||
local cx --represents the x position in the picture
|
||
local sx --represents the x position in the file
|
||
local output = {}
|
||
for y = 1, #tinput do
|
||
tcol,bcol = colors.white,colors.black
|
||
cx, sx = 1, 0
|
||
while sx < #tinput[y] do
|
||
sx = sx + 1
|
||
if tinput[y]:sub(sx,sx) == "\30" then
|
||
bcol = BTC(tinput[y]:sub(sx+1,sx+1))
|
||
sx = sx + 1
|
||
elseif tinput[y]:sub(sx,sx) == "\31" then
|
||
tcol = BTC(tinput[y]:sub(sx+1,sx+1))
|
||
sx = sx + 1
|
||
else
|
||
if tcol and bcol then
|
||
output[#output+1] = {
|
||
["x"] = cx,
|
||
["y"] = y,
|
||
["b"] = bcol,
|
||
["t"] = tcol,
|
||
["c"] = tinput[y]:sub(sx,sx),
|
||
["m"] = 0,
|
||
}
|
||
end
|
||
cx = cx + 1
|
||
end
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
exportToBLT = function(input,filename,doAllFrames,noSave)
|
||
local output = {}
|
||
local thisImage,pheight,pwidth,nfpinput
|
||
getBlittle()
|
||
for a = doAllFrames and 1 or frame, doAllFrames and #input or frame do
|
||
output[#output+1] = blittle.shrink(NFPserializeImage(exportToPaint(input[a]),true),colors.black)
|
||
end
|
||
if #output == 1 then output = output[1] end
|
||
if not noSave then
|
||
blittle.save(output,filename)
|
||
end
|
||
return output
|
||
end
|
||
|
||
importFromBLT = function(input) --takes in filename, not contents
|
||
local output = {}
|
||
getBlittle()
|
||
local wholePic = blittle.load(input)
|
||
if wholePic.height then wholePic = {wholePic} end
|
||
local image
|
||
for a = 1, #wholePic do
|
||
image = wholePic[a]
|
||
output[#output+1] = {}
|
||
for y = 1, image.height*3 do
|
||
for x = 1, math.max(#image[1][math.ceil(y/3)],#image[2][math.ceil(y/3)],#image[3][math.ceil(y/3)])*2 do
|
||
output[#output][#output[#output]+1] = {
|
||
m = 0,
|
||
x = x,
|
||
y = y,
|
||
t = BTC((image[2][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
|
||
b = BTC((image[3][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
|
||
c = BTC((image[1][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).." "):sub(1,1)),
|
||
}
|
||
end
|
||
end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local getTheDoots = function(pe)
|
||
local hasBadDots = false
|
||
local baddestX,baddestY = 1,1
|
||
for b = 1, #pe do
|
||
local doot = pe[b]
|
||
if doot.x <= 0 or doot.y <= 0 then
|
||
hasBadDots = true
|
||
if doot.x < baddestX then
|
||
baddestX = doot.x
|
||
end
|
||
if doot.y < baddestY then
|
||
baddestY = doot.y
|
||
end
|
||
end
|
||
if b % 64 == 0 then yield() end
|
||
end
|
||
return baddestX, baddestY
|
||
end
|
||
|
||
local checkBadDots = function()
|
||
local hasBadDots = false
|
||
for a = 1, #paintEncoded do
|
||
local radx,rady = getTheDoots(paintEncoded[a])
|
||
if radx ~= 1 or rady ~= 1 then
|
||
hasBadDots = true
|
||
end
|
||
end
|
||
if hasBadDots then
|
||
local ting = bottomPrompt("Dot(s) are OoB! Save or fix? (Y/N/F)",_,"ynf",{keys.leftCtrl,keys.rightCtrl})
|
||
if ting == "f" then
|
||
for a = 1, #paintEncoded do
|
||
local baddestX, baddestY = getTheDoots(paintEncoded[a])
|
||
paintEncoded[a] = movePaintEncoded(paintEncoded[a],-(baddestX-1),-(baddestY-1))
|
||
end
|
||
elseif ting ~= "y" then
|
||
barmsg = ""
|
||
return false
|
||
end
|
||
end
|
||
end
|
||
|
||
local convertToGrayscale = function(pe)
|
||
local output = pe
|
||
for a = 1, #pe do
|
||
for b = 1, #pe[a] do
|
||
output[a][b].b = grayOut(pe[a][b].b)
|
||
output[a][b].t = grayOut(pe[a][b].t)
|
||
if not output[a][b].m then output[a][b].m = 1 end
|
||
end
|
||
if a % 2 == 0 then yield() end
|
||
end
|
||
return output
|
||
end
|
||
|
||
local reRenderPAIN = function(overrideRenderBar)
|
||
local _reallyDoRenderBar = doRenderBar
|
||
-- doRenderBar = 1
|
||
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,overrideRenderBar)
|
||
doRenderBar = _reallyDoRenderBar
|
||
end
|
||
|
||
local fillTool = function(_frame,cx,cy,dot,isDeleting) -- "_frame" is the frame NUMBER
|
||
local maxX, maxY = 1, 1
|
||
local minX, minY = 1, 1
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
local frame = paintEncoded[_frame]
|
||
local scx, scy = cx+paint.scrollX, cy+paint.scrollY
|
||
local output = {}
|
||
for a = 1, #frame do
|
||
maxX = math.max(maxX, frame[a].x)
|
||
maxY = math.max(maxY, frame[a].y)
|
||
minX = math.min(minX, frame[a].x)
|
||
minY = math.min(minY, frame[a].y)
|
||
end
|
||
|
||
maxX = math.max(maxX, scx)
|
||
maxY = math.max(maxY, scy)
|
||
minX = math.min(minX, scx)
|
||
minY = math.min(minY, scy)
|
||
|
||
maxX = math.max(maxX, screenEdges[1])
|
||
maxY = math.max(maxY, screenEdges[2])
|
||
|
||
local doop = {}
|
||
local touched = {}
|
||
local check = {[scy] = {[scx] = true}}
|
||
for y = minY, maxY do
|
||
doop[y] = {}
|
||
touched[y] = {}
|
||
for x = minX, maxX do
|
||
doop[y][x] = {
|
||
c = " ",
|
||
b = 0,
|
||
t = 0
|
||
}
|
||
touched[y][x] = false
|
||
end
|
||
end
|
||
for a = 1, #frame do
|
||
doop[frame[a].y][frame[a].x] = {
|
||
c = frame[a].c,
|
||
t = frame[a].t,
|
||
b = frame[a].b
|
||
}
|
||
end
|
||
local initDot = {
|
||
c = doop[scy][scx].c,
|
||
t = doop[scy][scx].t,
|
||
b = doop[scy][scx].b
|
||
}
|
||
local chkpos = function(x, y, checkList)
|
||
if (x < minX or x > maxX) or (y < minY or y > maxY) then
|
||
return false
|
||
else
|
||
if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t) or (doop[y][x].c ~= initDot.c) then
|
||
return false
|
||
end
|
||
if check[y] then
|
||
if check[y][x] then
|
||
return false
|
||
end
|
||
end
|
||
if touched[y][x] then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
end
|
||
local doBreak
|
||
local step = 0
|
||
local currentlyOnScreen
|
||
while true do
|
||
doBreak = true
|
||
for chY, v in pairs(check) do
|
||
for chX, isTrue in pairs(v) do
|
||
currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y)
|
||
if isTrue and (not touched[chY][chX]) then
|
||
step = step + 1
|
||
if painconfig.doFillAnimation then
|
||
if currentlyOnScreen then
|
||
reRenderPAIN(true)
|
||
end
|
||
end
|
||
if isDeleting then
|
||
deleteDot(chX, chY)
|
||
else
|
||
frame[#frame+1] = {
|
||
x = chX,
|
||
y = chY,
|
||
c = dot.c,
|
||
t = dot.t,
|
||
b = dot.b
|
||
}
|
||
end
|
||
touched[chY][chX] = true
|
||
-- check adjacent
|
||
if chkpos(chX+1, chY) then
|
||
check[chY][chX+1] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX-1, chY) then
|
||
check[chY][chX-1] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX, chY+1) then
|
||
check[chY+1] = check[chY+1] or {}
|
||
check[chY+1][chX] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX, chY-1) then
|
||
check[chY-1] = check[chY-1] or {}
|
||
check[chY-1][chX] = true
|
||
doBreak = false
|
||
end
|
||
-- check diagonal
|
||
if painconfig.doFillDiagonal then
|
||
if chkpos(chX-1, chY-1) then
|
||
check[chY-1] = check[chY-1] or {}
|
||
check[chY-1][chX-1] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX+1, chY-1) then
|
||
check[chY-1] = check[chY-1] or {}
|
||
check[chY-1][chX+1] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX-1, chY+1) then
|
||
check[chY+1] = check[chY+1] or {}
|
||
check[chY+1][chX-1] = true
|
||
doBreak = false
|
||
end
|
||
if chkpos(chX+1, chY+1) then
|
||
check[chY+1] = check[chY+1] or {}
|
||
check[chY+1][chX+1] = true
|
||
doBreak = false
|
||
end
|
||
end
|
||
if step % ((painconfig.doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash
|
||
sleep(0)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if doBreak then
|
||
break
|
||
end
|
||
end
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
saveToUndoBuffer()
|
||
end
|
||
|
||
local boxCharSelector = function()
|
||
local co = function(pos)
|
||
if pos then
|
||
term.setTextColor(colors.lime)
|
||
term.setBackgroundColor(colors.green)
|
||
else
|
||
term.setTextColor(colors.lightGray)
|
||
term.setBackgroundColor(colors.gray)
|
||
end
|
||
end
|
||
local rend = function()
|
||
term.setCursorPos(1,scr_y)
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.setTextColor(colors.black)
|
||
term.clearLine()
|
||
term.write("Press CTRL or 'N' when ready.")
|
||
term.setCursorPos(1,scr_y-3) co(boxchar.topLeft) write("Q") co(boxchar.topRight) write("W")
|
||
term.setCursorPos(1,scr_y-2) co(boxchar.left) write("A") co(boxchar.right) write("S")
|
||
term.setCursorPos(1,scr_y-1) co(boxchar.bottomLeft) write("Z") co(boxchar.bottomRight) write("X")
|
||
end
|
||
while true do
|
||
rend()
|
||
local evt = {os.pullEvent()}
|
||
if evt[1] == "key" then
|
||
local key = evt[2]
|
||
if key == keys.leftCtrl or key == keys.rightCtrl or key == keys.n then
|
||
break
|
||
else
|
||
if key == keys.q then boxchar.topLeft = not boxchar.topLeft end
|
||
if key == keys.w then boxchar.topRight = not boxchar.topRight end
|
||
if key == keys.a then boxchar.left = not boxchar.left end
|
||
if key == keys.s then boxchar.right = not boxchar.right end
|
||
if key == keys.z then boxchar.bottomLeft = not boxchar.bottomLeft end
|
||
if key == keys.x then boxchar.bottomRight = not boxchar.bottomRight end
|
||
end
|
||
elseif evt[1] == "mouse_click" or evt[1] == "mouse_drag" then
|
||
local button, mx, my = evt[2], evt[3], evt[4]
|
||
if my >= scr_y-2 then
|
||
if mx == 1 then
|
||
if my == scr_y - 3 then boxchar.topLeft = not boxchar.topLeft end
|
||
if my == scr_y - 2 then boxchar.left = not boxchar.left end
|
||
if my == scr_y - 1 then boxchar.bottomLeft = not boxchar.bottomLeft end
|
||
elseif mx == 2 then
|
||
if my == scr_y - 3 then boxchar.topRight = not boxchar.topRight end
|
||
if my == scr_y - 2 then boxchar.right = not boxchar.right end
|
||
if my == scr_y - 1 then boxchar.bottomRight = not boxchar.bottomRight end
|
||
elseif evt[1] == "mouse_click" then
|
||
break
|
||
end
|
||
elseif evt[1] == "mouse_click" then
|
||
break
|
||
end
|
||
end
|
||
end
|
||
if boxchar.topLeft and boxchar.topRight and boxchar.left and boxchar.right and boxchar.bottomLeft and boxchar.bottomRight then
|
||
swapColors = false
|
||
return " "
|
||
else
|
||
local output = getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight)
|
||
swapColors = not output.inverted
|
||
return output.char
|
||
end
|
||
end
|
||
|
||
local specialCharSelector = function()
|
||
local chars = {}
|
||
local buff = 0
|
||
for y = 1, 16 do
|
||
for x = 1, 16 do
|
||
chars[y] = chars[y] or {}
|
||
chars[y][x] = string.char(buff)
|
||
buff = buff + 1
|
||
end
|
||
end
|
||
local sy = scr_y - (#chars + 1)
|
||
local char = paint.c
|
||
local render = function()
|
||
for y = 1, #chars do
|
||
for x = 1, #chars do
|
||
term.setCursorPos(x,y+sy)
|
||
if chars[y][x] == char then
|
||
term.blit(chars[y][x], "5", "d")
|
||
else
|
||
term.blit(chars[y][x], "8", "7")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
local evt, butt, x, y
|
||
render()
|
||
|
||
term.setCursorPos(1,scr_y)
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.setTextColor(colors.black)
|
||
term.clearLine()
|
||
term.write("Press CTRL or 'N' when ready.")
|
||
|
||
while true do
|
||
evt, butt, x, y = os.pullEvent()
|
||
if (evt == "mouse_click" or evt == "mouse_drag") then
|
||
if chars[y-sy] then
|
||
if chars[y-sy][x] then
|
||
if (chars[y-sy][x] ~= char) then
|
||
char = chars[y-sy][x]
|
||
render()
|
||
end
|
||
else
|
||
return char
|
||
end
|
||
else
|
||
return char
|
||
end
|
||
elseif evt == "key" then
|
||
if (butt == keys.n) or (butt == keys.leftCtrl) then
|
||
return char
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local dontDragThisTime = false
|
||
local resetInputState = function()
|
||
miceDown = {}
|
||
keysDown = {}
|
||
isDragging = false
|
||
dontDragThisTime = true
|
||
end
|
||
|
||
local gotoCoords = function()
|
||
local newX = bottomPrompt("Goto X:")
|
||
newX = tonumber(newX)
|
||
local newY
|
||
if newX then
|
||
newY = bottomPrompt("Goto Y:")
|
||
newY = tonumber(newY)
|
||
paint.scrollX = newX or paint.scrollX
|
||
paint.scrollY = newY or paint.scrollY
|
||
end
|
||
end
|
||
|
||
local renderAllPAIN = function()
|
||
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
|
||
end
|
||
|
||
local checkIfNFP = function(str) --does not check table format, only string format
|
||
local good = {
|
||
['0'] = true,
|
||
['1'] = true,
|
||
['2'] = true,
|
||
['3'] = true,
|
||
['4'] = true,
|
||
['5'] = true,
|
||
['6'] = true,
|
||
['7'] = true,
|
||
['8'] = true,
|
||
['9'] = true,
|
||
a = true,
|
||
b = true,
|
||
c = true,
|
||
d = true,
|
||
e = true,
|
||
f = true,
|
||
[" "] = true,
|
||
["\n"] = true
|
||
}
|
||
for a = 1, #str do
|
||
if not good[str:sub(a,a):lower()] then
|
||
return false
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
local selectRegion = function()
|
||
local position = {}
|
||
local mevt, id, x1, y1 = os.pullEvent("mouse_click")
|
||
local x2, y2, pos, redrawID
|
||
local renderRectangle = true
|
||
redrawID = os.startTimer(0.5)
|
||
while true do
|
||
mevt, id, x2, y2 = os.pullEvent()
|
||
if mevt == "mouse_up" or mevt == "mouse_drag" or mevt == "mouse_click" then
|
||
pos = {{
|
||
x1 < x2 and x1 or x2,
|
||
y1 < y2 and y1 or y2
|
||
},{
|
||
x1 < x2 and x2 or x1,
|
||
y1 < y2 and y2 or y1
|
||
}}
|
||
end
|
||
if mevt == "mouse_up" then
|
||
break
|
||
end
|
||
if (mevt == "mouse_drag") or (mevt == "timer" and id == redrawID) then
|
||
renderAllPAIN()
|
||
if renderRectangle then
|
||
term.setTextColor(rendback.t)
|
||
term.setBackgroundColor(rendback.b)
|
||
for y = pos[1][2], pos[2][2] do
|
||
if y ~= scr_y then
|
||
term.setCursorPos(pos[1][1], y)
|
||
if (y == pos[1][2] or y == pos[2][2]) then
|
||
term.write(("#"):rep(1 + pos[2][1] - pos[1][1]))
|
||
else
|
||
term.write("#")
|
||
term.setCursorPos(pos[2][1], y)
|
||
term.write("#")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if (mevt == "timer" and id == redrawID) then
|
||
renderRectangle = not renderRectangle
|
||
redrawID = os.startTimer(0.25)
|
||
end
|
||
end
|
||
local output = {}
|
||
for k,v in pairs(paintEncoded[frame]) do
|
||
if v.x >= pos[1][1] and v.x <= pos[2][1] then
|
||
if v.y >= pos[1][2] and v.y <= pos[2][2] then
|
||
output[#output+1] = {
|
||
x = v.x - pos[1][1],
|
||
y = v.y - pos[1][2],
|
||
t = v.t,
|
||
c = v.c,
|
||
b = v.b,
|
||
m = v.m
|
||
}
|
||
end
|
||
end
|
||
end
|
||
return output, pos[1][1], pos[1][2], pos[2][1], pos[2][2]
|
||
end
|
||
|
||
local openNewFile = function(fname, allowNonImageNFP)
|
||
local file = fs.open(fname,"r")
|
||
local contents = file.readAll()
|
||
file.close()
|
||
if type(tun(contents)) ~= "table" then
|
||
term.setTextColor(colors.white)
|
||
if contents:sub(1,3) == "BLT" then --thank you bomb bloke for this easy identifier
|
||
if pMode ~= 1 then print("Importing from BLT...") end
|
||
return importFromBLT(fname), 3
|
||
elseif contents:sub(1,3) == "GIF" then
|
||
if pMode ~= 1 then print("Importing from GIF, this'll take a while...") end
|
||
return importFromGIF(fname,true), 5
|
||
elseif contents:sub(1,4) == "?!7\2" then
|
||
if pMode ~= 1 then print("Importing from UCG...") end
|
||
return {importFromUCG(fname)}, 6
|
||
elseif contents:find(string.char(30)) and contents:find(string.char(31)) then
|
||
if pMode ~= 1 then print("Importing from NFT...") end
|
||
return {importFromNFT(contents)}, 2
|
||
elseif (checkIfNFP(contents) or allowNonImageNFP) then
|
||
print("Importing from NFP...")
|
||
return {importFromPaint(contents)}, 1
|
||
else
|
||
return false, "That is not a valid image file."
|
||
end
|
||
else
|
||
return tun(contents), 4
|
||
end
|
||
end
|
||
|
||
local editCopy = function()
|
||
local board = bottomPrompt("Copy to board: ")
|
||
renderAllPAIN()
|
||
renderBottomBar("Select region to copy.")
|
||
local selectedDots = selectRegion()
|
||
theClipboard[board] = selectedDots
|
||
barmsg = "Copied to '"..board.."'"
|
||
doRender = true
|
||
keysDown = {}
|
||
miceDown = {}
|
||
end
|
||
local editCut = function()
|
||
local board = bottomPrompt("Cut to board: ")
|
||
renderAllPAIN()
|
||
renderBottomBar("Select region to copy.")
|
||
local selectedDots, x1, y1, x2, y2 = selectRegion()
|
||
theClipboard[board] = selectedDots
|
||
local dot
|
||
for i = #paintEncoded[frame], 1, -1 do
|
||
dot = paintEncoded[frame][i]
|
||
if dot.x >= x1 and dot.x <= x2 then
|
||
if dot.y >= y1 and dot.y <= y2 then
|
||
table.remove(paintEncoded[frame], i)
|
||
end
|
||
end
|
||
end
|
||
barmsg = "Cut to '"..board.."'"
|
||
doRender = true
|
||
saveToUndoBuffer()
|
||
keysDown = {}
|
||
miceDown = {}
|
||
end
|
||
|
||
local editPaste = function()
|
||
local board = bottomPrompt("Paste from board: ")
|
||
renderAllPAIN()
|
||
renderBottomBar("Click to paste. (top left corner)")
|
||
if theClipboard[board] then
|
||
local mevt
|
||
repeat
|
||
mevt = {os.pullEvent()}
|
||
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) <= scr_y-1)
|
||
for k,v in pairs(theClipboard[board]) do
|
||
paintEncoded[frame][#paintEncoded[frame]+1] = {
|
||
x = v.x + paint.scrollX + (mevt[3]),
|
||
y = v.y + paint.scrollY + (mevt[4]),
|
||
c = v.c,
|
||
t = v.t,
|
||
b = v.b,
|
||
m = v.m
|
||
}
|
||
end
|
||
paintEncoded[frame] = clearRedundant(paintEncoded[frame])
|
||
barmsg = "Pasted from '"..board.."'"
|
||
doRender = true
|
||
saveToUndoBuffer()
|
||
keysDown = {}
|
||
miceDown = {}
|
||
else
|
||
barmsg = "No such clipboard."
|
||
doRender = true
|
||
end
|
||
end
|
||
|
||
local displayMenu = function()
|
||
menuOptions = {"File","Edit","Window","Set","About","Exit"}
|
||
local diss = " "..tableconcat(menuOptions," ")
|
||
local cleary = scr_y-math.floor(#diss/scr_x)
|
||
|
||
local fileSave = function()
|
||
checkBadDots()
|
||
local output = deepCopy(paintEncoded)
|
||
if paint.doGray then
|
||
output = convertToGrayscale(output)
|
||
end
|
||
doRender = true
|
||
if not fileName then
|
||
renderBottomBar("Save as: ")
|
||
local fnguess = read()
|
||
if fs.isReadOnly(fnguess) then
|
||
barmsg = "'"..fnguess.."' is read only."
|
||
return false
|
||
elseif fnguess:gsub(" ","") == "" then
|
||
return false
|
||
elseif fs.isDir(fnguess) then
|
||
barmsg = "'"..fnguess.."' is already a directory."
|
||
return false
|
||
elseif #fnguess > 255 then
|
||
barmsg = "Filename is too long."
|
||
return false
|
||
else
|
||
fileName = fnguess
|
||
end
|
||
end
|
||
saveFile(fileName,output)
|
||
term.setCursorPos(9,scr_y)
|
||
return fileName
|
||
end
|
||
local filePrint = function()
|
||
local usedDots, dot = {}, {}
|
||
for a = 1, #paintEncoded[frame] do
|
||
dot = paintEncoded[frame][a]
|
||
if dot.x > paint.scrollX and dot.x < (paint.scrollX + 25) and dot.y > paint.scrollX and dot.y < (paint.scrollY + 21) then
|
||
if dot.c ~= " " then
|
||
usedDots[dot.t] = usedDots[dot.t] or {}
|
||
usedDots[dot.t][#usedDots[dot.t]+1] = {
|
||
x = dot.x - paint.scrollX,
|
||
y = dot.y - paint.scrollY,
|
||
char = dot.c
|
||
}
|
||
end
|
||
end
|
||
end
|
||
local dyes = {
|
||
[1] = "bonemeal",
|
||
[2] = "orange dye",
|
||
[4] = "magenta dye",
|
||
[8] = "light blue dye",
|
||
[16] = "dandelion yellow",
|
||
[32] = "lime dye",
|
||
[64] = "pink dye",
|
||
[128] = "gray dye",
|
||
[256] = "light gray dye",
|
||
[512] = "cyan dye",
|
||
[1024] = "purple dye",
|
||
[2048] = "lapis lazuli",
|
||
[4096] = "cocoa beans",
|
||
[8192] = "cactus green",
|
||
[16384] = "rose red",
|
||
[32768] = "ink sac",
|
||
}
|
||
local printer = peripheral.find("printer")
|
||
if not printer then
|
||
barmsg = "No printer found."
|
||
return false
|
||
end
|
||
local page
|
||
for color, dotList in pairs(usedDots) do
|
||
term.setBackgroundColor(colors.black)
|
||
term.setTextColor((color == colors.black) and colors.gray or color)
|
||
term.clear()
|
||
cwrite("Please insert "..dyes[color].." into the printer.", nil, math.floor(scr_y/2))
|
||
term.setTextColor(colors.lightGray)
|
||
cwrite("Then, press spacebar.", nil, math.floor(scr_y/2) + 1)
|
||
local evt
|
||
sleep(0)
|
||
repeat
|
||
evt = {os.pullEvent("key")}
|
||
until evt[2] == keys.space
|
||
page = page or printer.newPage()
|
||
if not page then
|
||
barmsg = "Check ink/paper."
|
||
return
|
||
end
|
||
for k,v in pairs(usedDots[color]) do
|
||
printer.setCursorPos(v.x, v.y)
|
||
printer.write(v.char)
|
||
end
|
||
end
|
||
printer.endPage()
|
||
barmsg = "Printed."
|
||
end
|
||
local fileExport = function(menuX,getRightToIt,_fileName)
|
||
local exportMode
|
||
if not tonumber(getRightToIt) then
|
||
exportMode = makeSubMenu(menuX or 8,scr_y-2,{"Paint","NFT","BLT","PAIN Native","GIF","UCG"})
|
||
else
|
||
exportMode = getRightToIt
|
||
end
|
||
doRender = true
|
||
if exportMode == false then return false end
|
||
local pe, exportName, writeIndent, result
|
||
if exportMode == 4 then
|
||
local exNm = fileSave()
|
||
if exNm then
|
||
changedImage = false
|
||
return exNm
|
||
else
|
||
return nil
|
||
end
|
||
else
|
||
checkBadDots()
|
||
if _fileName then
|
||
exportName, writeIndent = _fileName, #_fileName
|
||
else
|
||
exportName, writeIndent = bottomPrompt("Export to: /")
|
||
end
|
||
nfpoutput = ""
|
||
if fs.combine("",exportName) == "" then
|
||
barmsg = "Export cancelled."
|
||
return
|
||
end
|
||
if fs.isReadOnly(exportName) then
|
||
barmsg = "That's read-only."
|
||
return
|
||
end
|
||
if fs.exists(exportName) and (not _fileName) then
|
||
local plea = (progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?"
|
||
result, _wIn = bottomPrompt(plea.." (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
writeIndent = writeIndent + _wIn
|
||
if result ~= "y" then return end
|
||
end
|
||
local output
|
||
pe = deepCopy(paintEncoded)
|
||
if paint.doGray then
|
||
pe = convertToGrayscale(pe)
|
||
end
|
||
local doSerializeBLT = false
|
||
end
|
||
if exportMode == 1 then
|
||
output = exportToPaint(pe[frame])
|
||
if askToSerialize then
|
||
result, _wIn = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{})
|
||
writeIndent = writeIndent + _wIn
|
||
else result, _wIn = "n", 0 end
|
||
if result == "y" then
|
||
output = textutils.serialize(NFPserializeImage(output)):gsub(" ",""):gsub("\n",""):gsub(",}","}")
|
||
end
|
||
elseif exportMode == 2 then
|
||
output = exportToNFT(pe[frame])
|
||
elseif exportMode == 3 then
|
||
local doAllFrames, _wIn = bottomPrompt("Save all frames, or current? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl},writeIndent)
|
||
writeIndent = writeIndent + _wIn
|
||
if askToSerialize then
|
||
doSerializeBLT = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{},writeIndent) == "y"
|
||
end
|
||
output = textutils.serialise(exportToBLT(pe,exportName,doAllFrames == "y",doSerializeBLT))
|
||
elseif exportMode == 5 then
|
||
getGIF()
|
||
GIF.saveGIF(exportToGIF(pe),exportName)
|
||
elseif exportMode == 6 then
|
||
exportToUCG(exportName,pe[frame])
|
||
end
|
||
if ((exportMode ~= 3) and (exportMode ~= 4) and (exportMode ~= 5) and (exportMode ~= 6)) or doSerializeBLT then
|
||
local file = fs.open(exportName,"w")
|
||
file.write(output)
|
||
file.close()
|
||
end
|
||
return exportName
|
||
end
|
||
|
||
local editClear = function(ignorePrompt)
|
||
local outcum = ignorePrompt and "y" or bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
if outcum == "y" then
|
||
paintEncoded[frame] = {}
|
||
saveToUndoBuffer()
|
||
barmsg = "Cleared frame "..frame.."."
|
||
end
|
||
doRender = true
|
||
end
|
||
|
||
local editDelFrame = function()
|
||
local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
doRender = true
|
||
if outcum == "y" then
|
||
if #paintEncoded == 1 then
|
||
return editClear(true)
|
||
end
|
||
table.remove(paintEncoded,frame)
|
||
barmsg = "Deleted frame "..frame.."."
|
||
if paintEncoded[frame-1] then
|
||
frame = frame - 1
|
||
else
|
||
frame = frame + 1
|
||
end
|
||
if #paintEncoded < frame then
|
||
repeat
|
||
frame = frame - 1
|
||
until #paintEncoded >= frame
|
||
end
|
||
saveToUndoBuffer()
|
||
end
|
||
end
|
||
local editCrop = function()
|
||
local outcum = bottomPrompt("Crop all but visible? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
if outcum == "y" then
|
||
local ppos = 1
|
||
local deletedAmnt = 0
|
||
for a = #paintEncoded[frame], 1, -1 do
|
||
local x, y = paintEncoded[frame][a].x, paintEncoded[frame][a].y
|
||
if (x <= paint.scrollX) or (x > paint.scrollX + scr_x) or (y <= paint.scrollY) or (y > paint.scrollY + scr_y) then
|
||
table.remove(paintEncoded[frame],a)
|
||
deletedAmnt = deletedAmnt + 1
|
||
else
|
||
ppos = ppos + 1
|
||
end
|
||
if ppos > #paintEncoded[frame] then break end
|
||
end
|
||
saveToUndoBuffer()
|
||
barmsg = "Cropped frame."
|
||
end
|
||
doRender = true
|
||
end
|
||
local editBoxCharSelector = function()
|
||
paint.c = boxCharSelector()
|
||
end
|
||
local editSpecialCharSelector = function()
|
||
paint.c = boxCharSelector()
|
||
end
|
||
|
||
local windowSetScrSize = function()
|
||
local x,y
|
||
x = bottomPrompt("Scr.X OR monitor name:",{},nil,{keys.leftCtrl,keys.rightCtrl})
|
||
if x == "" then
|
||
return
|
||
elseif x == "pocket" then
|
||
screenEdges = {26,20}
|
||
elseif x == "turtle" then
|
||
screenEdges = {39,13}
|
||
elseif x == "computer" then
|
||
screenEdges = {51,19}
|
||
elseif tonumber(x) then
|
||
if tonumber(x) <= 0 then
|
||
barmsg = "Screen X must be greater than 0."
|
||
return
|
||
end
|
||
screenEdges[1] = math.abs(tonumber(x))
|
||
y = bottomPrompt("Scr.Y:",{},nil,{keys.leftCtrl,keys.rightCtrl})
|
||
if tonumber(y) then
|
||
if tonumber(y) <= 0 then
|
||
barmsg = "Screen Y must be greater than 0."
|
||
return
|
||
end
|
||
screenEdges[2] = math.abs(tonumber(y))
|
||
end
|
||
barmsg = "Screen size changed."
|
||
else
|
||
local mon = peripheral.wrap(x)
|
||
if not mon then
|
||
barmsg = "No such monitor."
|
||
return
|
||
else
|
||
if peripheral.getType(x) ~= "monitor" then
|
||
barmsg = "That's not a monitor."
|
||
return
|
||
else
|
||
screenEdges[1], screenEdges[2] = mon.getSize()
|
||
barmsg = "Screen size changed."
|
||
return
|
||
end
|
||
end
|
||
end
|
||
end
|
||
local aboutPAIN = function()
|
||
local helpText = [[
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Advanced Paint Program
|
||
by LDDestroier
|
||
or EldidiStroyrr
|
||
if you please!
|
||
|
||
PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility.
|
||
|
||
The main focus during development is to add more functions that you might see in MSPAINT such as lines or a proper fill tool (which I don't have, grr hiss boo), as well as to export/import to and from as many image formats as possible.
|
||
|
||
My ultimate goal is to have PAIN be the default paint program for most every operating system on the forums. In order to do this, I'll need to make sure that PAIN is stable, easy to use, and can be easily limited by an OS to work with more menial tasks like making a single icon or what have you.
|
||
]]
|
||
guiHelp(helpText)
|
||
end
|
||
local aboutFileFormats = function()
|
||
local helpText = [[
|
||
Here's info on the file formats.
|
||
|
||
"NFP":
|
||
Used in rom/programs/paint, and the format for paintutils. It's a handy format, but the default rendering function is inefficient as hell, and it does not store text data, only background.
|
||
Cannot save multiple frames.
|
||
|
||
"NFT":
|
||
Used in npaintpro and most everything else, it's my favorite of the file formats because it does what NFP does, but allows for text in the pictures. Useful for storing screenshots or small icons where an added level of detail is handy. Created by nitrogenfingers, thank him.
|
||
Cannot save multiple frames.
|
||
|
||
"BLT":
|
||
Used exclusively with Bomb Bloke's BLittle API, and as such is handy with making pictures with block characters. Just keep in mind that those 2*3 grid squares in PAIN represent individual characters in BLT.
|
||
BLT can save multiple frames!
|
||
|
||
"PAIN Native":
|
||
The basic, tabular, and wholly inefficient format that PAIN uses. Useful for doing math within the program, not so much for long term file storage. It stores text, but just use NFT if you don't need multiple frames.
|
||
Obviously, this can save multiple frames.
|
||
|
||
"GIF":
|
||
The API was made by Bomb Bloke, huge thanks for that, but GIF is a universal file format used in real paint programs. Very useful for converting files on your computer to something like NFP, but doesn't store text. Be careful when opening up big GIF files, they can take a long time to load.
|
||
Being GIF, this saves multiple frames!
|
||
|
||
"UCG":
|
||
Stands for Universal Compressed Graphics. This format was made by ardera, and uses Huffman Code and run-length encoding in order to reduce file sizes tremendously. However, it only saves backgrounds and not text data.
|
||
Cannot save multiple frames.
|
||
|
||
|
||
I recommend using NFT if you don't need multiple frames, NFP if you don't need text, UCG if the picture is really big, Native PAIN if you need both text and multiframe support, and GIF if you want to use something like MS Paint or Pinta or GIMP or whatever.
|
||
]]
|
||
guiHelp(helpText)
|
||
end
|
||
local menuPoses = {}
|
||
local menuFunctions = {
|
||
[1] = function() --File
|
||
while true do
|
||
--renderAllPAIN()
|
||
local output, longestLen = makeSubMenu(1,cleary-1,{
|
||
"Save",
|
||
"Save As",
|
||
"Export",
|
||
"Open",
|
||
((peripheral.find("printer")) and "Print" or nil)
|
||
})
|
||
doRender = true
|
||
if output == 1 then -- Save
|
||
local _fname = fileExport(_,defaultSaveFormat,fileName)
|
||
if _fname then
|
||
barmsg = "Saved as '".._fname.."'"
|
||
lastPaintEncoded = deepCopy(paintEncoded)
|
||
changedImage = false
|
||
end
|
||
break
|
||
elseif output == 2 then -- Save As
|
||
local oldfilename = fileName
|
||
fileName = nil
|
||
local res = fileExport(_,defaultSaveFormat)
|
||
if not res then
|
||
fileName = oldfilename
|
||
end
|
||
barmsg = "Saved as '"..fileName.."'"
|
||
elseif output == 3 then --Export
|
||
local res = fileExport(longestLen+1)
|
||
if res then
|
||
barmsg = "Exported as '"..res.."'"
|
||
break
|
||
end
|
||
elseif output == 4 then -- Open
|
||
renderBottomBar("Pick an image file.")
|
||
local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(fileName or progname), false, false, false, true, false, nil, true)
|
||
if newPath then
|
||
local pen, form = openNewFile(newPath, painconfig.readNonImageAsNFP)
|
||
if not pen then
|
||
barmsg = form
|
||
else
|
||
fileName = newPath
|
||
paintEncoded, lastPaintEncoded = pen, deepCopy(pen)
|
||
defaultSaveFormat = form
|
||
undoPos = 1
|
||
undoBuffer = {deepCopy(paintEncoded)}
|
||
barmsg = "Opened '" .. fs.getName(newPath) .. "'"
|
||
paint.scrollX, paint.scrollY, paint.doGray = 1, 1, false
|
||
doRender = true
|
||
end
|
||
end
|
||
break
|
||
elseif output == 5 then -- Print
|
||
filePrint()
|
||
break
|
||
elseif output == false then
|
||
return "nobreak"
|
||
end
|
||
reRenderPAIN(true)
|
||
end
|
||
end,
|
||
[2] = function() --Edit
|
||
local output = makeSubMenu(6,cleary-1,{
|
||
"Delete Frame",
|
||
"Clear Frame",
|
||
"Crop Frame",
|
||
"Choose Box Character",
|
||
"Choose Special Character",
|
||
"BLittle Shrink",
|
||
"Copy Region",
|
||
"Cut Region",
|
||
"Paste Region"
|
||
})
|
||
doRender = true
|
||
if output == 1 then
|
||
editDelFrame()
|
||
elseif output == 2 then
|
||
editClear()
|
||
elseif output == 3 then
|
||
editCrop()
|
||
elseif output == 4 then
|
||
editBoxCharSelector()
|
||
elseif output == 5 then
|
||
editSpecialCharSelector()
|
||
elseif output == 6 then
|
||
local res = bottomPrompt("You sure? It's unreversable! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
if res == "y" then
|
||
getBlittle()
|
||
local bltPE = blittle.shrink(NFPserializeImage(exportToPaint(paintEncoded[frame])))
|
||
_G.SHRINKOUT = bltPE
|
||
paintEncoded[frame] = {}
|
||
for y = 1, bltPE.height do
|
||
for x = 1, bltPE.width do
|
||
paintEncoded[frame][#paintEncoded[frame]+1] = {
|
||
c = bltPE[1][y]:sub(x,x),
|
||
t = BTC(bltPE[2][y]:sub(x,x),true),
|
||
b = BTC(bltPE[3][y]:sub(x,x),true),
|
||
x = x,
|
||
y = y,
|
||
}
|
||
end
|
||
end
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
barmsg = "Shrunk image."
|
||
end
|
||
elseif output == 7 then
|
||
editCopy()
|
||
elseif output == 8 then
|
||
editCut()
|
||
elseif output == 9 then
|
||
editPaste()
|
||
elseif output == false then
|
||
return "nobreak"
|
||
end
|
||
end,
|
||
[3] = function() --Window
|
||
local output = makeSubMenu(11,cleary-1,{
|
||
"Set Screen Size",
|
||
"Set Scroll XY",
|
||
"Set Grid Colors"
|
||
})
|
||
doRender = true
|
||
if output == 1 then
|
||
windowSetScrSize()
|
||
elseif output == 2 then
|
||
gotoCoords()
|
||
elseif output == 3 then
|
||
rendback.b = paint.b
|
||
rendback.t = paint.t
|
||
doRender = true
|
||
elseif output == false then
|
||
return "nobreak"
|
||
end
|
||
end,
|
||
[4] = function() --Set
|
||
local output = makeSubMenu(17,cleary-1,{
|
||
(painconfig.readNonImageAsNFP and "(T)" or "(F)") .. " Load Non-images",
|
||
(painconfig.useFlattenGIF and "(T)" or "(F)") .. " Flatten GIFs",
|
||
(painconfig.gridBleedThrough and "(T)" or "(F)") .. " Always Render Grid",
|
||
(painconfig.doFillDiagonal and "(T)" or "(F)") .. " Fill Diagonally",
|
||
(painconfig.doFillAnimation and "(T)" or "(F)") .. " Do Fill Animation",
|
||
"(" .. painconfig.undoBufferSize .. ") Set Undo Buffer Size",
|
||
})
|
||
if output == 1 then
|
||
painconfig.readNonImageAsNFP = not painconfig.readNonImageAsNFP
|
||
elseif output == 2 then
|
||
painconfig.useFlattenGIF = not painconfig.useFlattenGIF
|
||
elseif output == 3 then
|
||
painconfig.gridBleedThrough = not painconfig.gridBleedThrough
|
||
elseif output == 4 then
|
||
painconfig.doFillDiagonal = not painconfig.doFillDiagonal
|
||
elseif output == 5 then
|
||
painconfig.doFillAnimation = not painconfig.doFillAnimation
|
||
elseif output == 6 then
|
||
local newUndoBufferSize = bottomPrompt("New undo buffer size: ")
|
||
if tonumber(newUndoBufferSize) then
|
||
painconfig.undoBufferSize = math.abs(tonumber(newUndoBufferSize))
|
||
undoBuffer = {deepCopy(paintEncoded)}
|
||
undoPos = 1
|
||
else
|
||
return
|
||
end
|
||
end
|
||
saveConfig()
|
||
end,
|
||
[5] = function() --About
|
||
local output = makeSubMenu(17,cleary-1,{
|
||
"PAIN",
|
||
"File Formats",
|
||
"Help!"
|
||
})
|
||
doRender = true
|
||
if output == 1 then
|
||
aboutPAIN()
|
||
elseif output == 2 then
|
||
aboutFileFormats()
|
||
elseif output == 3 then
|
||
guiHelp()
|
||
doRender = true
|
||
end
|
||
end,
|
||
[6] = function() --Exit
|
||
if changedImage then
|
||
local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
|
||
sleep(0)
|
||
if outcum == "y" then
|
||
return "exit"
|
||
else
|
||
doRender = true
|
||
return nil
|
||
end
|
||
else
|
||
return "exit"
|
||
end
|
||
end,
|
||
}
|
||
local cursor = 1
|
||
local redrawmenu = true
|
||
local initial = os.time()
|
||
local clickdelay = 0.003
|
||
|
||
local redrawTheMenu = function()
|
||
for a = cleary,scr_y do
|
||
term.setCursorPos(1,a)
|
||
term.setBackgroundColor(colors.lightGray)
|
||
term.clearLine()
|
||
end
|
||
term.setCursorPos(2,cleary)
|
||
for a = 1, #menuOptions do
|
||
if a == cursor then
|
||
term.setTextColor(colors.black)
|
||
term.setBackgroundColor(colors.white)
|
||
else
|
||
term.setTextColor(colors.black)
|
||
term.setBackgroundColor(colors.lightGray)
|
||
end
|
||
menuPoses[a] = {term.getCursorPos()}
|
||
write(menuOptions[a])
|
||
term.setBackgroundColor(colors.lightGray)
|
||
if a ~= #menuOptions then
|
||
write(" ")
|
||
end
|
||
end
|
||
redrawmenu = false
|
||
end
|
||
|
||
while true do
|
||
if redrawmenu then
|
||
redrawTheMenu()
|
||
redrawmenu = false
|
||
end
|
||
local event,key,x,y = getEvents("key","char","mouse_click","mouse_up","mouse_drag")
|
||
if event == "key" then
|
||
if key == keys.left then
|
||
redrawmenu = true
|
||
cursor = cursor - 1
|
||
elseif key == keys.right then
|
||
redrawmenu = true
|
||
cursor = cursor + 1
|
||
elseif key == keys.enter then
|
||
redrawmenu = true
|
||
local res = menuFunctions[cursor]()
|
||
if res == "exit" then
|
||
return "exit"
|
||
elseif res == "nobreak" then
|
||
reRenderPAIN(true)
|
||
else
|
||
return
|
||
end
|
||
elseif key == keys.leftCtrl or key == keys.rightCtrl then
|
||
doRender = true
|
||
return
|
||
end
|
||
elseif event == "char" then
|
||
for a = 1, #menuOptions do
|
||
if key:lower() == menuOptions[a]:sub(1,1):lower() and a ~= cursor then
|
||
cursor = a
|
||
redrawmenu = true
|
||
break
|
||
end
|
||
end
|
||
elseif event == "mouse_click" or event == "mouse_up" then
|
||
if y < cleary then
|
||
doRender = true
|
||
return
|
||
elseif key == 1 and initial+clickdelay < os.time() then --key? more like button
|
||
for a = 1, #menuPoses do
|
||
if y == menuPoses[a][2] then
|
||
if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
|
||
cursor = a
|
||
redrawTheMenu()
|
||
local res = menuFunctions[a]()
|
||
os.queueEvent("queue")
|
||
os.pullEvent("queue")
|
||
if res == "exit" then
|
||
return "exit"
|
||
else
|
||
return
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if (initial+clickdelay < os.time()) and string.find(event,"mouse") then
|
||
if key == 1 then --key? key? what key? all I see is button!
|
||
for a = 1, #menuPoses do
|
||
if y == menuPoses[a][2] then
|
||
if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
|
||
cursor = a
|
||
redrawmenu = true
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if cursor < 1 then
|
||
cursor = #menuOptions
|
||
elseif cursor > #menuOptions then
|
||
cursor = 1
|
||
end
|
||
end
|
||
end
|
||
|
||
local lastMX,lastMY,isDragging
|
||
|
||
local doNonEventDrivenMovement = function() --what a STUPID function name, dude
|
||
local didMove
|
||
while true do
|
||
didMove = false
|
||
if (not keysDown[keys.leftShift]) and (not isDragging) and (not keysDown[keys.tab]) then
|
||
if keysDown[keys.right] then
|
||
paint.scrollX = paint.scrollX + 1
|
||
didMove = true
|
||
elseif keysDown[keys.left] then
|
||
paint.scrollX = paint.scrollX - 1
|
||
didMove = true
|
||
end
|
||
if keysDown[keys.down] then
|
||
paint.scrollY = paint.scrollY + 1
|
||
didMove = true
|
||
elseif keysDown[keys.up] then
|
||
paint.scrollY = paint.scrollY - 1
|
||
didMove = true
|
||
end
|
||
if didMove then
|
||
if lastMX and lastMY then
|
||
if miceDown[1] then
|
||
os.queueEvent("mouse_click",1,lastMX,lastMY)
|
||
end
|
||
if miceDown[2] then
|
||
os.queueEvent("mouse_click",2,lastMX,lastMY)
|
||
end
|
||
end
|
||
doRender = true
|
||
end
|
||
end
|
||
sleep(0)
|
||
end
|
||
end
|
||
|
||
local linePoses = {}
|
||
local dragPoses = {}
|
||
|
||
local listAllMonitors = function()
|
||
term.setBackgroundColor(colors.gray)
|
||
term.setTextColor(colors.white)
|
||
local periphs = peripheral.getNames()
|
||
local mons = {}
|
||
for a = 1, #periphs do
|
||
if peripheral.getType(periphs[a]) == "monitor" then
|
||
mons[#mons+1] = periphs[a]
|
||
end
|
||
end
|
||
if #mons == 0 then
|
||
mons[1] = "No monitors found."
|
||
end
|
||
term.setCursorPos(3,1)
|
||
term.clearLine()
|
||
term.setTextColor(colors.yellow)
|
||
term.write("All monitors:")
|
||
term.setTextColor(colors.white)
|
||
for y = 1, #mons do
|
||
term.setCursorPos(2,y+1)
|
||
term.clearLine()
|
||
term.write(mons[y])
|
||
end
|
||
sleep(0)
|
||
getEvents("char","mouse_click")
|
||
doRender = true
|
||
end
|
||
|
||
local getInput = function() --gotta catch them all
|
||
local button, x, y, oldmx, oldmy, origx, origy
|
||
local isDragging = false
|
||
local proceed = false
|
||
renderBar(barmsg)
|
||
while true do
|
||
doRender = false
|
||
local oldx,oldy = paint.scrollX,paint.scrollY
|
||
local evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)}
|
||
if (evt[1] == "mouse_scroll") and (not viewing) then
|
||
local dir = evt[2]
|
||
if dir == 1 then
|
||
if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
|
||
paint.t = paint.t * 2
|
||
if paint.t > 32768 then
|
||
paint.t = 32768
|
||
end
|
||
else
|
||
paint.b = paint.b * 2
|
||
if paint.b > 32768 then
|
||
paint.b = 32768
|
||
end
|
||
end
|
||
else
|
||
if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
|
||
paint.t = math.ceil(paint.t / 2)
|
||
if paint.t < 1 then
|
||
paint.t = 1
|
||
end
|
||
else
|
||
paint.b = math.ceil(paint.b / 2)
|
||
if paint.b < 1 then
|
||
paint.b = 1
|
||
end
|
||
end
|
||
end
|
||
renderBar(barmsg)
|
||
elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then
|
||
if evt[1] == "mouse_click" then
|
||
origx, origy = evt[3], evt[4]
|
||
end
|
||
oldmx,oldmy = x or evt[3], y or evt[4]
|
||
lastMX,lastMY = evt[3],evt[4]
|
||
button,x,y = evt[2],evt[3],evt[4]
|
||
if renderBlittle then
|
||
x = 2*x
|
||
y = 3*y
|
||
lastMX = 2*lastMX
|
||
lastMY = 3*lastMY
|
||
end
|
||
linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}}
|
||
miceDown[button] = true
|
||
if y <= scr_y-(renderBlittle and 0 or doRenderBar) then
|
||
if (button == 3) then
|
||
putDownText(x,y)
|
||
miceDown = {}
|
||
keysDown = {}
|
||
doRender = true
|
||
elseif button == 1 then
|
||
if keysDown[keys.leftShift] and evt[1] == "mouse_click" then
|
||
isDragging = true
|
||
end
|
||
if isDragging then
|
||
if evt[1] == "mouse_click" or dontDragThisTime then
|
||
dragPoses[1] = {x=x,y=y}
|
||
end
|
||
dragPoses[2] = {x=x,y=y}
|
||
local points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
|
||
renderAllPAIN()
|
||
for a = 1, #points do
|
||
term.setCursorPos(points[a].x, points[a].y)
|
||
term.blit(paint.c, CTB(paint.t), CTB(paint.b))
|
||
end
|
||
elseif (not dontDragThisTime) then
|
||
if evt[1] == "mouse_drag" then
|
||
local points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y)
|
||
for a = 1, #points do
|
||
putDotDown({x=points[a].x, y=points[a].y})
|
||
end
|
||
else
|
||
putDotDown({x=x, y=y})
|
||
end
|
||
changedImage = true
|
||
doRender = true
|
||
end
|
||
dontDragThisTime = false
|
||
elseif button == 2 and y <= scr_y-(renderBlittle and 0 or doRenderBar) then
|
||
deleteDot(x+paint.scrollX,y+paint.scrollY)
|
||
changedImage = true
|
||
doRender = true
|
||
end
|
||
elseif origy >= scr_y-(renderBlittle and 0 or doRenderBar) then
|
||
miceDown = {}
|
||
keysDown = {}
|
||
isDragging = false
|
||
local res = displayMenu()
|
||
if res == "exit" then break end
|
||
doRender = true
|
||
end
|
||
elseif (evt[1] == "mouse_up") and (not viewing) then
|
||
origx,origy = 0,0
|
||
local button = evt[2]
|
||
miceDown[button] = false
|
||
oldmx,oldmy = nil,nil
|
||
lastMX, lastMY = nil,nil
|
||
if isDragging then
|
||
local points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
|
||
for a = 1, #points do
|
||
putDotDown({x=points[a].x, y=points[a].y})
|
||
end
|
||
changedImage = true
|
||
doRender = true
|
||
end
|
||
saveToUndoBuffer()
|
||
isDragging = false
|
||
elseif evt[1] == "key" then
|
||
local key = evt[2]
|
||
if (isDragging or not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then
|
||
if key == keys.right and (not keysDown[keys.right]) then
|
||
paint.scrollX = paint.scrollX + 1
|
||
doRender = true
|
||
elseif key == keys.left and (not keysDown[keys.left]) then
|
||
paint.scrollX = paint.scrollX - 1
|
||
doRender = true
|
||
end
|
||
if key == keys.down and (not keysDown[keys.down]) then
|
||
paint.scrollY = paint.scrollY + 1
|
||
doRender = true
|
||
elseif key == keys.up and (not keysDown[keys.up]) then
|
||
paint.scrollY = paint.scrollY - 1
|
||
doRender = true
|
||
end
|
||
end
|
||
keysDown[key] = true
|
||
if key == keys.space then
|
||
if keysDown[keys.leftShift] then
|
||
evenDrawGrid = not evenDrawGrid
|
||
else
|
||
doRenderBar = math.abs(doRenderBar-1)
|
||
end
|
||
doRender = true
|
||
end
|
||
if key == keys.b then
|
||
local blTerm, oldTerm = getBlittle()
|
||
renderBlittle = not renderBlittle
|
||
isDragging = false
|
||
term.setBackgroundColor(rendback.b)
|
||
term.clear()
|
||
if renderBlittle then
|
||
term.redirect(blTerm)
|
||
blTerm.setVisible(true)
|
||
else
|
||
term.redirect(oldTerm)
|
||
blTerm.setVisible(false)
|
||
end
|
||
doRender = true
|
||
scr_x, scr_y = term.current().getSize()
|
||
end
|
||
if keysDown[keys.leftAlt] then
|
||
if (not renderBlittle) then
|
||
if (key == keys.c) then
|
||
editCopy()
|
||
elseif (key == keys.x) then
|
||
editCut()
|
||
elseif (key == keys.v) then
|
||
editPaste()
|
||
end
|
||
end
|
||
else
|
||
if (key == keys.c) and (not renderBlittle) then
|
||
gotoCoords()
|
||
resetInputState()
|
||
doRender = true
|
||
end
|
||
end
|
||
if (keysDown[keys.leftShift]) and (not isDragging) then
|
||
if key == keys.left then
|
||
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0)
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
elseif key == keys.right then
|
||
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0)
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
elseif key == keys.up then
|
||
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1)
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
elseif key == keys.down then
|
||
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1)
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
end
|
||
end
|
||
if keysDown[keys.leftAlt] then
|
||
if #paintEncoded > 1 then
|
||
if key == keys.equals and paintEncoded[frame+1] then --basically plus
|
||
local first = deepCopy(paintEncoded[frame])
|
||
local next = deepCopy(paintEncoded[frame+1])
|
||
paintEncoded[frame] = next
|
||
paintEncoded[frame+1] = first
|
||
frame = frame + 1
|
||
barmsg = "Swapped prev frame."
|
||
doRender = true
|
||
changedImage = true
|
||
saveToUndoBuffer()
|
||
end
|
||
if key == keys.minus and paintEncoded[frame-1] then
|
||
local first = deepCopy(paintEncoded[frame])
|
||
local next = deepCopy(paintEncoded[frame-1])
|
||
paintEncoded[frame] = next
|
||
paintEncoded[frame-1] = first
|
||
frame = frame - 1
|
||
barmsg = "Swapped next frame."
|
||
doRender = true
|
||
changedImage = true
|
||
saveToUndoBuffer()
|
||
end
|
||
end
|
||
elseif keysDown[keys.leftShift] then
|
||
if #paintEncoded > 1 then
|
||
if key == keys.equals and paintEncoded[frame+1] then --basically plus
|
||
for a = 1, #paintEncoded[frame] do
|
||
paintEncoded[frame+1][#paintEncoded[frame+1] + 1] = paintEncoded[frame][a]
|
||
end
|
||
table.remove(paintEncoded, frame)
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
barmsg = "Merged next frame."
|
||
doRender = true
|
||
changedImage = true
|
||
saveToUndoBuffer()
|
||
end
|
||
if key == keys.minus and paintEncoded[frame-1] then
|
||
for a = 1, #paintEncoded[frame] do
|
||
paintEncoded[frame-1][#paintEncoded[frame-1] + 1] = paintEncoded[frame][a]
|
||
end
|
||
table.remove(paintEncoded, frame)
|
||
frame = frame - 1
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
barmsg = "Merged previous frame."
|
||
doRender = true
|
||
changedImage = true
|
||
saveToUndoBuffer()
|
||
end
|
||
end
|
||
else
|
||
if key == keys.equals then --basically 'plus'
|
||
if renderBlittle then
|
||
frame = frame + 1
|
||
if frame > #paintEncoded then frame = 1 end
|
||
else
|
||
if not paintEncoded[frame+1] then
|
||
paintEncoded[frame+1] = {}
|
||
local sheet = paintEncoded[frame]
|
||
if keysDown[keys.rightShift] then
|
||
paintEncoded[frame+1] = deepCopy(sheet)
|
||
end
|
||
end
|
||
frame = frame + 1
|
||
end
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
elseif key == keys.minus then
|
||
if renderBlittle then
|
||
frame = frame - 1
|
||
if frame < 1 then frame = #paintEncoded end
|
||
else
|
||
if frame > 1 then
|
||
frame = frame - 1
|
||
end
|
||
end
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
changedImage = true
|
||
end
|
||
end
|
||
if not renderBlittle then
|
||
if key == keys.m then
|
||
local incum = bottomPrompt("Set meta: ",metaHistory)
|
||
paint.m = incum:gsub(" ","") ~= "" and incum or paint.m
|
||
if paint.m ~= metaHistory[#metaHistory] then
|
||
metaHistory[#metaHistory+1] = paint.m
|
||
end
|
||
doRender = true
|
||
isDragging = false
|
||
end
|
||
if key == keys.f7 then
|
||
bepimode = not bepimode
|
||
doRender = true
|
||
end
|
||
if key == keys.t then
|
||
renderBottomBar("Click to place text.")
|
||
local mevt
|
||
repeat
|
||
mevt = {os.pullEvent()}
|
||
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) <= scr_y-(renderBlittle and 0 or doRenderBar))
|
||
if not (mevt[1] == "key" and mevt[2] == keys.x) then
|
||
local x,y = mevt[3],mevt[4]
|
||
if renderBlittle then
|
||
x = 2*x
|
||
y = 3*y
|
||
end
|
||
putDownText(x,y)
|
||
miceDown = {}
|
||
keysDown = {}
|
||
end
|
||
doRender = true
|
||
changedImage = true
|
||
isDragging = false
|
||
end
|
||
if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) and (not isCurrentlyFilling) then
|
||
renderBottomBar("Click to fill area.")
|
||
local mevt
|
||
repeat
|
||
mevt = {os.pullEvent()}
|
||
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] <= 2 and (mevt[4] or scr_y) <= scr_y-(renderBlittle and 0 or doRenderBar))
|
||
if not (mevt[1] == "key" and mevt[2] == keys.x) then
|
||
local x,y = mevt[3],mevt[4]
|
||
if renderBlittle then
|
||
x = 2*x
|
||
y = 3*y
|
||
end
|
||
os.queueEvent("filltool_async", frame, x, y, paint, mevt[2] == 2)
|
||
miceDown = {}
|
||
keysDown = {}
|
||
end
|
||
doRender = true
|
||
changedImage = true
|
||
isDragging = false
|
||
end
|
||
if key == keys.p then
|
||
renderBottomBar("Pick color with cursor:")
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
local mevt
|
||
repeat
|
||
mevt = {os.pullEvent()}
|
||
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[2] == 1 and mevt[4] <= scr_y)
|
||
if not (mevt[1] == "key" and mevt[2] == keys.x) then
|
||
local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY
|
||
if renderBlittle then
|
||
x = 2*x
|
||
y = 3*y
|
||
end
|
||
local p
|
||
for a = 1, #paintEncoded[frame] do
|
||
p = paintEncoded[frame][a]
|
||
if (p.x == x) and (p.y == y) then
|
||
paint.t = p.t or paint.t
|
||
paint.b = p.b or paint.b
|
||
paint.c = p.c or paint.c
|
||
paint.m = p.m or paint.m
|
||
miceDown = {}
|
||
keysDown = {}
|
||
doRender = true
|
||
isDragging = false
|
||
break
|
||
end
|
||
end
|
||
miceDown = {}
|
||
keysDown = {}
|
||
end
|
||
doRender = true
|
||
isDragging = false
|
||
end
|
||
if (key == keys.leftCtrl or key == keys.rightCtrl) then
|
||
keysDown = {[207] = keysDown[207]}
|
||
isDragging = false
|
||
local res = displayMenu()
|
||
paintEncoded = clearAllRedundant(paintEncoded)
|
||
if res == "exit" then break end
|
||
doRender = true
|
||
end
|
||
end
|
||
if (key == keys.f and keysDown[keys.leftShift]) then
|
||
local deredots = {}
|
||
changedImage = true
|
||
for a = 1, #paintEncoded[frame] do
|
||
local dot = paintEncoded[frame][a]
|
||
if dot.x-paint.scrollX > 0 and dot.x-paint.scrollX <= scr_x then
|
||
if dot.y-paint.scrollY > 0 and dot.y-paint.scrollY <= scr_y then
|
||
deredots[#deredots+1] = {dot.x-paint.scrollX, dot.y-paint.scrollY}
|
||
end
|
||
end
|
||
end
|
||
for y = 1, scr_y do
|
||
for x = 1, scr_x do
|
||
local good = true
|
||
for a = 1, #deredots do
|
||
if (deredots[a][1] == x) and (deredots[a][2] == y) then
|
||
good = bad
|
||
break
|
||
end
|
||
end
|
||
if good then
|
||
putDotDown({x=x, y=y})
|
||
end
|
||
end
|
||
end
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
end
|
||
if key == keys.g then
|
||
paint.doGray = not paint.doGray
|
||
changedImage = true
|
||
saveToUndoBuffer()
|
||
doRender = true
|
||
end
|
||
if key == keys.a then
|
||
paint.scrollX = 0
|
||
paint.scrollY = 0
|
||
doRender = true
|
||
end
|
||
if key == keys.n then
|
||
if keysDown[keys.leftShift] then
|
||
paint.c = specialCharSelector()
|
||
else
|
||
paint.c = boxCharSelector()
|
||
end
|
||
resetInputState()
|
||
doRender = true
|
||
end
|
||
if key == keys.f1 then
|
||
guiHelp()
|
||
resetInputState()
|
||
isDragging = false
|
||
end
|
||
if key == keys.f3 then
|
||
listAllMonitors()
|
||
resetInputState()
|
||
isDragging = false
|
||
end
|
||
if key == keys.leftBracket then
|
||
os.queueEvent("mouse_scroll",2,1,1)
|
||
elseif key == keys.rightBracket then
|
||
os.queueEvent("mouse_scroll",1,1,1)
|
||
end
|
||
if key == keys.z then
|
||
if keysDown[keys.leftAlt] and undoPos < #undoBuffer then
|
||
doRedo()
|
||
barmsg = "Redood."
|
||
doRender = true
|
||
elseif undoPos > 1 then
|
||
doUndo()
|
||
barmsg = "Undood."
|
||
doRender = true
|
||
end
|
||
end
|
||
elseif evt[1] == "key_up" then
|
||
local key = evt[2]
|
||
keysDown[key] = false
|
||
end
|
||
if (oldx~=paint.scrollX) or (oldy~=paint.scrollY) then
|
||
doRender = true
|
||
end
|
||
if doRender then
|
||
renderAllPAIN()
|
||
doRender = false
|
||
end
|
||
end
|
||
end
|
||
|
||
runPainEditor = function(...) --needs to be cleaned up
|
||
local tArg = table.pack(...)
|
||
if not (tArg[1] == "-n" or (not tArg[1])) then
|
||
fileName = shell.resolve(tostring(tArg[1]))
|
||
end
|
||
|
||
if not fileName then
|
||
paintEncoded = {{}}
|
||
elseif not fs.exists(fileName) then
|
||
local ex = fileName:sub(-4):lower()
|
||
if ex == ".nfp" then
|
||
defaultSaveFormat = 1
|
||
elseif ex == ".nft" then
|
||
defaultSaveFormat = 2
|
||
elseif ex == ".blt" then
|
||
defaultSaveFormat = 3
|
||
elseif ex == ".gif" then
|
||
defaultSaveFormat = 5
|
||
elseif ex == ".ucg" then
|
||
defaultSaveFormat = 6
|
||
else
|
||
defaultSaveFormat = 4
|
||
end
|
||
paintEncoded = {{}}
|
||
elseif fs.isDir(fileName) then
|
||
if math.random(1,32) == 1 then
|
||
write("Oh") sleep(0.2)
|
||
write(" My") sleep(0.2)
|
||
print(" God") sleep(0.3)
|
||
write("That is a") sleep(0.1) term.setTextColor(colors.red)
|
||
write(" FLIPPING") sleep(0.4)
|
||
print(" FOLDER.") sleep(0.2) term.setTextColor(colors.white)
|
||
print("You crazy person.") sleep(0.2)
|
||
else
|
||
print("That's a folder.")
|
||
end
|
||
return
|
||
else
|
||
paintEncoded, defaultSaveFormat = openNewFile(fileName, readNonImageAsNFP)
|
||
if not paintEncoded then
|
||
return print(defaultSaveFormat)
|
||
end
|
||
end
|
||
|
||
local asyncFillTool = function()
|
||
local event, frameNo, x, y, dot
|
||
isCurrentlyFilling = false
|
||
while true do
|
||
event, frameNo, x, y, dot, isDeleting = os.pullEvent("filltool_async")
|
||
isCurrentlyFilling = true
|
||
renderBottomBar("Filling area...")
|
||
fillTool(frameNo, x, y, dot, isDeleting)
|
||
isCurrentlyFilling = false
|
||
reRenderPAIN(doRenderBar == 0)
|
||
end
|
||
end
|
||
|
||
if not paintEncoded[frame] then paintEncoded = {paintEncoded} end
|
||
if pMode == 1 then
|
||
doRenderBar = 0
|
||
renderPAIN(paintEncoded[tonumber(tArg[5]) or 1],-(tonumber(tArg[3]) or 0),-(tonumber(tArg[4]) or 0)) -- 'pain filename view X Y frame'
|
||
sleep(0)
|
||
return
|
||
else
|
||
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
|
||
end
|
||
lastPaintEncoded = deepCopy(paintEncoded)
|
||
undoBuffer = {deepCopy(paintEncoded)}
|
||
parallel.waitForAny(getInput, doNonEventDrivenMovement, asyncFillTool)
|
||
|
||
term.setCursorPos(1,scr_y)
|
||
term.setBackgroundColor(colors.black)
|
||
term.clearLine()
|
||
end
|
||
|
||
if not shell then error("shell API is required, sorry") end
|
||
|
||
runPainEditor(...)
|