· 4 years ago · Jul 31, 2021, 05:36 PM
1-- Paint created by nitrogenfingers (edited by dan200)
2-- http://www.youtube.com/user/NitrogenFingers
3
4------------
5-- Fields --
6------------
7
8-- The width and height of the terminal
9local w, h = term.getSize()
10
11-- The selected colours on the left and right mouse button, and the colour of the canvas
12local leftColour, rightColour = colours.white, nil
13local canvasColour = colours.black
14
15-- The values stored in the canvas
16local canvas = {}
17
18-- The menu options
19local mChoices = { "Save", "Exit" }
20
21-- The message displayed in the footer bar
22local fMessage = "Press Ctrl or click here to access menu"
23
24-------------------------
25-- Initialisation --
26-------------------------
27
28-- Determine if we can even run this
29if not term.isColour() then
30 print("Requires an Advanced Computer")
31 return
32end
33
34-- Determines if the file exists, and can be edited on this computer
35local tArgs = { ... }
36if #tArgs == 0 then
37 local programName = arg[0] or fs.getName(shell.getRunningProgram())
38 print("Usage: " .. programName .. " <path>")
39 return
40end
41local sPath = shell.resolve(tArgs[1])
42local bReadOnly = fs.isReadOnly(sPath)
43if fs.exists(sPath) and fs.isDir(sPath) then
44 print("Cannot edit a directory.")
45 return
46end
47
48-- Create .nfp files by default
49if not fs.exists(sPath) and not string.find(sPath, "%.") then
50 local sExtension = settings.get("paint.default_extension")
51 if sExtension ~= "" and type(sExtension) == "string" then
52 sPath = sPath .. "." .. sExtension
53 end
54end
55
56
57---------------
58-- Functions --
59---------------
60
61local function getCanvasPixel(x, y)
62 if canvas[y] then
63 return canvas[y][x]
64 end
65 return nil
66end
67
68--[[
69 Converts a colour value to a text character
70 params: colour = the number to convert to a hex value
71 returns: a string representing the chosen colour
72]]
73local function getCharOf(colour)
74 -- Incorrect values always convert to nil
75 if type(colour) == "number" then
76 local value = math.floor(math.log(colour) / math.log(2)) + 1
77 if value >= 1 and value <= 16 then
78 return string.sub("0123456789abcdef", value, value)
79 end
80 end
81 return " "
82end
83
84--[[
85 Converts a text character to colour value
86 params: char = the char (from string.byte) to convert to number
87 returns: the colour number of the hex value
88]]
89local tColourLookup = {}
90for n = 1, 16 do
91 tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
92end
93local function getColourOf(char)
94 -- Values not in the hex table are transparent (canvas coloured)
95 return tColourLookup[char]
96end
97
98--[[
99 Loads the file into the canvas
100 params: path = the path of the file to open
101 returns: nil
102]]
103local function load(path)
104 -- Load the file
105 if fs.exists(path) then
106 local file = fs.open(sPath, "r")
107 local sLine = file.readLine()
108 while sLine do
109 local line = {}
110 for x = 1, w - 2 do
111 line[x] = getColourOf(string.byte(sLine, x, x))
112 end
113 table.insert(canvas, line)
114 sLine = file.readLine()
115 end
116 file.close()
117 end
118end
119
120--[[
121 Saves the current canvas to file
122 params: path = the path of the file to save
123 returns: true if save was successful, false otherwise
124]]
125local function save(path)
126 -- Open file
127 local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
128 if not fs.exists(sDir) then
129 fs.makeDir(sDir)
130 end
131
132 local file, err = fs.open(path, "w")
133 if not file then
134 return false, err
135 end
136
137 -- Encode (and trim)
138 local tLines = {}
139 local nLastLine = 0
140 for y = 1, h - 1 do
141 local sLine = ""
142 local nLastChar = 0
143 for x = 1, w - 2 do
144 local c = getCharOf(getCanvasPixel(x, y))
145 sLine = sLine .. c
146 if c ~= " " then
147 nLastChar = x
148 end
149 end
150 sLine = string.sub(sLine, 1, nLastChar)
151 tLines[y] = sLine
152 if #sLine > 0 then
153 nLastLine = y
154 end
155 end
156
157 -- Save out
158 for n = 1, nLastLine do
159 file.writeLine(tLines[n])
160 end
161 file.close()
162 return true
163end
164
165--[[
166 Draws colour picker sidebar, the pallette and the footer
167 returns: nil
168]]
169local function drawInterface()
170 -- Footer
171 term.setCursorPos(1, h)
172 term.setBackgroundColour(colours.black)
173 term.setTextColour(colours.yellow)
174 term.clearLine()
175 term.write(fMessage)
176
177 -- Colour Picker
178 for i = 1, 16 do
179 term.setCursorPos(w - 1, i)
180 term.setBackgroundColour(2 ^ (i - 1))
181 term.write(" ")
182 end
183
184 term.setCursorPos(w - 1, 17)
185 term.setBackgroundColour(canvasColour)
186 term.setTextColour(colours.grey)
187 term.write("\127\127")
188
189 -- Left and Right Selected Colours
190 do
191 term.setCursorPos(w - 1, 18)
192 if leftColour ~= nil then
193 term.setBackgroundColour(leftColour)
194 term.write(" ")
195 else
196 term.setBackgroundColour(canvasColour)
197 term.setTextColour(colours.grey)
198 term.write("\127")
199 end
200 if rightColour ~= nil then
201 term.setBackgroundColour(rightColour)
202 term.write(" ")
203 else
204 term.setBackgroundColour(canvasColour)
205 term.setTextColour(colours.grey)
206 term.write("\127")
207 end
208 end
209
210 -- Padding
211 term.setBackgroundColour(canvasColour)
212 for i = 20, h - 1 do
213 term.setCursorPos(w - 1, i)
214 term.write(" ")
215 end
216end
217
218--[[
219 Converts a single pixel of a single line of the canvas and draws it
220 returns: nil
221]]
222local function drawCanvasPixel(x, y)
223 local pixel = getCanvasPixel(x, y)
224 if pixel then
225 term.setBackgroundColour(pixel or canvasColour)
226 term.setCursorPos(x, y)
227 term.write(" ")
228 else
229 term.setBackgroundColour(canvasColour)
230 term.setTextColour(colours.grey)
231 term.setCursorPos(x, y)
232 term.write("\127")
233 end
234end
235
236local color_hex_lookup = {}
237for i = 0, 15 do
238 color_hex_lookup[2 ^ i] = string.format("%x", i)
239end
240
241--[[
242 Converts each colour in a single line of the canvas and draws it
243 returns: nil
244]]
245local function drawCanvasLine(y)
246 local text, fg, bg = "", "", ""
247 for x = 1, w - 2 do
248 local pixel = getCanvasPixel(x, y)
249 if pixel then
250 text = text .. " "
251 fg = fg .. "0"
252 bg = bg .. color_hex_lookup[pixel or canvasColour]
253 else
254 text = text .. "\127"
255 fg = fg .. color_hex_lookup[colours.grey]
256 bg = bg .. color_hex_lookup[canvasColour]
257 end
258 end
259
260 term.setCursorPos(1, y)
261 term.blit(text, fg, bg)
262end
263
264--[[
265 Converts each colour in the canvas and draws it
266 returns: nil
267]]
268local function drawCanvas()
269 for y = 1, h - 1 do
270 drawCanvasLine(y)
271 end
272end
273
274local menu_choices = {
275 Save = function()
276 if bReadOnly then
277 fMessage = "Access denied"
278 return false
279 end
280 local success, err = save(sPath)
281 if success then
282 fMessage = "Saved to " .. sPath
283 else
284 if err then
285 fMessage = "Error saving to " .. err
286 else
287 fMessage = "Error saving to " .. sPath
288 end
289 end
290 return false
291 end,
292 Exit = function()
293 sleep(0) -- Super janky, but consumes stray "char" events from pressing Ctrl then E separately.
294 return true
295 end,
296}
297
298--[[
299 Draws menu options and handles input from within the menu.
300 returns: true if the program is to be exited; false otherwise
301]]
302local function accessMenu()
303 -- Selected menu option
304 local selection = 1
305
306 term.setBackgroundColour(colours.black)
307
308 while true do
309 -- Draw the menu
310 term.setCursorPos(1, h)
311 term.clearLine()
312 term.setTextColour(colours.white)
313 for k, v in pairs(mChoices) do
314 if selection == k then
315 term.setTextColour(colours.yellow)
316 term.write("[")
317 term.setTextColour(colours.white)
318 term.write(v)
319 term.setTextColour(colours.yellow)
320 term.write("]")
321 term.setTextColour(colours.white)
322 else
323 term.write(" " .. v .. " ")
324 end
325 end
326
327 -- Handle input in the menu
328 local id, param1, param2, param3 = os.pullEvent()
329 if id == "key" then
330 local key = param1
331
332 -- Handle menu shortcuts.
333 for _, menu_item in ipairs(mChoices) do
334 local k = keys[menu_item:sub(1, 1):lower()]
335 if k and k == key then
336 return menu_choices[menu_item]()
337 end
338 end
339
340 if key == keys.right then
341 -- Move right
342 selection = selection + 1
343 if selection > #mChoices then
344 selection = 1
345 end
346
347 elseif key == keys.left and selection > 1 then
348 -- Move left
349 selection = selection - 1
350 if selection < 1 then
351 selection = #mChoices
352 end
353
354 elseif key == keys.enter or key == keys.numPadEnter then
355 -- Select an option
356 return menu_choices[mChoices[selection]]()
357 elseif key == keys.leftCtrl or keys == keys.rightCtrl then
358 -- Cancel the menu
359 return false
360 end
361 elseif id == "mouse_click" then
362 local cx, cy = param2, param3
363 if cy ~= h then return false end -- Exit the menu
364
365 local nMenuPosEnd = 1
366 local nMenuPosStart = 1
367 for _, sMenuItem in ipairs(mChoices) do
368 nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
369 if cx > nMenuPosStart and cx < nMenuPosEnd then
370 return menu_choices[sMenuItem]()
371 end
372 nMenuPosEnd = nMenuPosEnd + 1
373 nMenuPosStart = nMenuPosEnd
374 end
375 end
376 end
377end
378
379--[[
380 Runs the main thread of execution. Draws the canvas and interface, and handles
381 mouse and key events.
382 returns: nil
383]]
384local function handleEvents()
385 local programActive = true
386 while programActive do
387 local id, p1, p2, p3 = os.pullEvent()
388 if id == "mouse_click" or id == "mouse_drag" then
389 if p2 >= w - 1 and p3 >= 1 and p3 <= 17 then
390 if id ~= "mouse_drag" then
391 -- Selecting an items in the colour picker
392 if p3 <= 16 then
393 if p1 == 1 then
394 leftColour = 2 ^ (p3 - 1)
395 else
396 rightColour = 2 ^ (p3 - 1)
397 end
398 else
399 if p1 == 1 then
400 leftColour = nil
401 else
402 rightColour = nil
403 end
404 end
405 --drawCanvas()
406 drawInterface()
407 end
408 elseif p2 < w - 1 and p3 <= h - 1 then
409 -- Clicking on the canvas
410 local paintColour = nil
411 if p1 == 1 then
412 paintColour = leftColour
413 elseif p1 == 2 then
414 paintColour = rightColour
415 end
416 if not canvas[p3] then
417 canvas[p3] = {}
418 end
419 canvas[p3][p2] = paintColour
420
421 drawCanvasPixel(p2, p3)
422 elseif p3 == h and id == "mouse_click" then
423 -- Open menu
424 programActive = not accessMenu()
425 drawInterface()
426 end
427 elseif id == "key" then
428 if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
429 programActive = not accessMenu()
430 drawInterface()
431 end
432 elseif id == "term_resize" then
433 w, h = term.getSize()
434 drawCanvas()
435 drawInterface()
436 end
437 end
438end
439
440-- Init
441load(sPath)
442drawCanvas()
443drawInterface()
444
445-- Main loop
446handleEvents()
447
448-- Shutdown
449term.setBackgroundColour(colours.black)
450term.setTextColour(colours.white)
451term.clear()
452term.setCursorPos(1, 1)
453