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