· 4 years ago · Jun 11, 2021, 09:40 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 text, fg, bg = "", "", ""
246local function drawCanvasLine(y)
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 term.setCursorPos(1, y)
260 term.blit(text, fg, bg)
261end
262
263--[[
264 Converts each colour in the canvas and draws it
265 returns: nil
266]]
267local function drawCanvas()
268 for y = 1, h - 1 do
269 drawCanvasLine(y)
270 end
271end
272
273local menu_choices = {
274 Save = function()
275 if bReadOnly then
276 fMessage = "Access denied"
277 return false
278 end
279 local success, err = save(sPath)
280 if success then
281 fMessage = "Saved to " .. sPath
282 else
283 if err then
284 fMessage = "Error saving to " .. err
285 else
286 fMessage = "Error saving to " .. sPath
287 end
288 end
289 return false
290 end,
291 Exit = function()
292 return true
293 end,
294}
295
296--[[
297 Draws menu options and handles input from within the menu.
298 returns: true if the program is to be exited; false otherwise
299]]
300local function accessMenu()
301 -- Selected menu option
302 local selection = 1
303
304 term.setBackgroundColour(colours.black)
305
306 while true do
307 -- Draw the menu
308 term.setCursorPos(1, h)
309 term.clearLine()
310 term.setTextColour(colours.white)
311 for k, v in pairs(mChoices) do
312 if selection == k then
313 term.setTextColour(colours.yellow)
314 term.write("[")
315 term.setTextColour(colours.white)
316 term.write(v)
317 term.setTextColour(colours.yellow)
318 term.write("]")
319 term.setTextColour(colours.white)
320 else
321 term.write(" " .. v .. " ")
322 end
323 end
324
325 -- Handle input in the menu
326 local id, param1, param2, param3 = os.pullEvent()
327 if id == "key" then
328 local key = param1
329
330 -- Handle menu shortcuts.
331 for _, menu_item in ipairs(mChoices) do
332 local k = keys[menu_item:sub(1, 1):lower()]
333 if k and k == key then
334 return menu_choices[menu_item]()
335 end
336 end
337
338 if key == keys.right then
339 -- Move right
340 selection = selection + 1
341 if selection > #mChoices then
342 selection = 1
343 end
344
345 elseif key == keys.left and selection > 1 then
346 -- Move left
347 selection = selection - 1
348 if selection < 1 then
349 selection = #mChoices
350 end
351
352 elseif key == keys.enter or key == keys.numPadEnter then
353 -- Select an option
354 return menu_choices[mChoices[selection]]()
355 elseif key == keys.leftCtrl or keys == keys.rightCtrl then
356 -- Cancel the menu
357 return false
358 end
359 elseif id == "mouse_click" then
360 local cx, cy = param2, param3
361 if cy ~= h then return false end -- Exit the menu
362
363 local nMenuPosEnd = 1
364 local nMenuPosStart = 1
365 for _, sMenuItem in ipairs(mChoices) do
366 nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
367 if cx > nMenuPosStart and cx < nMenuPosEnd then
368 return menu_choices[sMenuItem]()
369 end
370 nMenuPosEnd = nMenuPosEnd + 1
371 nMenuPosStart = nMenuPosEnd
372 end
373 end
374 end
375end
376
377--[[
378 Runs the main thread of execution. Draws the canvas and interface, and handles
379 mouse and key events.
380 returns: nil
381]]
382local function handleEvents()
383 local programActive = true
384 while programActive do
385 local id, p1, p2, p3 = os.pullEvent()
386 if id == "mouse_click" or id == "mouse_drag" then
387 if p2 >= w - 1 and p3 >= 1 and p3 <= 17 then
388 if id ~= "mouse_drag" then
389 -- Selecting an items in the colour picker
390 if p3 <= 16 then
391 if p1 == 1 then
392 leftColour = 2 ^ (p3 - 1)
393 else
394 rightColour = 2 ^ (p3 - 1)
395 end
396 else
397 if p1 == 1 then
398 leftColour = nil
399 else
400 rightColour = nil
401 end
402 end
403 --drawCanvas()
404 drawInterface()
405 end
406 elseif p2 < w - 1 and p3 <= h - 1 then
407 -- Clicking on the canvas
408 local paintColour = nil
409 if p1 == 1 then
410 paintColour = leftColour
411 elseif p1 == 2 then
412 paintColour = rightColour
413 end
414 if not canvas[p3] then
415 canvas[p3] = {}
416 end
417 canvas[p3][p2] = paintColour
418
419 drawCanvasPixel(p2, p3)
420 elseif p3 == h and id == "mouse_click" then
421 -- Open menu
422 programActive = not accessMenu()
423 drawInterface()
424 end
425 elseif id == "key" then
426 if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
427 programActive = not accessMenu()
428 drawInterface()
429 end
430 elseif id == "term_resize" then
431 w, h = term.getSize()
432 drawCanvas()
433 drawInterface()
434 end
435 end
436end
437
438-- Init
439load(sPath)
440drawCanvas()
441drawInterface()
442
443-- Main loop
444handleEvents()
445
446-- Shutdown
447term.setBackgroundColour(colours.black)
448term.setTextColour(colours.white)
449term.clear()
450term.setCursorPos(1, 1)