SHOW:
|
|
- or go back to the newest paste.
1 | --[[ | |
2 | PAIN image editor for ComputerCraft | |
3 | Get it with | |
4 | - | wget https://raw.githubusercontent.com/LDDestroier/CC/master/pain.lua pain |
4 | + | wget https://raw.githubusercontent.com/LDDestroier/CC/beta/pain.lua painb |
5 | - | pastebin get wJQ7jav0 pain |
5 | + | pastebin get eWpW2W5n painb |
6 | std ld pain pain | |
7 | ||
8 | This is a beta release. You fool! | |
9 | - | local plc = {} -- pain local, to avoid upvalue limit |
9 | + | To do: |
10 | - | plc.askToSerialize = false |
10 | + | + add file > open using LDDFM |
11 | - | plc.defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name |
11 | + | |
12 | local askToSerialize = false | |
13 | - | plc.defaultSaveFormat possible parameters: |
13 | + | local defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name |
14 | --[[ | |
15 | defaultSaveFormat possible parameters: | |
16 | 1. NFP (paint) | |
17 | 2. NFT (npaintpro) | |
18 | 3. BLT (blittle) | |
19 | 4. Native PAIN | |
20 | 5. GIF | |
21 | 6. UCG | |
22 | - | plc.progname = fs.getName(shell.getRunningProgram()) |
22 | + | 7. BMP |
23 | - | plc.apipath = ".painapi" |
23 | + | |
24 | local readNonImageAsNFP = true | |
25 | - | local painconfig = { |
25 | + | local useFlattenGIF = true |
26 | - | undoBufferSize = 8, -- amount of times undo will save your neck |
26 | + | local undoBufferSize = 8 |
27 | - | readNonImageAsNFP = true, -- reads non-image files as NFP images |
27 | + | |
28 | - | useFlattenGIF = true, -- will flatten compressed GIFs |
28 | + | local doFillDiagonal = false -- checks for diagonal dots when using fill tool |
29 | - | gridBleedThrough = false, -- will draw grid instead of character value of dots |
29 | + | local doFillAnimation = false -- whether or not to animate the fill tool |
30 | - | doFillDiagonal = false, -- checks for diagonal dots when using fill tool |
30 | + | |
31 | - | doFillAnimation = false, -- whether or not to animate the fill tool |
31 | + | local progname = fs.getName(shell.getRunningProgram()) |
32 | - | useSetVisible = false, -- whether or not to use term.current().setVisible, if possible |
32 | + | |
33 | local displayHelp = function() | |
34 | print(progname) | |
35 | - | local useConfig = function(mode) |
35 | + | print(progname.." <filename>") |
36 | - | if mode == "save" then |
36 | + | print(progname.." [-h/--help]") |
37 | - | local file = fs.open(fs.combine(plc.apipath,"painconfig"), "w") |
37 | + | |
38 | - | file.write(textutils.serialize(painconfig)) |
38 | + | |
39 | - | file.close() |
39 | + | |
40 | - | elseif mode == "load" then |
40 | + | local tsv = term.current().setVisible |
41 | - | if fs.exists(fs.combine(plc.apipath,"painconfig")) then |
41 | + | local undoBuffer |
42 | - | local file = fs.open(fs.combine(plc.apipath,"painconfig"), "r") |
42 | + | local undoPos = 1 |
43 | - | painconfig = textutils.unserialize(file.readAll()) |
43 | + | local pMode = 0 |
44 | local scr_x, scr_y = term.getSize() | |
45 | screenEdges = { | |
46 | scr_x, | |
47 | scr_y, | |
48 | } | |
49 | - | useConfig("load") |
49 | + | |
50 | - | useConfig("save") |
50 | + | local tArg = {...} |
51 | if (tArg[1] == "--help" or tArg[1] == "-h") and shell then | |
52 | - | plc.displayHelp = function() |
52 | + | return displayHelp() |
53 | - | print(plc.progname) |
53 | + | |
54 | - | print(plc.progname.." <filename>") |
54 | + | |
55 | - | print(plc.progname.." [-h/--help]") |
55 | + | if tArg[2] == "view" then |
56 | pMode = 1 | |
57 | elseif (tArg[2] == "moo") and (not fs.exists("moo")) then | |
58 | return print("This PAIN does not have Super Cow Powers.") | |
59 | - | plc.tsv = function(visible) |
59 | + | |
60 | - | if term.current().setVisible and painconfig.useSetVisible then |
60 | + | |
61 | - | term.current().setVisible(visible) |
61 | + | local fileName |
62 | if (not term.isColor()) and (pMode ~= 1) then | |
63 | error("PAIN only works with Advanced Computers at the moment.") | |
64 | end | |
65 | - | --local undoBuffer |
65 | + | |
66 | - | plc.undoPos = 1 |
66 | + | local tse = textutils.serialise |
67 | - | plc.pMode = 0 |
67 | + | local tun = textutils.unserialise |
68 | local paintEncoded | |
69 | - | local screenEdges = { |
69 | + | |
70 | local frame = 1 | |
71 | local doRender = false | |
72 | local metaHistory = {} | |
73 | local bepimode = false -- this is a family-friendly program! now stand still while I murder you | |
74 | - | plc.tArg = {...} |
74 | + | local evenDrawGrid = true -- will you evenDraw(the)Grid ? |
75 | - | if (plc.tArg[1] == "--help" or plc.tArg[1] == "-h") and shell then |
75 | + | local renderBlittle = false -- whether or not to render all in blittle |
76 | - | return plc.displayHelp() |
76 | + | |
77 | local firstBG = term.getBackgroundColor() | |
78 | local firstTX = term.getTextColor() | |
79 | - | if plc.tArg[2] == "view" then |
79 | + | local changedImage = false |
80 | - | plc.pMode = 1 |
80 | + | local isCurrentlyFilling = false |
81 | - | elseif (plc.tArg[2] == "moo") and (not fs.exists("moo")) then |
81 | + | |
82 | local _ | |
83 | local tableconcat = table.concat | |
84 | ||
85 | - | -- plc.fileName |
85 | + | |
86 | - | if (not term.isColor()) and (plc.pMode ~= 1) then |
86 | + | |
87 | - | error("PAIN only works with Advanced Computers.") |
87 | + | |
88 | } | |
89 | ||
90 | local grid | |
91 | ||
92 | local yield = function() | |
93 | os.queueEvent("yield") | |
94 | os.pullEvent("yield") | |
95 | end | |
96 | ||
97 | - | plc.bepimode = false -- this is a family-friendly program! now stand still while I murder you |
97 | + | |
98 | - | plc.evenDrawGrid = true -- will you evenDraw(the)Grid ? |
98 | + | |
99 | - | plc.renderBlittle = false -- whether or not to render all in blittle |
99 | + | |
100 | - | plc.firstBG = term.getBackgroundColor() |
100 | + | |
101 | - | plc.firstTX = term.getTextColor() |
101 | + | |
102 | - | plc.changedImage = false |
102 | + | |
103 | - | plc.isCurrentlyFilling = false |
103 | + | |
104 | - | local theClipboard = {} |
104 | + | |
105 | } | |
106 | local boxchar = {topLeft = true, topRight = true, left = true, right = true, bottomLeft = true, bottomRight = true} | |
107 | local swapColors = false -- swaps background and text colors, for use with those tricky box characters | |
108 | local scrollX, scrollY = 0, 0 | |
109 | ||
110 | local keysDown = {} | |
111 | local miceDown = {} | |
112 | ||
113 | local doRenderBar = 1 -- Not true or false | |
114 | ||
115 | local fixstr = function(str) | |
116 | return str:gsub("\\(%d%d%d)",string.char) | |
117 | end | |
118 | ||
119 | local choice = function(input,breakkeys,returnNumber) | |
120 | local fpos = 0 | |
121 | repeat | |
122 | event, key = os.pullEvent("key") | |
123 | if type(key) == "number" then key = keys.getName(key) end | |
124 | if key == nil then key = " " end | |
125 | if type(breakkeys) == "table" then | |
126 | for a = 1, #breakkeys do | |
127 | if key == breakkeys[a] then | |
128 | return "" | |
129 | end | |
130 | end | |
131 | end | |
132 | fpos = string.find(input, key) | |
133 | until fpos | |
134 | return returnNumber and fpos or key | |
135 | end | |
136 | local explode = function(div,str) | |
137 | if (div=='') then return false end | |
138 | local pos,arr = 0,{} | |
139 | for st,sp in function() return string.find(str,div,pos,true) end do | |
140 | arr[#arr+1] = str:sub(pos,st-1) | |
141 | pos = sp + 1 | |
142 | end | |
143 | arr[#arr+1] = str:sub(pos) | |
144 | return arr | |
145 | end | |
146 | ||
147 | local cutString = function(max_line_length, str) -- from stack overflow | |
148 | local lines = {} | |
149 | local line | |
150 | str:gsub('(%s*)(%S+)', | |
151 | function(spc, word) | |
152 | if not line or #line + #spc + #word > max_line_length then | |
153 | lines[#lines+1] = line | |
154 | line = word | |
155 | else | |
156 | line = line..spc..word | |
157 | end | |
158 | end | |
159 | ) | |
160 | lines[#lines+1] = line | |
161 | return lines | |
162 | end | |
163 | ||
164 | local getDrawingCharacter = function(topLeft, topRight, left, right, bottomLeft, bottomRight) -- thank you oli414 | |
165 | local data = 128 | |
166 | if not bottomRight then | |
167 | data = data + (topLeft and 1 or 0) | |
168 | data = data + (topRight and 2 or 0) | |
169 | data = data + (left and 4 or 0) | |
170 | data = data + (right and 8 or 0) | |
171 | data = data + (bottomLeft and 16 or 0) | |
172 | else | |
173 | data = data + (topLeft and 0 or 1) | |
174 | - | str:gsub('(%s*)(%S+)', |
174 | + | |
175 | - | function(spc, word) |
175 | + | |
176 | data = data + (right and 0 or 8) | |
177 | data = data + (bottomLeft and 0 or 16) | |
178 | end | |
179 | return {char = string.char(data), inverted = bottomRight} | |
180 | end | |
181 | ||
182 | local cutUp = function(len,tbl) | |
183 | local output = {} | |
184 | local e = 0 | |
185 | local s | |
186 | for a = 1, #tbl do | |
187 | if #(tbl[a]:gsub(" ","")) == 0 then | |
188 | s = {""} | |
189 | else | |
190 | s = cutString(len,tbl[a]) | |
191 | end | |
192 | for b = 1, #s do | |
193 | output[#output+1] = s[b] | |
194 | end | |
195 | end | |
196 | return output | |
197 | end | |
198 | ||
199 | local getEvents = function(...) | |
200 | local arg, output = table.pack(...) | |
201 | while true do | |
202 | output = {os.pullEvent()} | |
203 | for a = 1, #arg do | |
204 | if type(arg[a]) == "boolean" then | |
205 | if doRender == arg[a] then | |
206 | return {} | |
207 | end | |
208 | elseif output[1] == arg[a] then | |
209 | return unpack(output) | |
210 | end | |
211 | end | |
212 | end | |
213 | end | |
214 | ||
215 | ||
216 | ||
217 | local sanitize = function(sani,tize) | |
218 | local _,x = string.find(sani,tize) | |
219 | if x then | |
220 | return sani:sub(x+1) | |
221 | else | |
222 | return sani | |
223 | end | |
224 | end | |
225 | local ro = function(input, max) | |
226 | return math.floor(input % max) | |
227 | end | |
228 | ||
229 | local guiHelp = function(inputText) | |
230 | term.redirect(firstTerm) | |
231 | scr_x, scr_y = term.current().getSize() | |
232 | local _helpText = inputText or [[ | |
233 | - | return table.unpack(output) |
233 | + | |
234 | 'PAIN' Help Page | |
235 | Programmed by LDDestroier/EldidiStroyrr | |
236 | ||
237 | (use UP/DOWN or scrollwheel, exit with Q) | |
238 | If you want to use PAIN to its full capacity, then READ EVERYTHING HERE! Its not TOO long, and it's completely worth it! | |
239 | ||
240 | Syntax: | |
241 | >pain <filename> [view] | |
242 | >pain [-n] | |
243 | >pain [-h/--help] | |
244 | ||
245 | [view]: disable all writing capability to view a file | |
246 | "-n" or no arguments: create new document, declare name upon saving | |
247 | "-h" or "--help": display short syntax help | |
248 | ||
249 | You can see what colors are selected based on the word "PAIN" on the hotbar. | |
250 | ||
251 | Hotkeys: | |
252 | left/right ctrl: toggle the menu | |
253 | ||
254 | left click: | |
255 | +shift = drag and let go to make a line | |
256 | -alone = place pixel | |
257 | ||
258 | - | 'PAIN' super-verbose help page |
258 | + | right click: delete pixel |
259 | - | Programmed by LDDestroier |
259 | + | |
260 | middle click OR "t": place text down with current colors, cancel with X | |
261 | ||
262 | - | If you wish to use PAIN to its fullest, read everything here. |
262 | + | "z": |
263 | - | You'll be image-editing like a pro in no time flat. |
263 | + | +left alt = redo |
264 | -alone = undo | |
265 | ||
266 | - | >pain <filename> [view] [x] [y] |
266 | + | "p": pick colors from position onscreen, cancel with X |
267 | ||
268 | "n": | |
269 | +left shift = change character to that of a special character | |
270 | - | [view]: renders the image once (optionally scrolling with [x] and [y]) |
270 | + | -alone = change box character for drawing |
271 | - | "-n" or no arguments: Create new document, declare name upon saving |
271 | + | |
272 | - | "-h" or "--help": Display short syntax help |
272 | + | |
273 | "[" or mouse scroll down: | |
274 | +shift = change to previous text color | |
275 | -alone = change to previous background color | |
276 | ||
277 | - | left/right ctrl: Toggle the menu |
277 | + | |
278 | +shift = change to next text color | |
279 | -alone = change to next background color | |
280 | - | +left shift = Drag and let go to draw a line |
280 | + | |
281 | - | -alone = Place a dot |
281 | + | |
282 | -alone = access help screen | |
283 | - | Right Click: delete pixel |
283 | + | |
284 | "F3:" | |
285 | - | Middle Click, or "T": Place text down with current colors; cancel with X |
285 | + | -alone = view all connected monitors |
286 | ||
287 | - | "Z": |
287 | + | spacebar: |
288 | - | +LeftAlt = Redo |
288 | + | +shift = toggle grid |
289 | - | -alone = Undo |
289 | + | -alone = toggle bar visibility |
290 | ||
291 | - | "P": Pick colors from position onscreen; cancel with X |
291 | + | arrow keys: |
292 | +shift = move entire picture | |
293 | - | "N": |
293 | + | +tab = move one pixel at a time |
294 | - | +LeftShift = Change character to that of a special character |
294 | + | -alone = looks around the canvas smoothly |
295 | - | -alone = Change box character for drawing |
295 | + | |
296 | "+" (or equals): | |
297 | +left alt = swap the current frame with the next frame | |
298 | -alone = change to next frame | |
299 | - | +LeftShift = Change to previous text color |
299 | + | |
300 | - | -alone = Change to previous background color |
300 | + | |
301 | +left alt = swap the current frame with the previous frame | |
302 | -alone = change to previous frame | |
303 | - | +LeftShift = Change to next text color |
303 | + | |
304 | - | -alone = Change to next background color |
304 | + | "a": set the coordinates to 0,0 |
305 | ||
306 | "n": open block character selection | |
307 | - | -alone = Access help screen |
307 | + | |
308 | "b": toggle redirect to blittle, to preview in teletext characters | |
309 | ||
310 | - | -alone = View all connected monitors |
310 | + | "c": input coordinates to scroll over to |
311 | ||
312 | - | Spacebar: |
312 | + | "g": toggle grayscale mode. everything is in shades of gray. if you Save, it saves in grayscale. |
313 | - | +LeftShift = Toggle background grid |
313 | + | |
314 | - | -alone = Toggle bar visibility |
314 | + | "f": |
315 | +shift = fill all empty pixels with background color and selected box character | |
316 | - | Arrow keys: |
316 | + | -alone = activate fill tool - click anywhere to fill with color |
317 | - | +LeftShift = Displaces the entire frame |
317 | + | |
318 | - | +Tab = Moves canvas one pixel at a time |
318 | + | "m": set metadata for pixels (for game makers, otherwise safe to ignore) |
319 | - | -alone = Looks around the canvas smoothly |
319 | + | |
320 | ||
321 | Thy Menu (accessible with CTRL): | |
322 | - | +LeftAlt = Swap the current frame with the next frame |
322 | + | |
323 | - | +LeftShift = Merge the current frame atop the next frame |
323 | + | -left click on a menu item to select it. |
324 | - | +RightShift = If you are making a new frame, duplicates the last frame |
324 | + | -if you click on the menubar, let go on an option to select it. |
325 | - | -alone = Change to next frame |
325 | + | |
326 | "File > Save" | |
327 | 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\39t use text or multiple frame. | |
328 | - | +LeftAlt = Swap the current frame with the previous frame |
328 | + | |
329 | - | +LeftShift = Merge the current frame atop the previous frame |
329 | + | |
330 | - | -alone = Change to previous frame |
330 | + | |
331 | ||
332 | - | (oh good, you're actually reading this stuff) |
332 | + | |
333 | Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format. | |
334 | - | "A": Set the coordinates to 0,0 |
334 | + | |
335 | "Edit > Delete Frame" | |
336 | - | "B": Toggle redirect to blittle, to preview in teletext characters |
336 | + | |
337 | ||
338 | - | "c": |
338 | + | |
339 | - | +LeftAlt = Select region to copy to specified clipboard |
339 | + | |
340 | - | -alone = Input coordinates to scroll over to |
340 | + | |
341 | "Edit > Crop Frame" | |
342 | - | "LeftAlt + X": Select region to cut to specified clipboard |
342 | + | |
343 | ||
344 | - | "LeftAlt + X": Pastes from specified clipboard |
344 | + | |
345 | Opens the block character selection. Used for making those delicious subpixel pictures. | |
346 | - | "G": toggle grayscale mode. |
346 | + | |
347 | - | Everything is in shades of gray. |
347 | + | |
348 | - | If you Save, it saves in grayscale. |
348 | + | |
349 | ||
350 | - | "F": |
350 | + | |
351 | - | +LeftShift = fill all empty pixels with background color and selected box character |
351 | + | Shrinks the current frame using the BLittle API. Very lossy, and unreversable without Undo. |
352 | - | -alone = activate fill tool - click anywhere to fill with color |
352 | + | |
353 | "Window > Set Screen Size" | |
354 | - | "M": set metadata for pixels (for game makers, otherwise please ignore) |
354 | + | |
355 | ||
356 | - | ================================== |
356 | + | |
357 | Sets the backdrop colors to your currently selected color configuration. | |
358 | - | ================================== |
358 | + | |
359 | "About > PAIN" | |
360 | - | Left click on a menu item to select it. |
360 | + | |
361 | - | If you click on the menubar, release on an option to select it. |
361 | + | |
362 | "About > File Formats" | |
363 | Tells you the ins and outs of the file formats, and a brief description of their creators. | |
364 | - | 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. |
364 | + | |
365 | "About > Help" | |
366 | Opens up this help page. | |
367 | ||
368 | "Exit" | |
369 | Durr I dunno, I think it exits. | |
370 | ||
371 | ||
372 | - | "File > Open" |
372 | + | |
373 | - | Opens up a file picker for you to change the image currently being edited. |
373 | + | |
374 | _helpText = explode("\n",_helpText) | |
375 | helpText = cutUp(scr_x,_helpText) | |
376 | local helpscroll = 0 | |
377 | term.setBackgroundColor(colors.gray) | |
378 | term.setTextColor(colors.white) | |
379 | term.clear() | |
380 | local evt, key | |
381 | while true do | |
382 | term.clear() | |
383 | for a = 1, scr_y do | |
384 | term.setCursorPos(1,a) | |
385 | term.clearLine() | |
386 | write(helpText[a-helpscroll] or "") | |
387 | end | |
388 | repeat | |
389 | evt,key = os.pullEvent() | |
390 | until evt == "key" or evt == "mouse_scroll" | |
391 | - | Shrinks the current frame using the BLittle API. Very lossy unless you use one color, or are careful with how you use colors. You can set "Always Render Grid" to true to assist in making blocky graphics. |
391 | + | |
392 | if key == keys.up then | |
393 | - | "Edit > BLittle Grow" |
393 | + | |
394 | - | Grows the image by (2x, 3y) to reverse the effects of "BLittle Shrink". This isn't lossy, since all it does is inflate an image's size and converts the corresponding block characters. |
394 | + | |
395 | helpscroll = helpscroll - 1 | |
396 | - | "Edit > Copy" |
396 | + | |
397 | - | Drag to select a region of the screen, and save it in a clipboard of a specified name. |
397 | + | |
398 | elseif key == keys.pageDown then | |
399 | - | "Edit > Cut" |
399 | + | |
400 | - | Same as Copy, but will delete the selected region on the screen. |
400 | + | |
401 | doRender = true | |
402 | - | "Edit > Paste" |
402 | + | if renderBlittle then term.redirect(blittleTerm) end |
403 | - | Takes the contents of the specified clipboard, and plops it on the canvas where the mouse is. |
403 | + | |
404 | - | (The mouse will indicate the top-left corner of the pasted selection) |
404 | + | |
405 | end | |
406 | - | "Set > ..." |
406 | + | |
407 | - | Each option will toggle a config option (or set it's value to something else). |
407 | + | |
408 | - | Changing a value is saved automatically, and effective immediately. |
408 | + | |
409 | if helpscroll > 0 then | |
410 | helpscroll = 0 | |
411 | elseif helpscroll < -(#helpText-(scr_y-3)) then | |
412 | - | You can also input the name of a monitor object, and it will use its size instead. |
412 | + | |
413 | end | |
414 | end | |
415 | end | |
416 | ||
417 | local tableRemfind = function(tbl, str) | |
418 | local out = tbl | |
419 | for a = 1, #tbl do | |
420 | if tbl[a] == str then | |
421 | table.remove(out,a) | |
422 | return out,a | |
423 | end | |
424 | end | |
425 | return {} | |
426 | end | |
427 | - | Closes PAIN. I know, riviting stuff. You can close out of this help page with "Q", speaking of. |
427 | + | |
428 | local stringShift = function(str,amt) | |
429 | return str:sub(ro(amt-1,#str)+1)..str:sub(1,ro(amt-1,#str)) | |
430 | end | |
431 | ||
432 | local deepCopy | |
433 | deepCopy = function(obj) | |
434 | if type(obj) ~= 'table' then return obj end | |
435 | local res = {} | |
436 | for k, v in pairs(obj) do res[deepCopy(k)] = deepCopy(v) end | |
437 | return res | |
438 | end | |
439 | ||
440 | local clearLines = function(y1, y2) | |
441 | local cx,cy = term.getCursorPos() | |
442 | for y = y1, y2 do | |
443 | term.setCursorPos(1,y) | |
444 | term.clearLine() | |
445 | end | |
446 | term.setCursorPos(cx,cy) | |
447 | end | |
448 | ||
449 | local renderBottomBar = function(txt,extraClearY) | |
450 | term.setCursorPos(1,scr_y - math.floor(#txt/scr_x)) | |
451 | term.setBackgroundColor(colors.lightGray) | |
452 | term.setTextColor(colors.black) | |
453 | clearLines(scr_y - (math.floor(#txt/scr_x) - (extraClearY or 0)), scr_y) | |
454 | return write(txt) | |
455 | end | |
456 | ||
457 | local bottomPrompt = function(txt,history,cho,breakkeys,returnNumber,writeIndent) | |
458 | local writeIndent = renderBottomBar(txt,writeIndent) | |
459 | local out | |
460 | - | if plc.renderBlittle then term.redirect(blittleTerm) end |
460 | + | |
461 | if cho then | |
462 | out = choice(cho,breakkeys,returnNumber) | |
463 | else | |
464 | out = read(_,history) | |
465 | end | |
466 | return out,writeIndent | |
467 | end | |
468 | ||
469 | local makeSubMenu = function(x,y,options) | |
470 | local longestLen = 0 | |
471 | for a = 1, #options do | |
472 | if #options[a] > longestLen then | |
473 | longestLen = #options[a] | |
474 | end | |
475 | end | |
476 | longestLen = longestLen + 1 | |
477 | term.setTextColor(colors.black) | |
478 | local sel = 1 | |
479 | local rend = function() | |
480 | for a = #options, 1, -1 do | |
481 | term.setCursorPos(x or 1, ((y or (scr_y-1)) - (#options-1)) + (a - 1)) | |
482 | term.setBackgroundColor(a == sel and colors.white or colors.lightGray) | |
483 | term.write(options[a]) | |
484 | term.setBackgroundColor(colors.lightGray) | |
485 | term.write((" "):rep(longestLen-#options[a])) | |
486 | end | |
487 | end | |
488 | local usingMouse = false | |
489 | while true do | |
490 | rend() | |
491 | local evt, key, mx, my = os.pullEvent() | |
492 | if evt == "key" then | |
493 | if key == keys.up then | |
494 | sel = sel - 1 | |
495 | elseif key == keys.down then | |
496 | sel = sel + 1 | |
497 | elseif (key == keys.enter) or (key == keys.right) then | |
498 | return sel, longestLen | |
499 | elseif (key == keys.leftCtrl) or (key == keys.rightCtrl) or (key == keys.backspace) or (key == keys.left) then | |
500 | return false, longestLen | |
501 | end | |
502 | elseif evt == "mouse_drag" or evt == "mouse_click" then | |
503 | if (mx >= x) and (mx < x+longestLen) and (my <= y and my > y-#options) then | |
504 | sel = math.min(#options,math.max(1,(my+#options) - y)) | |
505 | usingMouse = true | |
506 | else | |
507 | usingMouse = false | |
508 | if evt == "mouse_click" then | |
509 | return false | |
510 | end | |
511 | end | |
512 | elseif evt == "mouse_up" then | |
513 | if usingMouse then | |
514 | return sel, longestLen | |
515 | end | |
516 | end | |
517 | if sel > #options then sel = 1 elseif sel < 1 then sel = #options end | |
518 | end | |
519 | end | |
520 | ||
521 | local getDotsInLine = function( startX, startY, endX, endY ) -- stolen from the paintutils API...nwehehehe | |
522 | local out = {} | |
523 | startX = math.floor(startX) | |
524 | - | return out, writeIndent |
524 | + | |
525 | endX = math.floor(endX) | |
526 | endY = math.floor(endY) | |
527 | if startX == endX and startY == endY then | |
528 | out = {{x=startX,y=startY}} | |
529 | return out | |
530 | end | |
531 | local minX = math.min( startX, endX ) | |
532 | if minX == startX then | |
533 | minY = startY | |
534 | maxX = endX | |
535 | maxY = endY | |
536 | else | |
537 | minY = endY | |
538 | maxX = startX | |
539 | maxY = startY | |
540 | end | |
541 | local xDiff = maxX - minX | |
542 | local yDiff = maxY - minY | |
543 | if xDiff > math.abs(yDiff) then | |
544 | local y = minY | |
545 | local dy = yDiff / xDiff | |
546 | for x=minX,maxX do | |
547 | out[#out+1] = {x=x,y=math.floor(y+0.5)} | |
548 | y = y + dy | |
549 | end | |
550 | else | |
551 | local x = minX | |
552 | local dx = xDiff / yDiff | |
553 | if maxY >= minY then | |
554 | for y=minY,maxY do | |
555 | out[#out+1] = {x=math.floor(x+0.5),y=y} | |
556 | x = x + dx | |
557 | end | |
558 | else | |
559 | for y=minY,maxY,-1 do | |
560 | out[#out+1] = {x=math.floor(x+0.5),y=y} | |
561 | x = x - dx | |
562 | end | |
563 | end | |
564 | end | |
565 | return out | |
566 | end | |
567 | - | return false, longestLen |
567 | + | |
568 | local movePaintEncoded = function(pe,xdiff,ydiff) | |
569 | local outpootis = deepCopy(pe) | |
570 | for a = 1, #outpootis do | |
571 | outpootis[a].x = outpootis[a].x+xdiff | |
572 | outpootis[a].y = outpootis[a].y+ydiff | |
573 | end | |
574 | return outpootis | |
575 | end | |
576 | ||
577 | local clearRedundant = function(dots) | |
578 | local input = {} | |
579 | local pheight = 0 | |
580 | local pwidth = 0 | |
581 | local minX, minY = 0, 0 | |
582 | for a = 1, #dots do | |
583 | pheight = math.max(pheight, dots[a].y) | |
584 | pwidth = math.max(pwidth, dots[a].x) | |
585 | minX = math.min(minX, dots[a].x) | |
586 | minY = math.min(minY, dots[a].y) | |
587 | end | |
588 | for a = 1, #dots do | |
589 | if not input[dots[a].y] then input[dots[a].y] = {} end | |
590 | input[dots[a].y][dots[a].x] = dots[a] | |
591 | end | |
592 | local output = {} | |
593 | local frame = 0 | |
594 | for y = minY, pheight do | |
595 | for x = minX, pwidth do | |
596 | if input[y] then | |
597 | if input[y][x] then | |
598 | output[#output+1] = input[y][x] | |
599 | end | |
600 | end | |
601 | if frame >= 50 then | |
602 | -- yield() | |
603 | frame = 0 | |
604 | end | |
605 | end | |
606 | end | |
607 | return output | |
608 | end | |
609 | ||
610 | local grayOut = function(color) | |
611 | local c = deepCopy(_G.colors) | |
612 | local grays = { | |
613 | [c.white] = c.white, | |
614 | [c.orange] = c.lightGray, | |
615 | [c.magenta] = c.lightGray, | |
616 | [c.lightBlue] = c.lightGray, | |
617 | [c.yellow] = c.white, | |
618 | [c.lime] = c.lightGray, | |
619 | [c.pink] = c.lightGray, | |
620 | [c.gray] = c.gray, | |
621 | [c.lightGray] = c.lightGray, | |
622 | [c.cyan] = c.lightGray, | |
623 | [c.purple] = c.gray, | |
624 | [c.blue] = c.gray, | |
625 | [c.brown] = c.gray, | |
626 | [c.green] = c.lightGray, | |
627 | [c.red] = c.gray, | |
628 | [c.black] = c.black, | |
629 | } | |
630 | if (not color) or (color == " ") then return color end | |
631 | local newColor = grays[color] or 1 | |
632 | return newColor | |
633 | end | |
634 | ||
635 | local getOnscreenCoords = function(tbl,_x,_y) | |
636 | local screenTbl = {} | |
637 | for a = 1, #tbl do | |
638 | if tbl[a].x+paint.scrollX > 0 and tbl[a].x+paint.scrollX <= scr_x then | |
639 | if tbl[a].y+paint.scrollY > 0 and tbl[a].y+paint.scrollY <= scr_y then | |
640 | screenTbl[#screenTbl+1] = {tbl[a].x+paint.scrollX,tbl[a].y+paint.scrollY} | |
641 | end | |
642 | end | |
643 | end | |
644 | if not _x and _y then | |
645 | return screenTbl | |
646 | else | |
647 | for a = 1, #screenTbl do | |
648 | if screenTbl[a][1] == _x and screenTbl[a][2] == _y then | |
649 | return true | |
650 | end | |
651 | end | |
652 | return false | |
653 | end | |
654 | end | |
655 | ||
656 | local clearAllRedundant = function(info) | |
657 | local output = {} | |
658 | for a = 1, #info do | |
659 | output[a] = clearRedundant(info[a]) | |
660 | if a % 4 == 0 then yield() end | |
661 | end | |
662 | return output | |
663 | end | |
664 | ||
665 | local saveFile = function(path,info) | |
666 | local output = clearAllRedundant(info) | |
667 | local fileout = textutils.serialize(output):gsub(" ",""):gsub("\n",""):gsub(" = ","="):gsub(",}","}"):gsub("}},{{","}},\n{{") | |
668 | if #fileout >= fs.getFreeSpace(fs.getDir(path)) then | |
669 | barmsg = "Not enough space." | |
670 | return | |
671 | end | |
672 | local file = fs.open(path,"w") | |
673 | file.write(fileout) | |
674 | file.close() | |
675 | end | |
676 | local renderBar = function(msg,dontSetVisible) | |
677 | if (doRenderBar == 0) or renderBlittle then return end | |
678 | if tsv and (not dontSetVisible) then tsv(false) end | |
679 | term.setCursorPos(1,scr_y) | |
680 | term.setBackgroundColor(colors.lightGray) | |
681 | term.setTextColor(colors.black) | |
682 | term.clearLine() | |
683 | term.setBackgroundColor(paint.b or rendback.b) | |
684 | term.setTextColor(paint.t or rendback.t) | |
685 | term.setCursorPos(2,scr_y) | |
686 | term.write("PAIN") | |
687 | term.setBackgroundColor(colors.lightGray) | |
688 | term.setTextColor(colors.black) | |
689 | local fmsg = tableconcat({"Fr:",frame,"/",#paintEncoded," (",paint.scrollX,",",paint.scrollY,")"}) | |
690 | term.setCursorPos(7,scr_y) | |
691 | term.write(msg) | |
692 | term.setCursorPos(scr_x-(#fmsg),scr_y) | |
693 | term.write(fmsg) | |
694 | if tsv and (not dontSetVisible) then tsv(true) end | |
695 | end | |
696 | ||
697 | local getTablePaint = function(pe) | |
698 | local output = {} | |
699 | for a = 1, #pe do | |
700 | if not output[pe[a].y] then output[pe[a].y] = {} end | |
701 | output[pe[a].y][pe[a].x] = pe[a] | |
702 | end | |
703 | return output | |
704 | end | |
705 | ||
706 | local renderPainyThings = function(xscroll,yscroll,doGrid) | |
707 | local yadjust = (renderBlittle and 0 or doRenderBar) | |
708 | if bepimode then | |
709 | grid = { | |
710 | "Bepis", | |
711 | "episB", | |
712 | "pisBe", | |
713 | "isBep", | |
714 | "sBepi", | |
715 | } | |
716 | else | |
717 | grid = { | |
718 | "%%..", | |
719 | "%%..", | |
720 | "%%..", | |
721 | "..%%", | |
722 | "..%%", | |
723 | "..%%", | |
724 | } | |
725 | end | |
726 | term.setBackgroundColor(rendback.b) | |
727 | term.setTextColor(rendback.t) | |
728 | local badchar = "/" | |
729 | local blittlelabel = "blittle max" | |
730 | local screenlabel = "screen max" | |
731 | ||
732 | if doGrid then | |
733 | for y = 1, scr_y - yadjust do | |
734 | term.setCursorPos(1,y) | |
735 | - | if (doRenderBar == 0) or plc.renderBlittle then return end |
735 | + | |
736 | - | if not dontSetVisible then plc.tsv(false) end |
736 | + | |
737 | term.setCursorPos((xscroll <= 0) and (1-xscroll) or 0,y) | |
738 | if ((screenEdges[2]+1)-yscroll) == y then --regular limit | |
739 | 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]) ) | |
740 | elseif (((screenEdges[2]*3)+1)-yscroll) == y then --blittle limit | |
741 | 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) ) | |
742 | end | |
743 | -- Stupid easter eggs, ho! -- | |
744 | if 1000-yscroll == y then | |
745 | term.setCursorPos(1000-xscroll,y) | |
746 | term.write(" What ARE you doing? Stop messing around! ") | |
747 | end | |
748 | if 2016-yscroll == y then | |
749 | term.setCursorPos(200-xscroll,y) | |
750 | term.write(" Lines don't like to be intersected, you know. ") | |
751 | end | |
752 | - | if not dontSetVisible then plc.tsv(true) end |
752 | + | |
753 | term.setCursorPos(200-xscroll,y) | |
754 | term.write(" It makes them very crossed. ") | |
755 | - | local tableFormatPE = function(input) |
755 | + | |
756 | if 800-yscroll == y then | |
757 | term.setCursorPos(1700-xscroll,y) | |
758 | term.write(" You stare deeply into the void. ") | |
759 | end | |
760 | if 801-yscroll == y then | |
761 | term.setCursorPos(1704-xscroll,y) | |
762 | term.write(" And the void ") | |
763 | end | |
764 | if 802-yscroll == y then | |
765 | term.setCursorPos(1704-xscroll,y) | |
766 | term.write(" stares back. ") | |
767 | end | |
768 | --Is this the end?-- | |
769 | if (xscroll > ((screenEdges[1]*2)-scr_x)) then | |
770 | for y = 1, scr_y do | |
771 | if y+yscroll <= (screenEdges[2]*3) then | |
772 | if not (y == scr_y and doRenderBar == 1) then | |
773 | term.setCursorPos((screenEdges[1]+1)-(xscroll-screenEdges[1]),y) | |
774 | term.write("@") | |
775 | end | |
776 | end | |
777 | end | |
778 | end | |
779 | if (xscroll > (screenEdges[1]-scr_x)) then --regular limit | |
780 | for y = 1, scr_y do | |
781 | if y+yscroll <= screenEdges[2] then | |
782 | if not (y == scr_y and doRenderBar == 1) then | |
783 | - | return doot, pheight, pwidths |
783 | + | |
784 | term.write("@") | |
785 | end | |
786 | end | |
787 | end | |
788 | end | |
789 | end | |
790 | --render areas that won't save | |
791 | if xscroll < 0 then | |
792 | for y = 1, scr_y do | |
793 | if not (y == scr_y and doRenderBar == 1) then | |
794 | term.setCursorPos(1,y) | |
795 | term.write(badchar:rep(-xscroll)) | |
796 | end | |
797 | end | |
798 | end | |
799 | if yscroll < 0 then | |
800 | for y = 1, -yscroll do | |
801 | if not (y == scr_y and doRenderBar == 1) then | |
802 | term.setCursorPos(1,y) | |
803 | term.write(badchar:rep(scr_x)) | |
804 | end | |
805 | end | |
806 | end | |
807 | else | |
808 | for y = 1, scr_y - yadjust do | |
809 | term.setCursorPos(1,y) | |
810 | term.clearLine() | |
811 | end | |
812 | end | |
813 | end | |
814 | ||
815 | CTB = function(_color) --Color To Blit | |
816 | local blitcolors = { | |
817 | [0] = " ", | |
818 | [colors.white] = "0", | |
819 | [colors.orange] = "1", | |
820 | [colors.magenta] = "2", | |
821 | [colors.lightBlue] = "3", | |
822 | [colors.yellow] = "4", | |
823 | [colors.lime] = "5", | |
824 | [colors.pink] = "6", | |
825 | [colors.gray] = "7", | |
826 | [colors.lightGray] = "8", | |
827 | [colors.cyan] = "9", | |
828 | [colors.purple] = "a", | |
829 | [colors.blue] = "b", | |
830 | [colors.brown] = "c", | |
831 | [colors.green] = "d", | |
832 | [colors.red] = "e", | |
833 | [colors.black] = "f", | |
834 | } | |
835 | - | local yadjust = (plc.renderBlittle and 0 or doRenderBar) |
835 | + | |
836 | - | if plc.bepimode then |
836 | + | |
837 | end | |
838 | ||
839 | BTC = function(_color,allowZero) --Blit To Color | |
840 | local blitcolors = { | |
841 | [" "] = allowZero and 0 or nil, | |
842 | ["0"] = colors.white, | |
843 | ["1"] = colors.orange, | |
844 | ["2"] = colors.magenta, | |
845 | ["3"] = colors.lightBlue, | |
846 | ["4"] = colors.yellow, | |
847 | ["5"] = colors.lime, | |
848 | ["6"] = colors.pink, | |
849 | ["7"] = colors.gray, | |
850 | ["8"] = colors.lightGray, | |
851 | ["9"] = colors.cyan, | |
852 | ["a"] = colors.purple, | |
853 | ["b"] = colors.blue, | |
854 | ["c"] = colors.brown, | |
855 | ["d"] = colors.green, | |
856 | ["e"] = colors.red, | |
857 | ["f"] = colors.black, | |
858 | } | |
859 | if _color == nil then return nil end | |
860 | - | local dotBuffChar, dotBuffBack = "", "" --only used if gridBleedThrough is true |
860 | + | |
861 | - | local doot |
861 | + | |
862 | ||
863 | importFromPaint = function(theInput) | |
864 | local output = {} | |
865 | local input | |
866 | if type(theInput) == "string" then | |
867 | input = explode("\n",theInput) | |
868 | else | |
869 | input = {} | |
870 | for y = 1, #theInput do | |
871 | input[y] = "" | |
872 | for x = 1, #theInput[y] do | |
873 | input[y] = input[y]..(CTB(theInput[y][x]) or " ") | |
874 | end | |
875 | end | |
876 | end | |
877 | for a = 1, #input do | |
878 | line = input[a] | |
879 | for b = 1, #line do | |
880 | if (line:sub(b,b) ~= " ") and BTC(line:sub(b,b)) then | |
881 | output[#output+1] = { | |
882 | x = b, | |
883 | y = a, | |
884 | t = colors.white, | |
885 | b = BTC(line:sub(b,b)) or colors.black, | |
886 | c = " ", | |
887 | } | |
888 | end | |
889 | end | |
890 | end | |
891 | return output | |
892 | end | |
893 | ||
894 | local lddfm = { | |
895 | scroll = 0, | |
896 | ypaths = {}, | |
897 | } | |
898 | ||
899 | lddfm.scr_x, lddfm.scr_y = term.getSize() | |
900 | ||
901 | lddfm.setPalate = function(_p) | |
902 | if type(_p) ~= "table" then | |
903 | _p = {} | |
904 | end | |
905 | lddfm.p = { --the DEFAULT color palate | |
906 | bg = _p.bg or colors.gray, -- whole background color | |
907 | d_txt = _p.d_txt or colors.yellow, -- directory text color | |
908 | d_bg = _p.d_bg or colors.gray, -- directory bg color | |
909 | f_txt = _p.f_txt or colors.white, -- file text color | |
910 | f_bg = _p.f_bg or colors.gray, -- file bg color | |
911 | p_txt = _p.p_txt or colors.black, -- path text color | |
912 | p_bg = _p.p_bg or colors.lightGray, -- path bg color | |
913 | close_txt = _p.close_txt or colors.gray, -- close button text color | |
914 | close_bg = _p.close_bg or colors.lightGray,-- close button bg color | |
915 | scr = _p.scr or colors.lightGray, -- scrollbar color | |
916 | scrbar = _p.scrbar or colors.gray, -- scroll tab color | |
917 | } | |
918 | end | |
919 | ||
920 | lddfm.setPalate() | |
921 | ||
922 | lddfm.foldersOnTop = function(floop,path) | |
923 | local output = {} | |
924 | for a = 1, #floop do | |
925 | if fs.isDir(fs.combine(path,floop[a])) then | |
926 | table.insert(output,1,floop[a]) | |
927 | else | |
928 | table.insert(output,floop[a]) | |
929 | end | |
930 | end | |
931 | return output | |
932 | end | |
933 | ||
934 | lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden) | |
935 | local output = {} | |
936 | for a = 1, #list do | |
937 | local entry = fs.combine(path,list[a]) | |
938 | if fs.isDir(entry) then | |
939 | if entry == ".." then | |
940 | if not (_noCD or _noFolders) then table.insert(output,list[a]) end | |
941 | else | |
942 | if not ((not _doHidden) and list[a]:sub(1,1) == ".") then | |
943 | if not _noFolders then table.insert(output,list[a]) end | |
944 | end | |
945 | end | |
946 | else | |
947 | if not ((not _doHidden) and list[a]:sub(1,1) == ".") then | |
948 | - | if type(theInput) == "string" then |
948 | + | |
949 | - | input = explode("\n",theInput) |
949 | + | |
950 | end | |
951 | - | input = {} |
951 | + | |
952 | - | for y = 1, #theInput do |
952 | + | |
953 | - | input[y] = "" |
953 | + | |
954 | - | for x = 1, #theInput[y] do |
954 | + | |
955 | - | input[y] = input[y]..(CTB(theInput[y][x]) or " ") |
955 | + | |
956 | for k,v in pairs(colors) do | |
957 | if v == col then | |
958 | return true, k | |
959 | end | |
960 | end | |
961 | return false | |
962 | end | |
963 | ||
964 | lddfm.clearLine = function(x1,x2,_y,_bg,_char) | |
965 | local cbg, bg = term.getBackgroundColor() | |
966 | local x,y = term.getCursorPos() | |
967 | local sx,sy = term.getSize() | |
968 | if type(_char) == "string" then char = _char else char = " " end | |
969 | if type(_bg) == "number" then | |
970 | if lddfm.isColor(_bg) then bg = _bg | |
971 | else bg = cbg end | |
972 | else bg = cbg end | |
973 | term.setCursorPos(x1 or 1, _y or y) | |
974 | term.setBackgroundColor(bg) | |
975 | if x2 then --it pains me to add an if statement to something as simple as this | |
976 | term.write((char or " "):rep(x2-x1)) | |
977 | else | |
978 | term.write((char or " "):rep(sx-(x1 or 0))) | |
979 | end | |
980 | term.setBackgroundColor(cbg) | |
981 | term.setCursorPos(x,y) | |
982 | end | |
983 | ||
984 | lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY) | |
985 | local tsv = term.current().setVisible | |
986 | local px,py = term.getCursorPos() | |
987 | if tsv then tsv(false) end | |
988 | local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y | |
989 | local rlist = _rlist or {"Invalid directory."} | |
990 | local path = _path or "And that's terrible." | |
991 | ypaths = {} | |
992 | local rscroll = _rscroll or 0 | |
993 | for a = y1, y2 do | |
994 | lddfm.clearLine(x1,x2,a,lddfm.p.bg) | |
995 | end | |
996 | term.setCursorPos(x1,y1) | |
997 | term.setTextColor(lddfm.p.p_txt) | |
998 | lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg) | |
999 | term.setBackgroundColor(lddfm.p.p_bg) | |
1000 | term.write(("/"..path):sub(1,x2-x1)) | |
1001 | for a = 1,(y2-y1) do | |
1002 | if rlist[a+rscroll] then | |
1003 | term.setCursorPos(x1,a+(y1)) | |
1004 | if fs.isDir(fs.combine(path,rlist[a+rscroll])) then | |
1005 | lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg) | |
1006 | term.setTextColor(lddfm.p.d_txt) | |
1007 | term.setBackgroundColor(lddfm.p.d_bg) | |
1008 | else | |
1009 | lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg) | |
1010 | term.setTextColor(lddfm.p.f_txt) | |
1011 | term.setBackgroundColor(lddfm.p.f_bg) | |
1012 | end | |
1013 | term.write(rlist[a+rscroll]:sub(1,x2-x1)) | |
1014 | ypaths[a+(y1)] = rlist[a+rscroll] | |
1015 | else | |
1016 | lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg) | |
1017 | end | |
1018 | end | |
1019 | local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) ) | |
1020 | for a = y1+1, y2 do | |
1021 | term.setCursorPos(x2,a) | |
1022 | if a == scrbarY then | |
1023 | term.setBackgroundColor(lddfm.p.scrbar) | |
1024 | else | |
1025 | term.setBackgroundColor(lddfm.p.scr) | |
1026 | end | |
1027 | term.write(" ") | |
1028 | end | |
1029 | if _canClose then | |
1030 | term.setCursorPos(x2-4,y1) | |
1031 | term.setTextColor(lddfm.p.close_txt) | |
1032 | term.setBackgroundColor(lddfm.p.close_bg) | |
1033 | term.write("close") | |
1034 | end | |
1035 | term.setCursorPos(px,py) | |
1036 | if tsv then tsv(true) end | |
1037 | return scrbarY | |
1038 | end | |
1039 | ||
1040 | lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char) | |
1041 | local cx, cy = term.getCursorPos() | |
1042 | local bg, txt = term.getBackgroundColor(), term.getTextColor() | |
1043 | term.setTextColor(_txt or colors.white) | |
1044 | term.setBackgroundColor(_bg or colors.black) | |
1045 | local _uwah = 0 | |
1046 | for y = y1, y2 do | |
1047 | for x = x1, x2 do | |
1048 | _uwah = _uwah + 1 | |
1049 | term.setCursorPos(x,y) | |
1050 | term.write(char or " ") | |
1051 | if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0) _uwah = 0 end | |
1052 | end | |
1053 | end | |
1054 | term.setTextColor(txt) | |
1055 | term.setBackgroundColor(bg) | |
1056 | term.setCursorPos(cx,cy) | |
1057 | end | |
1058 | ||
1059 | lddfm.scrollMenu = function(amount,list,y1,y2) | |
1060 | if #list >= y2-y1 then | |
1061 | lddfm.scroll = lddfm.scroll + amount | |
1062 | if lddfm.scroll < 0 then | |
1063 | lddfm.scroll = 0 | |
1064 | end | |
1065 | if lddfm.scroll > #list-(y2-y1) then | |
1066 | lddfm.scroll = #list-(y2-y1) | |
1067 | end | |
1068 | - | plc.tsv(false) |
1068 | + | |
1069 | end | |
1070 | ||
1071 | --[[ | |
1072 | a quick explanation of the arguments for lddfm.makeMenu: | |
1073 | ||
1074 | x1 and y1: top-left corner coordinates of menu window. defaults to the top-left corner of the screen | |
1075 | x2 and y2: bottom-right corner coordinates of menu window. defaults to the bottom-right corner of the screen | |
1076 | _path: path to start viewing. defaults to "/" | |
1077 | _noFiles: whether or not to view files in the menu, mainly for picking a path for installing something. defaults to false | |
1078 | _noFolders: whether or not to view folders in the menu, mainly for choosing a file to run or whatever. defaults to false | |
1079 | _noCD: whether or not you can change the directory, mainly to limit choices to a single folder. defaults to false | |
1080 | _noSelectFolders: whether or not you can select folders to return. defaults to false | |
1081 | _doHidden: whether or not to hide hidden files (starts with "."). defaults to false | |
1082 | _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. | |
1083 | _canClose: whether or not you can click on the little top-right "Cancel" button. | |
1084 | --]] | |
1085 | ||
1086 | lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose) | |
1087 | if _noFiles and _noFolders then | |
1088 | return false, "C'mon, man..." | |
1089 | end | |
1090 | if _x1 == true then | |
1091 | return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help | |
1092 | end | |
1093 | lddfm.setPalate(_p) | |
1094 | local path, list = _path or "" | |
1095 | lddfm.scroll = 0 | |
1096 | local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor() | |
1097 | local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y | |
1098 | local keysDown = {} | |
1099 | local _barrY | |
1100 | while true do | |
1101 | list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path) | |
1102 | if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then | |
1103 | table.insert(list,1,"..") | |
1104 | end | |
1105 | _res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end) | |
1106 | if not _res then | |
1107 | local tsv = term.current().setVisible | |
1108 | if tsv then tsv(true) end | |
1109 | error(_barrY) | |
1110 | end | |
1111 | local evt = {os.pullEvent()} | |
1112 | if evt[1] == "mouse_scroll" then | |
1113 | lddfm.scrollMenu(evt[2],list,y1,y2) | |
1114 | elseif evt[1] == "mouse_click" then | |
1115 | local butt,mx,my = evt[2],evt[3],evt[4] | |
1116 | if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then | |
1117 | - | plc.tsv(true) |
1117 | + | |
1118 | term.setTextColor(_ptxt) term.setBackgroundColor(_pbg) | |
1119 | return false | |
1120 | elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh | |
1121 | if fs.isDir(fs.combine(path,ypaths[my])) then | |
1122 | if _noCD or butt == 3 then | |
1123 | if not _noSelectFolders or _noFolders then | |
1124 | --lddfm.coolOutro(x1,y1,x2,y2) | |
1125 | term.setTextColor(_ptxt) term.setBackgroundColor(_pbg) | |
1126 | return fs.combine(path,ypaths[my]) | |
1127 | end | |
1128 | else | |
1129 | path = fs.combine(path,ypaths[my]) | |
1130 | lddfm.scroll = 0 | |
1131 | end | |
1132 | else | |
1133 | term.setTextColor(_ptxt) term.setBackgroundColor(_pbg) | |
1134 | return fs.combine(path,ypaths[my]) | |
1135 | end | |
1136 | end | |
1137 | elseif evt[1] == "key" then | |
1138 | keysDown[evt[2]] = true | |
1139 | 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. | |
1140 | --lddfm.coolOutro(x1,y1,x2,y2) | |
1141 | term.setTextColor(_ptxt) term.setBackgroundColor(_pbg) | |
1142 | return path | |
1143 | end | |
1144 | if evt[2] == keys.up then | |
1145 | lddfm.scrollMenu(-1,list,y1,y2) | |
1146 | elseif evt[2] == keys.down then | |
1147 | lddfm.scrollMenu(1,list,y1,y2) | |
1148 | end | |
1149 | if evt[2] == keys.pageUp then | |
1150 | lddfm.scrollMenu(y1-y2,list,y1,y2) | |
1151 | elseif evt[2] == keys.pageDown then | |
1152 | lddfm.scrollMenu(y2-y1,list,y1,y2) | |
1153 | end | |
1154 | if evt[2] == keys.home then | |
1155 | lddfm.scroll = 0 | |
1156 | elseif evt[2] == keys["end"] then | |
1157 | if #list > (y2-y1) then | |
1158 | lddfm.scroll = #list-(y2-y1) | |
1159 | end | |
1160 | end | |
1161 | if evt[2] == keys.h then | |
1162 | if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then | |
1163 | _doHidden = not _doHidden | |
1164 | end | |
1165 | elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then | |
1166 | --lddfm.coolOutro(x1,y1,x2,y2) | |
1167 | term.setTextColor(_ptxt) term.setBackgroundColor(_pbg) | |
1168 | return false | |
1169 | end | |
1170 | elseif evt[1] == "key_up" then | |
1171 | keysDown[evt[2]] = false | |
1172 | end | |
1173 | - | plc.tsv(true) |
1173 | + | |
1174 | end | |
1175 | ||
1176 | local getBlittle = function() | |
1177 | if not blittle then | |
1178 | if fs.exists("/.painapi/blittle") then | |
1179 | os.loadAPI("/.painapi/blittle") | |
1180 | if not blittleTerm then | |
1181 | blittleTerm = blittle.createWindow() | |
1182 | end | |
1183 | return blittleTerm, firstTerm | |
1184 | else | |
1185 | local geet = http.get("http://pastebin.com/raw/ujchRSnU") | |
1186 | if not geet then | |
1187 | return false | |
1188 | else | |
1189 | geet = geet.readAll() | |
1190 | local file = fs.open("/.painapi/blittle","w") | |
1191 | file.write(geet) | |
1192 | file.close() | |
1193 | os.loadAPI("/.painapi/blittle") | |
1194 | --fs.delete("/.painapi/") | |
1195 | if not blittleTerm then | |
1196 | blittleTerm = blittle.createWindow() | |
1197 | end | |
1198 | return blittleTerm, firstTerm | |
1199 | end | |
1200 | end | |
1201 | else | |
1202 | if not blittleTerm then | |
1203 | blittleTerm = blittle.createWindow() | |
1204 | end | |
1205 | return blittleTerm, firstTerm | |
1206 | end | |
1207 | end | |
1208 | ||
1209 | local getUCG = function() | |
1210 | if not ucg then | |
1211 | if fs.exists("/.painapi/ucg") then | |
1212 | os.loadAPI("/.painapi/ucg") | |
1213 | return true | |
1214 | else | |
1215 | local geet = http.get("https://raw.githubusercontent.com/ardera/libucg/master/src/libucg") | |
1216 | if not geet then | |
1217 | return false | |
1218 | else | |
1219 | geet = geet.readAll() | |
1220 | local file = fs.open("/.painapi/ucg","w") | |
1221 | file.write(geet) | |
1222 | file.close() | |
1223 | os.loadAPI("/.painapi/ucg") | |
1224 | end | |
1225 | end | |
1226 | end | |
1227 | end | |
1228 | ||
1229 | local getBitmap = function() | |
1230 | if not bitmap then | |
1231 | if fs.exists("/.painapi/bitmap") then | |
1232 | os.loadAPI("/.painapi/bitmap") | |
1233 | return true | |
1234 | else | |
1235 | local geet = http.get("https://pastebin.com/raw/Y3JeZWzV") | |
1236 | if not geet then | |
1237 | return false | |
1238 | else | |
1239 | geet = geet.readAll() | |
1240 | local file = fs.open("/.painapi/bitmap","w") | |
1241 | file.write(geet) | |
1242 | file.close() | |
1243 | - | if fs.exists(fs.combine(plc.apipath,"blittle")) then |
1243 | + | os.loadAPI("/.painapi/bitmap") |
1244 | - | os.loadAPI(fs.combine(plc.apipath,"blittle")) |
1244 | + | |
1245 | end | |
1246 | end | |
1247 | end | |
1248 | ||
1249 | local getBBPack = function() | |
1250 | if not bbpack then | |
1251 | if fs.exists("/.painapi/bbpack") then | |
1252 | os.loadAPI("/.painapi/bbpack") | |
1253 | return true | |
1254 | else | |
1255 | - | local file = fs.open(fs.combine(plc.apipath,"blittle"),"w") |
1255 | + | |
1256 | if not geet then | |
1257 | return false | |
1258 | - | os.loadAPI(fs.combine(plc.apipath,"blittle")) |
1258 | + | |
1259 | - | --fs.delete(plc.apipath) |
1259 | + | |
1260 | local file = fs.open("/.painapi/bbpack","w") | |
1261 | file.write(geet) | |
1262 | file.close() | |
1263 | os.loadAPI("/.painapi/bbpack") | |
1264 | end | |
1265 | end | |
1266 | end | |
1267 | end | |
1268 | ||
1269 | local getGIF = function() | |
1270 | getBBPack() | |
1271 | if not GIF then | |
1272 | if fs.exists("/.painapi/GIF") then | |
1273 | os.loadAPI("/.painapi/GIF") | |
1274 | return true | |
1275 | else | |
1276 | - | if fs.exists(fs.combine(plc.apipath,"ucg")) then |
1276 | + | |
1277 | - | os.loadAPI(fs.combine(plc.apipath,"ucg")) |
1277 | + | |
1278 | return false | |
1279 | else | |
1280 | geet = geet.readAll() | |
1281 | local file = fs.open("/.painapi/GIF","w") | |
1282 | file.write(geet) | |
1283 | file.close() | |
1284 | os.loadAPI("/.painapi/GIF") | |
1285 | - | local file = fs.open(fs.combine(plc.apipath,"ucg"),"w") |
1285 | + | |
1286 | end | |
1287 | end | |
1288 | - | os.loadAPI(fs.combine(plc.apipath,"ucg")) |
1288 | + | |
1289 | ||
1290 | local NFPserializeImage = function(str) | |
1291 | local bepis = explode("\n",str) | |
1292 | local output = {} | |
1293 | for y = 1, #bepis do | |
1294 | output[y] = {} | |
1295 | for x = 1, #bepis[y] do | |
1296 | - | if fs.exists(fs.combine(plc.apipath,"bbpack")) then |
1296 | + | |
1297 | - | os.loadAPI(fs.combine(plc.apipath,"bbpack")) |
1297 | + | end |
1298 | end | |
1299 | return textutils.unserialize(textutils.serialize(output):gsub("\n",""):gsub(" ",""):gsub(",}","}")) | |
1300 | end | |
1301 | ||
1302 | local importFromBitmap = function(fileName) | |
1303 | getBitmap() | |
1304 | local image = bitmap.ImageHelpers.parseFile(fileName).pixels | |
1305 | - | local file = fs.open(fs.combine(plc.apipath,"bbpack"),"w") |
1305 | + | |
1306 | local closest, termSets = bitmap.Colors.findClosestColor, bitmap.Colors.termSets | |
1307 | for y = 1, #image do | |
1308 | - | os.loadAPI(fs.combine(plc.apipath,"bbpack")) |
1308 | + | local line = image[y] |
1309 | for x = 1, #line do | |
1310 | output[#output+1] = { | |
1311 | x = x, | |
1312 | y = y, | |
1313 | t = colors.white, | |
1314 | c = " ", | |
1315 | b = closest(termSets, unpack(line[x])), | |
1316 | m = 0, | |
1317 | - | if fs.exists(fs.combine(plc.apipath,"GIF")) then |
1317 | + | |
1318 | - | os.loadAPI(fs.combine(plc.apipath,"GIF")) |
1318 | + | |
1319 | end | |
1320 | return output | |
1321 | end | |
1322 | ||
1323 | local exportToBitmap = function(theInput) | |
1324 | getBitmap() | |
1325 | --er, I don't think this is possible at the moment | |
1326 | - | local file = fs.open(fs.combine(plc.apipath,"GIF"),"w") |
1326 | + | |
1327 | ||
1328 | local importFromGIF = function(filename,verbose) | |
1329 | - | os.loadAPI(fs.combine(plc.apipath,"GIF")) |
1329 | + | |
1330 | local output = {} | |
1331 | local image | |
1332 | local rawGif = GIF.loadGIF(filename) | |
1333 | if useFlattenGIF then | |
1334 | if verbose then | |
1335 | print("Flattening...") | |
1336 | end | |
1337 | rawGif = GIF.flattenGIF(rawGif) | |
1338 | sleep(0) | |
1339 | end | |
1340 | local cx, cy = term.getCursorPos() | |
1341 | for a = 1, #rawGif do | |
1342 | output[a] = importFromPaint(GIF.toPaintutils(rawGif[a])) | |
1343 | if verbose then | |
1344 | term.setCursorPos(cx,cy) | |
1345 | write("Did "..a.."/"..#rawGif.." ") | |
1346 | end | |
1347 | if a % 1 then sleep(0) end --used to be a % 2, might change later | |
1348 | end | |
1349 | return output | |
1350 | end | |
1351 | ||
1352 | - | if painconfig.useFlattenGIF then |
1352 | + | |
1353 | ||
1354 | local exportToGIF = function(input) | |
1355 | getGIF() | |
1356 | local outGIF = {} | |
1357 | for a = 1, #paintEncoded do | |
1358 | outGIF[a] = NFPserializeImage(exportToPaint(paintEncoded[a])) | |
1359 | sleep(0) | |
1360 | end | |
1361 | if useFlattenGIF then | |
1362 | return GIF.flattenGIF(GIF.buildGIF(table.unpack(outGIF)),true) | |
1363 | else | |
1364 | return GIF.buildGIF(table.unpack(outGIF)) | |
1365 | end | |
1366 | end | |
1367 | ||
1368 | local importFromUCG = function(filename) | |
1369 | getUCG() | |
1370 | return importFromPaint(ucg.readFile(filename)) | |
1371 | end | |
1372 | ||
1373 | local exportToUCG = function(filename, input) | |
1374 | getUCG() | |
1375 | ucg.writeFile(filename, NFPserializeImage(exportToPaint(input))) | |
1376 | end | |
1377 | ||
1378 | renderPAIN = function(dots,xscroll,yscroll,doPain,dontRenderBar) | |
1379 | if tsv then tsv(false) end | |
1380 | - | if painconfig.useFlattenGIF then |
1380 | + | |
1381 | local cx,cy = term.getCursorPos() | |
1382 | local FUCK, SHIT = pcall(function() | |
1383 | if doPain then | |
1384 | if (not renderBlittle) then | |
1385 | if not dontRenderBar then | |
1386 | renderBar(barmsg,true) | |
1387 | end | |
1388 | renderPainyThings(xscroll,yscroll,evenDrawGrid) | |
1389 | else | |
1390 | term.clear() | |
1391 | end | |
1392 | end | |
1393 | for a = 1, #dots do | |
1394 | local d = dots[a] | |
1395 | if doPain then | |
1396 | 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 | |
1397 | d = nil | |
1398 | - | plc.tsv(false) |
1398 | + | |
1399 | end | |
1400 | if d then | |
1401 | term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0)) | |
1402 | term.setTextColor( (paint.doGray and grayOut(d.t) or d.t) or rendback.t) | |
1403 | - | if (not plc.renderBlittle) then |
1403 | + | term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b) |
1404 | - | if not dontRenderBar then |
1404 | + | term.write(d.c or " ") |
1405 | - | renderBar(barmsg,true) |
1405 | + | |
1406 | end | |
1407 | - | renderPainyThings(xscroll,yscroll,plc.evenDrawGrid) |
1407 | + | |
1408 | term.setBackgroundColor(beforeBG or rendback.b) | |
1409 | - | term.clear() |
1409 | + | |
1410 | term.setCursorPos(cx,cy) | |
1411 | if tsv then tsv(true) end | |
1412 | - | for a = 1, #dots do |
1412 | + | |
1413 | - | local d = dots[a] |
1413 | + | |
1414 | - | if doPain then |
1414 | + | |
1415 | - | if not ((d.y-yscroll >= 1 and d.y-yscroll <= scr_y-(plc.renderBlittle and 0 or doRenderBar)) and (d.x-xscroll >= 1 and d.x-xscroll <= scr_x)) then |
1415 | + | |
1416 | - | d = nil |
1416 | + | local tun, tse = textutils.unserialize, textutils.serialize |
1417 | local file = fs.open(filename,"r") | |
1418 | local contents = file.readAll() | |
1419 | - | if d then |
1419 | + | |
1420 | - | term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0)) |
1420 | + | |
1421 | - | term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b) |
1421 | + | local tcontents = tun(contents) |
1422 | - | if painconfig.gridBleedThrough then |
1422 | + | |
1423 | - | term.setTextColor(rendback.t) |
1423 | + | |
1424 | - | 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 "/") |
1424 | + | |
1425 | amntFrames = #tcontents | |
1426 | - | term.setTextColor( (paint.doGray and grayOut(d.t) or d.t) or rendback.t) |
1426 | + | |
1427 | - | term.write(d.c or " ") |
1427 | + | |
1428 | renderPAIN(tcontents,xscroll,yscroll,doPain) | |
1429 | return amntFrames | |
1430 | end | |
1431 | ||
1432 | local putDotDown = function(dot) -- only 'x' and 'y' are required arguments | |
1433 | paintEncoded[frame][#paintEncoded[frame]+1] = { | |
1434 | x = dot.x + paint.scrollX, | |
1435 | - | plc.tsv(true) |
1435 | + | |
1436 | c = dot.c or paint.c, | |
1437 | b = dot.b or (swapColors and paint.t or paint.b), | |
1438 | t = dot.t or (swapColors and paint.b or paint.t), | |
1439 | m = dot.m or paint.m, | |
1440 | } | |
1441 | end | |
1442 | ||
1443 | local saveToUndoBuffer = function() | |
1444 | - | local tcontents = textutils.unserialize(contents) |
1444 | + | if undoPos < #undoBuffer then |
1445 | for a = #undoBuffer, undoPos+1, -1 do | |
1446 | table.remove(undoBuffer,a) | |
1447 | end | |
1448 | end | |
1449 | if undoPos >= undoBufferSize then | |
1450 | for a = 2, #undoBuffer do | |
1451 | undoBuffer[a-1] = undoBuffer[a] | |
1452 | end | |
1453 | undoBuffer[#undoBuffer] = deepCopy(paintEncoded) | |
1454 | else | |
1455 | undoPos = undoPos + 1 | |
1456 | undoBuffer[undoPos] = deepCopy(paintEncoded) | |
1457 | end | |
1458 | end | |
1459 | ||
1460 | local doUndo = function() | |
1461 | undoPos = math.max(1,undoPos-1) | |
1462 | paintEncoded = deepCopy(undoBuffer[undoPos]) | |
1463 | if not paintEncoded[frame] then | |
1464 | frame = #paintEncoded | |
1465 | end | |
1466 | end | |
1467 | - | if plc.undoPos < #plc.undoBuffer then |
1467 | + | |
1468 | - | for a = #plc.undoBuffer, plc.undoPos + 1, -1 do |
1468 | + | |
1469 | - | table.remove(plc.undoBuffer, a) |
1469 | + | undoPos = math.min(#undoBuffer,undoPos+1) |
1470 | paintEncoded = deepCopy(undoBuffer[undoPos]) | |
1471 | if not paintEncoded[frame] then | |
1472 | - | if plc.undoPos >= painconfig.undoBufferSize then |
1472 | + | |
1473 | - | for a = 2, #plc.undoBuffer do |
1473 | + | |
1474 | - | plc.undoBuffer[a - 1] = plc.undoBuffer[a] |
1474 | + | |
1475 | ||
1476 | - | plc.undoBuffer[#plc.undoBuffer] = deepCopy(paintEncoded) |
1476 | + | |
1477 | term.setCursorPos(x,y) | |
1478 | - | plc.undoPos = plc.undoPos + 1 |
1478 | + | |
1479 | - | plc.undoBuffer[plc.undoPos] = deepCopy(paintEncoded) |
1479 | + | |
1480 | local msg = read() | |
1481 | if #msg > 0 then | |
1482 | for a = 1, #msg do | |
1483 | putDotDown({x=(x+a)-1, y=y, c=msg:sub(a,a)}) | |
1484 | - | plc.undoPos = math.max(1, plc.undoPos - 1) |
1484 | + | |
1485 | - | paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos]) |
1485 | + | |
1486 | saveToUndoBuffer() | |
1487 | end | |
1488 | ||
1489 | local deleteDot = function(x,y) --deletes all dots at point x,y | |
1490 | local good = false | |
1491 | for a = #paintEncoded[frame],1,-1 do | |
1492 | - | plc.undoPos = math.min(#plc.undoBuffer, plc.undoPos + 1) |
1492 | + | |
1493 | - | paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos]) |
1493 | + | |
1494 | table.remove(paintEncoded[frame],a) | |
1495 | good = true | |
1496 | end | |
1497 | end | |
1498 | return good | |
1499 | end | |
1500 | ||
1501 | exportToPaint = function(input,noTransparent) --exports paintEncoded frame to regular paint format. input is expected to be paintEncoded[frame] | |
1502 | local doopTXT, doopTXCOL, doopBGCOL = {}, {}, {} | |
1503 | local p = input | |
1504 | local pheight = 0 | |
1505 | local pwidth = 0 | |
1506 | for a = 1, #p do | |
1507 | if p[a].y > pheight then | |
1508 | pheight = p[a].y | |
1509 | end | |
1510 | if p[a].x > pwidth then | |
1511 | pwidth = p[a].x | |
1512 | end | |
1513 | end | |
1514 | for k,v in pairs(p) do | |
1515 | if not doopBGCOL[v.y] then | |
1516 | doopBGCOL[v.y] = {} | |
1517 | doopTXCOL[v.y] = {} | |
1518 | doopTXT[v.y] = {} | |
1519 | end | |
1520 | doopBGCOL[v.y][v.x] = CTB(v.b) | |
1521 | doopTXCOL[v.y][v.x] = CTB(v.t) | |
1522 | doopTXT[v.y][v.x] = v.c | |
1523 | end | |
1524 | local nfpoutputTXT, nfpoutputTXCOL, nfpoutputBGCOL = "", "", "" | |
1525 | for y = 1, pheight do | |
1526 | if doopBGCOL[y] then | |
1527 | for x = 1, pwidth do | |
1528 | if doopBGCOL[y][x] then | |
1529 | nfpoutputBGCOL = nfpoutputBGCOL..doopBGCOL[y][x] | |
1530 | nfpoutputTXCOL = nfpoutputTXCOL..doopTXCOL[y][x] | |
1531 | nfpoutputTXT = nfpoutputTXT..(((doopTXT[y][x] == " " and noTransparent) and "\128" or doopTXT[y][x]) or " ") | |
1532 | else | |
1533 | nfpoutputBGCOL = nfpoutputBGCOL..(noTransparent and "0" or " ") | |
1534 | nfpoutputTXCOL = nfpoutputTXCOL..(noTransparent and "0" or " ") | |
1535 | nfpoutputTXT = nfpoutputTXT.." " | |
1536 | end | |
1537 | end | |
1538 | end | |
1539 | if y ~= pheight then | |
1540 | nfpoutputBGCOL = nfpoutputBGCOL.."\n" | |
1541 | nfpoutputTXCOL = nfpoutputTXCOL.."\n" | |
1542 | nfpoutputTXT = nfpoutputTXT.."\n" | |
1543 | end | |
1544 | end | |
1545 | return nfpoutputBGCOL, pheight, pwidth | |
1546 | end | |
1547 | ||
1548 | local exportToNFT = function(input) | |
1549 | local pwidths = {} | |
1550 | local doot = {} | |
1551 | local pheight = 0 | |
1552 | for k, dot in pairs(input) do | |
1553 | pwidths[dot.y] = math.max((pwidths[dot.y] or 0), dot.x) | |
1554 | pheight = math.max(pheight, dot.y) | |
1555 | doot[dot.y] = doot[dot.y] or {} | |
1556 | doot[dot.y][dot.x] = { | |
1557 | char = dot.c, | |
1558 | text = CTB(dot.t), | |
1559 | back = CTB(dot.b) | |
1560 | } | |
1561 | end | |
1562 | ||
1563 | local bgcode, txcode = "\30", "\31" | |
1564 | local output = "" | |
1565 | local text, back | |
1566 | ||
1567 | for y = 1, pheight do | |
1568 | pwidths[y] = pwidths[y] or 0 | |
1569 | if doot[y] then | |
1570 | for x = 1, pwidths[y] do | |
1571 | doot[y][x] = doot[y][x] or { | |
1572 | text = " ", | |
1573 | back = " ", | |
1574 | char = " ", | |
1575 | } | |
1576 | end | |
1577 | - | local doot, pheight, pwidths = tableFormatPE(input) |
1577 | + | |
1578 | doot[y] = false | |
1579 | end | |
1580 | end | |
1581 | ||
1582 | for y = 1, pheight do | |
1583 | ||
1584 | text, back = "0", "f" | |
1585 | if doot[y] then | |
1586 | for x = 1, pwidths[y] do | |
1587 | ||
1588 | if doot[y][x] then | |
1589 | if doot[y][x].back ~= back then | |
1590 | back = doot[y][x].back | |
1591 | output = output .. bgcode .. back | |
1592 | end | |
1593 | if doot[y][x].text ~= text then | |
1594 | text = doot[y][x].text | |
1595 | output = output .. txcode .. text | |
1596 | end | |
1597 | output = output .. doot[y][x].char | |
1598 | else | |
1599 | output = output .. " " | |
1600 | end | |
1601 | ||
1602 | end | |
1603 | end | |
1604 | ||
1605 | if y < pheight then | |
1606 | output = output .. "\n" | |
1607 | end | |
1608 | end | |
1609 | return output | |
1610 | end | |
1611 | ||
1612 | local importFromNFT = function(input) --imports NFT formatted string image to paintEncoded[frame] formatted table image. please return a paintEncoded[frame] formatted table. | |
1613 | local tinput = explode("\n",input) | |
1614 | local tcol,bcol | |
1615 | local cx --represents the x position in the picture | |
1616 | local sx --represents the x position in the file | |
1617 | local output = {} | |
1618 | for y = 1, #tinput do | |
1619 | tcol,bcol = colors.white,colors.black | |
1620 | cx, sx = 1, 0 | |
1621 | while sx < #tinput[y] do | |
1622 | sx = sx + 1 | |
1623 | if tinput[y]:sub(sx,sx) == "\30" then | |
1624 | bcol = BTC(tinput[y]:sub(sx+1,sx+1)) | |
1625 | sx = sx + 1 | |
1626 | elseif tinput[y]:sub(sx,sx) == "\31" then | |
1627 | tcol = BTC(tinput[y]:sub(sx+1,sx+1)) | |
1628 | sx = sx + 1 | |
1629 | else | |
1630 | if tcol and bcol then | |
1631 | output[#output+1] = { | |
1632 | ["x"] = cx, | |
1633 | ["y"] = y, | |
1634 | ["b"] = bcol, | |
1635 | ["t"] = tcol, | |
1636 | ["c"] = tinput[y]:sub(sx,sx), | |
1637 | ["m"] = 0, | |
1638 | } | |
1639 | end | |
1640 | cx = cx + 1 | |
1641 | end | |
1642 | end | |
1643 | end | |
1644 | return output | |
1645 | end | |
1646 | ||
1647 | exportToBLT = function(input,filename,doAllFrames,noSave) | |
1648 | local output = {} | |
1649 | local thisImage,pheight,pwidth,nfpinput | |
1650 | getBlittle() | |
1651 | for a = doAllFrames and 1 or frame, doAllFrames and #input or frame do | |
1652 | output[#output+1] = blittle.shrink(NFPserializeImage(exportToPaint(input[a]),true),colors.black) | |
1653 | end | |
1654 | if #output == 1 then output = output[1] end | |
1655 | if not noSave then | |
1656 | blittle.save(output,filename) | |
1657 | end | |
1658 | return output | |
1659 | end | |
1660 | ||
1661 | importFromBLT = function(input) --takes in filename, not contents | |
1662 | local output = {} | |
1663 | getBlittle() | |
1664 | local wholePic = blittle.load(input) | |
1665 | if wholePic.height then wholePic = {wholePic} end | |
1666 | local image | |
1667 | for a = 1, #wholePic do | |
1668 | image = wholePic[a] | |
1669 | output[#output+1] = {} | |
1670 | for y = 1, image.height*3 do | |
1671 | 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 | |
1672 | output[#output][#output[#output]+1] = { | |
1673 | m = 0, | |
1674 | x = x, | |
1675 | y = y, | |
1676 | t = BTC((image[2][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)), | |
1677 | b = BTC((image[3][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)), | |
1678 | c = BTC((image[1][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).." "):sub(1,1)), | |
1679 | } | |
1680 | end | |
1681 | end | |
1682 | end | |
1683 | return output | |
1684 | end | |
1685 | ||
1686 | local getTheDoots = function(pe) | |
1687 | local hasBadDots = false | |
1688 | local baddestX,baddestY = 1,1 | |
1689 | for b = 1, #pe do | |
1690 | local doot = pe[b] | |
1691 | if doot.x <= 0 or doot.y <= 0 then | |
1692 | hasBadDots = true | |
1693 | if doot.x < baddestX then | |
1694 | baddestX = doot.x | |
1695 | end | |
1696 | if doot.y < baddestY then | |
1697 | baddestY = doot.y | |
1698 | end | |
1699 | end | |
1700 | if b % 64 == 0 then yield() end | |
1701 | end | |
1702 | return baddestX, baddestY | |
1703 | end | |
1704 | ||
1705 | local checkBadDots = function() | |
1706 | local hasBadDots = false | |
1707 | for a = 1, #paintEncoded do | |
1708 | local radx,rady = getTheDoots(paintEncoded[a]) | |
1709 | if radx ~= 1 or rady ~= 1 then | |
1710 | hasBadDots = true | |
1711 | end | |
1712 | end | |
1713 | if hasBadDots then | |
1714 | local ting = bottomPrompt("Dot(s) are OoB! Save or fix? (Y/N/F)",_,"ynf",{keys.leftCtrl,keys.rightCtrl}) | |
1715 | if ting == "f" then | |
1716 | for a = 1, #paintEncoded do | |
1717 | local baddestX, baddestY = getTheDoots(paintEncoded[a]) | |
1718 | paintEncoded[a] = movePaintEncoded(paintEncoded[a],-(baddestX-1),-(baddestY-1)) | |
1719 | end | |
1720 | elseif ting ~= "y" then | |
1721 | barmsg = "" | |
1722 | return false | |
1723 | end | |
1724 | end | |
1725 | end | |
1726 | ||
1727 | local convertToGrayscale = function(pe) | |
1728 | local output = pe | |
1729 | for a = 1, #pe do | |
1730 | for b = 1, #pe[a] do | |
1731 | output[a][b].b = grayOut(pe[a][b].b) | |
1732 | output[a][b].t = grayOut(pe[a][b].t) | |
1733 | if not output[a][b].m then output[a][b].m = 1 end | |
1734 | end | |
1735 | if a % 2 == 0 then yield() end | |
1736 | end | |
1737 | return output | |
1738 | end | |
1739 | - | -- doRenderBar = 1 |
1739 | + | |
1740 | local reRenderPAIN = function(overrideRenderBar) | |
1741 | local _reallyDoRenderBar = doRenderBar | |
1742 | doRenderBar = 1 | |
1743 | renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,overrideRenderBar) | |
1744 | - | local fillTool = function(_frame,cx,cy,dot,isDeleting) -- "_frame" is the frame NUMBER |
1744 | + | |
1745 | - | local maxX, maxY = 1, 1 |
1745 | + | |
1746 | - | local minX, minY = 1, 1 |
1746 | + | |
1747 | local fillTool = function(_frame,cx,cy,dot) -- "_frame" is the frame NUMBER | |
1748 | local maxX, maxY = 0, 0 | |
1749 | local minX, minY = 0, 0 | |
1750 | paintEncoded = clearAllRedundant(paintEncoded) | |
1751 | local frame = paintEncoded[_frame] | |
1752 | local scx, scy = cx+paint.scrollX, cy+paint.scrollY | |
1753 | local output = {} | |
1754 | for a = 1, #frame do | |
1755 | maxX = math.max(maxX, frame[a].x) | |
1756 | maxY = math.max(maxY, frame[a].y) | |
1757 | minX = math.min(minX, frame[a].x) | |
1758 | minY = math.min(minY, frame[a].y) | |
1759 | end | |
1760 | ||
1761 | maxX = math.max(maxX, scx) | |
1762 | maxY = math.max(maxY, scy) | |
1763 | minX = math.min(minX, scx) | |
1764 | minY = math.min(minY, scy) | |
1765 | ||
1766 | maxX = math.max(maxX, screenEdges[1]) | |
1767 | maxY = math.max(maxY, screenEdges[2]) | |
1768 | ||
1769 | local doop = {} | |
1770 | local touched = {} | |
1771 | local check = {[scy] = {[scx] = true}} | |
1772 | for y = minY, maxY do | |
1773 | doop[y] = {} | |
1774 | touched[y] = {} | |
1775 | for x = minX, maxX do | |
1776 | doop[y][x] = { | |
1777 | c = " ", | |
1778 | b = 0, | |
1779 | t = 0 | |
1780 | } | |
1781 | touched[y][x] = false | |
1782 | end | |
1783 | end | |
1784 | for a = 1, #frame do | |
1785 | doop[frame[a].y][frame[a].x] = { | |
1786 | c = frame[a].c, | |
1787 | t = frame[a].t, | |
1788 | b = frame[a].b | |
1789 | } | |
1790 | end | |
1791 | local initDot = { | |
1792 | c = doop[scy][scx].c, | |
1793 | t = doop[scy][scx].t, | |
1794 | b = doop[scy][scx].b | |
1795 | } | |
1796 | local chkpos = function(x, y, checkList) | |
1797 | - | if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t and doop[y][x].c ~= " ") or (doop[y][x].c ~= initDot.c) then |
1797 | + | |
1798 | return false | |
1799 | else | |
1800 | if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t) or (doop[y][x].c ~= initDot.c) then | |
1801 | return false | |
1802 | end | |
1803 | if check[y] then | |
1804 | if check[y][x] then | |
1805 | return false | |
1806 | end | |
1807 | end | |
1808 | if touched[y][x] then | |
1809 | return false | |
1810 | end | |
1811 | return true | |
1812 | end | |
1813 | - | local currentlyOnScreen |
1813 | + | |
1814 | local doBreak | |
1815 | local step = 0 | |
1816 | local currentlyOnScreen | |
1817 | while true do | |
1818 | - | currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y) |
1818 | + | |
1819 | for chY, v in pairs(check) do | |
1820 | for chX, isTrue in pairs(v) do | |
1821 | - | if painconfig.doFillAnimation then |
1821 | + | currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y) |
1822 | if isTrue and (not touched[chY][chX]) then | |
1823 | step = step + 1 | |
1824 | if doFillAnimation then | |
1825 | if currentlyOnScreen then | |
1826 | - | if isDeleting then |
1826 | + | |
1827 | - | deleteDot(chX, chY) |
1827 | + | |
1828 | end | |
1829 | - | frame[#frame+1] = { |
1829 | + | frame[#frame+1] = { |
1830 | - | x = chX, |
1830 | + | x = chX, |
1831 | - | y = chY, |
1831 | + | y = chY, |
1832 | - | c = dot.c, |
1832 | + | c = dot.c, |
1833 | - | t = dot.t, |
1833 | + | t = dot.t, |
1834 | - | b = dot.b |
1834 | + | b = dot.b |
1835 | - | } |
1835 | + | |
1836 | touched[chY][chX] = true | |
1837 | -- check adjacent | |
1838 | if chkpos(chX+1, chY) then | |
1839 | check[chY][chX+1] = true | |
1840 | doBreak = false | |
1841 | end | |
1842 | if chkpos(chX-1, chY) then | |
1843 | check[chY][chX-1] = true | |
1844 | doBreak = false | |
1845 | end | |
1846 | if chkpos(chX, chY+1) then | |
1847 | check[chY+1] = check[chY+1] or {} | |
1848 | check[chY+1][chX] = true | |
1849 | doBreak = false | |
1850 | end | |
1851 | if chkpos(chX, chY-1) then | |
1852 | check[chY-1] = check[chY-1] or {} | |
1853 | check[chY-1][chX] = true | |
1854 | doBreak = false | |
1855 | end | |
1856 | -- check diagonal | |
1857 | if doFillDiagonal then | |
1858 | - | if painconfig.doFillDiagonal then |
1858 | + | |
1859 | check[chY-1] = check[chY-1] or {} | |
1860 | check[chY-1][chX-1] = true | |
1861 | doBreak = false | |
1862 | end | |
1863 | if chkpos(chX+1, chY-1) then | |
1864 | check[chY-1] = check[chY-1] or {} | |
1865 | check[chY-1][chX+1] = true | |
1866 | doBreak = false | |
1867 | end | |
1868 | if chkpos(chX-1, chY+1) then | |
1869 | check[chY+1] = check[chY+1] or {} | |
1870 | check[chY+1][chX-1] = true | |
1871 | doBreak = false | |
1872 | end | |
1873 | if chkpos(chX+1, chY+1) then | |
1874 | check[chY+1] = check[chY+1] or {} | |
1875 | check[chY+1][chX+1] = true | |
1876 | doBreak = false | |
1877 | end | |
1878 | end | |
1879 | if step % ((doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash | |
1880 | - | if step % ((painconfig.doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash |
1880 | + | |
1881 | end | |
1882 | end | |
1883 | end | |
1884 | end | |
1885 | if doBreak then | |
1886 | break | |
1887 | end | |
1888 | end | |
1889 | paintEncoded = clearAllRedundant(paintEncoded) | |
1890 | saveToUndoBuffer() | |
1891 | end | |
1892 | ||
1893 | local boxCharSelector = function() | |
1894 | local co = function(pos) | |
1895 | if pos then | |
1896 | term.setTextColor(colors.lime) | |
1897 | term.setBackgroundColor(colors.green) | |
1898 | else | |
1899 | term.setTextColor(colors.lightGray) | |
1900 | term.setBackgroundColor(colors.gray) | |
1901 | end | |
1902 | end | |
1903 | local rend = function() | |
1904 | term.setCursorPos(1,scr_y) | |
1905 | term.setBackgroundColor(colors.lightGray) | |
1906 | term.setTextColor(colors.black) | |
1907 | term.clearLine() | |
1908 | term.write("Press CTRL or 'N' when ready.") | |
1909 | term.setCursorPos(1,scr_y-3) co(boxchar.topLeft) write("Q") co(boxchar.topRight) write("W") | |
1910 | term.setCursorPos(1,scr_y-2) co(boxchar.left) write("A") co(boxchar.right) write("S") | |
1911 | term.setCursorPos(1,scr_y-1) co(boxchar.bottomLeft) write("Z") co(boxchar.bottomRight) write("X") | |
1912 | end | |
1913 | while true do | |
1914 | rend() | |
1915 | local evt = {os.pullEvent()} | |
1916 | if evt[1] == "key" then | |
1917 | local key = evt[2] | |
1918 | if key == keys.leftCtrl or key == keys.rightCtrl or key == keys.n then | |
1919 | break | |
1920 | else | |
1921 | if key == keys.q then boxchar.topLeft = not boxchar.topLeft end | |
1922 | if key == keys.w then boxchar.topRight = not boxchar.topRight end | |
1923 | if key == keys.a then boxchar.left = not boxchar.left end | |
1924 | if key == keys.s then boxchar.right = not boxchar.right end | |
1925 | if key == keys.z then boxchar.bottomLeft = not boxchar.bottomLeft end | |
1926 | if key == keys.x then boxchar.bottomRight = not boxchar.bottomRight end | |
1927 | end | |
1928 | elseif evt[1] == "mouse_click" or evt[1] == "mouse_drag" then | |
1929 | local button, mx, my = evt[2], evt[3], evt[4] | |
1930 | if my >= scr_y-2 then | |
1931 | if mx == 1 then | |
1932 | if my == scr_y - 3 then boxchar.topLeft = not boxchar.topLeft end | |
1933 | if my == scr_y - 2 then boxchar.left = not boxchar.left end | |
1934 | if my == scr_y - 1 then boxchar.bottomLeft = not boxchar.bottomLeft end | |
1935 | elseif mx == 2 then | |
1936 | if my == scr_y - 3 then boxchar.topRight = not boxchar.topRight end | |
1937 | if my == scr_y - 2 then boxchar.right = not boxchar.right end | |
1938 | if my == scr_y - 1 then boxchar.bottomRight = not boxchar.bottomRight end | |
1939 | elseif evt[1] == "mouse_click" then | |
1940 | break | |
1941 | end | |
1942 | elseif evt[1] == "mouse_click" then | |
1943 | break | |
1944 | end | |
1945 | end | |
1946 | end | |
1947 | if boxchar.topLeft and boxchar.topRight and boxchar.left and boxchar.right and boxchar.bottomLeft and boxchar.bottomRight then | |
1948 | swapColors = false | |
1949 | return " " | |
1950 | else | |
1951 | local output = getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight) | |
1952 | swapColors = not output.inverted | |
1953 | return output.char | |
1954 | end | |
1955 | end | |
1956 | ||
1957 | local specialCharSelector = function() | |
1958 | local chars = {} | |
1959 | local buff = 0 | |
1960 | for y = 1, 16 do | |
1961 | for x = 1, 16 do | |
1962 | chars[y] = chars[y] or {} | |
1963 | chars[y][x] = string.char(buff) | |
1964 | buff = buff + 1 | |
1965 | end | |
1966 | end | |
1967 | local sy = scr_y - (#chars + 1) | |
1968 | local char = paint.c | |
1969 | local render = function() | |
1970 | for y = 1, #chars do | |
1971 | for x = 1, #chars do | |
1972 | term.setCursorPos(x,y+sy) | |
1973 | if chars[y][x] == char then | |
1974 | term.blit(chars[y][x], "5", "d") | |
1975 | else | |
1976 | term.blit(chars[y][x], "8", "7") | |
1977 | end | |
1978 | end | |
1979 | end | |
1980 | end | |
1981 | local evt, butt, x, y | |
1982 | render() | |
1983 | ||
1984 | term.setCursorPos(1,scr_y) | |
1985 | term.setBackgroundColor(colors.lightGray) | |
1986 | term.setTextColor(colors.black) | |
1987 | term.clearLine() | |
1988 | term.write("Press CTRL or 'N' when ready.") | |
1989 | ||
1990 | while true do | |
1991 | evt, butt, x, y = os.pullEvent() | |
1992 | if (evt == "mouse_click" or evt == "mouse_drag") then | |
1993 | if chars[y-sy] then | |
1994 | if chars[y-sy][x] then | |
1995 | if (chars[y-sy][x] ~= char) then | |
1996 | char = chars[y-sy][x] | |
1997 | render() | |
1998 | end | |
1999 | else | |
2000 | return char | |
2001 | end | |
2002 | else | |
2003 | return char | |
2004 | end | |
2005 | elseif evt == "key" then | |
2006 | if (butt == keys.n) or (butt == keys.leftCtrl) then | |
2007 | return char | |
2008 | end | |
2009 | end | |
2010 | end | |
2011 | end | |
2012 | ||
2013 | local dontDragThisTime = false | |
2014 | local resetInputState = function() | |
2015 | miceDown = {} | |
2016 | keysDown = {} | |
2017 | isDragging = false | |
2018 | dontDragThisTime = true | |
2019 | end | |
2020 | ||
2021 | local gotoCoords = function() | |
2022 | local newX = bottomPrompt("Goto X:") | |
2023 | newX = tonumber(newX) | |
2024 | local newY | |
2025 | if newX then | |
2026 | newY = bottomPrompt("Goto Y:") | |
2027 | newY = tonumber(newY) | |
2028 | paint.scrollX = newX or paint.scrollX | |
2029 | paint.scrollY = newY or paint.scrollY | |
2030 | end | |
2031 | end | |
2032 | ||
2033 | local renderAllPAIN = function() | |
2034 | renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true) | |
2035 | end | |
2036 | ||
2037 | local checkIfNFP = function(str) --does not check table format, only string format | |
2038 | local good = { | |
2039 | - | ['0'] = true, |
2039 | + | [0] = true, |
2040 | - | ['1'] = true, |
2040 | + | [1] = true, |
2041 | - | ['2'] = true, |
2041 | + | [2] = true, |
2042 | - | ['3'] = true, |
2042 | + | [3] = true, |
2043 | - | ['4'] = true, |
2043 | + | [4] = true, |
2044 | - | ['5'] = true, |
2044 | + | [5] = true, |
2045 | - | ['6'] = true, |
2045 | + | [6] = true, |
2046 | - | ['7'] = true, |
2046 | + | [7] = true, |
2047 | - | ['8'] = true, |
2047 | + | [8] = true, |
2048 | - | ['9'] = true, |
2048 | + | [9] = true, |
2049 | a = true, | |
2050 | b = true, | |
2051 | c = true, | |
2052 | d = true, | |
2053 | e = true, | |
2054 | f = true, | |
2055 | [" "] = true, | |
2056 | ["\n"] = true | |
2057 | } | |
2058 | for a = 1, #str do | |
2059 | if not good[str:sub(a,a):lower()] then | |
2060 | return false | |
2061 | end | |
2062 | end | |
2063 | return true | |
2064 | end | |
2065 | ||
2066 | - | local selectRegion = function() |
2066 | + | |
2067 | - | local mevt, id, x1, y1 = os.pullEvent("mouse_click") |
2067 | + | |
2068 | - | local x2, y2, pos, redrawID |
2068 | + | |
2069 | - | local renderRectangle = true |
2069 | + | |
2070 | - | redrawID = os.startTimer(0.5) |
2070 | + | if type(tun(contents)) ~= "table" then |
2071 | term.setTextColor(colors.white) | |
2072 | - | mevt, id, x2, y2 = os.pullEvent() |
2072 | + | |
2073 | - | if mevt == "mouse_up" or mevt == "mouse_drag" or mevt == "mouse_click" then |
2073 | + | if pMode ~= 1 then print("Importing from BLT...") end |
2074 | - | pos = {{ |
2074 | + | |
2075 | - | x1 < x2 and x1 or x2, |
2075 | + | elseif contents:sub(1,2) == "BM" then |
2076 | - | y1 < y2 and y1 or y2 |
2076 | + | if pMode ~= 1 then print("Importing from BMP...") end |
2077 | - | },{ |
2077 | + | return {importFromBitmap(fname)}, 7 |
2078 | - | x1 < x2 and x2 or x1, |
2078 | + | |
2079 | - | y1 < y2 and y2 or y1 |
2079 | + | if pMode ~= 1 then print("Importing from GIF, this'll take a while...") end |
2080 | - | }} |
2080 | + | |
2081 | elseif contents:sub(1,4) == "?!7\2" then | |
2082 | - | if mevt == "mouse_up" then |
2082 | + | if pMode ~= 1 then print("Importing from UCG...") end |
2083 | return {importFromUCG(fname)}, 6 | |
2084 | elseif contents:find(string.char(30)) and contents:find(string.char(31)) then | |
2085 | - | if (mevt == "mouse_drag") or (mevt == "timer" and id == redrawID) then |
2085 | + | if pMode ~= 1 then print("Importing from NFT...") end |
2086 | return {importFromNFT(contents)}, 2 | |
2087 | - | if renderRectangle then |
2087 | + | |
2088 | - | term.setTextColor(rendback.t) |
2088 | + | |
2089 | return {importFromPaint(contents)}, 1 | |
2090 | - | for y = pos[1][2], pos[2][2] do |
2090 | + | |
2091 | - | if y ~= scr_y then |
2091 | + | |
2092 | - | term.setCursorPos(pos[1][1], y) |
2092 | + | |
2093 | - | if (y == pos[1][2] or y == pos[2][2]) then |
2093 | + | |
2094 | - | term.write(("#"):rep(1 + pos[2][1] - pos[1][1])) |
2094 | + | return tun(contents), 4 |
2095 | end | |
2096 | - | term.write("#") |
2096 | + | |
2097 | - | term.setCursorPos(pos[2][1], y) |
2097 | + | |
2098 | - | term.write("#") |
2098 | + | |
2099 | menuOptions = {"File","Edit","Window","About","Exit"} | |
2100 | local diss = " "..tableconcat(menuOptions," ") | |
2101 | local cleary = scr_y-math.floor(#diss/scr_x) | |
2102 | ||
2103 | local fileSave = function() | |
2104 | - | if (mevt == "timer" and id == redrawID) then |
2104 | + | |
2105 | - | renderRectangle = not renderRectangle |
2105 | + | |
2106 | - | redrawID = os.startTimer(0.25) |
2106 | + | |
2107 | output = convertToGrayscale(output) | |
2108 | end | |
2109 | doRender = true | |
2110 | - | pos[1][1] = pos[1][1] + paint.scrollX |
2110 | + | if not fileName then |
2111 | - | pos[2][1] = pos[2][1] + paint.scrollX |
2111 | + | |
2112 | - | pos[1][2] = pos[1][2] + paint.scrollY |
2112 | + | |
2113 | - | pos[2][2] = pos[2][2] + paint.scrollY |
2113 | + | |
2114 | - | for k,v in pairs(paintEncoded[frame]) do |
2114 | + | |
2115 | - | if v.x >= pos[1][1] and v.x <= pos[2][1] then |
2115 | + | |
2116 | - | if v.y >= pos[1][2] and v.y <= pos[2][2] then |
2116 | + | |
2117 | return false | |
2118 | - | x = v.x - pos[1][1], |
2118 | + | |
2119 | - | y = v.y - pos[1][2], |
2119 | + | |
2120 | - | t = v.t, |
2120 | + | |
2121 | - | c = v.c, |
2121 | + | |
2122 | - | b = v.b, |
2122 | + | |
2123 | - | m = v.m |
2123 | + | |
2124 | else | |
2125 | fileName = fnguess | |
2126 | end | |
2127 | end | |
2128 | - | return output, pos[1][1], pos[1][2], pos[2][1], pos[2][2] |
2128 | + | saveFile(fileName,output) |
2129 | term.setCursorPos(9,scr_y) | |
2130 | return fileName | |
2131 | end | |
2132 | local filePrint = function() | |
2133 | local usedDots, dot = {}, {} | |
2134 | for a = 1, #paintEncoded[frame] do | |
2135 | - | if type(textutils.unserialize(contents)) ~= "table" then |
2135 | + | |
2136 | if dot.x > paint.scrollX and dot.x < (paint.scrollX + 25) and dot.y > paint.scrollX and dot.y < (paint.scrollY + 21) then | |
2137 | if dot.c ~= " " then | |
2138 | - | if plc.pMode ~= 1 then print("Importing from BLT...") end |
2138 | + | |
2139 | usedDots[dot.t][#usedDots[dot.t]+1] = { | |
2140 | x = dot.x - paint.scrollX, | |
2141 | - | if plc.pMode ~= 1 then print("Importing from GIF, this'll take a while...") end |
2141 | + | |
2142 | char = dot.c | |
2143 | } | |
2144 | - | if plc.pMode ~= 1 then print("Importing from UCG...") end |
2144 | + | |
2145 | end | |
2146 | end | |
2147 | - | if plc.pMode ~= 1 then print("Importing from NFT...") end |
2147 | + | |
2148 | [1] = "bonemeal", | |
2149 | [2] = "orange dye", | |
2150 | [4] = "magenta dye", | |
2151 | [8] = "light blue dye", | |
2152 | [16] = "dandelion yellow", | |
2153 | [32] = "lime dye", | |
2154 | [64] = "pink dye", | |
2155 | [128] = "gray dye", | |
2156 | - | return textutils.unserialize(contents), 4 |
2156 | + | |
2157 | [512] = "cyan dye", | |
2158 | [1024] = "purple dye", | |
2159 | [2048] = "lapis lazuli", | |
2160 | - | local editFuncs = { |
2160 | + | |
2161 | - | copy = function() |
2161 | + | |
2162 | - | local board = bottomPrompt("Copy to board: ") |
2162 | + | |
2163 | - | renderAllPAIN() |
2163 | + | |
2164 | - | renderBottomBar("Select region to copy.") |
2164 | + | |
2165 | - | local selectedDots = selectRegion() |
2165 | + | |
2166 | - | theClipboard[board] = selectedDots |
2166 | + | |
2167 | - | barmsg = "Copied to '"..board.."'" |
2167 | + | |
2168 | return false | |
2169 | - | keysDown = {} |
2169 | + | |
2170 | - | miceDown = {} |
2170 | + | |
2171 | - | end, |
2171 | + | |
2172 | - | cut = function() |
2172 | + | |
2173 | - | local board = bottomPrompt("Cut to board: ") |
2173 | + | |
2174 | - | renderAllPAIN() |
2174 | + | |
2175 | - | renderBottomBar("Select region to cut.") |
2175 | + | |
2176 | - | local selectedDots, x1, y1, x2, y2 = selectRegion() |
2176 | + | |
2177 | - | theClipboard[board] = selectedDots |
2177 | + | |
2178 | - | local dot |
2178 | + | |
2179 | - | for i = #paintEncoded[frame], 1, -1 do |
2179 | + | |
2180 | - | dot = paintEncoded[frame][i] |
2180 | + | |
2181 | - | if dot.x >= x1 and dot.x <= x2 then |
2181 | + | |
2182 | - | if dot.y >= y1 and dot.y <= y2 then |
2182 | + | |
2183 | - | table.remove(paintEncoded[frame], i) |
2183 | + | |
2184 | - | end |
2184 | + | |
2185 | - | end |
2185 | + | |
2186 | return | |
2187 | - | barmsg = "Cut to '"..board.."'" |
2187 | + | |
2188 | for k,v in pairs(usedDots[color]) do | |
2189 | - | saveToUndoBuffer() |
2189 | + | |
2190 | - | keysDown = {} |
2190 | + | |
2191 | - | miceDown = {} |
2191 | + | |
2192 | - | end, |
2192 | + | |
2193 | - | paste = function() |
2193 | + | |
2194 | - | local board = bottomPrompt("Paste from board: ") |
2194 | + | |
2195 | - | renderAllPAIN() |
2195 | + | |
2196 | - | renderBottomBar("Click to paste. (top left corner)") |
2196 | + | |
2197 | - | if theClipboard[board] then |
2197 | + | |
2198 | - | local mevt |
2198 | + | |
2199 | - | repeat |
2199 | + | |
2200 | - | mevt = {os.pullEvent()} |
2200 | + | |
2201 | - | 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) |
2201 | + | |
2202 | - | for k,v in pairs(theClipboard[board]) do |
2202 | + | |
2203 | - | paintEncoded[frame][#paintEncoded[frame]+1] = { |
2203 | + | |
2204 | - | x = v.x + paint.scrollX + (mevt[3]), |
2204 | + | |
2205 | - | y = v.y + paint.scrollY + (mevt[4]), |
2205 | + | |
2206 | - | c = v.c, |
2206 | + | |
2207 | - | t = v.t, |
2207 | + | |
2208 | - | b = v.b, |
2208 | + | |
2209 | - | m = v.m |
2209 | + | changedImage = false |
2210 | - | } |
2210 | + | |
2211 | - | end |
2211 | + | |
2212 | - | paintEncoded[frame] = clearRedundant(paintEncoded[frame]) |
2212 | + | |
2213 | - | barmsg = "Pasted from '"..board.."'" |
2213 | + | |
2214 | - | doRender = true |
2214 | + | |
2215 | - | saveToUndoBuffer() |
2215 | + | |
2216 | - | keysDown = {} |
2216 | + | |
2217 | - | miceDown = {} |
2217 | + | |
2218 | else | |
2219 | - | barmsg = "No such clipboard." |
2219 | + | |
2220 | - | doRender = true |
2220 | + | |
2221 | nfpoutput = "" | |
2222 | if fs.combine("",exportName) == "" then | |
2223 | barmsg = "Export cancelled." | |
2224 | return | |
2225 | - | local blockEnlargeFrame = function(frameNo) |
2225 | + | |
2226 | if fs.isReadOnly(exportName) then | |
2227 | - | local frame = deepCopy(paintEncoded[frameNo]) |
2227 | + | |
2228 | - | local charConvert = { |
2228 | + | |
2229 | - | ["\129"] = {{true , false},{false, false},{false, false}}, |
2229 | + | |
2230 | - | ["\130"] = {{false, true },{false, false},{false, false}}, |
2230 | + | |
2231 | - | ["\131"] = {{true , true },{false, false},{false, false}}, |
2231 | + | local plea = (progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?" |
2232 | - | ["\132"] = {{false, false},{true , false},{false, false}}, |
2232 | + | |
2233 | - | ["\133"] = {{true , false},{true , false},{false, false}}, |
2233 | + | |
2234 | - | ["\134"] = {{false, true },{true , false},{false, false}}, |
2234 | + | |
2235 | - | ["\135"] = {{true , true },{true , false},{false, false}}, |
2235 | + | |
2236 | - | ["\136"] = {{false, false},{false, true },{false, false}}, |
2236 | + | |
2237 | - | ["\137"] = {{true , false},{false, true },{false, false}}, |
2237 | + | |
2238 | - | ["\138"] = {{false, true },{false, true },{false, false}}, |
2238 | + | |
2239 | - | ["\139"] = {{true , true },{false, true },{false, false}}, |
2239 | + | |
2240 | - | ["\140"] = {{false, false},{true , true },{false, false}}, |
2240 | + | |
2241 | - | ["\141"] = {{true , false},{true , true },{false, false}}, |
2241 | + | |
2242 | - | ["\142"] = {{false, true },{true , true },{false, false}}, |
2242 | + | |
2243 | - | ["\143"] = {{true , true },{true , true },{false, false}}, |
2243 | + | |
2244 | - | ["\144"] = {{false, false},{false, false},{true , false}}, |
2244 | + | |
2245 | - | ["\145"] = {{true , false},{false, false},{true , false}}, |
2245 | + | if askToSerialize then |
2246 | - | ["\146"] = {{false, true },{false, false},{true , false}}, |
2246 | + | |
2247 | - | ["\147"] = {{true , true },{false, false},{true , false}}, |
2247 | + | |
2248 | - | ["\148"] = {{false, false},{true , false},{true , false}}, |
2248 | + | |
2249 | - | ["\149"] = {{true , false},{true , false},{true , false}}, |
2249 | + | |
2250 | - | ["\150"] = {{false, true },{true , false},{true , false}}, |
2250 | + | |
2251 | - | ["\151"] = {{true , true },{true , false},{true , false}}, |
2251 | + | |
2252 | - | ["\152"] = {{false, false},{false, true },{true , false}}, |
2252 | + | |
2253 | - | ["\153"] = {{true , false},{false, true },{true , false}}, |
2253 | + | |
2254 | - | ["\154"] = {{false, true },{false, true },{true , false}}, |
2254 | + | |
2255 | - | ["\155"] = {{true , true },{false, true },{true , false}}, |
2255 | + | |
2256 | - | ["\156"] = {{false, false},{true , true },{true , false}}, |
2256 | + | |
2257 | - | ["\157"] = {{true , false},{true , true },{true , false}}, |
2257 | + | if askToSerialize then |
2258 | - | ["\158"] = {{false, true },{true , true },{true , false}}, |
2258 | + | |
2259 | - | ["\159"] = {{true , true },{true , true },{true , false}}, |
2259 | + | |
2260 | output = textutils.serialise(exportToBLT(pe,exportName,doAllFrames == "y",doSerializeBLT)) | |
2261 | - | local output, b = {} |
2261 | + | |
2262 | - | for k, dot in pairs(frame) do |
2262 | + | |
2263 | - | b = charConvert[dot.c] or {{false, false},{false, false},{false, false}} |
2263 | + | |
2264 | - | for y = 1, #b do |
2264 | + | |
2265 | - | for x = 1, #b[y] do |
2265 | + | |
2266 | end | |
2267 | - | x = (dot.x - 1) * 2 + (x - 0), |
2267 | + | |
2268 | - | y = (dot.y - 1) * 3 + (y - 0), |
2268 | + | |
2269 | file.write(output) | |
2270 | - | -- t = b[y][x] and dot.t or dot.b, |
2270 | + | |
2271 | - | -- b = b[y][x] and dot.b or dot.t |
2271 | + | |
2272 | - | t = b[y][x] and dot.b or dot.t, |
2272 | + | |
2273 | - | b = b[y][x] and dot.t or dot.b |
2273 | + | |
2274 | ||
2275 | local editDelFrame = function() | |
2276 | local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) | |
2277 | doRender = true | |
2278 | - | barmsg = "Grew image." |
2278 | + | |
2279 | if #paintEncoded == 1 then | |
2280 | barmsg = "Fat chance." | |
2281 | return | |
2282 | end | |
2283 | table.remove(paintEncoded,frame) | |
2284 | - | doRender = false |
2284 | + | |
2285 | - | menuOptions = {"File","Edit","Window","Set","About","Exit"} |
2285 | + | |
2286 | frame = frame - 1 | |
2287 | else | |
2288 | frame = frame + 1 | |
2289 | end | |
2290 | if #paintEncoded < frame then | |
2291 | repeat | |
2292 | frame = frame - 1 | |
2293 | until #paintEncoded >= frame | |
2294 | end | |
2295 | - | if not plc.fileName then |
2295 | + | |
2296 | end | |
2297 | end | |
2298 | local editClear = function() | |
2299 | local outcum = bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) | |
2300 | if outcum == "y" then | |
2301 | paintEncoded[frame] = {} | |
2302 | saveToUndoBuffer() | |
2303 | barmsg = "Cleared frame "..frame.."." | |
2304 | end | |
2305 | doRender = true | |
2306 | end | |
2307 | local editCrop = function() | |
2308 | local outcum = bottomPrompt("Crop all but visible? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) | |
2309 | if outcum == "y" then | |
2310 | - | plc.fileName = fnguess |
2310 | + | |
2311 | local deletedAmnt = 0 | |
2312 | for a = #paintEncoded[frame], 1, -1 do | |
2313 | - | saveFile(plc.fileName,output) |
2313 | + | |
2314 | if (x <= paint.scrollX) or (x > paint.scrollX + scr_x) or (y <= paint.scrollY) or (y > paint.scrollY + scr_y) then | |
2315 | - | return plc.fileName |
2315 | + | |
2316 | deletedAmnt = deletedAmnt + 1 | |
2317 | else | |
2318 | ppos = ppos + 1 | |
2319 | end | |
2320 | if ppos > #paintEncoded[frame] then break end | |
2321 | end | |
2322 | saveToUndoBuffer() | |
2323 | barmsg = "Cropped frame." | |
2324 | end | |
2325 | doRender = true | |
2326 | end | |
2327 | local editBoxCharSelector = function() | |
2328 | paint.c = boxCharSelector() | |
2329 | end | |
2330 | local editSpecialCharSelector = function() | |
2331 | paint.c = boxCharSelector() | |
2332 | end | |
2333 | ||
2334 | local windowSetScrSize = function() | |
2335 | local x,y | |
2336 | x = bottomPrompt("Scr.X OR monitor name:",{},nil,{keys.leftCtrl,keys.rightCtrl}) | |
2337 | if x == "" then | |
2338 | return | |
2339 | elseif x == "pocket" then | |
2340 | screenEdges = {26,20} | |
2341 | elseif x == "turtle" then | |
2342 | screenEdges = {39,13} | |
2343 | elseif x == "computer" then | |
2344 | screenEdges = {51,19} | |
2345 | elseif tonumber(x) then | |
2346 | if tonumber(x) <= 0 then | |
2347 | barmsg = "Screen X must be greater than 0." | |
2348 | return | |
2349 | end | |
2350 | screenEdges[1] = math.abs(tonumber(x)) | |
2351 | y = bottomPrompt("Scr.Y:",{},nil,{keys.leftCtrl,keys.rightCtrl}) | |
2352 | if tonumber(y) then | |
2353 | if tonumber(y) <= 0 then | |
2354 | barmsg = "Screen Y must be greater than 0." | |
2355 | return | |
2356 | end | |
2357 | screenEdges[2] = math.abs(tonumber(y)) | |
2358 | end | |
2359 | barmsg = "Screen size changed." | |
2360 | else | |
2361 | local mon = peripheral.wrap(x) | |
2362 | if not mon then | |
2363 | barmsg = "No such monitor." | |
2364 | return | |
2365 | else | |
2366 | if peripheral.getType(x) ~= "monitor" then | |
2367 | barmsg = "That's not a monitor." | |
2368 | return | |
2369 | else | |
2370 | screenEdges[1], screenEdges[2] = mon.getSize() | |
2371 | barmsg = "Screen size changed." | |
2372 | return | |
2373 | end | |
2374 | end | |
2375 | end | |
2376 | end | |
2377 | local aboutPAIN = function() | |
2378 | local helpText = [[ | |
2379 | ||
2380 | | |
2381 | | |
2382 | | |
2383 | | |
2384 | | |
2385 | ||
2386 | Advanced Paint Program | |
2387 | by LDDestroier | |
2388 | or EldidiStroyrr | |
2389 | if you please! | |
2390 | ||
2391 | PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility. | |
2392 | ||
2393 | - | plc.changedImage = false |
2393 | + | 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. |
2394 | ||
2395 | 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. | |
2396 | ]] | |
2397 | guiHelp(helpText) | |
2398 | end | |
2399 | local aboutFileFormats = function() | |
2400 | local helpText = [[ | |
2401 | Here's info on the file formats. | |
2402 | ||
2403 | "NFP": | |
2404 | 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. | |
2405 | Cannot save multiple frames. | |
2406 | ||
2407 | "NFT": | |
2408 | 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. | |
2409 | Cannot save multiple frames. | |
2410 | ||
2411 | "BLT": | |
2412 | 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. | |
2413 | BLT can save multiple frames! | |
2414 | ||
2415 | - | local plea = (plc.progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?" |
2415 | + | |
2416 | 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. | |
2417 | Obviously, this can save multiple frames. | |
2418 | ||
2419 | "GIF": | |
2420 | 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. | |
2421 | Being GIF, this saves multiple frames! | |
2422 | ||
2423 | "UCG": | |
2424 | 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. | |
2425 | Cannot save multiple frames. | |
2426 | ||
2427 | ||
2428 | 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. | |
2429 | - | if plc.askToSerialize then |
2429 | + | |
2430 | guiHelp(helpText) | |
2431 | end | |
2432 | local menuPoses = {} | |
2433 | local menuFunctions = { | |
2434 | [1] = function() --File | |
2435 | while true do | |
2436 | --renderAllPAIN() | |
2437 | local output, longestLen = makeSubMenu(1,cleary-1,{"Save","Save As","Export","Open",((peripheral.find("printer")) and "Print" or nil)}) | |
2438 | doRender = true | |
2439 | if output == 1 then -- Save | |
2440 | local _fname = fileExport(_,defaultSaveFormat,fileName) | |
2441 | - | if plc.askToSerialize then |
2441 | + | |
2442 | barmsg = "Saved as '".._fname.."'" | |
2443 | lastPaintEncoded = deepCopy(paintEncoded) | |
2444 | changedImage = false | |
2445 | end | |
2446 | break | |
2447 | elseif output == 2 then -- Save As | |
2448 | local oldfilename = fileName | |
2449 | fileName = nil | |
2450 | local res = fileExport(_,defaultSaveFormat) | |
2451 | if not res then | |
2452 | fileName = oldfilename | |
2453 | end | |
2454 | barmsg = "Saved as '"..fileName.."'" | |
2455 | elseif output == 3 then --Export | |
2456 | local res = fileExport(longestLen+1) | |
2457 | if res then | |
2458 | barmsg = "Exported as '"..res.."'" | |
2459 | - | local editClear = function(ignorePrompt) |
2459 | + | break |
2460 | - | local outcum = ignorePrompt and "y" or bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) |
2460 | + | |
2461 | elseif output == 4 then -- Open | |
2462 | renderBottomBar("Pick an image file.") | |
2463 | local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(fileName or progname), false, false, false, true, false, nil, true) | |
2464 | if newPath then | |
2465 | local pen, form = openNewFile(newPath, readNonImageAsNFP) | |
2466 | if not pen then | |
2467 | barmsg = form | |
2468 | else | |
2469 | fileName = newPath | |
2470 | paintEncoded, lastPaintEncoded = pen, deepCopy(pen) | |
2471 | defaultSaveFormat = form | |
2472 | - | return editClear(true) |
2472 | + | undoPos = 1 |
2473 | undoBuffer = {} | |
2474 | barmsg = "Opened '" .. fs.getName(newPath) .. "'" | |
2475 | paint.scrollX, paint.scrollY, paint.doGray = 1, 1, false | |
2476 | doRender = true | |
2477 | end | |
2478 | end | |
2479 | break | |
2480 | elseif output == 5 then -- Print | |
2481 | filePrint() | |
2482 | break | |
2483 | elseif output == false then | |
2484 | return "nobreak" | |
2485 | end | |
2486 | reRenderPAIN(true) | |
2487 | end | |
2488 | end, | |
2489 | [2] = function() --Edit | |
2490 | local output = makeSubMenu(6,cleary-1,{"Delete Frame","Clear Frame","Crop Frame","Choose Box Character","Choose Special Character","BLittle Shrink"}) | |
2491 | doRender = true | |
2492 | if output == 1 then | |
2493 | editDelFrame() | |
2494 | elseif output == 2 then | |
2495 | editClear() | |
2496 | elseif output == 3 then | |
2497 | editCrop() | |
2498 | elseif output == 4 then | |
2499 | editBoxCharSelector() | |
2500 | elseif output == 5 then | |
2501 | editSpecialCharSelector() | |
2502 | elseif output == 6 then | |
2503 | local res = bottomPrompt("You sure? It's unreversable! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) | |
2504 | if res == "y" then | |
2505 | getBlittle() | |
2506 | local bltPE = blittle.shrink(NFPserializeImage(exportToPaint(paintEncoded[frame]))) | |
2507 | _G.SHRINKOUT = bltPE | |
2508 | paintEncoded[frame] = {} | |
2509 | for y = 1, bltPE.height do | |
2510 | for x = 1, bltPE.width do | |
2511 | paintEncoded[frame][#paintEncoded[frame]+1] = { | |
2512 | - | paint.c = specialCharSelector() |
2512 | + | |
2513 | t = BTC(bltPE[2][y]:sub(x,x),true), | |
2514 | b = BTC(bltPE[3][y]:sub(x,x),true), | |
2515 | x = x, | |
2516 | y = y, | |
2517 | } | |
2518 | end | |
2519 | end | |
2520 | saveToUndoBuffer() | |
2521 | doRender = true | |
2522 | barmsg = "Shrunk image." | |
2523 | end | |
2524 | elseif output == false then | |
2525 | return "nobreak" | |
2526 | end | |
2527 | end, | |
2528 | [3] = function() --Window | |
2529 | local output = makeSubMenu(11,cleary-1,{"Set Screen Size","Set Scroll XY","Set Grid Colors"}) | |
2530 | doRender = true | |
2531 | if output == 1 then | |
2532 | windowSetScrSize() | |
2533 | elseif output == 2 then | |
2534 | gotoCoords() | |
2535 | elseif output == 3 then | |
2536 | rendback.b = paint.b | |
2537 | rendback.t = paint.t | |
2538 | doRender = true | |
2539 | elseif output == false then | |
2540 | return "nobreak" | |
2541 | end | |
2542 | end, | |
2543 | [4] = function() --About | |
2544 | local output = makeSubMenu(17,cleary-1,{"PAIN","File Formats","Help!"}) | |
2545 | doRender = true | |
2546 | if output == 1 then | |
2547 | aboutPAIN() | |
2548 | elseif output == 2 then | |
2549 | aboutFileFormats() | |
2550 | elseif output == 3 then | |
2551 | guiHelp() | |
2552 | doRender = true | |
2553 | end | |
2554 | end, | |
2555 | [5] = function() --Exit | |
2556 | if changedImage then | |
2557 | local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) | |
2558 | sleep(0) | |
2559 | if outcum == "y" then | |
2560 | return "exit" | |
2561 | else | |
2562 | doRender = true | |
2563 | return nil | |
2564 | end | |
2565 | else | |
2566 | return "exit" | |
2567 | end | |
2568 | end, | |
2569 | } | |
2570 | local cursor = 1 | |
2571 | local redrawmenu = true | |
2572 | local initial = os.time() | |
2573 | local clickdelay = 0.003 | |
2574 | - | The main focus during development is to add many functions that you might see in real programs like MSPAINT, such as lines or a proper fill tool, as well as to export/import to and from as many image formats as possible. |
2574 | + | |
2575 | local redrawTheMenu = function() | |
2576 | - | My ultimate goal is to have PAIN be the default paint program for most every operating system on the forums (for what it's worth). 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. |
2576 | + | |
2577 | term.setCursorPos(1,a) | |
2578 | - | I hope my PAIN brings you joy. |
2578 | + | |
2579 | term.clearLine() | |
2580 | end | |
2581 | term.setCursorPos(2,cleary) | |
2582 | for a = 1, #menuOptions do | |
2583 | if a == cursor then | |
2584 | term.setTextColor(colors.black) | |
2585 | term.setBackgroundColor(colors.white) | |
2586 | else | |
2587 | term.setTextColor(colors.black) | |
2588 | term.setBackgroundColor(colors.lightGray) | |
2589 | end | |
2590 | menuPoses[a] = {term.getCursorPos()} | |
2591 | write(menuOptions[a]) | |
2592 | term.setBackgroundColor(colors.lightGray) | |
2593 | if a ~= #menuOptions then | |
2594 | write(" ") | |
2595 | end | |
2596 | end | |
2597 | redrawmenu = false | |
2598 | end | |
2599 | ||
2600 | while true do | |
2601 | if redrawmenu then | |
2602 | redrawTheMenu() | |
2603 | redrawmenu = false | |
2604 | end | |
2605 | local event,key,x,y = getEvents("key","char","mouse_click","mouse_up","mouse_drag") | |
2606 | if event == "key" then | |
2607 | if key == keys.left then | |
2608 | redrawmenu = true | |
2609 | cursor = cursor - 1 | |
2610 | elseif key == keys.right then | |
2611 | redrawmenu = true | |
2612 | cursor = cursor + 1 | |
2613 | elseif key == keys.enter then | |
2614 | redrawmenu = true | |
2615 | local res = menuFunctions[cursor]() | |
2616 | if res == "exit" then | |
2617 | return "exit" | |
2618 | elseif res == "nobreak" then | |
2619 | - | local output, longestLen = makeSubMenu(1,cleary-1,{ |
2619 | + | |
2620 | - | "Save", |
2620 | + | |
2621 | - | "Save As", |
2621 | + | |
2622 | - | "Export", |
2622 | + | |
2623 | - | "Open", |
2623 | + | |
2624 | - | ((peripheral.find("printer")) and "Print" or nil) |
2624 | + | |
2625 | - | }) |
2625 | + | |
2626 | end | |
2627 | - | local _fname = fileExport(_,plc.defaultSaveFormat,plc.fileName) |
2627 | + | |
2628 | for a = 1, #menuOptions do | |
2629 | if key:lower() == menuOptions[a]:sub(1,1):lower() and a ~= cursor then | |
2630 | cursor = a | |
2631 | - | plc.changedImage = false |
2631 | + | |
2632 | break | |
2633 | end | |
2634 | end | |
2635 | - | local oldfilename = plc.fileName |
2635 | + | |
2636 | - | plc.fileName = nil |
2636 | + | |
2637 | - | local res = fileExport(_,plc.defaultSaveFormat) |
2637 | + | |
2638 | elseif key == 1 and initial+clickdelay < os.time() then --key? more like button | |
2639 | - | plc.fileName = oldfilename |
2639 | + | |
2640 | if y == menuPoses[a][2] then | |
2641 | - | barmsg = "Saved as '"..plc.fileName.."'" |
2641 | + | |
2642 | cursor = a | |
2643 | redrawTheMenu() | |
2644 | local res = menuFunctions[a]() | |
2645 | coroutine.yield() | |
2646 | if res == "exit" then | |
2647 | return "exit" | |
2648 | else | |
2649 | return | |
2650 | end | |
2651 | end | |
2652 | - | local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(plc.fileName or plc.progname), false, false, false, true, false, nil, true) |
2652 | + | |
2653 | end | |
2654 | - | local pen, form = openNewFile(newPath, painconfig.readNonImageAsNFP) |
2654 | + | |
2655 | end | |
2656 | if (initial+clickdelay < os.time()) and string.find(event,"mouse") then | |
2657 | if key == 1 then --key? key? what key? all I see is button! | |
2658 | - | plc.fileName = newPath |
2658 | + | |
2659 | if y == menuPoses[a][2] then | |
2660 | - | plc.defaultSaveFormat = form |
2660 | + | |
2661 | - | plc.undoPos = 1 |
2661 | + | |
2662 | - | plc.undoBuffer = {deepCopy(paintEncoded)} |
2662 | + | |
2663 | break | |
2664 | end | |
2665 | end | |
2666 | end | |
2667 | end | |
2668 | end | |
2669 | if cursor < 1 then | |
2670 | cursor = #menuOptions | |
2671 | elseif cursor > #menuOptions then | |
2672 | cursor = 1 | |
2673 | end | |
2674 | end | |
2675 | end | |
2676 | ||
2677 | local lastMX,lastMY,isDragging | |
2678 | - | local output = makeSubMenu(6,cleary-1,{ |
2678 | + | |
2679 | - | "Delete Frame", |
2679 | + | |
2680 | - | "Clear Frame", |
2680 | + | |
2681 | - | "Crop Frame", |
2681 | + | |
2682 | - | "Choose Box Character", |
2682 | + | |
2683 | - | "Choose Special Character", |
2683 | + | |
2684 | - | "BLittle Shrink", |
2684 | + | |
2685 | - | "BLittle Grow", |
2685 | + | |
2686 | - | "Copy Region", |
2686 | + | |
2687 | - | "Cut Region", |
2687 | + | |
2688 | - | "Paste Region" |
2688 | + | |
2689 | - | }) |
2689 | + | |
2690 | end | |
2691 | if keysDown[keys.down] then | |
2692 | paint.scrollY = paint.scrollY + 1 | |
2693 | didMove = true | |
2694 | elseif keysDown[keys.up] then | |
2695 | paint.scrollY = paint.scrollY - 1 | |
2696 | didMove = true | |
2697 | end | |
2698 | if didMove then | |
2699 | if lastMX and lastMY then | |
2700 | if miceDown[1] then | |
2701 | - | local res = bottomPrompt("You sure? It's lossy! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl}) |
2701 | + | |
2702 | end | |
2703 | if miceDown[2] then | |
2704 | os.queueEvent("mouse_click",2,lastMX,lastMY) | |
2705 | end | |
2706 | end | |
2707 | doRender = true | |
2708 | end | |
2709 | end | |
2710 | sleep(0) | |
2711 | end | |
2712 | end | |
2713 | ||
2714 | local linePoses = {} | |
2715 | local dragPoses = {} | |
2716 | ||
2717 | local listAllMonitors = function() | |
2718 | term.setBackgroundColor(colors.gray) | |
2719 | term.setTextColor(colors.white) | |
2720 | local periphs = peripheral.getNames() | |
2721 | - | elseif output == 7 then |
2721 | + | |
2722 | - | paintEncoded[frame] = blockEnlargeFrame(frame) |
2722 | + | |
2723 | - | elseif output == 8 then |
2723 | + | |
2724 | - | editFuncs.copy() |
2724 | + | |
2725 | - | elseif output == 9 then |
2725 | + | |
2726 | - | editFuncs.cut() |
2726 | + | |
2727 | - | elseif output == 10 then |
2727 | + | |
2728 | - | editFuncs.paste() |
2728 | + | |
2729 | end | |
2730 | term.setCursorPos(3,1) | |
2731 | term.clearLine() | |
2732 | term.setTextColor(colors.yellow) | |
2733 | term.write("All monitors:") | |
2734 | - | local output = makeSubMenu(11,cleary-1,{ |
2734 | + | |
2735 | - | "Set Screen Size", |
2735 | + | |
2736 | - | "Set Scroll XY", |
2736 | + | |
2737 | - | "Set Grid Colors" |
2737 | + | |
2738 | - | }) |
2738 | + | |
2739 | end | |
2740 | sleep(0) | |
2741 | getEvents("char","mouse_click") | |
2742 | doRender = true | |
2743 | end | |
2744 | ||
2745 | local getInput = function() --gotta catch them all | |
2746 | local button, x, y, oldmx, oldmy, origx, origy | |
2747 | local isDragging = false | |
2748 | local proceed = false | |
2749 | renderBar(barmsg) | |
2750 | - | [4] = function() --Set |
2750 | + | |
2751 | - | local output = makeSubMenu(17,cleary-1,{ |
2751 | + | doRender = false |
2752 | - | (painconfig.readNonImageAsNFP and "(T)" or "(F)") .. " Load Non-images", |
2752 | + | local oldx,oldy = paint.scrollX,paint.scrollY |
2753 | - | (painconfig.useFlattenGIF and "(T)" or "(F)") .. " Flatten GIFs", |
2753 | + | local evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)} |
2754 | - | (painconfig.gridBleedThrough and "(T)" or "(F)") .. " Always Render Grid", |
2754 | + | if (evt[1] == "mouse_scroll") and (not viewing) then |
2755 | - | (painconfig.doFillDiagonal and "(T)" or "(F)") .. " Fill Diagonally", |
2755 | + | local dir = evt[2] |
2756 | - | (painconfig.doFillAnimation and "(T)" or "(F)") .. " Do Fill Animation", |
2756 | + | if dir == 1 then |
2757 | - | (painconfig.useSetVisible and "(T)" or "(F)") .. " Use setVisible()", |
2757 | + | if keysDown[keys.leftShift] or keysDown[keys.rightShift] then |
2758 | - | "(" .. painconfig.undoBufferSize .. ") Set Undo Buffer Size", |
2758 | + | paint.t = paint.t * 2 |
2759 | - | }) |
2759 | + | if paint.t > 32768 then |
2760 | paint.t = 32768 | |
2761 | - | painconfig.readNonImageAsNFP = not painconfig.readNonImageAsNFP |
2761 | + | |
2762 | else | |
2763 | - | painconfig.useFlattenGIF = not painconfig.useFlattenGIF |
2763 | + | paint.b = paint.b * 2 |
2764 | if paint.b > 32768 then | |
2765 | - | painconfig.gridBleedThrough = not painconfig.gridBleedThrough |
2765 | + | paint.b = 32768 |
2766 | end | |
2767 | - | painconfig.doFillDiagonal = not painconfig.doFillDiagonal |
2767 | + | |
2768 | else | |
2769 | - | painconfig.doFillAnimation = not painconfig.doFillAnimation |
2769 | + | if keysDown[keys.leftShift] or keysDown[keys.rightShift] then |
2770 | paint.t = math.ceil(paint.t / 2) | |
2771 | - | painconfig.useSetVisible = not painconfig.useSetVisible |
2771 | + | if paint.t < 1 then |
2772 | - | elseif output == 7 then |
2772 | + | paint.t = 1 |
2773 | - | local newUndoBufferSize = bottomPrompt("New undo buffer size: ") |
2773 | + | |
2774 | - | if tonumber(newUndoBufferSize) then |
2774 | + | |
2775 | - | painconfig.undoBufferSize = math.abs(tonumber(newUndoBufferSize)) |
2775 | + | paint.b = math.ceil(paint.b / 2) |
2776 | - | plc.undoBuffer = {deepCopy(paintEncoded)} |
2776 | + | if paint.b < 1 then |
2777 | - | plc.undoPos = 1 |
2777 | + | paint.b = 1 |
2778 | end | |
2779 | end | |
2780 | end | |
2781 | renderBar(barmsg) | |
2782 | - | useConfig("save") |
2782 | + | elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then |
2783 | if evt[1] == "mouse_click" then | |
2784 | - | [5] = function() --About |
2784 | + | origx, origy = evt[3], evt[4] |
2785 | - | local output = makeSubMenu(17,cleary-1,{ |
2785 | + | |
2786 | - | "PAIN", |
2786 | + | oldmx,oldmy = x or evt[3], y or evt[4] |
2787 | - | "File Formats", |
2787 | + | lastMX,lastMY = evt[3],evt[4] |
2788 | - | "Help!" |
2788 | + | button,x,y = evt[2],evt[3],evt[4] |
2789 | - | }) |
2789 | + | if renderBlittle then |
2790 | x = 2*x | |
2791 | y = 3*y | |
2792 | lastMX = 2*lastMX | |
2793 | lastMY = 3*lastMY | |
2794 | end | |
2795 | linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}} | |
2796 | miceDown[button] = true | |
2797 | doRender = true | |
2798 | - | [6] = function() --Exit |
2798 | + | if y <= scr_y-(renderBlittle and 0 or doRenderBar) then |
2799 | - | if plc.changedImage then |
2799 | + | if (button == 3) then |
2800 | putDownText(x,y) | |
2801 | miceDown = {} | |
2802 | keysDown = {} | |
2803 | elseif button == 1 then | |
2804 | if keysDown[keys.leftShift] and evt[1] == "mouse_click" then | |
2805 | isDragging = true | |
2806 | end | |
2807 | if isDragging then | |
2808 | if evt[1] == "mouse_click" or dontDragThisTime then | |
2809 | dragPoses[1] = {x=x,y=y} | |
2810 | end | |
2811 | dragPoses[2] = {x=x,y=y} | |
2812 | elseif (not dontDragThisTime) then | |
2813 | if evt[1] == "mouse_drag" then | |
2814 | local points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y) | |
2815 | for a = 1, #points do | |
2816 | putDotDown({x=points[a].x, y=points[a].y}) | |
2817 | end | |
2818 | else | |
2819 | putDotDown({x=x, y=y}) | |
2820 | end | |
2821 | changedImage = true | |
2822 | end | |
2823 | dontDragThisTime = false | |
2824 | elseif button == 2 and y <= scr_y-(renderBlittle and 0 or doRenderBar) then | |
2825 | deleteDot(x+paint.scrollX,y+paint.scrollY) | |
2826 | changedImage = true | |
2827 | end | |
2828 | elseif origy >= scr_y-(renderBlittle and 0 or doRenderBar) then | |
2829 | keysDown = {} | |
2830 | local res = displayMenu() | |
2831 | if res == "exit" then break end | |
2832 | doRender = true | |
2833 | end | |
2834 | elseif (evt[1] == "mouse_up") and (not viewing) then | |
2835 | origx,origy = 0,0 | |
2836 | local button = evt[2] | |
2837 | miceDown[button] = false | |
2838 | oldmx,oldmy = nil,nil | |
2839 | lastMX, lastMY = nil,nil | |
2840 | if isDragging then | |
2841 | local points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y) | |
2842 | for a = 1, #points do | |
2843 | putDotDown({x=points[a].x, y=points[a].y}) | |
2844 | end | |
2845 | changedImage = true | |
2846 | doRender = true | |
2847 | end | |
2848 | saveToUndoBuffer() | |
2849 | isDragging = false | |
2850 | elseif evt[1] == "key" then | |
2851 | local key = evt[2] | |
2852 | if (not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then | |
2853 | if key == keys.right and (not keysDown[keys.right]) then | |
2854 | paint.scrollX = paint.scrollX + 1 | |
2855 | doRender = true | |
2856 | elseif key == keys.left and (not keysDown[keys.left]) then | |
2857 | paint.scrollX = paint.scrollX - 1 | |
2858 | doRender = true | |
2859 | end | |
2860 | if key == keys.down and (not keysDown[keys.down]) then | |
2861 | paint.scrollY = paint.scrollY + 1 | |
2862 | doRender = true | |
2863 | elseif key == keys.up and (not keysDown[keys.up]) then | |
2864 | paint.scrollY = paint.scrollY - 1 | |
2865 | doRender = true | |
2866 | end | |
2867 | end | |
2868 | keysDown[key] = true | |
2869 | if key == keys.space then | |
2870 | if keysDown[keys.leftShift] then | |
2871 | evenDrawGrid = not evenDrawGrid | |
2872 | else | |
2873 | doRenderBar = math.abs(doRenderBar-1) | |
2874 | end | |
2875 | doRender = true | |
2876 | end | |
2877 | if key == keys.b then | |
2878 | local blTerm, oldTerm = getBlittle() | |
2879 | renderBlittle = not renderBlittle | |
2880 | isDragging = false | |
2881 | term.setBackgroundColor(rendback.b) | |
2882 | term.clear() | |
2883 | if renderBlittle then | |
2884 | term.redirect(blTerm) | |
2885 | blTerm.setVisible(true) | |
2886 | else | |
2887 | term.redirect(oldTerm) | |
2888 | blTerm.setVisible(false) | |
2889 | end | |
2890 | doRender = true | |
2891 | scr_x, scr_y = term.current().getSize() | |
2892 | - | os.queueEvent("queue") |
2892 | + | |
2893 | - | os.pullEvent("queue") |
2893 | + | if (key == keys.c) and (not renderBlittle) then |
2894 | - | resetInputState() |
2894 | + | |
2895 | resetInputState() | |
2896 | doRender = true | |
2897 | end | |
2898 | if (keysDown[keys.leftShift]) and (not isDragging) then | |
2899 | if key == keys.left then | |
2900 | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0) | |
2901 | saveToUndoBuffer() | |
2902 | doRender = true | |
2903 | changedImage = true | |
2904 | elseif key == keys.right then | |
2905 | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0) | |
2906 | saveToUndoBuffer() | |
2907 | doRender = true | |
2908 | changedImage = true | |
2909 | elseif key == keys.up then | |
2910 | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1) | |
2911 | saveToUndoBuffer() | |
2912 | doRender = true | |
2913 | changedImage = true | |
2914 | elseif key == keys.down then | |
2915 | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1) | |
2916 | saveToUndoBuffer() | |
2917 | doRender = true | |
2918 | changedImage = true | |
2919 | end | |
2920 | end | |
2921 | if keysDown[keys.leftAlt] then | |
2922 | if #paintEncoded > 1 then | |
2923 | if key == keys.equals and paintEncoded[frame+1] then --basically plus | |
2924 | local first = deepCopy(paintEncoded[frame]) | |
2925 | local next = deepCopy(paintEncoded[frame+1]) | |
2926 | paintEncoded[frame] = next | |
2927 | paintEncoded[frame+1] = first | |
2928 | frame = frame + 1 | |
2929 | barmsg = "Swapped prev frame." | |
2930 | doRender = true | |
2931 | changedImage = true | |
2932 | saveToUndoBuffer() | |
2933 | end | |
2934 | if key == keys.minus and paintEncoded[frame-1] then | |
2935 | local first = deepCopy(paintEncoded[frame]) | |
2936 | local next = deepCopy(paintEncoded[frame-1]) | |
2937 | paintEncoded[frame] = next | |
2938 | paintEncoded[frame-1] = first | |
2939 | frame = frame - 1 | |
2940 | barmsg = "Swapped next frame." | |
2941 | doRender = true | |
2942 | changedImage = true | |
2943 | saveToUndoBuffer() | |
2944 | end | |
2945 | end | |
2946 | end | |
2947 | if not keysDown[keys.leftAlt] then | |
2948 | if key == keys.equals then --basically 'plus' | |
2949 | if renderBlittle then | |
2950 | frame = frame + 1 | |
2951 | if frame > #paintEncoded then frame = 1 end | |
2952 | else | |
2953 | if not paintEncoded[frame+1] then | |
2954 | paintEncoded[frame+1] = {} | |
2955 | local sheet = paintEncoded[frame] | |
2956 | if keysDown[keys.leftShift] then | |
2957 | paintEncoded[frame+1] = deepCopy(sheet) | |
2958 | end | |
2959 | end | |
2960 | frame = frame + 1 | |
2961 | end | |
2962 | saveToUndoBuffer() | |
2963 | doRender = true | |
2964 | changedImage = true | |
2965 | elseif key == keys.minus then | |
2966 | if renderBlittle then | |
2967 | frame = frame - 1 | |
2968 | if frame < 1 then frame = #paintEncoded end | |
2969 | else | |
2970 | if frame > 1 then | |
2971 | frame = frame - 1 | |
2972 | end | |
2973 | end | |
2974 | saveToUndoBuffer() | |
2975 | doRender = true | |
2976 | changedImage = true | |
2977 | end | |
2978 | end | |
2979 | if not renderBlittle then | |
2980 | if key == keys.m then | |
2981 | local incum = bottomPrompt("Set meta: ",metaHistory) | |
2982 | paint.m = incum:gsub(" ","") ~= "" and incum or paint.m | |
2983 | if paint.m ~= metaHistory[#metaHistory] then | |
2984 | metaHistory[#metaHistory+1] = paint.m | |
2985 | end | |
2986 | doRender = true | |
2987 | isDragging = false | |
2988 | end | |
2989 | if key == keys.f7 then | |
2990 | bepimode = not bepimode | |
2991 | doRender = true | |
2992 | end | |
2993 | if key == keys.t then | |
2994 | renderBottomBar("Click to place text.") | |
2995 | local mevt | |
2996 | - | local drawEveryEvent = false |
2996 | + | repeat |
2997 | - | local doot = function() |
2997 | + | mevt = {os.pullEvent()} |
2998 | - | local button, x, y, oldmx, oldmy, origx, origy |
2998 | + | 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)) |
2999 | - | local isDragging = false |
2999 | + | if not (mevt[1] == "key" and mevt[2] == keys.x) then |
3000 | - | local proceed = false |
3000 | + | local x,y = mevt[3],mevt[4] |
3001 | - | local evt, oldx, oldy = {} |
3001 | + | if renderBlittle then |
3002 | - | local button, points, key, dir |
3002 | + | x = 2*x |
3003 | - | renderBar(barmsg) |
3003 | + | y = 3*y |
3004 | - | while true do |
3004 | + | |
3005 | - | evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)} |
3005 | + | |
3006 | - | |
3006 | + | |
3007 | - | --doRender = false |
3007 | + | |
3008 | - | oldx, oldy = paint.scrollX,paint.scrollY |
3008 | + | |
3009 | - | |
3009 | + | |
3010 | - | if (evt[1] == "mouse_scroll") and (not viewing) then |
3010 | + | changedImage = true |
3011 | - | dir = evt[2] |
3011 | + | |
3012 | - | if dir == 1 then |
3012 | + | |
3013 | - | if keysDown[keys.leftShift] or keysDown[keys.rightShift] then |
3013 | + | if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) and (not isCurrentlyFilling) then |
3014 | - | paint.t = paint.t * 2 |
3014 | + | renderBottomBar("Click to fill area.") |
3015 | - | if paint.t > 32768 then |
3015 | + | local mevt |
3016 | - | paint.t = 32768 |
3016 | + | repeat |
3017 | mevt = {os.pullEvent()} | |
3018 | 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)) | |
3019 | - | paint.b = paint.b * 2 |
3019 | + | if not (mevt[1] == "key" and mevt[2] == keys.x) then |
3020 | - | if paint.b > 32768 then |
3020 | + | local x,y = mevt[3],mevt[4] |
3021 | - | paint.b = 32768 |
3021 | + | if renderBlittle then |
3022 | x = 2*x | |
3023 | y = 3*y | |
3024 | end | |
3025 | - | if keysDown[keys.leftShift] or keysDown[keys.rightShift] then |
3025 | + | os.queueEvent("filltool_async", frame, x, y, paint) |
3026 | - | paint.t = math.ceil(paint.t / 2) |
3026 | + | |
3027 | - | if paint.t < 1 then |
3027 | + | |
3028 | - | paint.t = 1 |
3028 | + | |
3029 | doRender = true | |
3030 | changedImage = true | |
3031 | - | paint.b = math.ceil(paint.b / 2) |
3031 | + | |
3032 | - | if paint.b < 1 then |
3032 | + | |
3033 | - | paint.b = 1 |
3033 | + | if key == keys.p then |
3034 | renderBottomBar("Pick color with cursor:") | |
3035 | paintEncoded = clearAllRedundant(paintEncoded) | |
3036 | local mevt | |
3037 | - | renderBar(barmsg) |
3037 | + | repeat |
3038 | - | elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then |
3038 | + | mevt = {os.pullEvent()} |
3039 | - | if evt[1] == "mouse_click" then |
3039 | + | until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[2] == 1 and mevt[4] <= scr_y) |
3040 | - | origx, origy = evt[3], evt[4] |
3040 | + | if not (mevt[1] == "key" and mevt[2] == keys.x) then |
3041 | local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY | |
3042 | - | oldmx,oldmy = x or evt[3], y or evt[4] |
3042 | + | if renderBlittle then |
3043 | - | lastMX,lastMY = evt[3],evt[4] |
3043 | + | x = 2*x |
3044 | - | button,x,y = evt[2],evt[3],evt[4] |
3044 | + | y = 3*y |
3045 | - | if plc.renderBlittle then |
3045 | + | |
3046 | - | x = 2*x |
3046 | + | local p |
3047 | - | y = 3*y |
3047 | + | for a = 1, #paintEncoded[frame] do |
3048 | - | lastMX = 2*lastMX |
3048 | + | p = paintEncoded[frame][a] |
3049 | - | lastMY = 3*lastMY |
3049 | + | if (p.x == x) and (p.y == y) then |
3050 | paint.t = p.t or paint.t | |
3051 | - | linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}} |
3051 | + | paint.b = p.b or paint.b |
3052 | - | miceDown[button] = true |
3052 | + | paint.c = p.c or paint.c |
3053 | - | if y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then |
3053 | + | paint.m = p.m or paint.m |
3054 | - | if (button == 3) then |
3054 | + | miceDown = {} |
3055 | keysDown = {} | |
3056 | doRender = true | |
3057 | isDragging = false | |
3058 | break | |
3059 | - | elseif button == 1 then |
3059 | + | |
3060 | - | if keysDown[keys.leftShift] and evt[1] == "mouse_click" then |
3060 | + | |
3061 | - | isDragging = true |
3061 | + | |
3062 | keysDown = {} | |
3063 | - | if isDragging then |
3063 | + | |
3064 | - | if evt[1] == "mouse_click" or dontDragThisTime then |
3064 | + | |
3065 | - | dragPoses[1] = {x=x,y=y} |
3065 | + | |
3066 | end | |
3067 | - | dragPoses[2] = {x=x,y=y} |
3067 | + | if (key == keys.leftCtrl or key == keys.rightCtrl) then |
3068 | - | points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y) |
3068 | + | keysDown = {[207] = keysDown[207]} |
3069 | - | renderAllPAIN() |
3069 | + | |
3070 | local res = displayMenu() | |
3071 | - | term.setCursorPos(points[a].x, points[a].y) |
3071 | + | paintEncoded = clearAllRedundant(paintEncoded) |
3072 | - | term.blit(paint.c, CTB(paint.t), CTB(paint.b)) |
3072 | + | |
3073 | doRender = true | |
3074 | - | elseif (not dontDragThisTime) then |
3074 | + | |
3075 | - | if evt[1] == "mouse_drag" then |
3075 | + | |
3076 | - | points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y) |
3076 | + | if (key == keys.f and keysDown[keys.leftShift]) then |
3077 | - | for a = 1, #points do |
3077 | + | local deredots = {} |
3078 | - | putDotDown({x=points[a].x, y=points[a].y}) |
3078 | + | changedImage = true |
3079 | - | end |
3079 | + | for a = 1, #paintEncoded[frame] do |
3080 | local dot = paintEncoded[frame][a] | |
3081 | - | putDotDown({x=x, y=y}) |
3081 | + | if dot.x-paint.scrollX > 0 and dot.x-paint.scrollX <= scr_x then |
3082 | if dot.y-paint.scrollY > 0 and dot.y-paint.scrollY <= scr_y then | |
3083 | - | plc.changedImage = true |
3083 | + | deredots[#deredots+1] = {dot.x-paint.scrollX, dot.y-paint.scrollY} |
3084 | end | |
3085 | end | |
3086 | - | dontDragThisTime = false |
3086 | + | |
3087 | - | elseif button == 2 and y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then |
3087 | + | |
3088 | - | deleteDot(x+paint.scrollX,y+paint.scrollY) |
3088 | + | for x = 1, scr_x do |
3089 | - | plc.changedImage = true |
3089 | + | local good = true |
3090 | for a = 1, #deredots do | |
3091 | if (deredots[a][1] == x) and (deredots[a][2] == y) then | |
3092 | - | elseif origy >= scr_y-(plc.renderBlittle and 0 or doRenderBar) then |
3092 | + | good = bad |
3093 | break | |
3094 | end | |
3095 | end | |
3096 | if good then | |
3097 | putDotDown({x=x, y=y}) | |
3098 | end | |
3099 | end | |
3100 | - | elseif (evt[1] == "mouse_up") and (not viewing) and (not plc.isCurrentlyFilling) then |
3100 | + | |
3101 | - | origx,origy = 0,0 |
3101 | + | |
3102 | - | button = evt[2] |
3102 | + | |
3103 | - | miceDown[button] = false |
3103 | + | |
3104 | - | oldmx,oldmy = nil,nil |
3104 | + | if key == keys.g then |
3105 | - | lastMX, lastMY = nil,nil |
3105 | + | paint.doGray = not paint.doGray |
3106 | - | if isDragging then |
3106 | + | changedImage = true |
3107 | - | points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y) |
3107 | + | |
3108 | - | for a = 1, #points do |
3108 | + | |
3109 | - | putDotDown({x=points[a].x, y=points[a].y}) |
3109 | + | |
3110 | if key == keys.a then | |
3111 | - | plc.changedImage = true |
3111 | + | paint.scrollX = 0 |
3112 | paint.scrollY = 0 | |
3113 | doRender = true | |
3114 | end | |
3115 | if key == keys.n then | |
3116 | - | elseif evt[1] == "key" then |
3116 | + | if keysDown[keys.leftShift] then |
3117 | - | key = evt[2] |
3117 | + | paint.c = specialCharSelector() |
3118 | - | if (isDragging or not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then |
3118 | + | |
3119 | - | if key == keys.right and (not keysDown[keys.right]) then |
3119 | + | paint.c = boxCharSelector() |
3120 | - | paint.scrollX = paint.scrollX + 1 |
3120 | + | |
3121 | resetInputState() | |
3122 | - | elseif key == keys.left and (not keysDown[keys.left]) then |
3122 | + | |
3123 | - | paint.scrollX = paint.scrollX - 1 |
3123 | + | |
3124 | if key == keys.f1 then | |
3125 | guiHelp() | |
3126 | - | if key == keys.down and (not keysDown[keys.down]) then |
3126 | + | |
3127 | - | paint.scrollY = paint.scrollY + 1 |
3127 | + | |
3128 | end | |
3129 | - | elseif key == keys.up and (not keysDown[keys.up]) then |
3129 | + | if key == keys.f3 then |
3130 | - | paint.scrollY = paint.scrollY - 1 |
3130 | + | listAllMonitors() |
3131 | resetInputState() | |
3132 | isDragging = false | |
3133 | end | |
3134 | - | keysDown[key] = true |
3134 | + | if key == keys.leftBracket then |
3135 | - | if key == keys.space then |
3135 | + | os.queueEvent("mouse_scroll",2,1,1) |
3136 | - | if keysDown[keys.leftShift] then |
3136 | + | elseif key == keys.rightBracket then |
3137 | - | plc.evenDrawGrid = not plc.evenDrawGrid |
3137 | + | os.queueEvent("mouse_scroll",1,1,1) |
3138 | end | |
3139 | - | doRenderBar = math.abs(doRenderBar-1) |
3139 | + | if key == keys.z then |
3140 | if keysDown[keys.leftAlt] and undoPos < #undoBuffer then | |
3141 | doRedo() | |
3142 | barmsg = "Redood." | |
3143 | - | if key == keys.b then |
3143 | + | |
3144 | - | local blTerm, oldTerm = getBlittle() |
3144 | + | elseif undoPos > 1 then |
3145 | - | plc.renderBlittle = not plc.renderBlittle |
3145 | + | doUndo() |
3146 | barmsg = "Undood." | |
3147 | - | term.setBackgroundColor(rendback.b) |
3147 | + | |
3148 | - | term.clear() |
3148 | + | |
3149 | - | if plc.renderBlittle then |
3149 | + | |
3150 | - | term.redirect(blTerm) |
3150 | + | |
3151 | - | blTerm.setVisible(true) |
3151 | + | |
3152 | keysDown[key] = false | |
3153 | - | term.redirect(oldTerm) |
3153 | + | |
3154 | - | blTerm.setVisible(false) |
3154 | + | if (oldx~=paint.scrollX) or (oldy~=paint.scrollY) then |
3155 | doRender = true | |
3156 | end | |
3157 | - | scr_x, scr_y = term.current().getSize() |
3157 | + | if doRender then |
3158 | renderAllPAIN() | |
3159 | - | if keysDown[keys.leftAlt] then |
3159 | + | doRender = false |
3160 | - | if (not plc.renderBlittle) then |
3160 | + | |
3161 | - | if (key == keys.c) then |
3161 | + | |
3162 | - | editFuncs.copy() |
3162 | + | |
3163 | - | elseif (key == keys.x) then |
3163 | + | |
3164 | - | editFuncs.cut() |
3164 | + | |
3165 | - | elseif (key == keys.v) then |
3165 | + | |
3166 | - | editFuncs.paste() |
3166 | + | |
3167 | fileName = shell.resolve(tostring(tArg[1])) | |
3168 | end | |
3169 | ||
3170 | - | if (key == keys.c) and (not plc.renderBlittle) then |
3170 | + | if not fileName then |
3171 | - | gotoCoords() |
3171 | + | |
3172 | - | resetInputState() |
3172 | + | elseif not fs.exists(fileName) then |
3173 | local ex = fileName:sub(-4):lower() | |
3174 | if ex == ".nfp" then | |
3175 | defaultSaveFormat = 1 | |
3176 | - | if (keysDown[keys.leftShift]) and (not isDragging) then |
3176 | + | |
3177 | - | if key == keys.left then |
3177 | + | defaultSaveFormat = 2 |
3178 | - | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0) |
3178 | + | |
3179 | defaultSaveFormat = 3 | |
3180 | elseif ex == ".gif" then | |
3181 | - | plc.changedImage = true |
3181 | + | defaultSaveFormat = 5 |
3182 | - | elseif key == keys.right then |
3182 | + | |
3183 | - | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0) |
3183 | + | defaultSaveFormat = 6 |
3184 | else | |
3185 | defaultSaveFormat = 4 | |
3186 | - | plc.changedImage = true |
3186 | + | |
3187 | - | elseif key == keys.up then |
3187 | + | |
3188 | - | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1) |
3188 | + | elseif fs.isDir(fileName) then |
3189 | if math.random(1,32) == 1 then | |
3190 | write("Oh") | |
3191 | - | plc.changedImage = true |
3191 | + | sleep(0.2) |
3192 | - | elseif key == keys.down then |
3192 | + | write(" My") |
3193 | - | paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1) |
3193 | + | sleep(0.2) |
3194 | print(" God") | |
3195 | sleep(0.3) | |
3196 | - | plc.changedImage = true |
3196 | + | write("That is a") |
3197 | sleep(0.1) | |
3198 | term.setTextColor(colors.red) | |
3199 | - | if keysDown[keys.leftAlt] then |
3199 | + | write(" DAMNED") |
3200 | - | if #paintEncoded > 1 then |
3200 | + | sleep(0.4) |
3201 | - | if key == keys.equals and paintEncoded[frame+1] then --basically plus |
3201 | + | print(" FOLDER.") |
3202 | - | local first = deepCopy(paintEncoded[frame]) |
3202 | + | term.setTextColor(colors.white) |
3203 | - | local next = deepCopy(paintEncoded[frame+1]) |
3203 | + | sleep(0.2) |
3204 | - | paintEncoded[frame] = next |
3204 | + | print("You crazy person.") |
3205 | - | paintEncoded[frame+1] = first |
3205 | + | sleep(0.2) |
3206 | - | frame = frame + 1 |
3206 | + | |
3207 | - | barmsg = "Swapped prev frame." |
3207 | + | |
3208 | end | |
3209 | - | plc.changedImage = true |
3209 | + | |
3210 | - | saveToUndoBuffer() |
3210 | + | |
3211 | paintEncoded, defaultSaveFormat = openNewFile(fileName, readNonImageAsNFP) | |
3212 | - | if key == keys.minus and paintEncoded[frame-1] then |
3212 | + | |
3213 | - | local first = deepCopy(paintEncoded[frame]) |
3213 | + | return print(defaultSaveFormat) |
3214 | - | local next = deepCopy(paintEncoded[frame-1]) |
3214 | + | |
3215 | - | paintEncoded[frame] = next |
3215 | + | |
3216 | - | paintEncoded[frame-1] = first |
3216 | + | |
3217 | local asyncFillTool = function() | |
3218 | - | barmsg = "Swapped next frame." |
3218 | + | local event, frameNo, x, y, dot |
3219 | isCurrentlyFilling = false | |
3220 | - | plc.changedImage = true |
3220 | + | while true do |
3221 | - | saveToUndoBuffer() |
3221 | + | event, frameNo, x, y, dot = os.pullEvent("filltool_async") |
3222 | isCurrentlyFilling = true | |
3223 | renderBottomBar("Filling area...") | |
3224 | - | elseif keysDown[keys.leftShift] then |
3224 | + | fillTool(frameNo, x, y, dot) |
3225 | - | if #paintEncoded > 1 then |
3225 | + | isCurrentlyFilling = false |
3226 | - | if key == keys.equals and paintEncoded[frame+1] then --basically plus |
3226 | + | reRenderPAIN(false) |
3227 | - | for a = 1, #paintEncoded[frame] do |
3227 | + | |
3228 | - | paintEncoded[frame+1][#paintEncoded[frame+1] + 1] = paintEncoded[frame][a] |
3228 | + | |
3229 | ||
3230 | - | table.remove(paintEncoded, frame) |
3230 | + | |
3231 | - | paintEncoded = clearAllRedundant(paintEncoded) |
3231 | + | if pMode == 1 then |
3232 | - | barmsg = "Merged next frame." |
3232 | + | |
3233 | renderPAIN(paintEncoded[tonumber(tArg[5]) or 1],-(tonumber(tArg[3]) or 0),-(tonumber(tArg[4]) or 0)) -- 'pain filename view X Y frame' | |
3234 | - | plc.changedImage = true |
3234 | + | |
3235 | - | saveToUndoBuffer() |
3235 | + | |
3236 | else | |
3237 | - | if key == keys.minus and paintEncoded[frame-1] then |
3237 | + | |
3238 | - | for a = 1, #paintEncoded[frame] do |
3238 | + | |
3239 | - | paintEncoded[frame-1][#paintEncoded[frame-1] + 1] = paintEncoded[frame][a] |
3239 | + | |
3240 | undoBuffer = {deepCopy(paintEncoded)} | |
3241 | - | table.remove(paintEncoded, frame) |
3241 | + | |
3242 | ||
3243 | - | paintEncoded = clearAllRedundant(paintEncoded) |
3243 | + | |
3244 | - | barmsg = "Merged previous frame." |
3244 | + | |
3245 | term.clearLine() | |
3246 | - | plc.changedImage = true |
3246 | + | |
3247 | - | saveToUndoBuffer() |
3247 | + | |
3248 | if not shell then return end | |
3249 | ||
3250 | runPainEditor(...) |