· 4 years ago · Aug 09, 2021, 10:58 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 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 ~= "" 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 = fs.open( path, "w" )
132 if not file then
133 return false
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 string.len( 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 for i=18,18 do
190 term.setCursorPos(w-1, i)
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
255--[[
256 Draws menu options and handles input from within the menu.
257 returns: true if the program is to be exited; false otherwise
258]]
259local function accessMenu()
260 -- Selected menu option
261 local selection = 1
262
263 term.setBackgroundColour(colours.black)
264 while true do
265 -- Draw the menu
266 term.setCursorPos(1,h)
267 term.clearLine()
268 term.setTextColour(colours.white)
269 for k,v in pairs(mChoices) do
270 if selection==k then
271 term.setTextColour(colours.yellow)
272 local ox,_ = term.getCursorPos()
273 term.write("["..string.rep(" ",#v).."]")
274 term.setCursorPos(ox+1,h)
275 term.setTextColour(colours.white)
276 term.write(v)
277 term.setCursorPos(term.getCursorPos()+1,h)
278 else
279 term.write(" "..v.." ")
280 end
281 end
282
283 -- Handle input in the menu
284 local id,key = os.pullEvent("key")
285 if id == "key" then
286 -- S and E are shortcuts
287 if key == keys.s then
288 selection = 1
289 key = keys.enter
290 elseif key == keys.e then
291 selection = 2
292 key = keys.enter
293 end
294
295 if key == keys.right then
296 -- Move right
297 selection = selection + 1
298 if selection > #mChoices then
299 selection = 1
300 end
301
302 elseif key == keys.left and selection > 1 then
303 -- Move left
304 selection = selection - 1
305 if selection < 1 then
306 selection = #mChoices
307 end
308
309 elseif key == keys.enter then
310 -- Select an option
311 if mChoices[selection]=="Save" then
312 if bReadOnly then
313 fMessage = "Access denied"
314 return false
315 end
316 local success = save(sPath)
317 if success then
318 fMessage = "Saved to "..sPath
319 else
320 fMessage = "Error saving to "..sPath
321 end
322 return false
323 elseif mChoices[selection]=="Exit" then
324 return true
325 end
326 elseif key == keys.leftCtrl or keys == keys.rightCtrl then
327 -- Cancel the menu
328 return false
329 end
330 end
331 end
332end
333
334--[[
335 Runs the main thread of execution. Draws the canvas and interface, and handles
336 mouse and key events.
337 returns: nil
338]]
339local function handleEvents()
340 local programActive = true
341 while programActive do
342 local id,p1,p2,p3 = os.pullEvent()
343 if id=="mouse_click" or id=="mouse_drag" then
344 if p2 >= w-1 and p3 >= 1 and p3 <= 17 then
345 if id ~= "mouse_drag" then
346 -- Selecting an items in the colour picker
347 if p3 <= 16 then
348 if p1==1 then
349 leftColour = 2^(p3-1)
350 else
351 rightColour = 2^(p3-1)
352 end
353 else
354 if p1==1 then
355 leftColour = nil
356 else
357 rightColour = nil
358 end
359 end
360 --drawCanvas()
361 drawInterface()
362 end
363 elseif p2 < w-1 and p3 <= h-1 then
364 -- Clicking on the canvas
365 local paintColour = nil
366 if p1==1 then
367 paintColour = leftColour
368 elseif p1==2 then
369 paintColour = rightColour
370 end
371 if not canvas[p3] then
372 canvas[p3] = {}
373 end
374 canvas[p3][p2] = paintColour
375
376 drawCanvasPixel( p2, p3 )
377 end
378 elseif id=="key" then
379 if p1==keys.leftCtrl or p1==keys.rightCtrl then
380 programActive = not accessMenu()
381 drawInterface()
382 end
383 elseif id=="term_resize" then
384 w,h = term.getSize()
385 drawCanvas()
386 drawInterface()
387 end
388 end
389end
390
391-- Init
392load(sPath)
393drawCanvas()
394drawInterface()
395
396-- Main loop
397handleEvents()
398
399-- Shutdown
400term.setBackgroundColour(colours.black)
401term.setTextColour(colours.white)
402term.clear()
403term.setCursorPos(1,1)