· 8 years ago · Dec 04, 2017, 12:36 PM
1--[[type="executable_package", name="n"]]
2local files = {["App"]={content="\
3\
4App.public \"name\"\
5App.public.name.write = false\
6App.public \"session\"\
7App.public.session.write = false\
8App.public \"runmode\"\
9App.public.runmode.write = false\
10App.public \"conf\"\
11App.public.conf.write = false\
12App.public \"icon\"\
13App.public.icon.write = false\
14App.public \"installpath\"\
15App.public.installpath.write = false\
16App.public \"single\"\
17App.public.single.write = false\
18\
19function App:App( session, path )\
20 local readfile\
21 if filesystem.isDirectory( path ) then\
22 function readfile( p )\
23 local data = filesystem.readfile( path .. \"/\" .. p )\
24 if data then\
25 return data.content\
26 end\
27 end\
28 elseif filesystem.exists( path ) and filesystem.getType( path ) == \"archive\" then\
29 return false, \"cannot load apps from archives just now...\"\
30 -- read from an archive\
31 elseif filesystem.exists( path ) then\
32 function readfile( p )\
33 if p == \"main.lua\" then\
34 local data = filesystem.readfile( path )\
35 if data then\
36 return data.content\
37 end\
38 else\
39 return false\
40 end\
41 end\
42 else\
43 return false, \"app not found\"\
44 end\
45\
46 self.single = false\
47\
48 self.readfile = readfile\
49 self.installpath = path\
50\
51 self.name = filesystem.getName( path, true )\
52 local conf = readfile \"appconfig.txt\"\
53 if conf then\
54 conf = textutils.unserialize( \"{\" .. conf .. \"}\" ) or { }\
55 else\
56 conf = { }\
57 end\
58 if type( conf.name ) == \"string\" then\
59 self.name = name\
60 end\
61 self.conf = conf\
62 if not readfile \"main.lua\" then\
63 return false, \"no main file\"\
64 end\
65\
66 if type( conf.handles ) == \"table\" then\
67 for i = 1, #conf.handles do\
68 filesystem.addHandler( conf.handles[i], self.name )\
69 end\
70 end\
71 if type( conf.filetypes ) == \"table\" then\
72 for k, v in pairs( conf.filetypes ) do\
73 filesystem.addTypeDescription( k, v )\
74 end\
75 end\
76 if type( conf.extensions ) == \"table\" then\
77 for k, v in pairs( conf.extensions ) do\
78 filesystem.addExtension( k, v )\
79 end\
80 end\
81 if conf.runmode == \"compatibility\" then\
82 self.runmode = \"compatibility\"\
83 elseif conf.runmode == \"single\" then\
84 self.single = true\
85 self.runmode = \"normal\"\
86 else\
87 self.runmode = \"normal\"\
88 end\
89\
90 self.session = session\
91\
92 if type( conf.services ) == \"table\" then\
93 for i = 1, #conf.services do\
94 local content = readfile( conf.services[i] .. \".lua\" )\
95 if content then\
96 local f, err = loadstring( content, conf.services[i] )\
97 if f then\
98 session:newThread( Thread( function( )\
99 local instance = session:launchBackground( self.name, { } )\
100 if instance then\
101 instance:newThread( Thread( f ) )\
102 end\
103 end ) )\
104 else\
105 core.log( \"Service error\", self.name, core.services[i], err )\
106 end\
107 else\
108 core.log( \"Service not found\", self.name, conf.services[i] )\
109 end\
110 end\
111 end\
112\
113 local icon = readfile \"icon.nim\"\
114 if icon then\
115 self.icon = Image( 9, 3 )\
116 self.icon:loadstr( icon )\
117 self.icon:resize( 9, 3, 1, 1, \" \" )\
118 end\
119\
120 return self.public\
121end\
122\
123function App.public:launch( args, noGUI )\
124 return AppInstance( self.public, self.session, args, noGUI )\
125end\
126\
127function App.public:readFile( path )\
128 return self.readfile( path )\
129end", meta={
130 type = "class",
131}};["Thread"]={content="\
132\
133local threads = { }\
134\
135Thread.public \"environment\"\
136function Thread.public.environment:read( )\
137 return self.env\
138end\
139function Thread.public.environment:write( value )\
140 if type( value ) ~= \"table\" then\
141 error( \"expected table\", 3 )\
142 end\
143 self.env = value\
144end\
145\
146Thread.public \"onException\" \"function\"\
147Thread.public \"onFinish\" \"function\"\
148\
149function Thread:Thread( thread )\
150 self.state = \"running\"\
151 self.thread = thread\
152 self.env = setmetatable( { wait = coroutine.yield, thread = self.public }, { __index = getfenv( ) } )\
153 setfenv( self.thread, setmetatable( { }, { __index = function( _, k ) return self.env[k] end, __newindex = function( _, k, v ) self.env[k] = v end } ) )\
154 self.co = coroutine.create( thread )\
155\
156 table.insert( threads, self.public )\
157\
158 return self.public\
159end\
160\
161function Thread.public:pause( )\
162 if self.state == \"running\" then\
163 self.state = \"paused\"\
164 return true\
165 end\
166 return false\
167end\
168\
169function Thread.public:resume( )\
170 if self.state == \"paused\" then\
171 self.state = \"running\"\
172 return true\
173 end\
174 return false\
175end\
176\
177function Thread.public:restart( )\
178 self.co = coroutine.create( self.thread )\
179 self.state = \"running\"\
180end\
181\
182function Thread.public:stop( )\
183 self.state = \"stopped\"\
184end\
185\
186function Thread.public:isRunning( )\
187 return self.state == \"running\"\
188end\
189\
190function Thread.public:isPaused( )\
191 return self.state == \"paused\"\
192end\
193\
194function Thread.public:update( args )\
195 if self.state == \"stopped\" then\
196 return false, \"finished\"\
197 end\
198 if self.state ~= \"running\" then\
199 return false, \"not running\"\
200 end\
201 local ok, data = coroutine.resume( self.co, unpack( args ) )\
202 if not ok then\
203 self.state = \"stopped\"\
204 if type( self.onException ) == \"function\" then\
205 pcall( self.onException, self.public, data )\
206 end\
207 if self.state == \"stopped\" then\
208 return false, \"finished\"\
209 end\
210 return false, \"errored\"\
211 end\
212 if coroutine.status( self.co ) == \"dead\" then -- give it a chance to restart\
213 if type( self.onFinish ) == \"function\" then\
214 pcall( self.onFinish, self.public )\
215 end\
216 end\
217 if coroutine.status( self.co ) == \"dead\" then\
218 self.state = \"stopped\"\
219 return false, \"finished\"\
220 end\
221 return true, data\
222end\
223\
224function Thread.static.update( args )\
225 for i = #threads, 1, -1 do\
226 local ok, data = threads[i]:update( args )\
227 if not ok and data == \"finished\" then\
228 table.remove( threads, i )\
229 end\
230 end\
231end", meta={
232 type = "class",
233}};["UICode"]={content="\
234\
235require \"UIElement\"\
236\
237UICode:extends( UIElement )\
238\
239UICode.handlesKeys = true\
240UICode.handlesScroll = true\
241UICode.isScrollTarget = true\
242UICode.handlesTab = true\
243UICode.scrollDirection = \"vertical\"\
244\
245UICode.public \"showLines\" \"boolean\"\
246UICode.public \"syntax\" \"table\"\
247\
248UICode.public \"onChange\" \"function\"\
249UICode.public \"onCtrlKey\" \"function\"\
250\
251UICode.public \"selected\"\
252UICode.public.selected.write = false\
253function UICode.public.selected:read( )\
254 return not not self.selection\
255end\
256\
257function UICode:UICode( x, y, w, h, syntax )\
258 self:UIElement( x, y, w, h )\
259\
260 self.showLines = true\
261 self.lineWidth = 4\
262 self.linechars = { }\
263\
264 self.selection = false\
265\
266 self.scrollx = 0\
267 self.scrolly = 0\
268 self.cursorx = 1\
269 self.cursory = 1\
270\
271 self.lastclick = false\
272\
273 self.syntax = syntax or {\
274 default = { bc = colours.white, tc = colours.black };\
275 words = { };\
276 blocks = { };\
277 linen = { bc = colours.grey, tc = colours.white };\
278 string = { tc = colours.lightGrey };\
279 symbols = { };\
280 selection = { bc = colours.blue, tc = colours.white };\
281 }\
282\
283 self.lines = { \"\" }\
284 self.characters = { }\
285\
286 return self.public\
287end\
288\
289function UICode.public:updateCharacters( )\
290 self.linechars = { [1] = 1 }\
291 local str = table.concat( self.lines, \"\\n\" )\
292 local characters = { }\
293 local line = 1\
294 local pos = 1\
295 local done = true\
296 local function nextC( )\
297 local char = str:sub( pos, pos )\
298 pos = pos + 1\
299 done = false\
300 return char\
301 end\
302 local function setC( colour, char, d )\
303 if done and not d then return end\
304 if char == \"\\n\" then\
305 done = true\
306 line = line + 1\
307 -- self.linechars[line] = pos\
308 return\
309 end\
310 if #char == 0 then return end\
311 done = true\
312 table.insert( characters, { colour = colour, char = char, line = line } )\
313 end\
314 while pos <= #str do\
315 local c = nextC( )\
316 if c == \"\\\"\" then\
317 setC( self.syntax.string, c )\
318 local escape = false\
319 for i = pos, #str do\
320 c = nextC( )\
321 if escape then\
322 setC( self.syntax.string.escape or self.syntax.string, c )\
323 escape = false\
324 else\
325 if c == \"\\\\\" then\
326 setC( self.syntax.string.escape or self.syntax.string, c )\
327 escape = true\
328 elseif c == \"\\\"\" then\
329 setC( self.syntax.string, c )\
330 break\
331 else\
332 setC( self.syntax.string, c )\
333 end\
334 end\
335 end\
336 elseif c:find \"[abcdefghijklmnoparstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_%.]\" then\
337 local word = c\
338 c = nextC( )\
339 while c:find \"[%w_%.]\" do\
340 word = word .. c\
341 c = nextC( )\
342 end\
343 for i = 1, #word do\
344 if self.syntax.words[word] then\
345 setC( self.syntax.words[word], word:sub( i, i ), true )\
346 else\
347 setC( self.syntax.default, word:sub( i, i ), true )\
348 end\
349 end\
350 done = false\
351 elseif self.syntax.symbols[c] then\
352 setC( self.syntax.symbols[c], c )\
353 end\
354 if not done then\
355 for i = 1, #self.syntax.blocks do\
356 if c == self.syntax.blocks[i].start:sub( 1, 1 ) then\
357 if str:sub( pos, pos + #self.syntax.blocks[i].start - 2 ) == self.syntax.blocks[i].start:sub( 2 ) then\
358 local block = self.syntax.blocks[i]\
359 setC( block, c )\
360 for i = 1, #block.start - 1 do\
361 c = nextC( )\
362 setC( block, c )\
363 end\
364 while #c > 0 do\
365 if str:sub( pos, pos + #block.finish - 1 ) == block.finish then\
366 for i = 1, #block.finish do\
367 c = nextC( )\
368 setC( block, c )\
369 end\
370 break\
371 end\
372 c = nextC( )\
373 setC( block, c )\
374 end\
375 break\
376 end\
377 end\
378 end\
379 end\
380 setC( self.syntax.default, c )\
381 end\
382 self.characters = characters\
383 if self.showLines then\
384 self.lineWidth = math.floor( math.log( #self.lines ) / math.log( 10 ) ) + 4\
385 else\
386 self.lineWidth = 0\
387 end\
388end\
389\
390function UICode.public:update( dt )\
391 if self.cursory > #self.lines then\
392 self.cursory = #self.lines\
393 end\
394 if self.cursorx > #( self.lines[self.cursory] or \"\" ) + 1 then\
395 self.cursorx = #( self.lines[self.cursory] or \"\" ) + 1\
396 end\
397 local c = { }\
398 for i, child in ipairs( self.children ) do\
399 c[i] = child\
400 end\
401 for i, child in ipairs( c ) do\
402 child:update( dt )\
403 end\
404end\
405\
406local tabsize = 4\
407\
408function UICode.public:draw( x, y )\
409 local layer = stencil.addLayer( x, y, self.w, self.h )\
410 local characters = self.characters\
411 stencil.rectangle( x, y, self.w, self.h, self.syntax.default.bc, self.syntax.default.tc, \" \" )\
412 if self.showLines then\
413 for i = 1, self.h do\
414 if i + self.scrolly <= #self.lines then\
415 local n = tostring( i + self.scrolly )\
416 stencil.textLine( x, y + i - 1, self.syntax.linen.bc, self.syntax.linen.tc, (\" \"):rep( self.lineWidth - 2 - #n ) .. n .. \" \" )\
417 else\
418 stencil.textLine( x, y + i - 1, self.syntax.linen.bc, 1, (\" \"):rep( self.lineWidth - 1 ) )\
419 end\
420 end\
421 end\
422 local xx, yy, cx = 1, 1, 1\
423 local tabthisline = true\
424 local i = self.linechars[self.scrolly + 1] or 1\
425 while i <= #characters do\
426 if characters[i].line > yy then\
427 xx = 1\
428 cx = 1\
429 yy = characters[i].line\
430 tabthisline = true\
431 end\
432\
433 local bc, tc = characters[i].colour.bc or self.syntax.default.bc, characters[i].colour.tc or self.syntax.default.tc\
434 if self.cursory == characters[i].line then\
435 bc = characters[i].colour.lbc or characters[i].colour.bc or self.syntax.default.lbc or self.syntax.default.bc\
436 tc = characters[i].colour.ltc or characters[i].colour.tc or self.syntax.default.ltc or self.syntax.default.tc\
437 end\
438 if self.selection then\
439 local ssx, ssy, sex, sey\
440 if self.selection.y < self.cursory then\
441 ssy = self.selection.y\
442 ssx = self.selection.x\
443 sey = self.cursory\
444 sex = self.cursorx\
445 elseif self.selection.y > self.cursory then\
446 ssy = self.cursory\
447 ssx = self.cursorx\
448 sey = self.selection.y\
449 sex = self.selection.x\
450 else\
451 ssy = self.selection.y\
452 ssx = math.min( self.selection.x, self.cursorx )\
453 sey = self.cursory\
454 sex = math.max( self.selection.x, self.cursorx )\
455 end\
456 if yy == ssy then\
457 if yy == sey then\
458 if cx >= ssx and cx <= sex then\
459 bc = self.syntax.selection.bc\
460 tc = self.syntax.selection.tc\
461 end\
462 else\
463 if cx >= ssx then\
464 bc = self.syntax.selection.bc\
465 tc = self.syntax.selection.tc\
466 end\
467 end\
468 elseif yy == sey then\
469 if cx <= sex then\
470 bc = self.syntax.selection.bc\
471 tc = self.syntax.selection.tc\
472 end\
473 elseif yy > ssy and yy < sey then\
474 bc = self.syntax.selection.bc\
475 tc = self.syntax.selection.tc\
476 end\
477 end\
478 local rx = xx - self.scrollx\
479 local ry = yy - self.scrolly\
480 if rx >= 1 and rx <= self.w - self.lineWidth and ry >= 1 and ry <= self.h then\
481 if characters[i].char ~= \" \" then\
482 tabthisline = false\
483 stencil.pixel( x + rx - 1 + self.lineWidth, y + ry - 1, bc, tc, characters[i].char )\
484 else\
485 for i = 1, ( tabsize - ( xx - 1 ) % tabsize ) - 1 do\
486 stencil.pixel( x + rx - 2 + self.lineWidth + i, y + ry - 1, bc, tc, \" \" )\
487 end\
488 if self.syntax.tab and tabthisline then\
489 stencil.pixel( x + rx - 2 + self.lineWidth + ( tabsize - ( xx - 1 ) % tabsize ), y + ry - 1, self.syntax.tab.bc or bc, self.syntax.tab.tc or tc, self.syntax.tab.char or \" \" )\
490 else\
491 stencil.pixel( x + rx - 2 + self.lineWidth + ( tabsize - ( xx - 1 ) % tabsize ), y + ry - 1, bc, tc, \" \" )\
492 end\
493 end\
494 end\
495 if characters[i].char == \" \" then\
496 xx = xx + ( tabsize - ( xx - 1 ) % tabsize )\
497 else\
498 xx = xx + 1\
499 end\
500 if ry > self.h then\
501 break\
502 end\
503 cx = cx + 1\
504 i = i + 1\
505 end\
506 if self.focussed and not self.selection then\
507 local xx, yy = self:getCursorPos( )\
508 stencil.setCursorBlink( x + xx - 1 + self.lineWidth, y + yy - 1, self.syntax.default.tc )\
509 end\
510 local c = { }\
511 for i, child in ipairs( self.children ) do\
512 c[i] = child\
513 end\
514 for i, child in ipairs( c ) do\
515 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
516 end\
517 stencil.closeLayer( layer )\
518end\
519\
520function UICode:setCursorPos( x, y )\
521 self.cursorx = x\
522 self.cursory = y\
523 local ry = self.cursory\
524 local l = self.lines[self.cursory]\
525 local rx = 0\
526 for i = 1, self.cursorx - 1 do\
527 if l:sub( i, i ) == \" \" then\
528 rx = rx + ( tabsize - rx % tabsize )\
529 else\
530 rx = rx + 1\
531 end\
532 end\
533 rx = rx + 1\
534 if ry <= self.scrolly then\
535 self.scrolly = ry - 1\
536 elseif ry >= self.scrolly + self.h then\
537 self.scrolly = ry - self.h\
538 end\
539 if rx <= self.scrollx then\
540 self.scrollx = rx - 1\
541 elseif rx >= self.scrollx + ( self.w - self.lineWidth ) then\
542 self.scrollx = rx - ( self.w - self.lineWidth )\
543 end\
544end\
545\
546function UICode:getCursorPos( )\
547 local y = self.cursory - self.scrolly\
548 local l = self.lines[self.cursory] or \"\"\
549 local x = 0\
550 for i = 1, self.cursorx - 1 do\
551 if l:sub( i, i ) == \" \" then\
552 x = x + ( tabsize - x % tabsize )\
553 else\
554 x = x + 1\
555 end\
556 end\
557 return x + 1 - self.scrollx, y\
558end\
559\
560function UICode:coordsToCursor( x, y )\
561 if x <= self.lineWidth then\
562 return\
563 end\
564 x = x + self.scrollx - self.lineWidth\
565 if self.lines[y+self.scrolly] then\
566 local l = self.lines[y + self.scrolly]\
567 local cx = 0\
568 for i = 1, #l do\
569 if l:sub( i, i ) == \" \" then\
570 cx = cx + ( tabsize - ( cx ) % tabsize )\
571 else\
572 cx = cx + 1\
573 end\
574 if cx >= x then\
575 return i, y + self.scrolly\
576 end\
577 end\
578 return #l + 1, y + self.scrolly\
579 end\
580 return #self.lines[#self.lines] + 1, #self.lines\
581end\
582\
583function UICode:getSelectionBounds( )\
584 local ssx, ssy, sex, sey\
585 if self.selection.y < self.cursory then\
586 ssy = self.selection.y\
587 ssx = self.selection.x\
588 sey = self.cursory\
589 sex = self.cursorx\
590 elseif self.selection.y > self.cursory then\
591 ssy = self.cursory\
592 ssx = self.cursorx\
593 sey = self.selection.y\
594 sex = self.selection.x\
595 else\
596 ssy = self.selection.y\
597 ssx = math.min( self.selection.x, self.cursorx )\
598 sey = self.cursory\
599 sex = math.max( self.selection.x, self.cursorx )\
600 end\
601 return ssx, ssy, sex, sey\
602end\
603\
604function UICode:write( str )\
605 local cx, cy = self.cursorx, self.cursory\
606 for i = 1, #str do\
607 if str:sub( i, i ) == \"\\n\" then\
608 cy = cy + 1\
609 cx = 1\
610 table.insert( self.lines, cy, \"\" )\
611 else\
612 self.lines[cy] = self.lines[cy]:sub( 1, cx - 1 ) .. str:sub( i, i ) .. self.lines[cy]:sub( cx )\
613 cx = cx + 1\
614 end\
615 end\
616 self:setCursorPos( cx, cy )\
617end\
618\
619function UICode:setSelection( str )\
620 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
621 local line_end = self.lines[sey]:sub( sex + 1 )\
622 self.lines[ssy] = self.lines[ssy]:sub( 1, ssx - 1 )\
623 self:setCursorPos( ssx, ssy )\
624 for i = ssy + 1, sey do\
625 table.remove( self.lines, ssy + 1 )\
626 end\
627 self:write( str .. line_end )\
628 self:setCursorPos( self.cursorx - #line_end, self.cursory )\
629 self.selection = false\
630end\
631\
632function UICode.public:getSelection( str )\
633 if self.selection then\
634 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
635 if ssy == sey then\
636 return self.lines[ssy]:sub( ssx, sex )\
637 end\
638 local selected = self.lines[ssy]:sub( ssx )\
639 for i = ssy + 1, sey - 1 do\
640 selected = selected .. \"\\n\" .. self.lines[i]\
641 end\
642 selected = selected .. \"\\n\" .. self.lines[sey]:sub( 1, sex )\
643 return selected\
644 end\
645end\
646\
647function UICode.public:setSelection( str )\
648 if self.selection then\
649 self:setSelection( str )\
650 self.public:updateCharacters( )\
651 end\
652end\
653\
654function UICode.public:write( str )\
655 self:write( str )\
656 self.public:updateCharacters( )\
657end\
658\
659function UICode.public:onMouseClick( rx, ry, button )\
660 if self.selection then\
661 self.selection = false\
662 end\
663 local x, y = self:coordsToCursor( rx, ry )\
664 if self.lastclick and self.lastclick.x == rx and self.lastclick.y == ry and os.clock( ) - self.lastclick.time <= 0.3 and button == 1 then\
665 local bounds = { min = x, max = x }\
666 local line = self.lines[y]\
667 local c = line:sub( x, x )\
668 if c:find \"%s\" then\
669 for i = x - 1, 1, -1 do\
670 if not line:sub( i, i ):find \"%s\" then\
671 break\
672 end\
673 bounds.min = i\
674 end\
675 for i = x + 1, #line do\
676 if not line:sub( i, i ):find \"%s\" then\
677 break\
678 end\
679 bounds.max = i\
680 end\
681 elseif c:find \"[%w_]\" then\
682 for i = x - 1, 1, -1 do\
683 if not line:sub( i, i ):find \"[%w_]\" then\
684 break\
685 end\
686 bounds.min = i\
687 end\
688 for i = x + 1, #line do\
689 if not line:sub( i, i ):find \"[%w_]\" then\
690 break\
691 end\
692 bounds.max = i\
693 end\
694 else\
695 for i = x - 1, 1, -1 do\
696 if line:sub( i, i ):find \"%s\" or line:sub( i, i ):find \"[%w_]\" then\
697 break\
698 end\
699 bounds.min = i\
700 end\
701 for i = x + 1, #line do\
702 if line:sub( i, i ):find \"%s\" or line:sub( i, i ):find \"[%w_]\" then\
703 break\
704 end\
705 bounds.max = i\
706 end\
707 end\
708 self.selection = { x = bounds.min, y = y }\
709 self.cursorx = bounds.max\
710 self.cursory = y\
711 self.lastclick = false\
712 return\
713 elseif button == 1 then\
714 self.lastclick = { x = rx, y = ry, time = os.clock( ) }\
715 end\
716 if x then\
717 self:setCursorPos( x, y )\
718 end\
719end\
720\
721function UICode.public:onMouseDrag( rx, ry, cx, cy, button )\
722 local x, y = self:coordsToCursor( rx, ry )\
723 if x then\
724 if not self.selection then\
725 self.selection = { x = self.cursorx, y = self.cursory }\
726 end\
727 self:setCursorPos( x, y )\
728 end\
729end\
730\
731function UICode.public:onMouseScroll( rx, ry, dir )\
732 if self.scrolly > 0 and dir == -1 then\
733 self.scrolly = self.scrolly - 1\
734 elseif self.scrolly < #self.lines - self.h and dir == 1 then\
735 self.scrolly = self.scrolly + 1\
736 end\
737end\
738\
739function UICode.public:onKeyPress( key, lastkey )\
740 if not self.focussed then\
741 return\
742 end\
743 if lastkey == 29 then\
744 if key == keys.c or key == keys.x or key == keys.b then\
745 if key == keys.c then\
746 if self.selection then\
747 clipboard.set( \"plaintext\", self.public:getSelection( ) )\
748 else\
749 clipboard.set( \"plaintext\", self.lines[self.cursory] or \"\" )\
750 end\
751 elseif key == keys.x then\
752 if self.selection then\
753 clipboard.set( \"plaintext\", self.public:getSelection( ) )\
754 self:setSelection \"\"\
755 self.selection = false\
756 else\
757 clipboard.set( \"plaintext\", self.lines[self.cursory] or \"\" )\
758 table.remove( self.lines, self.cursory )\
759 end\
760 self.public:updateCharacters( )\
761 if self.onChange then\
762 self.onChange( self.public, \"cut\" )\
763 end\
764 elseif key == keys.b then\
765 local mode, data = clipboard.get( )\
766 if mode == \"plaintext\" then\
767 if self.selection then\
768 self:setSelection( data )\
769 self.selection = false\
770 else\
771 self:write( data )\
772 end\
773 self.public:updateCharacters( )\
774 elseif self.onCtrlKey then\
775 self.onCtrlKey( self.public, key )\
776 end\
777 if self.onChange then\
778 self.onChange( self.public, \"paste\" )\
779 end\
780 end\
781 return\
782 end\
783 if self.onCtrlKey then\
784 self.onCtrlKey( self.public, key )\
785 end\
786 return\
787 end\
788 if key == keys.left then\
789 if self.selection then\
790 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
791 self:setCursorPos( ssx, ssy )\
792 self.selection = false\
793 else\
794 if self.cursorx == 1 then\
795 if self.cursory > 1 then\
796 self:setCursorPos( #self.lines[self.cursory - 1] + 1, self.cursory - 1 )\
797 end\
798 else\
799 self:setCursorPos( self.cursorx - 1, self.cursory )\
800 end\
801 end\
802 elseif key == keys.right then\
803 if self.selection then\
804 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
805 self:setCursorPos( sex, sey )\
806 self.selection = false\
807 else\
808 if self.cursorx >= #self.lines[self.cursory] + 1 then\
809 if self.cursory < #self.lines then\
810 self:setCursorPos( 1, self.cursory + 1 )\
811 else\
812 self.cursorx = #self.lines[self.cursory] + 1\
813 end\
814 else\
815 self:setCursorPos( self.cursorx + 1, self.cursory )\
816 end\
817 end\
818 elseif key == keys.up and self.cursory > 1 then\
819 if self.selection then\
820 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
821 self:setCursorPos( ssx, ssy )\
822 self.selection = false\
823 else\
824 local x, y = self:getCursorPos( )\
825 self:setCursorPos( self:coordsToCursor( x + self.lineWidth, y - 1 ) )\
826 end\
827 elseif key == keys.down and self.cursory < #self.lines then\
828 if self.selection then\
829 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
830 self:setCursorPos( sex, sey )\
831 self.selection = false\
832 else\
833 local x, y = self:getCursorPos( )\
834 self:setCursorPos( self:coordsToCursor( x + self.lineWidth, y + 1 ) )\
835 end\
836 elseif key == keys.enter then\
837 if self.selection then\
838 self:setSelection \"\\n\"\
839 else\
840 local whitespace = self.lines[self.cursory]:match \"^(%s*)\"\
841 table.insert( self.lines, self.cursory + 1, whitespace .. self.lines[self.cursory]:sub( self.cursorx ) )\
842 self.lines[self.cursory] = self.lines[self.cursory]:sub( 1, self.cursorx - 1 )\
843 self:setCursorPos( #whitespace + 1, self.cursory + 1 )\
844 end\
845 self.public:updateCharacters( )\
846 if self.onChange then\
847 self.onChange( self.public, \"\\n\" )\
848 end\
849 elseif key == keys.tab then\
850 if self.selection then\
851 local ssx, ssy, sex, sey = self:getSelectionBounds( )\
852 if ssy == sey then\
853 self:setSelection \" \"\
854 else\
855 for y = ssy, sey do\
856 if lastkey == keys.leftShift then\
857 self.lines[y] = self.lines[y]:gsub( \"^ \", \"\", 1 )\
858 else\
859 self.lines[y] = \" \" .. self.lines[y]\
860 end\
861 end\
862 end\
863 else\
864 self.lines[self.cursory] = self.lines[self.cursory]:sub( 1, self.cursorx - 1 ) .. \" \" .. self.lines[self.cursory]:sub( self.cursorx )\
865 self:setCursorPos( self.cursorx + 1, self.cursory )\
866 end\
867 self.public:updateCharacters( )\
868 if self.onChange then\
869 self.onChange( self.public, \" \" )\
870 end\
871 elseif key == keys.delete then\
872 if self.selection then\
873 self:setSelection \"\"\
874 else\
875 if self.cursorx == #self.lines[self.cursory] + 1 then\
876 if self.lines[self.cursory + 1] then\
877 self.lines[self.cursory] = self.lines[self.cursory] .. self.lines[self.cursory + 1]\
878 table.remove( self.lines, self.cursory + 1 )\
879 end\
880 else\
881 self.lines[self.cursory] = self.lines[self.cursory]:sub( 1, self.cursorx - 1 ) .. self.lines[self.cursory]:sub( self.cursorx + 1 )\
882 end\
883 end\
884 self.public:updateCharacters( )\
885 if self.onChange then\
886 self.onChange( self.public, \"delete\" )\
887 end\
888 elseif key == keys.backspace then\
889 if self.selection then\
890 self:setSelection \"\"\
891 else\
892 if self.cursorx == 1 then\
893 if self.cursory > 1 then\
894 self:setCursorPos( #self.lines[self.cursory - 1] + 1, self.cursory - 1 )\
895 self.lines[self.cursory] = self.lines[self.cursory] .. self.lines[self.cursory + 1]\
896 table.remove( self.lines, self.cursory + 1 )\
897 end\
898 else\
899 self.lines[self.cursory] = self.lines[self.cursory]:sub( 1, self.cursorx - 2 ) .. self.lines[self.cursory]:sub( self.cursorx )\
900 self:setCursorPos( self.cursorx - 1, self.cursory )\
901 end\
902 end\
903 self.public:updateCharacters( )\
904 if self.onChange then\
905 self.onChange( self.public, \"backspace\" )\
906 end\
907 elseif key == 199 then -- home\
908 local tabs = #( self.lines[self.cursory]:match \"^( *)\" )\
909 self:setCursorPos( tabs + 1, self.cursory )\
910 elseif key == 207 then -- end\
911 self:setCursorPos( #self.lines[self.cursory] + 1, self.cursory )\
912 end\
913end\
914\
915function UICode.public:onTextInput( text )\
916 if not self.focussed then\
917 return\
918 end\
919 if self.selection then\
920 self:setSelection( text )\
921 else\
922 self.lines[self.cursory] = self.lines[self.cursory]:sub( 1, self.cursorx - 1 ) .. text .. self.lines[self.cursory]:sub( self.cursorx )\
923 self:setCursorPos( self.cursorx + 1, self.cursory )\
924 end\
925 self.public:updateCharacters( )\
926 if self.onChange then\
927 self.onChange( self.public, text )\
928 end\
929end\
930\
931function UICode.public:setCode( code )\
932 self.lines = { }\
933 local last = 1\
934 for i = 1, #code do\
935 if code:sub( i, i ) == \"\\n\" then\
936 table.insert( self.lines, code:sub( last, i - 1 ) )\
937 last = i + 1\
938 end\
939 end\
940 table.insert( self.lines, code:sub( last ) )\
941 self.public:updateCharacters( )\
942end\
943\
944function UICode.public:getCode( )\
945 return table.concat( self.lines, \"\\n\" )\
946end\
947\
948function UICode.public:onFocus( )\
949 self.focussed = true\
950 self.handlesKeys = true\
951 if type( self.whenFocussed ) == \"function\" then\
952 self.whenFocussed( self.public )\
953 end\
954end\
955\
956function UICode.public:onUnFocus( )\
957 self.handlesKeys = false\
958 self.focussed = false\
959 if type( self.whenUnFocussed ) == \"function\" then\
960 self.whenUnFocussed( self.public )\
961 end\
962end\
963\
964function UICode.public:focusOn( )\
965 local handler = self.public:getHandler( )\
966 handler:setFocus( self.public )\
967end\
968\
969function UICode.public:getDisplaySizeH( )\
970 return self.w - self.lineWidth\
971end\
972function UICode.public:getDisplaySizeV( )\
973 return self.h\
974end\
975function UICode.public:getContentSizeH( )\
976 local max = 1\
977 for i = 1, #self.lines do\
978 max = math.max( max, #self.lines[i] )\
979 end\
980 return max\
981end\
982function UICode.public:getContentSizeV( )\
983 return #self.lines\
984end\
985function UICode.public:getContentScrollH( )\
986 return self.scrollx\
987end\
988function UICode.public:getContentScrollV( )\
989 return self.scrolly\
990end\
991function UICode.public:setContentScrollH( scroll )\
992 self.scrollx = scroll\
993end\
994function UICode.public:setContentScrollV( scroll )\
995 self.scrolly = scroll\
996end", meta={
997 type = "class",
998}};["kernel"]={content="\
999\
1000-- initiate UI\
1001\
1002require \"core\"\
1003require \"nova_markup\"\
1004require \"Thread\"\
1005require \"UIHandler\"\
1006\
1007core.UI = UIHandler( 1, 1, term.getSize( ) )\
1008\
1009local UIThread = Thread( function( ... )\
1010 local ev = { ... }\
1011 while true do\
1012 if ev[1] == \"update\" then\
1013 core.UI:update( ev[2] )\
1014 core.UI:draw( )\
1015 else\
1016 core.UI:event( ev )\
1017 end\
1018 ev = { coroutine.yield( ) }\
1019 end\
1020end )\
1021function UIThread:onException( err )\
1022 core.log( \"UI error\", err )\
1023 self:restart( )\
1024end\
1025\
1026-- sessions\
1027\
1028function setup( welcome )\
1029 local w, h = term.getSize( )\
1030 if welcome then\
1031 local welcome = core.UI:newChild( UIText( 1, 1, 20, 1, \"Welcome to NovaOS!\" ) )\
1032 welcome:centre( )\
1033 welcome.bc = 1\
1034 welcome.tc = colours.lightGrey\
1035 local t = os.clock( )\
1036 while os.clock( ) - t < 1.3 do\
1037 coroutine.yield( )\
1038 end\
1039 welcome:remove( )\
1040 end\
1041\
1042 local finished = false\
1043\
1044 local frame = core.UI:newChild( UIFrame( 2, 3, w - 2, h - 3 ) )\
1045 local title = frame:newChild( UIText( 1, 1, core.UI.w - 2, 2, \"Please set up a user account.\" ) )\
1046 title.tc = colours.grey\
1047 title:centreX( )\
1048 local ut = frame:newChild( UIText( 0, 6, 8, 1, \"username\" ) )\
1049 ut.tc = colours.lightGrey\
1050 ut:centreX( )\
1051 local username = frame:newChild( UIInput( 0, 7, math.floor( w * 2 / 3 ), 1 ) )\
1052 username.tc = 1\
1053 username:centreX( )\
1054 username:focusOn( )\
1055 local pt = frame:newChild( UIText( 0, 9, 8, 1, \"password\" ) )\
1056 pt.tc = colours.lightGrey\
1057 pt:centreX( )\
1058 local password = frame:newChild( UIInput( 0, 10, math.floor( w * 2 / 3 ), 1, \"*\" ) )\
1059 password.tc = 1\
1060 password:centreX( )\
1061\
1062 local continue = frame:newChild( UIButton( 0, frame.h - 2, 20, 3, \"continue\" ) )\
1063 continue:centreX( )\
1064 continue.tc = 1\
1065 function continue:onClick( )\
1066 finished = true\
1067 end\
1068 continue.tabIndex = true\
1069 function continue:whenFocussed( )\
1070 self.bc = colours.lightBlue\
1071 end\
1072 function continue:whenUnFocussed( )\
1073 self.bc = colours.lightGrey\
1074 end\
1075\
1076 function username:onEnter( )\
1077 password:focusOn( )\
1078 end\
1079 function password:onEnter( )\
1080 finished = true\
1081 end\
1082\
1083 while not finished do\
1084 coroutine.yield( )\
1085 end\
1086 frame:remove( )\
1087\
1088 Account.create( username.text, password.text )\
1089 core.log( \"created user\", username.text )\
1090end\
1091\
1092if #fs.list( core.path .. \"/user\" ) == 0 then\
1093 setup( true )\
1094end\
1095\
1096core.session = Session( )", meta={
1097 type = "lib",
1098}};["MarkupGenericObject"]={content="\
1099\
1100require \"MarkupPage\"\
1101\
1102MarkupGenericObject.public \"page\" (MarkupPage)\
1103MarkupGenericObject.public.etype = \"unknown\"\
1104MarkupGenericObject.public \"static\"\
1105MarkupGenericObject.public.static.write = false\
1106MarkupGenericObject.public \"remove\" \"boolean\"\
1107\
1108function MarkupGenericObject:MarkupGenericObject( source )\
1109 self.width = { type = \"text\", value = \"auto\" }\
1110 self.height = { type = \"text\", value = \"auto\" }\
1111\
1112 self.source = source\
1113end\
1114\
1115function MarkupGenericObject.public:setAttribute( attribute, value )\
1116 if MarkupGenericObject.attributes[attribute] then\
1117 MarkupGenericObject.attributes[attribute]( self, value )\
1118 end\
1119 -- custom attributes\
1120 self.page.needsReposition = true\
1121end\
1122\
1123function MarkupGenericObject.public:addObject( object )\
1124 if self.children then\
1125 table.insert( self.children, object )\
1126 return true\
1127 else\
1128 return false\
1129 end\
1130end\
1131\
1132function MarkupGenericObject.public:loadObject( positionHandler )\
1133 -- generate a list of MarkupLoadedObjects\
1134end\
1135\
1136function MarkupGenericObject.public:applyAreaValues( pw, ph, object )\
1137 if self.width.type == \"percent\" then\
1138 object.content.width = math.floor( pw * self.width.value / 100 + .5 )\
1139 elseif self.width.type == \"pixel\" then\
1140 object.content.width = self.width.value\
1141 end\
1142 if self.height.type == \"percent\" then\
1143 object.content.height = math.floor( pw * self.height.value / 100 + .5 )\
1144 elseif self.height.type == \"pixel\" then\
1145 object.content.height = self.height.value\
1146 end\
1147end\
1148\
1149function MarkupGenericObject.public:scriptObject( )\
1150 return setmetatable( {}, {\
1151 __newindex = function( _, k, v )\
1152 if type( v ) ~= \"string\" then\
1153 v = tostring( v )\
1154 end\
1155 return self.public:setAttribute( k, v )\
1156 end;\
1157 __index = function( _, k )\
1158 if k == \"width\" then\
1159 return ( self.width.type == \"percent\" and self.width.value .. \"%\" ) or ( self.width.type == \"pixel\" and self.width.value .. \"px\" ) or ( self.width.type == \"text\" and self.width.value )\
1160 elseif k == \"height\" then\
1161 return ( self.height.type == \"percent\" and self.height.value .. \"%\" ) or ( self.height.type == \"pixel\" and self.height.value .. \"px\" ) or ( self.height.type == \"text\" and self.height.value )\
1162 else\
1163 return error \"cannot access this attribute\"\
1164 end\
1165 end;\
1166 } )\
1167end\
1168\
1169MarkupGenericObject.static.attributes = { }\
1170\
1171function MarkupGenericObject.attributes:width( value )\
1172 if value:find \"%d+%%\" then\
1173 self.width = { type = \"percent\", value = tonumber( value:match \"(%d+)%%\" ) }\
1174 elseif value:find \"%d+px?\" then\
1175 self.width = { type = \"pixel\", value = tonumber( value:match \"(%d+)px?\" ) }\
1176 elseif value == \"auto\" then\
1177 self.width = { type = \"text\", value = \"auto\" }\
1178 else\
1179 table.insert( self.page.errors, { source = self.source, level = \"warning\", message = \"expected percentage or pixel for attribute width\" } )\
1180 end\
1181end\
1182\
1183function MarkupGenericObject.attributes:height( value )\
1184 if value:find \"%d+%%\" then\
1185 self.height = { type = \"percent\", value = tonumber( value:match \"(%d+)%%\" ) }\
1186 elseif value:find \"%d+px?\" then\
1187 self.height = { type = \"pixel\", value = tonumber( value:match \"(%d+)px?\" ) }\
1188 elseif value == \"auto\" then\
1189 self.height = { type = \"text\", value = \"auto\" }\
1190 else\
1191 table.insert( self.page.errors, { source = self.source, level = \"warning\", message = \"expected percentage or pixel for attribute height\" } )\
1192 end\
1193end\
1194\
1195function MarkupGenericObject.attributes:static( value )\
1196 if tostring( value ) == \"true\" then\
1197 self.static = true\
1198 elseif tostring( value ) == \"false\" then\
1199 self.static = false\
1200 else\
1201 table.insert( self.page.errors, { source = self.source, level = \"warning\", message = \"expected boolean for attribute static\" } )\
1202 end\
1203end\
1204\
1205function MarkupGenericObject.attributes:id( id )\
1206 if self.id then\
1207 self.page.ids[self.id] = nil\
1208 end\
1209 self.id = id\
1210 self.page.ids[id] = self.public\
1211end", meta={
1212 type = "class",
1213}};["markup_parse"]={content="\
1214\
1215local parse_element\
1216\
1217local function getLine( str, i )\
1218 local line = 1\
1219 for p = 1, i do\
1220 if str:sub( p, p ) == \"\\n\" then\
1221 line = line + 1\
1222 end\
1223 end\
1224 return line\
1225end\
1226\
1227local alias = {\
1228 p = \"paragraph\";\
1229 t = \"text\";\
1230 h = \"header\";\
1231 img = \"image\";\
1232 hr = \"rule\";\
1233 br = \"newline\";\
1234 nl = \"newline\";\
1235 s = \"space\";\
1236 a = \"link\";\
1237 div = \"frame\";\
1238 f = \"frame\";\
1239}\
1240local s_tags = {\
1241 rule = true;\
1242 newline = true;\
1243 image = true;\
1244 input = true;\
1245 space = true;\
1246 iframe = true;\
1247}\
1248\
1249function parse_element( str, page, i )\
1250 local name = \"\"\
1251 local line = getLine( str, i )\
1252 i = i + 1\
1253 while str:sub( i, i ):find \"%S\" and str:sub( i, i ) ~= \">\" do\
1254 name = name .. str:sub( i, i )\
1255 i = i + 1\
1256 end\
1257 if alias[name] then name = alias[name] end\
1258 local attributes = { }\
1259 local function loadAttribute( )\
1260 if i > #str then return end\
1261 while str:sub( i, i ):find \"%s\" do\
1262 i = i + 1 -- whitespace\
1263 end\
1264 if str:sub( i, i ) == \">\" then\
1265 i = i + 1\
1266 return\
1267 end\
1268 local a = \"\"\
1269 local strstarted = false\
1270 local escape = false\
1271 while true do\
1272 if i > #str then break end\
1273 if escape then\
1274 a = a .. str:sub( i, i )\
1275 escape = false\
1276 elseif str:sub( i, i ) == \"\\\"\" then\
1277 strstarted = not strstarted\
1278 elseif str:sub( i, i ) == \"\\\\\" and strstarted then\
1279 escape = escape\
1280 elseif str:sub( i, i ):find \"[%s>]\" and not strstarted then\
1281 break\
1282 else\
1283 a = a .. str:sub( i, i )\
1284 end\
1285 i = i + 1\
1286 end\
1287 for i = 1, #a do\
1288 if a:sub( i, i ) == \":\" or a:sub( i, i ) == \"=\" then\
1289 local name = a:sub( 1, i - 1 )\
1290 local value = a:sub( i + 1 )\
1291 if value:sub( 1, 1 ) == \"\\\"\" then\
1292 value = textutils.unserialize( value )\
1293 if not value then\
1294 table.insert( page.errors, { source = getLine( str, i - #a + #name + 2 ), level = \"warning\", message = \"syntax error in string value for attribute \" .. name } )\
1295 end\
1296 end\
1297 if value then\
1298 attributes[name] = value\
1299 end\
1300 return true\
1301 end\
1302 end\
1303 attributes[a] = \"true\"\
1304 return true\
1305 end\
1306 while loadAttribute( ) do end\
1307 local pos = i\
1308 if not s_tags[name] then\
1309 local content\
1310 content, pos = parse( str, page, i, name, name == \"script\" )\
1311 attributes.innerNML = content\
1312 end\
1313 local element = {\
1314 type = name;\
1315 attributes = attributes;\
1316 source = line;\
1317 }\
1318 return element, pos\
1319end\
1320\
1321function parse( str, page, i, close, blockElements )\
1322 local i = i or 1\
1323 local ast = { }\
1324 while i <= #str do\
1325 if str:sub( i, i ) == \"<\" then\
1326 if str:sub( i + 1, i + 1 ) == \"/\" then\
1327 local name = str:match( \"(%w+)>\", i + 2 )\
1328 name = alias[name] or name\
1329 if name == close then\
1330 return ast, i + 3 + #str:match( \"(%w+)>\", i + 2 )\
1331 else\
1332 if type( ast[#ast] ) == \"string\" then\
1333 ast[#ast] = ast[#ast] .. \"<\"\
1334 else\
1335 ast[#ast + 1] = \"<\"\
1336 end\
1337 if not blockElements then\
1338 table.insert( page.errors, { source = getLine( str, i ), level = \"warning\", message = \"unexpected ending tag for \" .. name } )\
1339 end\
1340 i = i + 1\
1341 end\
1342 elseif not blockElements then\
1343 local element\
1344 element, i = parse_element( str, page, i )\
1345 ast[#ast + 1] = element\
1346 else\
1347 if type( ast[#ast] ) == \"string\" then\
1348 ast[#ast] = ast[#ast] .. \"<\"\
1349 else\
1350 ast[#ast + 1] = \"<\"\
1351 end\
1352 i = i + 1\
1353 end\
1354 else\
1355 if type( ast[#ast] ) == \"string\" then\
1356 ast[#ast] = ast[#ast] .. str:sub( i, i )\
1357 else\
1358 ast[#ast + 1] = str:sub( i, i )\
1359 end\
1360 i = i + 1\
1361 end\
1362 end\
1363 return ast, #str + 1\
1364end", meta={
1365 type = "lib",
1366}};["UIKeyHandler"]={content="\
1367\
1368require \"UIElement\"\
1369UIKeyHandler:extends( UIElement )\
1370\
1371UIKeyHandler.handlesMouse = false\
1372UIKeyHandler.handlesKeys = true\
1373\
1374UIKeyHandler.onKey = false\
1375UIKeyHandler.public \"onKey\" \"function\"\
1376UIKeyHandler.onChar = false\
1377UIKeyHandler.public \"onChar\" \"function\"\
1378\
1379function UIKeyHandler:UIKeyHandler( )\
1380 self:UIElement( 0, 0, 0, 0 )\
1381 return self.public\
1382end\
1383\
1384function UIKeyHandler.public:onKeyPress( key, lastkey )\
1385 if self.onKey then\
1386 self.onKey( self.public, key, lastkey )\
1387 end\
1388end\
1389function UIKeyHandler.public:onTextInput( char, lastkey )\
1390 if self.onChar then\
1391 self.onChar( self.public, char, lastkey )\
1392 end\
1393end", meta={
1394 type = "class",
1395}};["UIButton"]={content="\
1396\
1397require \"UIElement\"\
1398\
1399UIButton:extends( UIElement )\
1400\
1401UIButton.text = \"\"\
1402UIButton.public \"text\"\
1403\
1404UIButton.public \"bc\" \"number\"\
1405UIButton.public \"tc\" \"number\"\
1406\
1407UIButton.onClick = false\
1408UIButton.public \"onClick\" \"function\"\
1409UIButton.onDrag = false\
1410UIButton.public \"onDrag\" \"function\"\
1411\
1412UIButton.align = true\
1413UIButton.public \"align\" \"boolean\"\
1414\
1415UIButton.handlesEnter = true\
1416\
1417function UIButton:UIButton( x, y, w, h, text )\
1418 self:UIElement( x, y, w, h )\
1419 self.text = text\
1420 self.bc = colours.white\
1421 self.tc = colours.blue\
1422 return self.public\
1423end\
1424\
1425function UIButton.public:draw( x, y )\
1426 local layer = stencil.addLayer( x, y, self.w, self.h )\
1427 local text = tostring( self.text )\
1428 if type( self.text ) == \"function\" then\
1429 text = self.text( self.public )\
1430 end\
1431 if self.align then\
1432 stencil.text( x, y, self.w, self.h, self.bc, self.tc, text, function( lines )\
1433 while #lines < self.h do\
1434 table.insert( lines, 1, string.rep( \" \", self.w ) )\
1435 table.insert( lines, string.rep( \" \", self.w ) )\
1436 end\
1437 if #lines > self.h then\
1438 table.remove( lines, 1 )\
1439 end\
1440 for i = 1, #lines do\
1441 lines[i] = string.rep( \" \", math.floor( ( self.w - #lines[i] ) / 2 ) ) .. lines[i]\
1442 end\
1443 end )\
1444 else\
1445 stencil.text( x, y, self.w, self.h, self.bc, self.tc, text )\
1446 end\
1447 local c = { }\
1448 for i, child in ipairs( self.children ) do\
1449 c[i] = child\
1450 end\
1451 for i, child in ipairs( c ) do\
1452 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
1453 end\
1454 stencil.closeLayer( layer )\
1455end\
1456\
1457function UIButton.public:onMouseClick( rx, ry, button )\
1458 if self.onClick then\
1459 self.onClick( self.public, rx, ry, button, self.last and self.last.x == rx and self.last.y == ry and os.clock( ) - self.last.time <= 0.5 )\
1460 end\
1461 self.last = {\
1462 x = rx;\
1463 y = ry;\
1464 time = os.clock( );\
1465 }\
1466end\
1467\
1468function UIButton.public:onMouseDrag( x, y, cx, cy, button )\
1469 if self.onDrag then\
1470 self.onDrag( self.public, x, y, cx, cy, button )\
1471 end\
1472end\
1473\
1474function UIButton.public:onKeyPress( key )\
1475 if key == keys.enter and self.onClick then\
1476 self.onClick( self.public, 1, 1, \"enter\" )\
1477 end\
1478end", meta={
1479 type = "class",
1480}};["MarkupButton"]={content="\
1481\
1482require \"MarkupGenericObject\"\
1483\
1484MarkupButton:extends( MarkupGenericObject )", meta={
1485 type = "class",
1486}};["com"]={content="\
1487\
1488require \"encryption\"\
1489require \"network\"\
1490\
1491local modem\
1492local mside\
1493local messages = { }\
1494\
1495function setModem( side )\
1496 if mside ~= side then\
1497 modem = peripheral.wrap( side )\
1498 modem.open( 2514 ) -- messages\
1499 modem.open( 2515 ) -- pings\
1500 mside = side\
1501 end\
1502end\
1503\
1504function updateModems( )\
1505 for _, side in pairs( peripheral.getNames( ) ) do\
1506 if peripheral.getType( side ) == \"modem\" then\
1507 setModem( side )\
1508 return true\
1509 end\
1510 end\
1511end\
1512\
1513function send( id, data, key, seed )\
1514 local channel = \"public\"\
1515 if key then\
1516 data = encryption.encrypt( textutils.serialize( data ), key )\
1517 math.randomseed( seed )\
1518 channel = math.random( 1, 32768 )\
1519 end\
1520 local t = {\
1521 target = id;\
1522 sender = os.getComputerID( );\
1523 data = data;\
1524 id = os.time( ) .. \":\" .. os.getComputerID( );\
1525 channel = channel;\
1526 }\
1527 if id == os.getComputerID( ) then\
1528 os.queueEvent( \"modem_message\", \"top\", 2514, 2514, t, 0 )\
1529 messages[t.id] = os.clock( )\
1530 return true\
1531 end\
1532 if not modem then\
1533 if not updateModems( ) then\
1534 return false, \"no modem\"\
1535 end\
1536 end\
1537 modem.transmit( 2514, 2514, t )\
1538 messages[t.id] = os.clock( )\
1539 return true\
1540end\
1541\
1542function receive( id, key, timeout, seed )\
1543 if not modem then\
1544 updateModems( )\
1545 end\
1546 local timer = timeout and os.startTimer( timeout )\
1547 while true do\
1548 local ev = { coroutine.yield( ) }\
1549 if ev[1] == \"timer\" and ev[2] == timer then\
1550 return false, \"timeout\"\
1551 end\
1552 if ev[1] == \"modem_message\" and ev[3] == 2514 and ev[4] == 2514 and type( ev[5] ) == \"table\" then -- a normal message\
1553 if ev[5].target == os.getComputerID( ) then\
1554 if id == ev[5].sender then\
1555 local data = ev[5].data\
1556 if key then\
1557 data = textutils.unserialize( encryption.decrypt( data, key ) )\
1558 math.randomseed( seed )\
1559 local channel = math.random( 1, 32768 )\
1560 if data.channel == channel then\
1561 return true, data\
1562 end\
1563 else\
1564 return true, data\
1565 end\
1566 end\
1567 elseif not messages[ev[5].id] then\
1568 modem.transmit( 2514, 2514, ev[5] )\
1569 end\
1570 messages[ev[5].id] = os.clock( )\
1571 elseif ev[1] == \"modem_message\" and ev[3] == 2515 and ev[4] == 2515 and type( ev[5] ) == \"string\" and ev[5]:sub( 1, 15 ) then\
1572 local mode, id = ev[5]:match \":(.-):\", ev[5]:match \":.-:(%d+)\"\
1573 if mode and id then\
1574 if not messages[ev[5]] and tonumber( id ) ~= os.getComputerID( ) then\
1575 if mode == \"Request\" then\
1576 modem.transmit( 2515, 2515, \"NovaNetworkPing:Response:\" .. id .. \":\" .. os.getComputerID( ) )\
1577 else\
1578 modem.transmit( 2515, 2515, ev[5] )\
1579 end\
1580 messages[ev[5]] = os.clock( )\
1581 end\
1582 end\
1583 end\
1584 end\
1585end\
1586\
1587function ping( )\
1588 if not modem then\
1589 if not updateModems( ) then\
1590 return false, \"no modem\"\
1591 end\
1592 end\
1593 modem.transmit( 2515, 2515, \"NovaNetworkPing:Request:\" .. os.getComputerID( ) )\
1594 local time = os.clock( )\
1595 local devices = { }\
1596 while os.clock( ) - time < 1 do\
1597 local ev = { coroutine.yield( ) }\
1598 if ev[1] == \"modem_message\" and ev[3] == 2515 and ev[4] == 2515 and type( ev[5] ) == \"string\" and ev[5]:sub( 1, 15 ) then\
1599 local mode, id, sender = ev[5]:match \":(.-):\", ev[5]:match \":.-:(%d+)\", ev[5]:match \":.-:%d+:(%d+)\"\
1600 if mode and id and sender then\
1601 if tonumber( id ) == os.getComputerID( ) then\
1602 if mode == \"Response\" then\
1603 table.insert( devices, tonumber( sender ) )\
1604 end\
1605 end\
1606 end\
1607 end\
1608 end\
1609end\
1610\
1611function listen( event ) -- waiting for connections to establish, not pings or normal messages...\
1612 if not modem then\
1613 updateModems( )\
1614 end\
1615 if event[1] == \"modem_message\" and event[3] == 2514 and event[4] == 2514 and type( event[5] ) == \"table\" then\
1616 if event[5].target == os.getComputerID( ) then\
1617 local data = event[5].data\
1618 if type( data ) == \"table\" and data.request == \"connection\" then\
1619 local response, key = handshake.generateResponseData( data.handshake )\
1620 if data.insecure then\
1621 network.addChannel( event[5].sender, data.protocol )\
1622 else\
1623 network.addChannel( event[5].sender, data.protocol, network.genkey( key ), key )\
1624 end\
1625 send( event[5].sender, response )\
1626 if data.insecure then\
1627 core.log( \"Network\", \"Insecure connection established with \" .. tostring( event[5].sender ) )\
1628 else\
1629 core.log( \"Network\", \"Secure connection established with \" .. tostring( event[5].sender ) )\
1630 end\
1631 end\
1632 elseif not messages[event[5].id] then\
1633 modem.transmit( 2514, 2514, event[5] )\
1634 end\
1635 messages[event[5].id] = os.clock( )\
1636 end\
1637end\
1638\
1639function clear( )\
1640 local time = os.clock( )\
1641 local r = { }\
1642 for k, v in pairs( messages ) do\
1643 if time - v > 5 then\
1644 r[#r+1] = k\
1645 end\
1646 end\
1647 for i = 1, #r do\
1648 messages[r[i]] = nil\
1649 end\
1650end", meta={
1651 type = "lib",
1652}};["UIImage"]={content="\
1653\
1654require \"Image\"\
1655require \"UIElement\"\
1656\
1657UIImage.public \"image\" (Image)\
1658UIImage.public \"onClick\" \"function\"\
1659UIImage.public \"onDrag\" \"function\"\
1660UIImage.public \"onScroll\" \"function\"\
1661\
1662UIImage:extends( UIElement )\
1663\
1664function UIImage:UIImage( x, y, w, h, image ) -- Image object\
1665 self:UIElement( x, y, w, h )\
1666 self.image = image\
1667 return self.public\
1668end\
1669\
1670function UIImage.public:draw( x, y )\
1671 local layer = stencil.addLayer( x, y, self.w, self.h )\
1672 if not self.image then return end\
1673 for xx = 1, self.w do\
1674 for yy = 1, self.h do\
1675 local bc, tc, char = self.image:getPixel( xx, yy )\
1676 if bc then\
1677 if bc ~= 0 or char ~= \" \" then\
1678 stencil.pixel( x + xx - 1, y + yy - 1, bc, tc, char )\
1679 end\
1680 end\
1681 end\
1682 end\
1683 local c = { }\
1684 for i, child in ipairs( self.children ) do\
1685 c[i] = child\
1686 end\
1687 for i, child in ipairs( c ) do\
1688 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
1689 end\
1690 stencil.closeLayer( layer )\
1691end\
1692\
1693function UIImage.public:onMouseClick( rx, ry, button )\
1694 if self.onClick then\
1695 self.onClick( self.public, rx, ry, button )\
1696 end\
1697end\
1698\
1699function UIImage.public:onMouseDrag( rx, ry, cx, cy, button )\
1700 if self.onDrag then\
1701 self.onDrag( self.public, rx, ry, cx, cy, button )\
1702 end\
1703end\
1704\
1705function UIImage.public:onMouseScroll( rx, ry, dir )\
1706 if self.onScroll then\
1707 self.onScroll( self.public, rx, ry, dir )\
1708 end\
1709end", meta={
1710 type = "class",
1711}};["nova_markup"]={content="\
1712\
1713require \"markup_parse\"\
1714\
1715objects = {\
1716 text = MarkupText;\
1717 paragraph = MarkupParagraph;\
1718 header = MarkupHeader;\
1719 image = MarkupImage;\
1720 rule = MarkupRule;\
1721 frame = MarkupFrame;\
1722 button = MarkupButton;\
1723 input = MarkupInput;\
1724 code = MarkupCode;\
1725 newline = MarkupNewline;\
1726 space = MarkupSpace;\
1727 script = MarkupScript;\
1728}", meta={
1729 type = "lib",
1730}};["FileData"]={content="\
1731\
1732FileData.public \"content\" \"string\"\
1733FileData.public \"meta\"\
1734FileData.public \"password\" \"string\"\
1735\
1736function FileData.public.meta:write( value )\
1737 if type( value ) == \"table\" then\
1738 self.metadata = value\
1739 else\
1740 error( \"expected table meta\", 3 )\
1741 end\
1742end\
1743function FileData.public.meta:read( )\
1744 return self.metadata\
1745end\
1746\
1747function FileData:FileData( content, password ) -- string content\
1748 \
1749 if content and content:find \"^--%[%[.-%]%]\" then\
1750 self.content = content:gsub( \"^--%[%[.-%]%]\", \"\", 1 )\
1751 self.metadata = textutils.unserialize( \"{\" .. content:match \"^--%[%[(.-)%]%]\" .. \"}\" ) or { }\
1752 else\
1753 self.content = content or \"\"\
1754 self.metadata = { }\
1755 end\
1756\
1757 if self.metadata.password then\
1758 if password then\
1759 self.content = encryption.decrypt( self.content, password )\
1760 end\
1761 self.metadata.password = nil\
1762 end\
1763\
1764 self.password = false\
1765\
1766 return self.public\
1767end\
1768\
1769function FileData.public:compile( )\
1770 local content = self.content\
1771 if self.password then\
1772 content = encryption.encrypt( content, self.password )\
1773 self.metadata.password = true\
1774 end\
1775 if not next( self.metadata ) then\
1776 return content\
1777 end\
1778 local metadata = \"--[[\" .. textutils.serialize( self.metadata ):sub( 2, -2 ) .. \"]]\"\
1779 return metadata .. \"\" .. content\
1780end", meta={
1781 type = "class",
1782}};["AppInstance"]={content="\
1783\
1784AppInstance.public \"app\"\
1785AppInstance.public.app.write = false\
1786AppInstance.public \"session\"\
1787AppInstance.public.session.write = false\
1788AppInstance.public \"UI\"\
1789AppInstance.public.UI.write = false\
1790AppInstance.public \"events\"\
1791AppInstance.public.events.write = false\
1792AppInstance.public \"content\"\
1793AppInstance.public.content.write = false\
1794AppInstance.public \"sandbox\"\
1795AppInstance.public.sandbox.write = false\
1796AppInstance.public \"public_variables\"\
1797AppInstance.public.public_variables.write = false\
1798AppInstance.public \"title\" \"string\"\
1799\
1800function AppInstance:AppInstance( app, session, args, noGUI )\
1801 if not noGUI then\
1802 self.UI = session.UI:newChild( UIFrame( 1, 1, term.getSize( ) ) )\
1803 self.content = self.UI:newChild( UIFrame( 1, 2, self.UI.w, self.UI.h - 1 ) )\
1804 end\
1805 self.app = app\
1806 self.session = session\
1807 self.title = app.name\
1808 self.events = { }\
1809 self.sandbox = Sandbox( self.public, args )\
1810 self.threads = { }\
1811 self.public_variables = { }\
1812\
1813 if self.UI then\
1814 local nova = self.UI:newChild( UIButton( 1, 1, 4, 1, \"Nova\" ) )\
1815 nova.bc = colours.grey\
1816 nova.tc = colours.white\
1817 function nova.onClick( )\
1818 self.session:showOverlay( )\
1819 end\
1820\
1821 local title = self.UI:newChild( UIText( 5, 1, self.UI.w - 5, 1, function( )\
1822 return \" \" .. self.title\
1823 end ) )\
1824 title.bc = colours.grey\
1825 title.tc = colours.lightGrey\
1826\
1827 local close = self.UI:newChild( UIButton( self.UI.w, 1, 1, 1, \"x\" ) )\
1828 close.bc = colours.grey\
1829 close.tc = colours.cyan\
1830 function close.onClick( )\
1831 self.public:close \"user\"\
1832 end\
1833\
1834 local nextapp = self.UI:newChild( UIButton( self.UI.w - 2, 1, 1, 1, \"<\" ) )\
1835 nextapp.bc = colours.grey\
1836 nextapp.tc = colours.cyan\
1837 function nextapp.onClick( )\
1838 self.session:hide( )\
1839 self.session:show( )\
1840 end\
1841\
1842 if app.runmode == \"compatibility\" then\
1843 if app.conf.fullscreen then\
1844 self.canvas = self.UI:newChild( UICanvas( 1, 1, self.UI.w, self.UI.h ) )\
1845 else\
1846 self.canvas = self.content:newChild( UICanvas( 1, 1, self.content.w, self.content.h ) )\
1847 end\
1848 end\
1849 end\
1850\
1851 self.public:newThread( Thread( function( )\
1852 while true do\
1853 for i = #self.threads, 1, -1 do\
1854 if not self.threads[i]:isRunning( ) and not self.threads[i]:isPaused( ) then\
1855 self.threads[i]:stop( ) -- just to make sure it stops\
1856 table.remove( self.threads, i )\
1857 end\
1858 end\
1859 coroutine.yield( )\
1860 end\
1861 end ) )\
1862\
1863 if self.UI then\
1864 if app.runmode == \"compatibility\" then\
1865 local s = app:readFile( app.conf.filename or \"main.lua\" )\
1866 if s then\
1867 local f, err = loadstring( s, filesystem.getName( app.conf.filename or \"main.lua\", true ) )\
1868 if f then\
1869 self.canvas:setTask( function( ... )\
1870 setfenv( f, getfenv( ) )\
1871 local ok, err = pcall( f, ... )\
1872 if not ok and err ~= nil then\
1873 self.public:showError( err )\
1874 return\
1875 end\
1876 print( \"App has finished,\" .. \" press any key to exit.\" )\
1877 parallel.waitForAny( function( ) os.pullEvent \"key\" end, function( ) os.pullEvent \"mouse_click\" end )\
1878 self.public:close( )\
1879 end )\
1880 self.canvas:passEvent( unpack( args ) )\
1881 else\
1882 self.public:showError( err:gsub( \"AppInstance.lua:74: \", \"\" ) )\
1883 end\
1884 else\
1885 self.public:showError \"Could not load main file\"\
1886 end\
1887 else\
1888 self.sandbox.environment.Nova.app.require \"main\"\
1889 end\
1890 end\
1891\
1892 return self.public\
1893end\
1894\
1895function AppInstance.public:event( name, data )\
1896 if self.events[name] then\
1897 local ok, data = pcall( self.events[name], data )\
1898 if ok and ( data or data == nil ) then\
1899 return true\
1900 end\
1901 return false\
1902 end\
1903 return true\
1904end\
1905\
1906function AppInstance.public:showUI( )\
1907 self.public:event \"onFocusPre\"\
1908 if self.UI then\
1909 self.session.UI:newChild( self.UI )\
1910 end\
1911 self.public:event \"onFocusPost\"\
1912end\
1913\
1914function AppInstance.public:hideUI( )\
1915 self.public:event \"onUnFocusPre\"\
1916 if self.UI then\
1917 self.UI:remove( )\
1918 end\
1919 self.public:event \"onUnFocusPost\"\
1920end\
1921\
1922function AppInstance.public:close( reason )\
1923 if self.public:canClose( reason ) then\
1924 if self.public:event( \"onClose\", reason ) then\
1925 self.session:close( self.public )\
1926 for i = 1, #self.threads do\
1927 self.threads[i]:stop( )\
1928 end\
1929 if self.UI then\
1930 self.UI:remove( )\
1931 end\
1932 return true\
1933 end\
1934 end\
1935 return false\
1936end\
1937\
1938function AppInstance.public:newThread( thread )\
1939 table.insert( self.threads, thread )\
1940 thread.environment = self.sandbox.environment\
1941 function thread.onException( _, err )\
1942 if self.UI then\
1943 self.public:showError( err )\
1944 else\
1945 core.log( \"Thread error\", self.app.name, err )\
1946 end\
1947 end\
1948 return thread\
1949end\
1950\
1951function AppInstance.public:showError( err )\
1952 if self.UI then\
1953 local msg = self.UI:newChild( UIButton( 1, 2, self.UI.w, self.UI.h - 1, tostring( err ) .. \"\\nClick to close\" ) )\
1954 msg.tc = colours.red\
1955 function msg.onClick( )\
1956 self.public:close( )\
1957 end\
1958 end\
1959end\
1960\
1961function AppInstance.public:canClose( reason )\
1962 local env = self.sandbox.environment\
1963 if type( env.Nova ) == \"table\" and type( env.Nova.app ) == \"table\" and type( env.Nova.app.canClose ) == \"function\" then\
1964 local ok, data = pcall( env.Nova.app.canClose, reason )\
1965 if not ok or data or data == nil then\
1966 return true\
1967 end\
1968 return false\
1969 end\
1970 return true\
1971end", meta={
1972 type = "class",
1973}};["Archive"]={content="\
1974\
1975require \"Drive\"\
1976\
1977local rfs = fs\
1978\
1979function Archive:Archive( savepath )\
1980 self.files = { }\
1981 self.savepath = savepath\
1982\
1983 return self.public\
1984end\
1985\
1986function Archive.public:loadDirectory( path )\
1987 if not filesystem.isDirectory( path ) then\
1988 return false, \"not a directory\"\
1989 end\
1990 local function loadDirectory( path, t )\
1991 local files = filesystem.listFiles( path )\
1992 for i = 1, #files do\
1993 if filesystem.isDirectory( path .. \"/\" .. files[i] ) then\
1994 t[files[i]] = { }\
1995 loadDirectory( path .. \"/\" .. files[i], t[files[i]] )\
1996 else\
1997 local filedata, err = filesystem.readfile( path .. \"/\" .. files[i] )\
1998 if filedata then\
1999 t[files[i]] = filedata:compile( )\
2000 end\
2001 end\
2002 end\
2003 end\
2004 self.files = { }\
2005 loadDirectory( path, self.files )\
2006end\
2007\
2008function Archive.public:loadFile( path, password )\
2009 if not filesystem.isFile( path ) then\
2010 return false, \"not a file\"\
2011 end\
2012 local filedata, err = filesystem.readfile( path, password )\
2013 if not filedata then\
2014 return false, err\
2015 end\
2016 if filedata.meta.password then\
2017 return false, \"password protected\"\
2018 end\
2019 local files = textutils.unserialize( filedata.content )\
2020 if type( files ) == \"table\" then\
2021 self.files = files\
2022 return true\
2023 end\
2024 return false, \"corrupted archive\"\
2025end\
2026\
2027function Archive.public:saveDirectory( path )\
2028 if filesystem.isFile( path ) then\
2029 return false, \"path exists\"\
2030 elseif not filesystem.exists( path ) then\
2031 filesystem.newDirectory( path )\
2032 end\
2033 local function saveFile( file, p )\
2034 if type( file ) == \"table\" then\
2035 for k, v in pairs( file ) do\
2036 saveFile( v, p .. \"/\" .. k )\
2037 end\
2038 else\
2039 filesystem.writefile( path .. p, FileData( file ) )\
2040 end\
2041 end\
2042 saveFile( self.files, \"\" )\
2043 return true\
2044end\
2045\
2046function Archive.public:saveFile( path, password )\
2047 if filesystem.exists( path ) then\
2048 return false, \"file exists\"\
2049 end\
2050 local content = \"--[[type=\\\"archive\\\"]]\" .. textutils.serialize( self.files )\
2051 local filedata = FileData( content )\
2052 if password then\
2053 filedata.password = password\
2054 end\
2055 return filesystem.writefile( path, filedata )\
2056end\
2057\
2058function Archive.public:createDrive( )\
2059 \
2060 local fs = { }\
2061 local function splitPath( path )\
2062 local parts = { }\
2063 for part in path:gmatch \"([^/]+)\" do\
2064 table.insert( parts, part )\
2065 end\
2066 return parts\
2067 end\
2068 local function getDir( path )\
2069 if type( path ) ~= \"string\" then\
2070 return false, \"string expected\"\
2071 end\
2072 if path == \"\" or path == \"/\" then\
2073 return true, { [\"\"] = self.files }, \"\"\
2074 end\
2075 local parts = splitPath( path )\
2076 local f = self.files\
2077 for i = 1, #parts - 1 do\
2078 if type( f[parts[i]] ) == \"nil\" then\
2079 f[parts[i]] = { }\
2080 end\
2081 if type( f[parts[i]] ) == \"table\" then\
2082 f = f[parts[i]]\
2083 else\
2084 return false, \"invalid path\"\
2085 end\
2086 end\
2087 return true, f, parts[#parts]\
2088 end\
2089\
2090 function fs.open( path, mode )\
2091 local ok, data, file = getDir( path )\
2092 if not ok then\
2093 return false, data\
2094 end\
2095 if type( data[file] ) == \"table\" then\
2096 return false, \"cannot open directory\"\
2097 elseif mode == \"r\" and data[file] == nil then\
2098 return false, \"file doesn't exist\"\
2099 end\
2100 local handle = { }\
2101 local open = true\
2102 function handle.close( )\
2103 open = false\
2104 end\
2105\
2106 if mode == \"w\" then\
2107 data[file] = \"\"\
2108 elseif mode == \"a\" then\
2109 data[file] = data[file] or \"\"\
2110 elseif mode ~= \"r\" then\
2111 return false, \"unsupported mode\"\
2112 end\
2113 local filedata = FileData( data[file] )\
2114\
2115 if mode == \"w\" or mode == \"a\" then\
2116 function handle.write( str )\
2117 if not open then return end\
2118 filedata.content = filedata.content .. tostring( str )\
2119 end\
2120 function handle.writeLine( )\
2121 if not open then return end\
2122 filedata.content = filedata.content .. tostring( str ) .. \"\\n\"\
2123 end\
2124 function handle.flush( )\
2125 if not open then return end\
2126 data[file] = filedata:compile( )\
2127 if self.savepath then\
2128 filesystem.delete( self.savepath )\
2129 self.public:saveFile( self.savepath )\
2130 end\
2131 end\
2132 function handle.close( )\
2133 if not open then return end\
2134 handle.flush( )\
2135 open = false\
2136 end\
2137 elseif mode == \"r\" then\
2138 local lcontent = filedata.content\
2139 function handle.readLine( )\
2140 if not open then return end\
2141 local line = lcontent:match \"(.-)\\n\"\
2142 if line then\
2143 lcontent = lcontent:gsub( \".-\\n\", \"\" )\
2144 else\
2145 if #lcontent > 0 then\
2146 line = lcontent\
2147 lcontent = \"\"\
2148 else\
2149 line = nil\
2150 end\
2151 end\
2152 return line\
2153 end\
2154 function handle.readAll( )\
2155 if not open then return end\
2156 return filedata.content\
2157 end\
2158 end\
2159\
2160 return handle\
2161 end\
2162\
2163 function fs.list( path )\
2164 local ok, data, file = getDir( path )\
2165 if not ok then\
2166 return false, data\
2167 end\
2168 if type( data[file] ) ~= \"table\" then\
2169 return false, \"not a directory\"\
2170 end\
2171 local t = { }\
2172 for k, v in pairs( data[file] ) do\
2173 t[#t+1] = k\
2174 end\
2175 return t\
2176 end\
2177 function fs.exists( p )\
2178 local ok, data, file = getDir( p )\
2179 if not ok then\
2180 return false, data\
2181 end\
2182 return data[file] ~= nil\
2183 end\
2184 function fs.isDir( p )\
2185 local ok, data, file = getDir( p )\
2186 if not ok then\
2187 return false, data\
2188 end\
2189 return type( data[file] ) == \"table\"\
2190 end\
2191 function fs.isReadOnly( p )\
2192 return false\
2193 end\
2194 function fs.getDrive( )\
2195 return \"archive\"\
2196 end\
2197 function fs.getSize( path )\
2198 local ok, data, file = getDir( path )\
2199 if not ok then\
2200 return false, data\
2201 end\
2202 if not data[file] then\
2203 return false, \"file doesn't exist\"\
2204 end\
2205 if type( data[file] ) == \"table\" then\
2206 local n = 0\
2207 for k, v in pairs( data[file] ) do\
2208 n = n + fs.getSize( path .. \"/\" .. k )\
2209 end\
2210 return n\
2211 end\
2212 return #data[file] + #path -- each character is a byte right? 2^8 (a byte) = 256, ascii is 256 characters\
2213 end\
2214 function fs.getFreeSpace( )\
2215 return math.huge\
2216 end\
2217 function fs.makeDir( path )\
2218 local ok, data, file = getDir( path )\
2219 if not ok then\
2220 return false, data\
2221 end\
2222 if type( data[file] ) == \"string\" then\
2223 return false, \"path is a file\"\
2224 end\
2225 data[file] = data[file] or { }\
2226 if self.savepath then\
2227 filesystem.delete( self.savepath )\
2228 self.public:saveFile( self.savepath )\
2229 end\
2230 return true\
2231 end\
2232 function fs.move( path1, path2 )\
2233 local ok, data, file = getDir( path )\
2234 if not ok then\
2235 return false, data\
2236 end\
2237 local ok, data2, file2 = getDir( path )\
2238 if not ok then\
2239 return false, data2\
2240 end\
2241 if data[file] then\
2242 data2[file2] = data[file]\
2243 data[file] = nil\
2244 end\
2245 if self.savepath then\
2246 filesystem.delete( self.savepath )\
2247 self.public:saveFile( self.savepath )\
2248 end\
2249 return true\
2250 end\
2251 function fs.copy( path1, path2 )\
2252 local ok, data, file = getDir( path )\
2253 if not ok then\
2254 return false, data\
2255 end\
2256 local ok, data2, file2 = getDir( path )\
2257 if not ok then\
2258 return false, data2\
2259 end\
2260 if data[file] then\
2261 data2[file2] = data[file]\
2262 end\
2263 if self.savepath then\
2264 filesystem.delete( self.savepath )\
2265 self.public:saveFile( self.savepath )\
2266 end\
2267 return true\
2268 end\
2269 function fs.delete( path )\
2270 local ok, data, file = getDir( path )\
2271 if not ok then\
2272 return false, data\
2273 end\
2274 data[file] = nil\
2275 if self.savepath then\
2276 filesystem.delete( self.savepath )\
2277 self.public:saveFile( self.savepath )\
2278 end\
2279 return true\
2280 end\
2281 function fs.find( path )\
2282 error( \"not supported\", 2 )\
2283 end\
2284 fs.combine = rfs.combine\
2285 fs.getDir = rfs.getDir\
2286 fs.getName = rfs.getName\
2287\
2288 return Drive( fs )\
2289end", meta={
2290 type = "class",
2291}};["markup"]={content="\
2292\
2293require \"parser\"\
2294\
2295local function loadStandardAttributes( class, attributes, scripts, ids, errors, parent )\
2296 local x = type( attributes.x ) == \"number\" and attributes.x or 1\
2297 local y = type( attributes.y ) == \"number\" and attributes.y or 1\
2298 local w = type( attributes.width ) == \"number\" and attributes.width or 19\
2299 local h = type( attributes.height ) == \"number\" and attributes.height or 1\
2300 if not attributes.x or not attributes.y then\
2301 local children = parent:getChildren( )\
2302 if children[#children] then\
2303 if attributes.float == \"below\" then\
2304 x = children[#children].x\
2305 y = children[#children].y + children[#children].h\
2306 else\
2307 x = children[#children].x + children[#children].w\
2308 y = children[#children].y\
2309 end\
2310 end\
2311 end\
2312 if attributes.width == \"expand\" then\
2313 w = parent.w - x + 1\
2314 end\
2315 if attributes.height == \"expand\" then\
2316 h = parent.h - y + 1\
2317 end\
2318 if attributes.x == \"right\" then\
2319 x = parent.w - w + 1\
2320 end\
2321 if attributes.y == \"bottom\" then\
2322 y = parent.h - h + 1\
2323 end\
2324 if not ( attributes.x or attributes.y ) and ( attributes.float == \"right\" or not attributes.float ) and x + w > parent.w then\
2325 local c = parent:getChildren( )\
2326 if c[#c] then\
2327 x = c[#c].x\
2328 y = c[#c].y + c[#c].h\
2329 end\
2330 end\
2331 local ob = parent:newChild( class( x, y, w, h ) )\
2332 if attributes.x == \"centre\" then\
2333 ob:centreX( )\
2334 end\
2335 if attributes.y == \"centre\" then\
2336 ob:centreY( )\
2337 end\
2338 if attributes.id then\
2339 ids[attributes.id] = ob\
2340 end\
2341 if attributes.onFocus then\
2342 local f, err = loadstring( attributes.onFocus, \"onFocus\" )\
2343 if f then\
2344 ob.whenFocussed = f\
2345 table.insert( scripts, { script = f } )\
2346 else\
2347 table.insert( errors, err )\
2348 end\
2349 end\
2350 if attributes.onUnFocus then\
2351 local f, err = loadstring( attributes.onUnFocus, \"onUnFocus\" )\
2352 if f then\
2353 ob.whenUnFocussed = f\
2354 table.insert( scripts, { script = f } )\
2355 else\
2356 table.insert( errors, err )\
2357 end\
2358 end\
2359 if type( attributes.align ) == \"string\" then\
2360 local children = parent:getChildren( )\
2361 if #children > 1 then\
2362 ob:alignTo( attributes.align, children[#children-1], tonumber( attributes.spacing ) )\
2363 ob.w = w\
2364 ob.h = h\
2365 end\
2366 end\
2367 return ob\
2368end\
2369\
2370local function loadColours( ob, attributes )\
2371 local bc = type( attributes.bc ) == \"number\" and attributes.bc\
2372 local tc = type( attributes.tc ) == \"number\" and attributes.tc\
2373 if colours[attributes.bc] then bc = colours[attributes.bc] end\
2374 if attributes.bc == \"transparent\" then bc = 0 end\
2375 if colours[attributes.tc] then tc = colours[attributes.tc] end\
2376 if attributes.tc == \"transparent\" then tc = 0 end\
2377 if bc then\
2378 ob.bc = bc\
2379 end\
2380 if tc then\
2381 ob.tc = tc\
2382 end\
2383end\
2384\
2385parser.registerTag( \"text\", true, false, function( parent, attributes, content, scripts, ids, errors )\
2386 if attributes.width == \"auto\" or not attributes.width then\
2387 attributes.width = #content\
2388 end\
2389 local ob = loadStandardAttributes( UIText, attributes, scripts, ids, errors, parent )\
2390 loadColours( ob, attributes )\
2391 ob.text = content\
2392 return ob\
2393end, nil, nil )\
2394\
2395parser.registerTag( \"frame\", true, true, function( parent, attributes, content, scripts, ids, errors )\
2396 local ob = loadStandardAttributes( UIFrame, attributes, scripts, ids, errors, parent )\
2397 return ob\
2398end, nil, nil )\
2399\
2400parser.registerTag( \"button\", true, false, function( parent, attributes, content, scripts, ids, errors )\
2401 if attributes.width == \"auto\" or not attributes.width then\
2402 attributes.width = #content\
2403 end\
2404 local ob = loadStandardAttributes( UIButton, attributes, scripts, ids, errors, parent )\
2405 loadColours( ob, attributes )\
2406 ob.text = content\
2407 if attributes.onClick then\
2408 local f, err = loadstring( attributes.onClick, \"onClick\" )\
2409 if f then\
2410 ob.onClick = f\
2411 table.insert( scripts, { script = f } )\
2412 else\
2413 table.insert( errors, err )\
2414 end\
2415 end\
2416 return ob\
2417end, nil, nil )\
2418\
2419parser.registerTag( \"input\", false, false, function( parent, attributes, content, scripts, ids, errors )\
2420 local ob = loadStandardAttributes( UIInput, attributes, scripts, ids, errors, parent )\
2421 loadColours( ob, attributes )\
2422 local fbc = type( attributes.fbc ) == \"number\" and attributes.fbc\
2423 if colours[attributes.fbc] then fbc = colours[attributes.fbc] end\
2424 if attributes.fbc == \"transparent\" then fbc = 0 end\
2425 if fbc then\
2426 ob.fbc = fbc\
2427 end\
2428 if attributes.mask then\
2429 ob.mask = tostring( attributes.mask )\
2430 end\
2431 return ob\
2432end, nil, nil )\
2433\
2434parser.registerTag( \"image\", false, false, function( parent, attributes, content, scripts, ids, errors )\
2435 local ob = loadStandardAttributes( UIImage, attributes, scripts, ids, errors, parent )\
2436 if attributes.source then\
2437 local source = tostring( attributes.source )\
2438 local h = fs.open( source, \"r\" )\
2439 if h then\
2440 local content = h.readAll( )\
2441 h.close( )\
2442 ob.image = Image( )\
2443 ob.image:loadstr( content )\
2444 ob.w, ob.h = ob.image:getSize( )\
2445 else\
2446 ob:remove( )\
2447 ob = UIButton( ob.x, ob.y, ob.w, ob.h, attributes.alt or \"could not load image\" )\
2448 ob.tc = colours.lightGrey\
2449 end\
2450 else\
2451 ob.image = Image( ob.w, ob.h )\
2452 ob.image:foreach( function( )\
2453 return colours.white, 1, \" \"\
2454 end )\
2455 end\
2456 return ob\
2457end, nil, nil )\
2458\
2459function load( str, frame, penv )\
2460 local body, scripts, ids, errors = parser.load( str, frame or UIFrame( 1, 1, term.getSize( ) ) )\
2461\
2462 ids.body = body\
2463\
2464 local env = { }\
2465 env.document = { }\
2466 local meta = { __index = penv or { } }\
2467 if penv then meta.__newindex = penv end\
2468 setmetatable( env, meta )\
2469\
2470 function env.document.getElementById( id )\
2471 return ids[id]\
2472 end\
2473 function env.document.addContent( str, id )\
2474 if not id then id = \"body\" end\
2475 if ids[id] then\
2476 local _, errs = load( str, ids[id], penv )\
2477 for i = 1, #errs do\
2478 table.insert( errors, errs[i] )\
2479 end\
2480 return true\
2481 else\
2482 return false, \"no such id\"\
2483 end\
2484 end\
2485\
2486 for i = 1,#scripts do\
2487 setfenv( scripts[i].script, env )\
2488 if scripts[i].run then\
2489 scripts[i].script( )\
2490 end\
2491 end\
2492\
2493 return frame, errors, env.document\
2494end", meta={
2495 type = "lib",
2496}};["UIText"]={content="\
2497\
2498require \"UIElement\"\
2499\
2500UIText:extends( UIElement )\
2501\
2502UIText.text = \"\"\
2503UIText.public \"text\"\
2504\
2505UIText.public \"bc\" \"number\"\
2506UIText.public \"tc\" \"number\"\
2507\
2508function UIText:UIText( x, y, w, h, text )\
2509 self:UIElement( x, y, w, h )\
2510 self.text = text\
2511 self.bc = colours.white\
2512 self.tc = colours.grey\
2513 return self.public\
2514end\
2515\
2516function UIText.public:draw( x, y )\
2517 local layer = stencil.addLayer( x, y, self.w, self.h )\
2518 local text = tostring( self.text )\
2519 if type( self.text ) == \"function\" then\
2520 text = self.text( self.public )\
2521 end\
2522 stencil.text( x, y, self.w, self.h, self.bc, self.tc, text )\
2523 local c = { }\
2524 for i, child in ipairs( self.children ) do\
2525 c[i] = child\
2526 end\
2527 for i, child in ipairs( c ) do\
2528 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
2529 end\
2530 stencil.closeLayer( layer )\
2531end", meta={
2532 type = "class",
2533}};["network"]={content="\
2534\
2535require \"com\"\
2536require \"handshake\"\
2537\
2538local buffers = {\
2539 established = { }; -- .listen()\
2540}\
2541local channels = { }\
2542local opening = false\
2543local channelID = 0\
2544\
2545function genkey( seed )\
2546 math.randomseed( seed )\
2547 local s = \"\"\
2548 for i = 1, 64 do\
2549 s = s .. string.char( math.random( 0, 255 ) )\
2550 end\
2551 return s\
2552end\
2553\
2554function open( peer, protocol, insecure ) -- opens a secure channel with peer with [protocol]\
2555 if peer == os.getComputerID( ) then\
2556 local key = math.random( 1, 2^16 )\
2557 local t = { }\
2558 if insecure then\
2559 channels[t] = { peer = peer }\
2560 else\
2561 channels[t] = { peer = peer, key = genkey( key ), rawkey = key }\
2562 end\
2563 table.insert( buffers.established, { protocol = protocol, id = t } )\
2564 return t\
2565 end\
2566 local ok, err = com.send( peer, {\
2567 request = \"connection\";\
2568 protocol = type( protocol ) == \"string\" and protocol or \"none\";\
2569 handshake = handshake.generateInitiatorData( );\
2570 insecure = insecure\
2571 } )\
2572 if not ok then return false, err end\
2573 local ok, message = com.receive( peer, nil, 1 )\
2574 if ok then\
2575 local key = handshake.generateResponseData( message )\
2576 local t = { }\
2577 if insecure then\
2578 channels[t] = { peer = peer }\
2579 else\
2580 channels[t] = { peer = peer, key = genkey( key ), rawkey = key }\
2581 end\
2582 if insecure then\
2583 core.log( \"Network\", \"Insecure connection established with \" .. tostring( peer ) )\
2584 else\
2585 core.log( \"Network\", \"Secure connection established with \" .. tostring( peer ) )\
2586 end\
2587 return t\
2588 end\
2589 return false, message\
2590end\
2591\
2592function close( channel, reason ) -- closes an open channel with [reason]\
2593 if not channels[channel] then\
2594 return false, \"channel not open\"\
2595 end\
2596 local ok, data = com.send( channels[channel].peer, {\
2597 request = \"ConnectionClose\";\
2598 reason = type( reason ) == \"string\" and reason or \"unknown\";\
2599 }, channels[channel].key, channels[channel].rawkey )\
2600 if ok then\
2601 channels[channel] = nil\
2602 end\
2603 return ok, data\
2604end\
2605\
2606function send( channel, data ) -- sends a message over an open channel\
2607 if not channels[channel] then\
2608 return false, \"channel not open\"\
2609 end\
2610 return com.send( channels[channel].peer, data, channels[channel].key, channels[channel].rawkey )\
2611end\
2612\
2613function receive( channel, timeout ) -- waits for messages on an open channel for [timeout] seconds\
2614 if not channels[channel] then\
2615 return false, \"channel not open\"\
2616 end\
2617 local ok, data = com.receive( channels[channel].peer, channels[channel].key, timeout, channels[channel].rawkey )\
2618 if not ok then\
2619 return false, data\
2620 end\
2621 if type( data ) == \"table\" and data.request == \"ConnectionClose\" then\
2622 return false, \"closed\", data.reason\
2623 end\
2624 return true, data\
2625end\
2626\
2627function listen( protocol, timeout ) -- waits for a channel to be opened with [protocol] for [timeout] seconds\
2628 local time = os.clock( )\
2629 while not timeout or os.clock( ) - time <= timeout do\
2630 for i = #buffers.established, 1, -1 do\
2631 local channel = buffers.established[i]\
2632 if channel.protocol == protocol then\
2633 table.remove( buffers.established, i )\
2634 return channel.id\
2635 end\
2636 end\
2637 coroutine.yield( )\
2638 end\
2639 return false\
2640end\
2641\
2642-- internal functions\
2643\
2644function addToBuffer( buffer, data )\
2645 buffers[buffer] = buffers[buffer] or { }\
2646 table.insert( buffers[buffer], data )\
2647end\
2648\
2649function addChannel( peer, protocol, key, rawkey )\
2650 local t = { }\
2651 channels[t] = {\
2652 peer = peer;\
2653 key = key;\
2654 rawkey = rawkey;\
2655 }\
2656 addToBuffer( \"established\", { protocol = protocol, id = t } )\
2657end\
2658\
2659function clearBuffers( )\
2660 for key in pairs( buffers ) do\
2661 buffers[key] = { }\
2662 end\
2663end", meta={
2664 type = "lib",
2665}};["MarkupParagraph"]={content="\
2666\
2667require \"MarkupGenericObject\"\
2668\
2669MarkupParagraph:extends( MarkupGenericObject )\
2670\
2671MarkupParagraph.public.etype = \"paragraph\"\
2672\
2673function MarkupParagraph:MarkupParagraph( ... )\
2674 self:MarkupGenericObject( ... )\
2675\
2676 self.innerNML = { }\
2677\
2678 return self.public\
2679end\
2680\
2681function MarkupParagraph.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
2682 if MarkupGenericObject.attributes[attribute] then\
2683 MarkupGenericObject.attributes[attribute]( self, value )\
2684 end\
2685 if attribute == \"tc\" or attribute == \"textColour\" then\
2686 if colours[value] or value == \"transparent\" then\
2687 self.tc = colours[value] or 0\
2688 end\
2689 end\
2690 if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
2691 if colours[value] or value == \"transparent\" then\
2692 self.bc = colours[value] or 0\
2693 end\
2694 end\
2695 if attribute == \"align\" then\
2696 if value == \"left\" or value == \"right\" or value == \"centre\" then\
2697 self.align = value\
2698 end\
2699 end\
2700 if attribute == \"innerNML\" then\
2701 self.innerNML = self.page:loadAST( value )\
2702 end\
2703 self.page.needsReposition = true\
2704end\
2705\
2706function MarkupParagraph.public:loadObject( positionHandler )\
2707\
2708 self.page:newStyle {\
2709 bc = self.bc;\
2710 tc = self.tc;\
2711 align = self.align;\
2712 }\
2713 for i = #self.innerNML, 1, -1 do\
2714 if self.innerNML[i].remove then\
2715 table.remove( self.objects, i )\
2716 end\
2717 end\
2718 local objects = { }\
2719 if self.width.type == \"text\" and self.width.value == \"auto\" and self.height.type == \"text\" and self.height.value == \"auto\" then\
2720 for i = 1, #self.innerNML do\
2721 local o = positionHandler:newObject( self.innerNML[i], self.page )\
2722 for i = 1, #o do\
2723 table.insert( objects, o[i] )\
2724 end\
2725 end\
2726 local ob = MarkupLoadedObject( )\
2727 ob:initialise( UIText, \"\" )\
2728 ob.x = positionHandler.x\
2729 ob.y = positionHandler.y\
2730 table.insert( objects, ob )\
2731 else\
2732 local w, h = 0, 0\
2733 if self.width.type == \"text\" and self.width.value == \"auto\" then\
2734 w = positionHandler.w - positionHandler.x + 1\
2735 elseif self.width.type == \"percent\" then\
2736 w = self.width.value / 100 * positionHandler.w\
2737 elseif self.width.type == \"pixel\" then\
2738 w = self.width.value\
2739 end\
2740 if self.height.type == \"text\" and self.height.value == \"auto\" then\
2741 h = positionHandler.h - positionHandler.y + 1\
2742 elseif self.height.type == \"percent\" then\
2743 h = self.height.value / 100 * positionHandler.h\
2744 elseif self.height.type == \"pixel\" then\
2745 h = self.height.value\
2746 end\
2747 local pos = MarkupPositionHandler( w, h )\
2748 for i = 1, #self.innerNML do\
2749 local o = pos:newObject( self.innerNML[i], self.page )\
2750 for i = 1, #o do\
2751 table.insert( objects, o[i] )\
2752 end\
2753 end\
2754 if self.height.type == \"text\" and self.height.value == \"auto\" then\
2755 h = 0\
2756 for i = 1, #objects do\
2757 if objects[i].y + objects[i].content.height - 1 > h then\
2758 h = objects[i].y + objects[i].content.height - 1\
2759 end\
2760 end\
2761 end\
2762 local rw, rh = w, h\
2763 local scrollbarv, scrollbarh = false, false\
2764 for i = 1, #objects do\
2765 if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
2766 w = w - 1\
2767 end\
2768 if objects[i].x + objects[i].content.width - 1 > w and not scrollbarh then\
2769 h = h - 1\
2770 scrollbarh = true\
2771 end\
2772 if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
2773 scrollbarv = true\
2774 end\
2775 if scrollbarv and scrollbarh then\
2776 break\
2777 end\
2778 end\
2779 local loadedObject = MarkupLoadedObject( )\
2780 loadedObject.x = positionHandler.x\
2781 loadedObject.y = positionHandler.y\
2782 loadedObject.content.width = rw\
2783 loadedObject.content.height = rh\
2784 loadedObject:initialise( UIFrame )\
2785 local f = loadedObject.element\
2786 f:newChild( UIText( 1, 1, rw, rh, \"\" ) ).bc = self.page:getStyle \"bc\"\
2787 local cf\
2788 if not scrollbarv and not scrollbarh then\
2789 cf = f\
2790 else\
2791 cf = f:newChild( UIFrame( 1, 1, w, h ) )\
2792 end\
2793 for i = 1, #objects do\
2794 cf:newChild( objects[i].element )\
2795 end\
2796 if scrollbarv then\
2797 f:newChild( UIScrollBar( rw, 1, 1, h, cf ) )\
2798 end\
2799 if scrollbarh then\
2800 f:newChild( UIScrollBar( 1, rh, rw, 1, cf, \"horizontal\" ) )\
2801 end\
2802 objects = { loadedObject }\
2803 end\
2804\
2805 self.page:removeStyle( )\
2806 return objects\
2807 -- generate a list of MarkupLoadedObjects\
2808end", meta={
2809 type = "class",
2810}};["DocumentEnvironment"]={content="\
2811\
2812function DocumentEnvironment:DocumentEnvironment( page, rawpage )\
2813\
2814 local env = { }\
2815 env._G = env\
2816\
2817 env._VERSION = _VERSION;\
2818 env.pairs = pairs;\
2819 env.ipairs = ipairs;\
2820 env.select = select;\
2821 env.unpack = unpack;\
2822 env.setfenv = setfenv;\
2823 env.getfenv = getfenv;\
2824 env.setmetatable = setmetatable;\
2825 env.getmetatable = getmetatable;\
2826 env.next = next;\
2827 env.rawset = rawset;\
2828 env.rawget = rawget;\
2829 env.rawequal = rawequal;\
2830 env.type = type;\
2831 env.tostring = tostring;\
2832 env.tonumber = tonumber;\
2833 env.pcall = pcall;\
2834 env.xpcall = xpcall;\
2835 env.loadstring = loadstring;\
2836 env.assert = assert;\
2837 env.error = error;\
2838 env.sleep = function( n )\
2839 local t = os.clock()\
2840 coroutine.yield()\
2841 while os.clock() - t < n do\
2842 coroutine.yield()\
2843 end\
2844 end;\
2845 env.__inext = __inext;\
2846\
2847 env.math = math;\
2848 env.string = string;\
2849 env.table = table;\
2850 env.coroutine = coroutine;\
2851 env.sha256 = sha256;\
2852 env.encryption = encryption;\
2853 env.stringutils = stringutils;\
2854 env.keys = keys;\
2855 env.colours = colours;\
2856 env.colors = colors;\
2857 env.vector = vector;\
2858 env.bit = bit;\
2859 env.http = http;\
2860 env.textutils = textutils;\
2861 env.rednet = rednet;\
2862 env.os = {\
2863 clock = os.clock;\
2864 time = os.time;\
2865 sleep = function( n )\
2866 env.sleep( n )\
2867 end;\
2868 }\
2869\
2870 env.class = class;\
2871\
2872 env.clipboard = clipboard\
2873\
2874 env.document = { }\
2875 function env.document.getElementById( id )\
2876 if page.ids[id] then\
2877 local s = page.ids[id]:scriptObject( )\
2878 rawpage.scriptelements[s] = page.ids[id]\
2879 return s\
2880 end\
2881 return false\
2882 end\
2883 function env.document.addObject( object, id )\
2884 if id and page.ids[id] then\
2885 if type( object ) == \"string\" or rawpage.scriptelements[object] then\
2886 if not page.ids[id]:addObject( rawpage.scriptelements[object] or object ) then\
2887 return error \"cannot add objects to this element\"\
2888 end\
2889 page.needsReposition = true\
2890 else\
2891 return error \"invalid object\"\
2892 end\
2893 else\
2894 if type( object ) == \"string\" or rawpage.scriptelements[object] then\
2895 table.insert( rawpage.objects, rawpage.scriptelements[object] or object )\
2896 page.needsReposition = true\
2897 else\
2898 return error \"invalid object\"\
2899 end\
2900 end\
2901 end\
2902 function env.document.createObject( name )\
2903 if nova_markup.objects[name] then\
2904 local ob = nova_markup.objects[name] \"script\"\
2905 ob.page = page\
2906 local rawob = ob:scriptObject( )\
2907 rawpage.scriptelements[rawob] = ob\
2908 return rawob\
2909 else\
2910 return error \"no such object\"\
2911 end\
2912 end\
2913 function env.document.removeObject( id )\
2914 if page.ids[id] then\
2915 page.ids[id].remove = true\
2916 page.needsReposition = true\
2917 return true\
2918 end\
2919 end\
2920\
2921 env.page = { }\
2922\
2923 env.randomColour = function( min, max )\
2924 local n = 2 ^ math.random( min or 0, max or 15 )\
2925 for name, col in pairs( colours ) do\
2926 if col == n then\
2927 return name\
2928 end\
2929 end\
2930 end\
2931\
2932 return env\
2933end", meta={
2934 type = "class",
2935}};["buffer"]={content="\
2936\
2937local screen = { }\
2938local last = { }\
2939local w, h = term.getSize( )\
2940local bgc = colours.white\
2941local cb\
2942\
2943for y = 1, h do\
2944 screen[y] = { }\
2945 last[y] = { }\
2946 for x = 1, w do\
2947 screen[y][x] = { bc = 1, tc = 32768, char = \" \" }\
2948 last[y][x] = { }\
2949 end\
2950end\
2951\
2952function setPixel( x, y, bc, tc, char )\
2953 if screen[y] and screen[y][x] then\
2954 if bc ~= 0 then\
2955 screen[y][x].bc = bc\
2956 end\
2957 if tc == 0 then\
2958 char = \"\"\
2959 else\
2960 screen[y][x].tc = tc\
2961 end\
2962 if char ~= \"\" then\
2963 screen[y][x].char = char\
2964 end\
2965 return true\
2966 end\
2967 return false\
2968end\
2969\
2970function getPixel( x, y )\
2971 if last[y] and last[y][x] then\
2972 return last[y][x].bc, last[y][x].tc, last[y][x].char\
2973 end\
2974 return false\
2975end\
2976\
2977function hasChanged( x, y )\
2978 if screen[y] and screen[y][x] then\
2979 return screen[y][x].bc ~= last[y][x].bc or screen[y][x].tc ~= last[y][x].tc or screen[y][x].char ~= last[y][x].char\
2980 end\
2981 return false\
2982end\
2983\
2984function setCursorBlink( x, y, col )\
2985 cb = { x = x, y = y, col = col }\
2986end\
2987\
2988function drawChanges( )\
2989 local lasts = last\
2990 for y = 1, h do\
2991 local last\
2992 for x = 1, w do\
2993 if hasChanged( x, y ) then\
2994 if last then\
2995 if last.bc == screen[y][x].bc and ( last.tc == screen[y][x].tc or screen[y][x].char == \" \" ) then\
2996 last.char = last.char .. screen[y][x].char\
2997 else\
2998 term.setCursorPos( last.x, last.y )\
2999 term.setBackgroundColour( last.bc )\
3000 term.setTextColour( last.tc )\
3001 term.write( last.char )\
3002 last = { x = x, y = y, bc = screen[y][x].bc, tc = screen[y][x].tc, char = screen[y][x].char }\
3003 end\
3004 else\
3005 last = { x = x, y = y, bc = screen[y][x].bc, tc = screen[y][x].tc, char = screen[y][x].char }\
3006 end\
3007 lasts[y][x] = { bc = screen[y][x].bc, tc = screen[y][x].tc, char = screen[y][x].char }\
3008 elseif last then\
3009 term.setCursorPos( last.x, last.y )\
3010 term.setBackgroundColour( last.bc )\
3011 term.setTextColour( last.tc )\
3012 term.write( last.char )\
3013 last = nil\
3014 end\
3015 end\
3016 if last then\
3017 term.setCursorPos( last.x, last.y )\
3018 term.setBackgroundColour( last.bc )\
3019 term.setTextColour( last.tc )\
3020 term.write( last.char )\
3021 end\
3022 end\
3023 if cb then\
3024 if cb.col then\
3025 term.setTextColour( cb.col )\
3026 end\
3027 term.setCursorPos( cb.x, cb.y )\
3028 term.setCursorBlink( true )\
3029 else\
3030 term.setCursorBlink( false )\
3031 end\
3032end\
3033\
3034function clear( )\
3035 for x = 1, w do\
3036 for y = 1, h do\
3037 screen[y][x].bc = bgc\
3038 screen[y][x].char = \" \"\
3039 end\
3040 end\
3041 cb = false\
3042end\
3043\
3044function setBackgroundColour( col )\
3045 bgc = col\
3046end\
3047\
3048function reset( )\
3049 for y = 1, h do\
3050 last[y] = { }\
3051 for x = 1, w do\
3052 last[y][x] = { }\
3053 end\
3054 end\
3055end", meta={
3056 type = "lib",
3057}};["sha256"]={content="\
3058-- Adaptation of the Secure Hashing Algorithm (SHA-244/256)\
3059-- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm\
3060-- Using an adapted version of the bit library\
3061-- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua\
3062-- Compressed using Lua Minifier (https://mothereff.in/lua-minifier)\
3063local a=2^32;local b=a-1;local function c(d)local mt={}local e=setmetatable({},mt)function mt:__index(f)local g=d(f)e[f]=g;return g end;return e end;local function h(e,i)local function j(k,l)local m,o=0,1;while k~=0 and l~=0 do local p,q=k%i,l%i;m=m+e[p][q]*o;k=(k-p)/i;l=(l-q)/i;o=o*i end;m=m+(k+l)*o;return m end;return j end;local function r(e)local s=h(e,2^1)local t=c(function(k)return c(function(l)return s(k,l)end)end)return h(t,2^e.n or 1)end;local u=r({[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0},n=4})local function v(k,l,w,...)local x=nil;if l then k=k%a;l=l%a;x=u(k,l)if w then x=v(x,w,...)end;return x elseif k then return k%a else return 0 end end;local function y(k,l,w,...)local x;if l then k=k%a;l=l%a;x=(k+l-u(k,l))/2;if w then x=bit32_band(x,w,...)end;return x elseif k then return k%a else return b end end;local function z(A)return(-1-A)%a end;local function B(k,C)if C<0 then return lshift(k,-C)end;return math.floor(k%2^32/2^C)end;local function D(A,C)if C>31 or C<-31 then return 0 end;return B(A%a,C)end;local function lshift(k,C)if C<0 then return D(k,-C)end;return k*2^C%2^32 end;local function E(A,C)A=A%a;C=C%32;local F=y(A,2^C-1)return D(A,C)+lshift(F,32-C)end;local f={0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2}local function G(H)return string.gsub(H,\".\",function(w)return string.format(\"%02x\",string.byte(w))end)end;local function I(J,n)local H=\"\"for K=1,n do local L=J%256;H=string.char(L)..H;J=(J-L)/256 end;return H end;local function M(H,K)local n=0;for K=K,K+3 do n=n*256+string.byte(H,K)end;return n end;local function N(O,len)local P=64-(len+9)%64;len=I(8*len,8)O=O..\"\\128\"..string.rep(\"\\0\",P)..len;assert(#O%64==0)return O end;local function Q(R)R[1]=0x6a09e667;R[2]=0xbb67ae85;R[3]=0x3c6ef372;R[4]=0xa54ff53a;R[5]=0x510e527f;R[6]=0x9b05688c;R[7]=0x1f83d9ab;R[8]=0x5be0cd19;return R end;local function S(O,K,R)local T={}for U=1,16 do T[U]=M(O,K+(U-1)*4)end;for U=17,64 do local g=T[U-15]local V=v(E(g,7),E(g,18),D(g,3))g=T[U-2]T[U]=T[U-16]+V+T[U-7]+v(E(g,17),E(g,19),D(g,10))end;local k,l,w,W,X,d,Y,Z=R[1],R[2],R[3],R[4],R[5],R[6],R[7],R[8]for K=1,64 do local V=v(E(k,2),E(k,13),E(k,22))local _=v(y(k,l),y(k,w),y(l,w))local a0=V+_;local a1=v(E(X,6),E(X,11),E(X,25))local a2=v(y(X,d),y(z(X),Y))local a3=Z+a1+a2+f[K]+T[K]Z,Y,d,X,W,w,l,k=Y,d,X,W+a3,w,l,k,a3+a0 end;R[1]=y(R[1]+k)R[2]=y(R[2]+l)R[3]=y(R[3]+w)R[4]=y(R[4]+W)R[5]=y(R[5]+X)R[6]=y(R[6]+d)R[7]=y(R[7]+Y)R[8]=y(R[8]+Z)end;local function a4(O)O=N(O,string.len(O))local R=Q({})for K=1,#O,64 do S(O,K,R)end;return G(I(R[1],4)..I(R[2],4)..I(R[3],4)..I(R[4],4)..I(R[5],4)..I(R[6],4)..I(R[7],4)..I(R[8],4))end;return a4", meta={
3064 type = "lib",
3065}};["parser"]={content="\
3066\
3067local function split( str, pat )\
3068 local sopened = false\
3069 local opened = false\
3070 local parts = { }\
3071 local last = 1\
3072 for i = 1,#str do\
3073 if str:sub( i, i ) == \"\\\"\" then\
3074 local count = 0\
3075 for ii = i - 1, 1, -1 do\
3076 if str:sub( ii, ii ) ~= \"\\\\\" then\
3077 break\
3078 end\
3079 count = count + 1\
3080 end\
3081 if math.floor( count / 2 ) == count / 2 then -- an even number of \\s therefore a string opener\
3082 if sopened and sopened == str:sub( i, i ) then\
3083 sopened = false\
3084 elseif not sopened then\
3085 sopened = str:sub( i, i )\
3086 end\
3087 end\
3088 elseif not sopened then\
3089 if str:sub( i, i + #pat - 1 ) == pat then\
3090 table.insert( parts, str:sub( last, i - 1 ) )\
3091 last = i + #pat\
3092 end\
3093 end\
3094 end\
3095 table.insert( parts, str:sub( last ) )\
3096 return parts\
3097end\
3098\
3099local function findTags( str )\
3100 local sopened = false\
3101 local opened = false\
3102 local tags = { }\
3103 for i = 1,#str do\
3104 if str:sub( i, i ) == \"\\\"\" then\
3105 local count = 0\
3106 for ii = i - 1, 1, -1 do\
3107 if str:sub( ii, ii ) ~= \"\\\\\" then\
3108 break\
3109 end\
3110 count = count + 1\
3111 end\
3112 if math.floor( count / 2 ) == count / 2 then -- an even number of \\s therefore a string opener\
3113 if sopened and sopened == str:sub( i, i ) then\
3114 sopened = false\
3115 elseif not sopened then\
3116 sopened = str:sub( i, i )\
3117 end\
3118 end\
3119 elseif not sopened then\
3120 if str:sub( i, i ) == \"<\" then\
3121 opened = i\
3122 elseif str:sub( i, i ) == \">\" then\
3123 table.insert( tags, { start = opened, finish = i, content = str:sub( opened + 1, i - 1 ) } )\
3124 end\
3125 end\
3126 end\
3127 return tags\
3128end\
3129\
3130local function readTags( tags )\
3131 for i = 1,#tags do\
3132 local c = tags[i].content\
3133 tags[i].content = nil\
3134 if c:sub( 1, 1 ) == \"/\" then\
3135 tags[i].type = \"close\"\
3136 tags[i].name = c:sub( 2 )\
3137 else\
3138 local parts = split( c, \" \" )\
3139 tags[i].type = \"open\"\
3140 tags[i].name = parts[1]\
3141 tags[i].attributes = { }\
3142 for ii = 2,#parts do\
3143 local s, f, name, value = parts[ii]:find( \"([%w_]-)[:=](.+)\" )\
3144 if s then\
3145 tags[i].attributes[name] = value\
3146 end\
3147 end\
3148 end\
3149 end\
3150 return tags\
3151end\
3152\
3153local function loadAttributes( at )\
3154 local vals = { }\
3155 for k, v in pairs( at ) do\
3156 if v:sub( 1, 1 ) == \"\\\"\" and v:sub( #v ) == \"\\\"\" then\
3157 local str = loadstring( \"return \" .. v, \"string\" )\
3158 if str then\
3159 vals[k] = str( )\
3160 end\
3161 else\
3162 if v == \"true\" or v == \"false\" then\
3163 vals[k] = v == \"true\"\
3164 elseif tonumber( v ) then\
3165 vals[k] = tonumber( v )\
3166 else\
3167 vals[k] = v\
3168 end\
3169 end\
3170 end\
3171 return vals\
3172end\
3173\
3174local _tags = {\
3175 [\"script\"] = {\
3176 content = true;\
3177 children = false;\
3178 whenloaded = function( parent, attributes, content, scripts, ids, errors )\
3179 local name = attributes.name or \"script\"\
3180 local f, err = loadstring( content, name )\
3181 if f then\
3182 table.insert( scripts, { run = true, script = f } )\
3183 else\
3184 table.insert( errors, err )\
3185 end\
3186 end;\
3187 };\
3188 [\"style\"] = {\
3189 content = true;\
3190 children = false;\
3191 whenloaded = function( parent, attributes, content, scripts, ids )\
3192 local s = Style( content )\
3193 s:save( attributes.name or \"default\" )\
3194 end;\
3195 };\
3196}\
3197\
3198function registerTag( name, content, children, whenloaded, parents, childrenallowed )\
3199 _tags[name] = { content = content, children = children, whenloaded = whenloaded, parents = parents, childrenallowed = childrenallowed }\
3200end\
3201\
3202local loadString\
3203\
3204local function groupTags( tags, str, parenttype )\
3205 local t = { }\
3206 local i = 1\
3207 while i <= #tags do\
3208 local name = tags[i].name\
3209 if _tags[name] then\
3210 if tags[i].type == \"open\" then\
3211 if _tags[name].parents then\
3212 local ok = false\
3213 for i = 1,#_tags[name].parents do\
3214 if _tags[name].parents[i] == parenttype then\
3215 ok = true\
3216 break\
3217 end\
3218 end\
3219 if not ok then\
3220 error( \"cannot have \" .. parenttype .. \" as a parent to \" .. name, 0 )\
3221 end\
3222 end\
3223 local tag = {\
3224 name = name;\
3225 attributes = loadAttributes( tags[i].attributes );\
3226 whenloaded = _tags[name].whenloaded;\
3227 }\
3228 if _tags[name].content then\
3229 local layer = 1\
3230 local found = false\
3231 for c = i + 1, #tags do\
3232 if tags[c].name == name then\
3233 if tags[c].type == \"open\" then\
3234 layer = layer + 1\
3235 else\
3236 layer = layer - 1\
3237 if layer == 0 then\
3238 found = c\
3239 break\
3240 end\
3241 end\
3242 end\
3243 end\
3244 if found then\
3245 tag.content = str:sub( tags[i].finish + 1, tags[found].start - 1 )\
3246 i = found\
3247 else\
3248 error( \"ending tag expected for \" .. name, 0 )\
3249 end\
3250 end\
3251 if tag.content and _tags[name].children then\
3252 tag.children = loadString( tag.content, tag.name )\
3253 tag.content = nil\
3254 if _tags[name].childrenallowed then\
3255 for i = 1,#tag.children do\
3256 local ok = false\
3257 for ii = 1,#_tags[name].childrenallowed do\
3258 if _tags[name].childrenallowed[ii] == tag.children[i].name then\
3259 ok = true\
3260 break\
3261 end\
3262 end\
3263 if not ok then\
3264 error( \"not allowed \" .. tag.children[i].name .. \" inside a \" .. tag.name, 0 )\
3265 end\
3266 end\
3267 end\
3268 end\
3269 table.insert( t, tag )\
3270 i = i + 1\
3271 else\
3272 error( \"expected opening tag before closing tag for \" .. name, 0 )\
3273 end\
3274 else\
3275 error( \"unknown tag: \" .. name, 0 )\
3276 end\
3277 end\
3278 return t\
3279end\
3280\
3281function loadString( str, parent )\
3282 return groupTags( readTags( findTags( str ) ), str, parent or \"frame\" )\
3283end\
3284\
3285local loadElements\
3286function loadElements( children, parent, scripts, ids, errors )\
3287 if not scripts then\
3288 scripts = { }\
3289 ids = { }\
3290 errors = { }\
3291 end\
3292 for i = 1,#children do\
3293 local child = children[i].whenloaded( parent, children[i].attributes, children[i].content, scripts, ids, errors )\
3294 if children[i].children then\
3295 loadElements( children[i].children, child, scripts, ids, errors )\
3296 end\
3297 end\
3298 return parent, scripts, ids, errors\
3299end\
3300\
3301function load( str, frame )\
3302 local tags = loadString( str )\
3303 return loadElements( tags, frame )\
3304end", meta={
3305 type = "lib",
3306}};["UITextbox"]={content="\
3307\
3308local function linewrap( str, w )\
3309 for i = 1, w + 1 do\
3310 if str:sub( i, i ) == \"\\n\" then\
3311 return str:sub( 1, i - 1 ), str:sub( i + 1 )\
3312 end\
3313 end\
3314 if #str <= w then\
3315 return str, \"\"\
3316 end\
3317 if str:sub( w + 1, w + 1 ):find \"%s\" then\
3318 return str:sub( 1, w ), str:sub( w + 1 ):gsub( \"^%s+\", \"\" )\
3319 end\
3320 for i = w, 1, -1 do\
3321 if str:sub( i, i ):find \"%s\" then\
3322 return str:sub( 1, i ), str:sub( i + 1 )\
3323 end\
3324 end\
3325 return str:sub( 1, w ), str:sub( w + 1 )\
3326end\
3327\
3328local function wordwrap( str, w, h )\
3329 local lines = { }\
3330 local line\
3331 while #str > 0 do\
3332 line, str = linewrap( str, w )\
3333 table.insert( lines, line )\
3334 end\
3335 if h then\
3336 while #lines > h do\
3337 lines[#lines] = nil\
3338 end\
3339 end\
3340 return lines\
3341end\
3342\
3343require \"UIElement\"\
3344UITextbox:extends( UIElement )\
3345\
3346UITextbox.handlesKeys = true\
3347UITextbox.tabIndex = true\
3348\
3349UITextbox.public \"text\"\
3350\
3351UITextbox.public \"onChange\" \"function\"\
3352function UITextbox.public.text:write( value )\
3353 local t = tostring( value )\
3354 t = t:sub( 1, self.w * self.h )\
3355 while true do\
3356 local lines = wordwrap( t, self.w )\
3357 if #lines <= self.h then\
3358 break\
3359 end\
3360 t = t:sub( 1, -2 )\
3361 end\
3362 self.text = t\
3363end\
3364\
3365UITextbox.bc = colours.lightGrey\
3366UITextbox.fbc = colours.white\
3367UITextbox.tc = colours.black\
3368\
3369UITextbox.public \"bc\" \"number\"\
3370UITextbox.public \"fbc\" \"number\"\
3371UITextbox.public \"tc\" \"number\"\
3372\
3373UITextbox.onEnter = false\
3374UITextbox.public \"onEnter\" \"function\"\
3375\
3376function UITextbox:UITextbox( x, y, w, h, text, session )\
3377 self:UIElement( x, y, w, h )\
3378\
3379 self.public.text = text\
3380 self.cursor = cursor\
3381\
3382 return self.public\
3383end\
3384\
3385function UITextbox:getCursorXY( )\
3386 local lines = wordwrap( self.text, self.w, self.h )\
3387 local c = self.cursor\
3388 for i = 1, #lines do\
3389 if #lines[i] <= c then\
3390 c = c - #lines[i]\
3391 else\
3392 return c, i\
3393 end\
3394 end\
3395 if #lines == 0 then\
3396 return 1, 1\
3397 elseif self.text:sub( -1, -1 ) == \"\\n\" then\
3398 return 1, #lines + 1\
3399 else\
3400 return #lines[#lines] + 1, #lines\
3401 end\
3402end\
3403\
3404function UITextbox:getCursorPos( x, y )\
3405 local lines = wordwrap( self.text, self.w, self.h )\
3406 if lines[y] then\
3407 local pos = 0\
3408 for i = 1, y - 1 do\
3409 pos = pos + #lines[i]\
3410 end\
3411 if #lines[y] >= x then\
3412 pos = pos + x\
3413 else\
3414 pos = pos + #lines[y] + 1\
3415 end\
3416 return pos\
3417 else\
3418 return #self.text + 1\
3419 end\
3420end\
3421\
3422function UITextbox.public:draw( x, y )\
3423 local layer = stencil.addLayer( x, y, self.w, self.h )\
3424 local text = tostring( self.text )\
3425 local bc = self.bc\
3426 if self.focussed then\
3427 bc = self.fbc\
3428 end\
3429 stencil.text( x, y, self.w, self.h, bc, self.tc, self.text )\
3430 if self.focussed then\
3431 local cx, cy = self:getCursorXY( )\
3432 stencil.setCursorBlink( x + cx - 1, y + cy - 1 )\
3433 end\
3434 local c = { }\
3435 for i, child in ipairs( self.children ) do\
3436 c[i] = child\
3437 end\
3438 for i, child in ipairs( c ) do\
3439 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
3440 end\
3441 stencil.closeLayer( layer )\
3442end\
3443\
3444function UITextbox.public:onMouseClick( rx, ry, button )\
3445 local pos = self:getCursorPos( rx, ry )\
3446 self.cursor = pos\
3447end\
3448\
3449function UITextbox.public:onKeyPress( key, lastkey )\
3450 if not self.focussed then\
3451 return\
3452 end\
3453 if key == keys.enter then\
3454 local c = self.cursor\
3455 local t = self.text\
3456 self.text = self.text:sub( 1, self.cursor - 1 ) .. \"\\n\" .. self.text:sub( self.cursor )\
3457 self.cursor = self.cursor + 1\
3458 if type( self.onChange ) == \"function\" then\
3459 self.onChange( self.public )\
3460 end\
3461 local lines = wordwrap( self.text, self.w )\
3462 if #lines > self.h then\
3463 self.text = t\
3464 self.cursor = c\
3465 end\
3466 elseif key == keys.left and self.cursor > 1 then\
3467 self.cursor = self.cursor - 1\
3468 elseif key == keys.right and self.cursor <= #self.text then\
3469 self.cursor = self.cursor + 1\
3470 elseif key == keys.backspace and self.cursor > 1 then\
3471 self.text = self.text:sub( 1, self.cursor - 2 ) .. self.text:sub( self.cursor )\
3472 self.cursor = self.cursor - 1\
3473 elseif key == keys.delete then\
3474 self.text = self.text:sub( 1, self.cursor - 1 ) .. self.text:sub( self.cursor + 1 )\
3475 end\
3476end\
3477\
3478function UITextbox.public:onTextInput( text, lastkey )\
3479 if not self.focussed then\
3480 return\
3481 end\
3482 if lastkey ~= keys.leftCtrl then\
3483 local c = self.cursor\
3484 local t = self.text\
3485 self.text = self.text:sub( 1, self.cursor - 1 ) .. text .. self.text:sub( self.cursor )\
3486 self.cursor = self.cursor + 1\
3487 if type( self.onChange ) == \"function\" then\
3488 self.onChange( self.public )\
3489 end\
3490 local lines = wordwrap( self.text, self.w )\
3491 if #lines > self.h then\
3492 self.text = t\
3493 self.cursor = c\
3494 end\
3495 end\
3496end\
3497\
3498function UITextbox.public:onFocus( )\
3499 self.focussed = true\
3500 self.handlesKeys = true\
3501 if type( self.whenFocussed ) == \"function\" then\
3502 self.whenFocussed( self.public )\
3503 end\
3504end\
3505\
3506function UITextbox.public:onUnFocus( )\
3507 self.focussed = false\
3508 self.handlesKeys = false\
3509 if type( self.whenUnFocussed ) == \"function\" then\
3510 self.whenUnFocussed( self.public )\
3511 end\
3512end\
3513\
3514function UITextbox.public:focusOn( )\
3515 local handler = self.public:getHandler( )\
3516 handler:setFocus( self.public )\
3517end", meta={
3518 type = "class",
3519}};["UIScrollBar"]={content="\
3520\
3521require \"UIElement\"\
3522\
3523UIScrollBar:extends( UIElement )\
3524\
3525UIScrollBar.public \"bc\" \"number\"\
3526UIScrollBar.public \"tc\" \"number\"\
3527\
3528UIScrollBar.handlesScroll = true\
3529\
3530function UIScrollBar:UIScrollBar( x, y, w, h, target, direction )\
3531 self:UIElement( x, y, w, h )\
3532\
3533 self.bc = colours.lightGrey\
3534 self.tc = colours.grey\
3535 self.direction = direction or \"vertical\"\
3536\
3537 if target then\
3538 if class.typeOf( target, UIElement ) then\
3539 if target.isScrollTarget then\
3540 self.target = target\
3541 else\
3542 error( \"cannot set \" .. target:type( ) .. \" as scroll target\", 3 )\
3543 end\
3544 else\
3545 error( \"expected UIElement target\", 3 )\
3546 end\
3547 end\
3548 return self.public\
3549end\
3550\
3551function UIScrollBar.public:setScrollTarget( target )\
3552 if class.typeOf( target, UIElement ) then\
3553 if target.isScrollTarget then\
3554 self.target = target\
3555 else\
3556 error( \"cannot set \" .. target:type( ) .. \" as scroll target\", 2 )\
3557 end\
3558 else\
3559 error( \"expected UIElement target\", 2 )\
3560 end\
3561end\
3562\
3563--[[\
3564 barsize/traysize = framesize/contentsize\
3565 barposition = traysize * contentscroll / contentsize\
3566]]\
3567\
3568function UIScrollBar:getBar( )\
3569 if not self.target then\
3570 return 0, self.direction == \"vertical\" and self.h or self.w\
3571 end\
3572 local traysize\
3573 if self.direction == \"vertical\" then\
3574 traysize = self.h\
3575 else\
3576 traysize = self.w\
3577 end\
3578 local framesize, contentsize, contentscroll\
3579 if self.direction == \"vertical\" then\
3580 framesize = self.target:getDisplaySizeV( )\
3581 contentsize = self.target:getContentSizeV( )\
3582 contentscroll = self.target:getContentScrollV( )\
3583 else\
3584 framesize = self.target:getDisplaySizeH( )\
3585 contentsize = self.target:getContentSizeH( )\
3586 contentscroll = self.target:getContentScrollH( )\
3587 end\
3588\
3589 local barsize = math.max( math.floor( traysize * framesize / contentsize ), 1 )\
3590\
3591 local barpos = traysize * contentscroll / contentsize\
3592 return math.ceil( barpos ), barsize\
3593end\
3594\
3595function UIScrollBar.public:draw( x, y )\
3596 local layer = stencil.addLayer( x, y, self.w, self.h )\
3597 if self.target then\
3598 local pos, size = self:getBar( )\
3599 stencil.rectangle( x, y, self.w, self.h, self.tc, 1, \" \" )\
3600 if self.direction == \"vertical\" then\
3601 stencil.rectangle( x, y + pos, self.w, size, self.bc, 1, \" \" )\
3602 else\
3603 stencil.rectangle( x + pos, y, size, self.h, self.bc, 1, \" \" )\
3604 end\
3605 else\
3606 stencil.rectangle( x, y, self.w, self.h, self.bc, 1, \" \" )\
3607 end\
3608 local c = { }\
3609 for i, child in ipairs( self.children ) do\
3610 c[i] = child\
3611 end\
3612 for i, child in ipairs( c ) do\
3613 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
3614 end\
3615 stencil.closeLayer( layer )\
3616end\
3617\
3618function UIScrollBar.public:onMouseClick( rx, ry, button )\
3619 if not self.target then return end\
3620 local pos, size = self:getBar( )\
3621 local traysize\
3622 if self.direction == \"vertical\" then\
3623 self.downoffset = ry - pos\
3624 if ry > pos and ry < pos + size - 1 then return end\
3625 pos = ry - 1\
3626 traysize = self.h\
3627 else\
3628 self.downoffset = rx - pos\
3629 if rx > pos and rx < pos + size - 1 then return end\
3630 pos = rx - 1\
3631 traysize = self.w\
3632 end\
3633 pos = math.max( math.min( pos, traysize - size), 0 )\
3634 if self.direction == \"vertical\" then\
3635 self.target:setContentScrollV( math.floor( pos / traysize * self.target:getContentSizeV( ) ) )\
3636 else\
3637 self.target:setContentScrollH( math.floor( pos / traysize * self.target:getContentSizeH( ) ) )\
3638 end\
3639end\
3640\
3641function UIScrollBar.public:onMouseDrag( rx, ry, cx, cy, button )\
3642 if not self.target then return end\
3643 local pos, size = self:getBar( )\
3644 local traysize\
3645 if self.direction == \"vertical\" then\
3646 pos = ry - self.downoffset\
3647 traysize = self.h\
3648 else\
3649 pos = rx - self.downoffset\
3650 traysize = self.w\
3651 end\
3652 pos = math.max( math.min( pos, traysize - size), 0 )\
3653 if self.direction == \"vertical\" then\
3654 self.target:setContentScrollV( math.floor( pos / traysize * self.target:getContentSizeV( ) ) )\
3655 else\
3656 self.target:setContentScrollH( math.floor( pos / traysize * self.target:getContentSizeH( ) ) )\
3657 end\
3658end\
3659\
3660function UIScrollBar.public:onMouseScroll( rx, ry, dir )\
3661 if not self.target then return end\
3662 local pos, size = self:getBar( )\
3663 local traysize\
3664 if self.direction == \"vertical\" then\
3665 traysize = self.h\
3666 else\
3667 traysize = self.w\
3668 end\
3669 pos = math.max( math.min( pos + dir, traysize - size), 0 )\
3670 if self.direction == \"vertical\" then\
3671 self.target:setContentScrollV( math.floor( pos / traysize * self.target:getContentSizeV( ) ) )\
3672 else\
3673 self.target:setContentScrollH( math.floor( pos / traysize * self.target:getContentSizeH( ) ) )\
3674 end\
3675end", meta={
3676 type = "class",
3677}};["Drive"]={content="\
3678\
3679function Drive:Drive( methods ) -- table methods\
3680 if type( methods ) ~= \"table\" then\
3681 error( \"expected table methods\", 2 )\
3682 end\
3683 for k, v in pairs( fs ) do\
3684 if not methods[k] then\
3685 error( \"expected \" .. k .. \" method in table\", 2 )\
3686 end\
3687 end\
3688\
3689 self.methods = methods\
3690\
3691 return self.public\
3692end\
3693\
3694function Drive.meta:index( key )\
3695 return self.methods[key]\
3696end\
3697\
3698for k, v in pairs( fs ) do\
3699 Drive.public( k )\
3700 Drive.public[k].write = false\
3701 Drive.public[k].read = function( self )\
3702 return self.methods[k]\
3703 end\
3704end\
3705\
3706local function combine( p1, p2 )\
3707 if type( p2 ) ~= \"string\" then\
3708 return error \"expected string\"\
3709 end\
3710 return fs.combine( p1, p2 )\
3711end\
3712\
3713function Drive.static.redirect( path )\
3714\
3715 local mfs = setmetatable( { }, { __index = fs } )\
3716 mfs.open = function( p, mode )\
3717 return fs.open( combine( path, p ), mode )\
3718 end\
3719 mfs.list = function( p )\
3720 return fs.list( combine( path, p ) )\
3721 end\
3722 mfs.exists = function( p )\
3723 return fs.exists( combine( path, p ) )\
3724 end\
3725 mfs.isDir = function( p )\
3726 return fs.isDir( combine( path, p ) )\
3727 end\
3728 mfs.isReadOnly = function( p )\
3729 return fs.isReadOnly( combine( path, p ) )\
3730 end\
3731 mfs.getDrive = function( p )\
3732 return fs.getDrive( combine( path, p ) )\
3733 end\
3734 mfs.getSize = function( p )\
3735 if mfs.isDir( p ) then\
3736 local files = mfs.list( p )\
3737 local n = 0\
3738 for i = 1, #files do\
3739 n = n + mfs.getSize( p .. \"/\" .. files[i] )\
3740 end\
3741 return n\
3742 else\
3743 return fs.getSize( combine( path, p ) )\
3744 end\
3745 end\
3746 mfs.getFreeSpace = function( p )\
3747 return fs.getFreeSpace( combine( path, p ) )\
3748 end\
3749 mfs.makeDir = function( p )\
3750 return fs.makeDir( combine( path, p ) )\
3751 end\
3752 mfs.move = function( p1, p2 )\
3753 return fs.move( combine( path, p1 ), combine( path, p2 ) )\
3754 end\
3755 mfs.copy = function( p1, p2 )\
3756 return fs.copy( combine( path, p1 ), combine( path, p2 ) )\
3757 end\
3758 mfs.delete = function( p )\
3759 return fs.delete( combine( path, p ) )\
3760 end\
3761 mfs.find = function( p )\
3762 return fs.find( combine( path, p ) )\
3763 end\
3764\
3765 return Drive( mfs )\
3766\
3767end", meta={
3768 type = "class",
3769}};["MarkupPositionHandler"]={content="\
3770\
3771MarkupPositionHandler.public \"x\"\
3772MarkupPositionHandler.public.x.write = false\
3773MarkupPositionHandler.public \"y\"\
3774MarkupPositionHandler.public.y.write = false\
3775MarkupPositionHandler.public \"w\" \"number\"\
3776MarkupPositionHandler.public \"h\" \"number\"\
3777\
3778function MarkupPositionHandler:MarkupPositionHandler( w, h )\
3779 self.x = 1\
3780 self.y = 1\
3781 self.w = w\
3782 self.h = h\
3783\
3784 self.last = false\
3785 self.tallest = 0\
3786\
3787 return self.public\
3788end\
3789\
3790local inline = {\
3791 text = true;\
3792 image = true;\
3793 frame = true;\
3794 button = true;\
3795 input = true;\
3796 space = true;\
3797 newline = true;\
3798}\
3799\
3800function MarkupPositionHandler.public:newObject( object, page ) -- MarkupGenericObject\
3801 if not object.static then\
3802 if not self.last or self.last.mode == \"block\" or ( not inline[object.etype] and type( object ) ~= \"string\" ) then\
3803 self.x = 1\
3804 self.y = self.tallest + 1\
3805 else\
3806 self.x = self.last.x + self.last.content.width\
3807 self.y = self.last.y\
3808 end\
3809 end\
3810 local objects\
3811 if type( object ) == \"string\" then\
3812 objects = { }\
3813 local rem = object \
3814 while rem do\
3815 local x, y, str\
3816 x, y, str, rem = self.public:addTextContent( rem, page )\
3817 local ob = MarkupLoadedObject( )\
3818 ob.mode = \"inline\"\
3819 ob.x = x\
3820 ob.y = y\
3821 ob.content.width = #str\
3822 ob.content.height = 1\
3823 ob:initialise( UIText )\
3824 ob.element.text = str\
3825 ob.element.bc = page:getStyle \"bc\"\
3826 ob.element.tc = page:getStyle \"tc\"\
3827 table.insert( objects, ob )\
3828 end\
3829 end\
3830 if not objects then\
3831 objects = object:loadObject( self.public )\
3832 end\
3833 if not object.static then\
3834 if #objects > 0 then\
3835 self.last = objects[#objects]\
3836 end\
3837 for i = 1, #objects do\
3838 if objects[i].y + objects[i].content.height - 1 > self.tallest then\
3839 self.tallest = objects[i].y + objects[i].content.height - 1\
3840 end\
3841 end\
3842 end\
3843 return objects\
3844end\
3845\
3846function MarkupPositionHandler.public:addTextContent( text, page )\
3847 local line, remain\
3848 local x, y, nx = self.x, self.y, 0\
3849 for i = 1, self.w - self.x + 1 do\
3850 if text:sub( i, i ) == \"\\n\" then\
3851 line = text:sub( 1, i - 1 )\
3852 remain = text:sub( i + 1 )\
3853 self.y = self.y + 1\
3854 if self.y <= self.tallest then\
3855 nx = self.last.x + self.last.content.width\
3856 else\
3857 nx = 1\
3858 end\
3859 break\
3860 end\
3861 end\
3862 if not line then\
3863 if #text > self.w - self.x + 1 then\
3864 for i = self.w - self.x + 2, 1, -1 do\
3865 if text:sub( i, i ):find \"%s\" then\
3866 line = text:sub( 1, i - 1 )\
3867 remain = text:sub( i + 1 )\
3868 break\
3869 end\
3870 end\
3871 if not line then\
3872 line = text:sub( 1, self.w - self.x + 1 )\
3873 remain = text:sub( self.w - self.x + 2 )\
3874 end\
3875 self.y = self.y + 1\
3876 if self.y <= self.tallest then\
3877 nx = self.last.x + self.last.content.width\
3878 else\
3879 nx = 1\
3880 end\
3881 else\
3882 line = text\
3883 remain = false\
3884 nx = self.x + #text\
3885 end\
3886 end\
3887 if page:getStyle \"align\" == \"right\" then\
3888 x = self.w - x - #line + 2\
3889 elseif page:getStyle \"align\" == \"centre\" then\
3890 x = math.ceil( self.w / 2 - ( x + #line - 1 ) / 2 ) + 1\
3891 end\
3892 self.x = nx\
3893 return x, y, line, remain and ( #remain > 0 and remain or false )\
3894end", meta={
3895 type = "class",
3896}};["UI"]={content="\
3897require \"buffer\"\
3898require \"display\"\
3899require \"markup\"\
3900require \"parser\"\
3901require \"stencil\"", meta={}};["display"]={content="\
3902\
3903local function bluebox( frame, height )\
3904 local w, h = frame.w, frame.h\
3905 local f = frame:newChild( UIFrame( 1, math.ceil( h / 2 - height / 2 ) + 1, w, height ) )\
3906 f:newChild( UIText( 1, 1, f.w, f.h, \"\" ) ).bc = colours.blue\
3907 return f\
3908end\
3909\
3910function alert( frame, message, wait )\
3911 local active = true\
3912 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
3913 close.bc = 0\
3914 close.align = false\
3915 local f = bluebox( frame, 6 )\
3916 function close:onClick( )\
3917 close:remove( )\
3918 f:remove( )\
3919 active = false\
3920 end\
3921 local title = f:newChild( UIText( 0, 2, math.min( #message, f.w - 2 ), 2, message ) )\
3922 title.bc = 0\
3923 title.tc = colours.lightBlue\
3924 title:centreX( )\
3925 local ok = f:newChild( UIButton( 0, 5, 2, 1, \"ok\" ) )\
3926 ok.bc = 0\
3927 ok.tc = colours.white\
3928 ok:centreX( )\
3929 function ok:onClick( )\
3930 close:remove( )\
3931 f:remove( )\
3932 active = false\
3933 end\
3934 if wait then\
3935 while active do\
3936 coroutine.yield( )\
3937 end\
3938 end\
3939end\
3940\
3941function response( frame, title, mask )\
3942 local active = true\
3943 local r = false\
3944 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
3945 close.bc = 0\
3946 close.align = false\
3947 local f = bluebox( frame, 6 )\
3948 function close:onClick( )\
3949 close:remove( )\
3950 f:remove( )\
3951 active = false\
3952 end\
3953 local title = f:newChild( UIText( 0, 2, math.min( #title, f.w - 2 ), 2, title ) )\
3954 title.bc = 0\
3955 title.tc = colours.lightBlue\
3956 title:centreX( )\
3957 local input = f:newChild( UIInput( 2, 5, frame.w - 2, 1, mask ) )\
3958 input.bc = 0\
3959 input.fbc = 0\
3960 input.tc = colours.white\
3961 input:focusOn( )\
3962 function input:onEnter( )\
3963 close:remove( )\
3964 f:remove( )\
3965 active = false\
3966 r = self.text\
3967 end\
3968 while active do\
3969 coroutine.yield( )\
3970 end\
3971 return r\
3972end\
3973\
3974function confirm( frame, title )\
3975 local result = \"none\"\
3976 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
3977 close.bc = 0\
3978 close.align = false\
3979 local f = bluebox( frame, 6 )\
3980 function close:onClick( )\
3981 close:remove( )\
3982 f:remove( )\
3983 result = nil\
3984 end\
3985 local t = f:newChild( UIText( 2, 2, math.min( #title, f.w - 2 ), 2, title ) )\
3986 t.bc = colours.blue\
3987 t.tc = colours.lightBlue\
3988 t:centreX( )\
3989 local yes = f:newChild( UIButton( 2, 5, math.floor( ( f.w - 2 ) / 2 ), 1, \"yes\" ) )\
3990 yes.bc = colours.blue\
3991 yes.tc = colours.white\
3992 function yes:onClick( )\
3993 f:remove( )\
3994 close:remove( )\
3995 result = true\
3996 end\
3997 local no = f:newChild( UIButton( f.w - yes.w, 5, math.floor( ( f.w - 2 ) / 2 ), 1, \"no\" ) )\
3998 no.bc = colours.blue\
3999 no.tc = colours.white\
4000 function no:onClick( )\
4001 f:remove( )\
4002 close:remove( )\
4003 result = false\
4004 end\
4005 while result == \"none\" do\
4006 coroutine.yield( )\
4007 end\
4008 return result\
4009end\
4010\
4011function sframe( frame, x, y, w, h ) -- scrolling frame (auto scrollbar)\
4012\
4013end\
4014\
4015function help( frame, title, content )\
4016 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
4017 close.bc = 0\
4018 close.align = false\
4019 local f = bluebox( frame, 14 )\
4020 local t = f:newChild( UIText( 2, 2, #title, 1, title ) )\
4021 t.bc = colours.blue\
4022 t.tc = colours.white\
4023 t:centreX( )\
4024 f:newChild( UIText( 3, 5, f.w - 3, f.h - 7, \"\" ) ).bc = colours.grey\
4025 local c = f:newChild( UIText( 2, 4, f.w - 3, f.h - 7, content ) )\
4026 c.tc = colours.grey\
4027 local ok = f:newChild( UIButton( 2, f.h - 1, f.w - 2, 1, \"Got it!\" ) )\
4028 ok.bc = colours.blue\
4029 ok.tc = colours.white\
4030 function ok:onClick( )\
4031 close:remove( )\
4032 f:remove( )\
4033 end\
4034 function close:onClick( )\
4035 close:remove( )\
4036 f:remove( )\
4037 end\
4038end\
4039\
4040function menu( frame, options )\
4041 frame:clearChildren( )\
4042 if options.width then\
4043 frame.w = options.width\
4044 end\
4045 if options.height then\
4046 frame.h = options.height\
4047 end\
4048 if options.shadow then\
4049 frame.w = frame.w - 1\
4050 frame.h = frame.h - 1\
4051 frame:newChild( UIText( 2, 2, frame.w, frame.h, \"\" ) ).bc = options.shadow\
4052 end\
4053 local sframe = frame\
4054 local y = 1\
4055 local bc = options.colour or 1\
4056 frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) ).bc = bc\
4057 if #options > frame.h then\
4058 sframe = frame:newChild( UIFrame( 1, 1, frame.w - 1, frame.h ) )\
4059 local scrollbar = frame:newChild( UIScrollBar( frame.w, 1, 1, frame.h, sframe ) )\
4060 end\
4061 for i = 1, #options do\
4062 if options[i] == \"rule\" then\
4063 local r = sframe:newChild( UIText( 2, y, sframe.w - 2, 1, (\"-\"):rep( math.max( sframe.w - 2, 0 ) ) ) )\
4064 r.tc = colours.lightGrey\
4065 r.bc = 0\
4066 elseif options[i] ~= \"space\" then\
4067 if options[i].type == \"menu\" then\
4068 local arrow = sframe:newChild( UIText( sframe.w - 1, y, 1, 1, \">\" ) )\
4069 arrow.bc = 0\
4070 arrow.tc = colours.blue\
4071 local button = sframe:newChild( UIButton( options.spacing == false and 2 or 3, y, sframe.w - ( options.spacing == false and 2 or 3 ), 1, options[i].name ) )\
4072 button.bc = 0\
4073 button.tc = colours.grey\
4074 button.align = false\
4075 local cx, cy = frame.x + frame.w, y - 1\
4076 function button:onClick( )\
4077 if frame.parent then\
4078 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
4079 close.bc, close.align = 0, false\
4080 local f = UIFrame( cx, cy + frame.y + sframe.cy, frame.w, frame.h )\
4081 local w, h = frame.parent.w, frame.parent.h\
4082 menu( f, options[i].options )\
4083 if f.x + f.w > w + 1 then\
4084 f.x = w - f.w + 1\
4085 end\
4086 if f.y + f.h > h + 1 then\
4087 f.y = h - f.h + 1\
4088 end\
4089 frame.parent:newChild( f )\
4090 function close:onClick( )\
4091 f:remove( )\
4092 close:remove( )\
4093 end\
4094 end\
4095 end\
4096 elseif options[i].type == \"button\" then\
4097 local button = sframe:newChild( UIButton( options.spacing == false and 2 or 4, y, sframe.w - ( options.spacing == false and 2 or 4 ), 1, options[i].name ) )\
4098 button.bc = 0\
4099 button.tc = options[i].blue and colours.blue or colours.grey\
4100 button.align = false\
4101 function button:onClick( x, y, b )\
4102 if type( options[i].onClick ) == \"function\" then\
4103 options[i].onClick( button, frame, b )\
4104 end\
4105 end\
4106 elseif options[i].type == \"display\" then\
4107 local text = sframe:newChild( UIText( options.spacing == false and 2 or 4, y, sframe.w - ( options.spacing == false and 2 or 4 ), 1, function( )\
4108 if type( options[i].getDisplay ) == \"function\" then\
4109 return options[i].name .. tostring( options[i].getDisplay( ) )\
4110 end\
4111 return options[i].name\
4112 end ) )\
4113 text.bc = 0\
4114 text.tc = colours.grey\
4115 elseif options[i].type == \"input\" then\
4116 if options[i].label then\
4117 local label = sframe:newChild( UIText( options.spacing == false and 2 or 5, y, sframe.w - ( options.spacing == false and 2 or 5 ), 1, options[i].label ) )\
4118 label.bc = 0\
4119 label.tc = colours.lightGrey\
4120 y = y + 1\
4121 end\
4122 local input = sframe:newChild( UIInput( options.spacing == false and 2 or 4, y, sframe.w - ( options.spacing == false and 2 or 4 ), 1, options[i].mask ) )\
4123 input.bc = colours.lightGrey\
4124 input.fbc = colours.lightGrey\
4125 function input:onEnter( )\
4126 if type( options[i].onEnter ) == \"function\" then\
4127 options[i].onEnter( input )\
4128 end\
4129 end\
4130 function input:whenUnFocussed( )\
4131 self.text = \"\"\
4132 end\
4133 elseif options[i].type == \"label\" then\
4134 local text = sframe:newChild( UIText( 2, y, sframe.w - 2, 1, options[i].name ) )\
4135 text.bc = 0\
4136 text.tc = colours.lightGrey\
4137 end\
4138 end\
4139 y = y + 1\
4140 end\
4141 if options.shadow then\
4142 frame.w = frame.w + 1\
4143 frame.h = frame.h + 1\
4144 end\
4145 return sframe\
4146end", meta={
4147 type = "lib",
4148}};["UIFrame"]={content="\
4149\
4150require \"UIElement\"\
4151\
4152UIFrame:extends( UIElement )\
4153\
4154UIFrame.handlesMouse = false\
4155UIFrame.handlesScroll = true\
4156UIFrame.isScrollTarget = true\
4157UIFrame.scrollDirection = \"vertical\"\
4158\
4159UIFrame.scrolls = true\
4160UIFrame.public \"scrolls\" \"boolean\"\
4161\
4162function UIFrame:UIFrame( x, y, w, h, direction )\
4163 self:UIElement( x, y, w, h )\
4164 self.scrollDirection = direction or \"vertical\"\
4165 return self.public\
4166end\
4167\
4168function UIFrame.public:update( dt )\
4169 local max = self.scrollDirection == \"vertical\" and self.h or self.w\
4170 if self.scrollDirection == \"vertical\" then\
4171 for i = 1, #self.children do\
4172 max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
4173 end\
4174 else\
4175 for i = 1, #self.children do\
4176 max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
4177 end\
4178 end\
4179 if self.scrollDirection == \"vertical\" then\
4180 if max - self.h < -self.cy then\
4181 self.cy = -max + self.h\
4182 end\
4183 else\
4184 if max - self.w < -self.cx then\
4185 self.cx = -max + self.w\
4186 end\
4187 end\
4188 if max == ( self.scrollDirection == \"vertical\" and self.h or self.w ) or not self.scrolls then\
4189 self.handlesScroll = false\
4190 else\
4191 self.handlesScroll = true\
4192 end\
4193 local c = { }\
4194 for i, child in ipairs( self.children ) do\
4195 c[i] = child\
4196 end\
4197 for i, child in ipairs( c ) do\
4198 child:update( dt )\
4199 end\
4200end\
4201\
4202function UIFrame.public:onMouseScroll( rx, ry, dir )\
4203 if self.scrollDirection == \"vertical\" then\
4204 self.cy = self.cy - dir\
4205 local max = 0\
4206 for i = 1, #self.children do\
4207 max = math.max( max, self.children[i].y + self.children[i].h )\
4208 end\
4209 max = max - self.h - 1\
4210 if self.cy < -max then\
4211 self.cy = -max\
4212 end\
4213 if self.cy > 0 then self.cy = 0 end\
4214 else\
4215 self.cx = self.cx - dir\
4216 local max = 0\
4217 for i = 1, #self.children do\
4218 max = math.max( max, self.children[i].x + self.children[i].w )\
4219 end\
4220 max = max - self.w - 1\
4221 if self.cx < -max then\
4222 self.cx = -max\
4223 end\
4224 if self.cx > 0 then self.cx = 0 end\
4225 end\
4226end\
4227\
4228function UIFrame.public:getDisplaySizeH( )\
4229 return self.w\
4230end\
4231function UIFrame.public:getDisplaySizeV( )\
4232 return self.h\
4233end\
4234\
4235function UIFrame.public:getContentSizeH( )\
4236 local max = self.w\
4237 for i = 1, #self.children do\
4238 max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
4239 end\
4240 return max\
4241end\
4242function UIFrame.public:getContentSizeV( )\
4243 local max = self.h\
4244 for i = 1, #self.children do\
4245 max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
4246 end\
4247 return max\
4248end\
4249\
4250function UIFrame.public:getContentScrollH( )\
4251 return -self.cx\
4252end\
4253function UIFrame.public:getContentScrollV( )\
4254 return -self.cy\
4255end\
4256\
4257function UIFrame.public:setContentScrollH( scroll )\
4258 self.cx = -scroll\
4259end\
4260function UIFrame.public:setContentScrollV( scroll )\
4261 self.cy = -scroll\
4262end", meta={
4263 type = "class",
4264}};["UIInput"]={content="\
4265\
4266require \"UIElement\"\
4267\
4268UIInput:extends( UIElement )\
4269\
4270UIInput.handlesKeys = true\
4271UIInput.handlesScroll = true\
4272UIInput.tabIndex = true\
4273\
4274UIInput.bc = colours.grey\
4275UIInput.fbc = colours.lightGrey\
4276UIInput.tc = colours.black\
4277UIInput.hbc = colours.blue\
4278UIInput.htc = colours.white\
4279\
4280UIInput.public \"bc\" \"number\"\
4281UIInput.public \"fbc\" \"number\"\
4282UIInput.public \"tc\" \"number\"\
4283UIInput.public \"hbc\" \"number\"\
4284UIInput.public \"htc\" \"number\"\
4285\
4286UIInput.onEnter = false\
4287UIInput.public \"onEnter\" \"function\"\
4288UIInput.public \"onCtrlKey\" \"function\"\
4289\
4290UIInput.public \"text\" \"string\"\
4291UIInput.public \"mask\" \"string\"\
4292\
4293UIInput.public \"scroll\" \"number\"\
4294\
4295UIInput.public \"mask\"\
4296function UIInput.public.mask:write( value )\
4297 if not value or type( value ) == \"string\" then\
4298 self.mask = value\
4299 else\
4300 error( \"expected string mask\", 3 )\
4301 end\
4302end\
4303\
4304function UIInput:UIInput( x, y, w, h, mask )\
4305 self:UIElement( x, y, w, h )\
4306 self.mask = mask\
4307 self.cursor = 1\
4308 self.scroll = 0\
4309 self.text = \"\"\
4310 self.focussed = false\
4311 self.selection = false\
4312 return self.public\
4313end\
4314\
4315function UIInput.public:draw( x, y )\
4316 if self.cursor > #self.text + 1 then\
4317 self.cursor = #self.text + 1\
4318 end\
4319 local layer = stencil.addLayer( x, y, self.w, self.h )\
4320 local bc = self.bc\
4321 if self.focussed then\
4322 bc = self.fbc\
4323 end\
4324 local text = self.text\
4325 if self.mask then\
4326 text = text:gsub( \".\", self.mask:sub( 1, 1 ) )\
4327 end\
4328 if self.selection then\
4329 text = text .. string.rep( \" \", math.max( self.w - #text, 0 ) )\
4330 for i = 1, #text do\
4331 local bc = self.bc\
4332 local tc = self.tc\
4333 if i >= math.min( self.selection, self.cursor ) and i <= math.max( self.selection, self.cursor ) then\
4334 bc = self.hbc\
4335 tc = self.htc\
4336 elseif self.focussed then\
4337 bc = self.fbc\
4338 end\
4339 stencil.pixel( x + i - self.scroll - 1, y, bc, tc, text:sub( i, i ) )\
4340 end\
4341 else\
4342 text = text:sub( self.scroll + 1 )\
4343 text = text:sub( 1, self.w )\
4344 text = text .. string.rep( \" \", self.w - #text )\
4345 stencil.textLine( x, y, bc, self.tc, text )\
4346 if self.focussed then\
4347 stencil.setCursorBlink( x + self.cursor - 1 - self.scroll, y, self.tc )\
4348 end\
4349 end\
4350 local c = { }\
4351 for i, child in ipairs( self.children ) do\
4352 c[i] = child\
4353 end\
4354 for i, child in ipairs( c ) do\
4355 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
4356 end\
4357 stencil.closeLayer( layer )\
4358end\
4359\
4360function UIInput:setCursorPos( n )\
4361 self.cursor = math.max( math.min( n, #self.text + 1 ), 1 )\
4362 if self.cursor > self.scroll + self.w then\
4363 self.scroll = self.cursor - self.w\
4364 end\
4365 if self.cursor - 1 <= self.scroll then\
4366 self.scroll = math.max( self.cursor - 1, 1 ) - 1\
4367 end\
4368end\
4369\
4370function UIInput:write( str )\
4371 self.text = self.text:sub( 1, self.cursor - 1 ) .. str:gsub( \"\\n\", \" \" ) .. self.text:sub( self.cursor )\
4372 self:setCursorPos( self.cursor + #str )\
4373end\
4374\
4375function UIInput:setSelection( text )\
4376 self.text = self.text:sub( 1, math.min( self.selection, self.cursor ) - 1 ) .. self.text:sub( math.max( self.selection, self.cursor ) + 1 )\
4377 self.cursor = math.min( self.selection, self.cursor )\
4378 self:write( text )\
4379end\
4380\
4381function UIInput:getSelection( )\
4382 return self.text:sub( math.min( self.selection, self.cursor ), math.max( self.selection, self.cursor ) )\
4383end\
4384\
4385function UIInput.public:select( min, max )\
4386 self.selection = min\
4387 self.cursor = max\
4388end\
4389\
4390function UIInput.public:onMouseClick( rx, ry, button )\
4391 self.selection = false\
4392 self:setCursorPos( rx + self.scroll )\
4393end\
4394\
4395function UIInput.public:onMouseDrag( rx, ry )\
4396 if not self.selection then\
4397 self.selection = self.cursor\
4398 end\
4399 self:setCursorPos( rx + self.scroll )\
4400end\
4401\
4402function UIInput.public:onMouseScroll( rx, ry, dir )\
4403 self.scroll = math.max( math.min( self.scroll + dir, #self.text - self.w + 1 ), 0 )\
4404end\
4405\
4406function UIInput.public:onKeyPress( key, lastkey )\
4407 if not self.focussed then\
4408 return\
4409 end\
4410 if lastkey == 29 then\
4411 if ( ( key == keys.c or key == keys.x ) and not self.mask ) or key == keys.b then\
4412 if key == keys.c then\
4413 if self.selection then\
4414 clipboard.set( \"plaintext\", self:getSelection( ) )\
4415 else\
4416 clipboard.set( \"plaintext\", self.text )\
4417 end\
4418 elseif key == keys.x then\
4419 if self.selection then\
4420 clipboard.set( \"plaintext\", self:getSelection( ) )\
4421 self:setSelection \"\"\
4422 self.selection = false\
4423 else\
4424 clipboard.set( \"plaintext\", self.text )\
4425 self.text = \"\"\
4426 end\
4427 elseif key == keys.b then\
4428 local mode, data = clipboard.get( )\
4429 if mode == \"plaintext\" then\
4430 if self.selection then\
4431 self:setSelection( data )\
4432 self.selection = false\
4433 else\
4434 self:write( data )\
4435 end\
4436 elseif self.onCtrlKey then\
4437 self.onCtrlKey( self.public, key )\
4438 end\
4439 end\
4440 return\
4441 end\
4442 if self.onCtrlKey then\
4443 self.onCtrlKey( self.public, key )\
4444 end\
4445 return\
4446 end\
4447 if key == keys.enter then\
4448 if type( self.onEnter ) == \"function\" then\
4449 self.onEnter( self.public )\
4450 end\
4451 local handler = self.public:getHandler( )\
4452 if handler and handler.focus == self.public then\
4453 handler:unFocus( )\
4454 end\
4455 elseif key == keys.left then\
4456 if self.selection then\
4457 self:setCursorPos( math.min( self.selection, self.cursor ) - 1 )\
4458 self.selection = false\
4459 elseif self.cursor > 1 then\
4460 self:setCursorPos( self.cursor - 1 )\
4461 end\
4462 elseif key == keys.right then\
4463 if self.selection then\
4464 self:setCursorPos( math.max( self.selection, self.cursor ) + 1 )\
4465 self.selection = false\
4466 else\
4467 self:setCursorPos( self.cursor + 1 )\
4468 end\
4469 elseif key == keys.backspace and self.cursor > 1 then\
4470 if self.selection then\
4471 self:setSelection \"\"\
4472 self.selection = false\
4473 else\
4474 self.text = self.text:sub( 1, self.cursor - 2 ) .. self.text:sub( self.cursor )\
4475 self:setCursorPos( self.cursor - 1 )\
4476 end\
4477 elseif key == keys.delete then\
4478 if self.selection then\
4479 self:setSelection \"\"\
4480 self.selection = false\
4481 else\
4482 self.text = self.text:sub( 1, self.cursor - 1 ) .. self.text:sub( self.cursor + 1 )\
4483 end\
4484 end\
4485end\
4486\
4487function UIInput.public:onTextInput( text, lastkey )\
4488 if not self.focussed then\
4489 return\
4490 end\
4491 if self.selection then\
4492 self:setSelection( text )\
4493 self.selection = false\
4494 else\
4495 self.text = self.text:sub( 1, self.cursor - 1 ) .. text .. self.text:sub( self.cursor )\
4496 self:setCursorPos( self.cursor + 1 )\
4497 end\
4498end\
4499\
4500function UIInput.public:onFocus( )\
4501 self.focussed = true\
4502 self.handlesKeys = true\
4503 if type( self.whenFocussed ) == \"function\" then\
4504 self.whenFocussed( self.public )\
4505 end\
4506end\
4507\
4508function UIInput.public:onUnFocus( )\
4509 self.focussed = false\
4510 self.handlesKeys = false\
4511 self.selection = false\
4512 if type( self.whenUnFocussed ) == \"function\" then\
4513 self.whenUnFocussed( self.public )\
4514 end\
4515end\
4516\
4517function UIInput.public:focusOn( )\
4518 local handler = self.public:getHandler( )\
4519 if handler then\
4520 handler:setFocus( self.public )\
4521 end\
4522end", meta={
4523 type = "class",
4524}};["stringutils"]={content="\
4525\
4526function gfind( str, pat, pos )\
4527 local t = { }\
4528 local params = { str:find( pat, pos ) }\
4529 while params[1] do\
4530 table.insert( t, params )\
4531 params = { str:find( pat, params[2] + 1 ) }\
4532 end\
4533 local i = 0\
4534 return function( )\
4535 i = i + 1\
4536 if t[i] then\
4537 return unpack( t[i] )\
4538 end\
4539 end\
4540end\
4541\
4542function split( str, pat, pos )\
4543 local last = 1\
4544 local parts = { }\
4545 for s, f in gfind( str, pat, pos ) do\
4546 table.insert( parts, str:sub( last, s - 1 ) )\
4547 last = f + 1\
4548 end\
4549 table.insert( parts, str:sub( last ) )\
4550 return parts\
4551end\
4552\
4553local function linewrap( str, w )\
4554 for i = 1, w do\
4555 if str:sub( i, i ) == \"\\n\" then\
4556 return str:sub( 1, i - 1 ), str:sub( i + 1 )\
4557 end\
4558 end\
4559 if #str < w then\
4560 return str, \"\"\
4561 end\
4562 for i = w + 1, 1, -1 do\
4563 if str:sub( i, i ):find \"%s\" then\
4564 return str:sub( 1, i - 1 ), str:sub( i + 1 )\
4565 end\
4566 end\
4567 return str:sub( 1, w ), str:sub( w + 1 )\
4568end\
4569\
4570function wordwrap( str, w, h )\
4571 local s, f = linewrap( str, w )\
4572 local lines = { s }\
4573 while #f > 0 do\
4574 s, f = linewrap( f, w )\
4575 table.insert( lines, s )\
4576 end\
4577 while #lines > h do\
4578 table.remove( lines, #lines )\
4579 end\
4580 return lines\
4581end", meta={
4582 type = "lib",
4583}};["UIElement"]={content="\
4584\
4585require \"UIHandler\"\
4586\
4587UIElement.public \"x\" \"number\"\
4588UIElement.public \"y\" \"number\"\
4589UIElement.public \"w\" \"number\"\
4590UIElement.public \"h\" \"number\"\
4591UIElement.public \"cx\" \"number\"\
4592UIElement.public \"cy\" \"number\"\
4593\
4594UIElement.public \"parent\"\
4595function UIElement.public.parent:write( value )\
4596 if value == false or class.typeOf( value, UIElement ) or class.typeOf( value, UIHandler ) then\
4597 self.parent = value\
4598 else\
4599 error( \"expected UIElement parent\", 3 )\
4600 end\
4601end\
4602\
4603UIElement.handlesMouse = true\
4604UIElement.handlesScroll = false\
4605UIElement.handlesTab = false\
4606UIElement.tabIndex = false\
4607\
4608UIElement.public \"handlesMouse\"\
4609UIElement.public.handlesMouse.write = false\
4610UIElement.public \"handlesKeys\"\
4611UIElement.public.handlesKeys.write = false\
4612UIElement.public \"handlesScroll\"\
4613UIElement.public.handlesScroll.write = false\
4614UIElement.public \"handlesTab\"\
4615UIElement.public.handlesTab.write = false\
4616UIElement.public \"handlesEnter\"\
4617UIElement.public.handlesEnter.write = false\
4618UIElement.public \"tabIndex\" \"boolean\"\
4619\
4620UIElement.isScrollTarget = false\
4621UIElement.public \"isScrollTarget\"\
4622UIElement.public.isScrollTarget.write = false\
4623UIElement.scrollDirection = \"none\"\
4624UIElement.public \"scrollDirection\"\
4625UIElement.public.scrollDirection.write = false\
4626\
4627UIElement.public \"whenFocussed\" \"function\"\
4628UIElement.public \"whenUnFocussed\" \"function\"\
4629\
4630function UIElement:UIElement( x, y, w, h )\
4631 self.x = x\
4632 self.y = y\
4633 self.w = w\
4634 self.h = h\
4635 self.cx = 0 -- children offset\
4636 self.cy = 0\
4637 self.children = { }\
4638 return self.public\
4639end\
4640\
4641-- children\
4642\
4643function UIElement.public:newChild( child )\
4644 if class.typeOf( child, UIElement ) then\
4645 table.insert( self.children, child )\
4646 child.parent = self.public\
4647 return child\
4648 else\
4649 error( \"expected UIElement child\", 2 )\
4650 end\
4651end\
4652\
4653function UIElement.public:removeChild( child )\
4654 if class.typeOf( child, UIElement ) then\
4655 for i = 1, #self.children do\
4656 if self.children[i] == child then\
4657 table.remove( self.children, i )\
4658 child.parent = false\
4659 return true\
4660 end\
4661 end\
4662 return false\
4663 else\
4664 error( \"expected UIElement child\", 3 )\
4665 end\
4666end\
4667\
4668function UIElement.public:getChildren( )\
4669 local t = { }\
4670 for i = 1, #self.children do\
4671 t[i] = self.children[i]\
4672 end\
4673 return t\
4674end\
4675\
4676function UIElement.public:clearChildren( )\
4677 for i = #self.children, 1, -1 do\
4678 self.children[i]:remove( )\
4679 end\
4680end\
4681\
4682local function merge( t1, t2 )\
4683 local t = { }\
4684 for i, v in ipairs( t1 ) do\
4685 table.insert( t, v )\
4686 end\
4687 for i, v in ipairs( t2 ) do\
4688 table.insert( t, v )\
4689 end\
4690 return t\
4691end\
4692\
4693function UIElement.public:getTabIndexes( )\
4694 local t = { }\
4695 if self.tabIndex then\
4696 table.insert( t, self.public )\
4697 end\
4698 for i = 1, #self.children do\
4699 local t2 = self.children[i]:getTabIndexes( )\
4700 if #t2 > 0 then\
4701 t = merge( t, t2 )\
4702 end\
4703 end\
4704 return t\
4705end\
4706\
4707-- parent dependant\
4708\
4709function UIElement.public:positionIn( parent )\
4710 local x, y = self.x, self.y\
4711 local p = self.parent\
4712 while p and p ~= parent and p:type( ) ~= \"UIHandler\" do\
4713 x = x + p.x\
4714 y = y + p.y\
4715 p = p.parent\
4716 end\
4717 return x, y\
4718end\
4719\
4720function UIElement.public:remove( )\
4721 if self.parent then\
4722 return self.parent:removeChild( self.public )\
4723 end\
4724 return false\
4725end\
4726\
4727function UIElement.public:getHandler( )\
4728 if self.parent then\
4729 local h = self.parent\
4730 while h:type( ) ~= \"UIHandler\" do\
4731 h = h.parent\
4732 if not h then return false end\
4733 end\
4734 return h\
4735 end\
4736 return false\
4737end\
4738\
4739function UIElement.public:centreX( )\
4740 if self.parent then\
4741 self.x = math.ceil( self.parent.w / 2 - self.w / 2 ) + 1\
4742 end\
4743 return self.public\
4744end\
4745\
4746function UIElement.public:centreY( )\
4747 if self.parent then\
4748 self.y = math.ceil( self.parent.h / 2 - self.h / 2 ) + 1\
4749 end\
4750 return self.public\
4751end\
4752\
4753function UIElement.public:centre( )\
4754 self.public:centreX( )\
4755 self.public:centreY( )\
4756end\
4757\
4758-- sibling dependant\
4759\
4760function UIElement.public:align( mode, sibling, spacing )\
4761 if mode == \"above\" then\
4762 self.x = sibling.x\
4763 self.w = sibling.w\
4764 self.y = sibling.y - self.h - ( spacing or 0 )\
4765 elseif mode == \"below\" then\
4766 self.x = sibling.x\
4767 self.w = sibling.w\
4768 self.y = sibling.y + sibling.h + ( spacing or 0 )\
4769 elseif mode == \"left\" then\
4770 self.y = sibling.y\
4771 self.h = sibling.h\
4772 self.x = sibling.x - self.w - ( spacing or 0 )\
4773 elseif mode == \"right\" then\
4774 self.y = sibling.y\
4775 self.h = sibling.h\
4776 self.x = sibling.x + sibling.w + ( spacing or 0 )\
4777 end\
4778 return self.public\
4779end\
4780\
4781function UIElement.public:alignTo( mode, sibling, spacing )\
4782 if mode == \"above\" then\
4783 self.x = sibling.x\
4784 self.w = sibling.w\
4785 self.y = sibling.y - self.h - ( spacing or 0 )\
4786 elseif mode == \"below\" then\
4787 self.x = sibling.x\
4788 self.w = sibling.w\
4789 self.y = sibling.y + sibling.h + ( spacing or 0 )\
4790 elseif mode == \"left\" then\
4791 self.y = sibling.y\
4792 self.h = sibling.h\
4793 self.x = sibling.x - self.w - ( spacing or 0 )\
4794 elseif mode == \"right\" then\
4795 self.y = sibling.y\
4796 self.h = sibling.h\
4797 self.x = sibling.x + sibling.w + ( spacing or 0 )\
4798 end\
4799 return self.public\
4800end\
4801\
4802-- callbacks\
4803\
4804function UIElement.public:update( dt )\
4805 local c = { }\
4806 for i, child in ipairs( self.children ) do\
4807 c[i] = child\
4808 end\
4809 for i, child in ipairs( c ) do\
4810 child:update( dt )\
4811 end\
4812end\
4813\
4814function UIElement.public:draw( x, y )\
4815 local layer = stencil.addLayer( x, y, self.w, self.h )\
4816 local c = { }\
4817 for i, child in ipairs( self.children ) do\
4818 c[i] = child\
4819 end\
4820 for i, child in ipairs( c ) do\
4821 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
4822 end\
4823 stencil.closeLayer( layer )\
4824end\
4825\
4826function UIElement.public:onMouseClick( rx, ry, button )\
4827\
4828end\
4829\
4830function UIElement.public:onMouseDrag( rx, ry, cx, cy, button )\
4831\
4832end\
4833\
4834function UIElement.public:onMouseScroll( rx, ry, dir )\
4835\
4836end\
4837\
4838function UIElement.public:onKeyPress( key, lastkey )\
4839\
4840end\
4841\
4842function UIElement.public:onTextInput( text, lastkey )\
4843\
4844end\
4845\
4846function UIElement.public:onFocus( )\
4847 if type( self.whenFocussed ) == \"function\" then\
4848 self.whenFocussed( self.public )\
4849 end\
4850end\
4851\
4852function UIElement.public:onUnFocus( )\
4853 if type( self.whenUnFocussed ) == \"function\" then\
4854 self.whenUnFocussed( self.public )\
4855 end\
4856end", meta={
4857 type = "class",
4858}};["filesystem"]={content="\
4859\
4860-- drives\
4861\
4862local drives = { }\
4863drives.C = Drive.redirect \"\"\
4864\
4865local function formatDriveName( name )\
4866 if type( name ) == \"string\" then\
4867 return name\
4868 end\
4869 return false\
4870end\
4871\
4872local function getDrive( path )\
4873 if not path:find \":\" then\
4874 return drives.C, \"no such drive C\"\
4875 end\
4876 local drive = formatDriveName( path:gsub( \":.*\", \"\" ) )\
4877 if drives[drive] then\
4878 return drives[drive]\
4879 end\
4880 return false, \"no such drive \" .. drive\
4881end\
4882\
4883function mount( drive, name )\
4884 name = formatDriveName( name )\
4885 if not name then\
4886 return false, \"expected string name\"\
4887 end\
4888 if drives[name] then\
4889 return false, \"drive already mounted\"\
4890 end\
4891 if not class.typeOf( drive, Drive ) then\
4892 return false, \"expected Drive object\"\
4893 end\
4894 drives[name] = drive\
4895 return true\
4896end\
4897\
4898function unmount( name )\
4899 drives[formatDriveName( name )] = nil\
4900end\
4901\
4902function getmounted( name )\
4903 return drives[formatDriveName( name )]\
4904end\
4905\
4906function listmounted( )\
4907 local t = { }\
4908 for name in pairs( drives ) do\
4909 t[#t+1] = name\
4910 end\
4911 return t\
4912end\
4913\
4914-- types\
4915\
4916local extensions = {\
4917 txt = \"text\";\
4918 lua = \"lua\";\
4919 nim = \"nova_image\";\
4920 nac = \"archive\";\
4921}\
4922local types = {\
4923 text = \"Plain text file\";\
4924 lua = \"Lua script file\";\
4925 unknown = \"Unknown file type\";\
4926 archive = \"Nova archive\";\
4927 class = \"Class file\";\
4928 folder = \"Folder\";\
4929 lib = \"Lua library\";\
4930 nova_image = \"Nova image file\";\
4931 shortcut = \"Shortcut\";\
4932 -- design_file\
4933 -- design_project\
4934}\
4935local handlers = { }\
4936\
4937function addExtension( ext, type )\
4938 extensions[ext] = type\
4939end\
4940\
4941function addTypeDescription( type, description )\
4942 types[type] = description\
4943end\
4944\
4945function getTypeDescription( type )\
4946 return types[type]\
4947end\
4948\
4949function addHandler( type, handler )\
4950 handlers[type] = handlers[type] or { }\
4951 for i = 1, #handlers[type] do\
4952 if handlers[type][i] == handler then\
4953 return\
4954 end\
4955 end\
4956 table.insert( handlers[type], handler )\
4957end\
4958\
4959function setDefaultHandler( type, handler )\
4960 handlers[type] = handlers[type] or { }\
4961 for i = 1, #handlers[type] do\
4962 if handlers[type][i] == handler then\
4963 table.remove( handlers[type], i )\
4964 break\
4965 end\
4966 end\
4967 table.insert( handlers[type], handler )\
4968end\
4969\
4970function getHandlers( type )\
4971 return { unpack( handlers[type] or { } ) }\
4972end\
4973\
4974function getAllHandlers( )\
4975 local t = { }\
4976 for k, v in pairs( handlers ) do\
4977 for i = 1, #v do\
4978 t[v[i]] = true\
4979 end\
4980 end\
4981 local t2 = { }\
4982 for k, v in pairs( t ) do\
4983 t2[#t2+1] = k\
4984 end\
4985 return t2\
4986end\
4987\
4988-- caching\
4989\
4990local cache = { }\
4991local function filecacheset( path, value )\
4992 path = path:lower( )\
4993 cache[path] = value\
4994end\
4995local function filecacheget( path )\
4996 path = path:lower( )\
4997 return cache[path]\
4998end\
4999\
5000function clearCache( )\
5001 cache = { }\
5002end\
5003\
5004-- read/write stuff (cache stuff!)\
5005\
5006local function read( path )\
5007 if type( filecacheget( path ) ) == \"table\" then return false, \"could not read folder\" end\
5008 local drive, err = getDrive( path )\
5009 if not drive then\
5010 return false, err\
5011 end\
5012 local content = filecacheget( path )\
5013 if content then\
5014 return content\
5015 end\
5016 local h = drive.open( path:gsub( \".-:\", \"\", 1 ), \"r\" )\
5017 if h then\
5018 local content = h.readAll( )\
5019 h.close( )\
5020 return content\
5021 else\
5022 return false, \"could not open file\"\
5023 end\
5024end\
5025local function write( path, content )\
5026 if type( filecacheget( path ) ) == \"table\" then return false, \"could not write to folder\" end\
5027 local drive, err = getDrive( path )\
5028 if not drive then\
5029 return false, err\
5030 end\
5031 local h = drive.open( path:gsub( \".-:\", \"\", 1 ), \"w\" )\
5032 if h then\
5033 h.write( content )\
5034 h.close( )\
5035 filecacheset( path, content )\
5036 return true\
5037 else\
5038 return false, \"could not open file\"\
5039 end\
5040end\
5041\
5042-- name operations\
5043\
5044function getDirectory( path )\
5045 if not path:find \"/\" then return \"\" end\
5046 return path:gsub( \"/.-$\", \"\" ) -- remove filename\
5047end\
5048\
5049function getPath( path )\
5050 if not path:find \"/\" then return \"\" end\
5051 return path:gsub( \"/.-$\", \"\" ) -- remove filename\
5052end\
5053\
5054function getName( path, trimExt )\
5055 path = path:gsub( \"^.+/\", \"\" ) -- remove directories\
5056 if trimExt and path:sub( 2 ):find \"%.\" then\
5057 return path:sub( 1, 1 ) .. path:sub( 2 ):gsub( \"%.%w+$\", \"\" ) -- remove extension\
5058 end\
5059 return path\
5060end\
5061\
5062function getExtension( path )\
5063 path = path:gsub( \"^.+/\", \"\" )\
5064 if not path:sub( 2 ):find \"%.\" then\
5065 return \"\"\
5066 end\
5067 return path:gsub( \".+%.\", \"\" ) -- remove directories, then stuff before the .\
5068end\
5069\
5070function merge( path, ... )\
5071 local paths = { ... }\
5072 for i = 1, #paths do\
5073 path = path .. \"/\" .. tostring( paths[i] )\
5074 end\
5075 return path:gsub( \"//+\", \"/\" )\
5076end\
5077\
5078-- file methods\
5079\
5080function readfile( path, password )\
5081 local content, err = read( path )\
5082 if content then\
5083 return FileData( content, password )\
5084 end\
5085 return false, err\
5086end\
5087\
5088function writefile( path, data, password )\
5089 if password then\
5090 data.password = password\
5091 end\
5092 return write( path, data:compile( ) )\
5093end\
5094\
5095function appendfile( path, new, password, readpass )\
5096 local data, err = readfile( path, readpass or password )\
5097 if data then\
5098 data.content = data.content .. new\
5099 if password then\
5100 data.password = password\
5101 end\
5102 return write( path, password, data:compile( ) )\
5103 end\
5104 return false, err\
5105end\
5106\
5107function getType( path )\
5108 if type( filecacheget( path ) ) ~= \"string\" and ( type( filecacheget( path ) ) == \"table\" or isDirectory( path ) ) then\
5109 filecacheset( path, { } )\
5110 return \"folder\"\
5111 end\
5112 local content, err = read( path )\
5113 if not content then\
5114 return \"unknown\"\
5115 end\
5116 local data = FileData( content )\
5117 if data.meta.type then\
5118 return data.meta.type\
5119 end\
5120 local ext = getExtension( path )\
5121 if #ext == 0 then return \"unknown\" end\
5122 return extensions[ext] or \".\" .. ext\
5123end\
5124\
5125function getFileMetaValue( path, index )\
5126 if type( filecacheget( path ) ) == \"table\" or ( type( filecacheget( path ) ) ~= \"string\" and isDirectory( path ) ) then\
5127 return false, \"folders can't have metadata\"\
5128 end\
5129 local content, err = read( path )\
5130 if not content then\
5131 return false, err\
5132 end\
5133 return true, FileData( content ).meta[index]\
5134end\
5135\
5136function isProtected( path )\
5137 if type( filecacheget( path ) ) == \"table\" then return false end\
5138 local content, err = read( path )\
5139 if content then\
5140 if content:find \"^--%[%[.-%]%]\" then\
5141 local metadata = textutils.unserialize( \"{\" .. content:match \"^--%[%[(.-)%]%]\" .. \"}\" ) or { }\
5142 return metadata.password\
5143 end\
5144 return false\
5145 end\
5146 return false, err\
5147end\
5148\
5149function newFile( path, content, password )\
5150 if filecacheget( path ) then return false, \"file exists\" end\
5151 if exists( path ) then return false, \"file exists\" end\
5152 local fd = FileData( content )\
5153 if password then\
5154 fd.password = password\
5155 end\
5156 write( path, fd:compile( ) )\
5157 return true\
5158end\
5159\
5160-- directory methods\
5161\
5162function newDirectory( path )\
5163 if type( filecacheget( path ) ) == \"string\" then return false, \"file exists\" end\
5164 if filecacheget( path ) then return true end\
5165 local drive, err = getDrive( path )\
5166 if not drive then\
5167 return false, err\
5168 end\
5169 if drive.exists( path:gsub( \"^.-:\", \"\", 1 ) ) then\
5170 if drive.isDir( path:gsub( \"^.-:\", \"\", 1 ) ) then\
5171 filecacheset( path, { } )\
5172 else\
5173 return false, \"file exists\"\
5174 end\
5175 else\
5176 drive.makeDir( path:gsub( \"^.-:\", \"\", 1 ) )\
5177 filecacheset( path, { } )\
5178 end\
5179end\
5180\
5181function listFiles( path, sorter )\
5182 if type( filecacheget( path ) ) == \"string\" then return false, \"not a directory\" end\
5183 local drive, err = getDrive( path )\
5184 if not drive then\
5185 return false, err\
5186 end\
5187 if type( filecacheget( path ) ) == \"string\" or drive.isDir( path:gsub( \"^.-:\", \"\", 1 ) ) then\
5188 local files = drive.list( path:gsub( \"^.-:\", \"\", 1 ) )\
5189 if files then\
5190 if sorter then\
5191 sorter( files )\
5192 end\
5193 end\
5194 return files\
5195 end\
5196 return false, \"not a directory\"\
5197end\
5198\
5199function emptyDirectory( path, filter )\
5200\
5201end\
5202\
5203-- existance checking\
5204\
5205function isFile( path ) -- bool isFile\
5206 if type( filecacheget( path ) ) == \"string\" then return true end\
5207 if type( filecacheget( path ) ) == \"table\" then return false end\
5208 local drive, err = getDrive( path )\
5209 if not drive then\
5210 return false\
5211 end\
5212 return drive.exists( path:gsub( \"^.-:\", \"\", 1 ) ) and not drive.isDir( path:gsub( \"^.-:\", \"\", 1 ) )\
5213end\
5214\
5215function isDirectory( path )\
5216 if type( filecacheget( path ) ) == \"string\" then return false end\
5217 if type( filecacheget( path ) ) == \"table\" then return true end\
5218 local drive, err = getDrive( path )\
5219 if not drive then\
5220 return false\
5221 end\
5222 local isdir = drive.isDir( path:gsub( \"^.-:\", \"\", 1 ) )\
5223 if isdir then filecacheset( path, { } ) end\
5224 return isdir\
5225end\
5226\
5227function exists( path )\
5228 if filecacheget( path ) then return true end\
5229 local drive, err = getDrive( path )\
5230 if not drive then\
5231 return false\
5232 end\
5233 return drive.exists( path:gsub( \"^.-:\", \"\", 1 ) )\
5234end\
5235\
5236-- space checking\
5237\
5238function getSize( path )\
5239 local drive, err = getDrive( path )\
5240 if not drive then\
5241 return 0\
5242 end\
5243 return drive.getSize( path:gsub( \"^.-:\", \"\", 1 ) )\
5244end\
5245\
5246function getSpace( path )\
5247 local drive, err = getDrive( path )\
5248 if not drive then\
5249 return 0\
5250 end\
5251 return drive.getFreeSpace( path:gsub( \"^.-:\", \"\", 1 ) )\
5252end\
5253\
5254function isReadOnly( path )\
5255 local drive, err = getDrive( path )\
5256 if not drive then\
5257 return false\
5258 end\
5259 return drive.isReadOnly( path:gsub( \"^.-:\", \"\", 1 ) )\
5260end\
5261\
5262-- mixed methods\
5263\
5264function delete( path )\
5265 local drive, err = getDrive( path )\
5266 if not drive then\
5267 return false, err\
5268 end\
5269 filecacheset( path, nil )\
5270 return drive.delete( path:gsub( \"^.-:\", \"\", 1 ) )\
5271end\
5272\
5273function copy( path )\
5274 local drive, err = getDrive( path )\
5275 if not drive then\
5276 return false, err\
5277 end\
5278 if isFile( path ) then\
5279 local data, err = readfile( path )\
5280 if data then\
5281 clipboard.set( \"file\", { name = getName( path ), file = data } )\
5282 else\
5283 return false, err\
5284 end\
5285 elseif isDirectory( path ) then\
5286 local a = Archive( )\
5287 a:loadDirectory( path )\
5288 clipboard.set( \"file\", { name = getName( path ), file = a } )\
5289 else\
5290 return false, \"no such file\"\
5291 end\
5292 return true\
5293end\
5294\
5295function cut( path )\
5296 local drive, err = getDrive( path )\
5297 if not drive then\
5298 return false, err\
5299 end\
5300 if isFile( path ) then\
5301 local data, err = readfile( path )\
5302 if data then\
5303 clipboard.set( \"file\", { name = getName( path ), file = data } )\
5304 else\
5305 return false, err\
5306 end\
5307 elseif isDirectory( path ) then\
5308 local a = Archive( )\
5309 a:loadDirectory( path )\
5310 clipboard.set( \"file\", { name = getName( path ), file = a } )\
5311 else\
5312 return false, \"no such file\"\
5313 end\
5314 delete( path )\
5315 return true\
5316end\
5317\
5318function paste( up_path )\
5319 up_path = type( up_path ) == \"string\" and up_path or \"\"\
5320 local mode, data = clipboard.get( )\
5321 if mode == \"file\" then\
5322 local n = up_path .. \"/\" .. data.name\
5323 if filesystem.exists( n ) then\
5324 local name = up_path .. \"/\" .. getName( data.name, true ) .. \" - Copy (\"\
5325 local ext = getExtension( data.name )\
5326 if #ext > 0 then\
5327 ext = \".\" .. ext\
5328 end\
5329 local i = 1\
5330 while filesystem.exists( name .. i .. \")\" .. ext ) do\
5331 i = i + 1\
5332 end\
5333 n = name .. i .. \").\" .. ext\
5334 end\
5335 if data.file:type( ) == \"Archive\" then\
5336 data.file:saveDirectory( n )\
5337 else\
5338 write( n, data.file:compile( ) )\
5339 end\
5340 end\
5341end\
5342\
5343function rename( path, name )\
5344 local drive, err = getDrive( path )\
5345 if not drive then\
5346 return false, err\
5347 end\
5348 filecacheset( path, nil )\
5349 local path = path:gsub( \"^.-:\", \"\", 1 )\
5350 return drive.move( path, path:gsub( getName( path ) .. \"$\", name, 1 ) )\
5351end\
5352\
5353function move( path, path2 )\
5354 local drive, err = getDrive( path )\
5355 if not drive then\
5356 return false, err\
5357 end\
5358 filecacheset( path, nil )\
5359 local path = path:gsub( \"^.-:\", \"\", 1 )\
5360 local path2 = path2:gsub( \"^.-:\", \"\", 1 )\
5361 return drive.move( path, path2 )\
5362end", meta={
5363 type = "lib",
5364}};["Session"]={content="\
5365\
5366local imageraw = { }\
5367imageraw[0] = [[ F BF BF BF F \
5368BF F F F BF \
5369BF F F F BF \
5370BF F F F BF \
5371BF F F F BF \
5372BF F F F BF \
5373 F BF BF BF F ]]\
5374imageraw[1] = [[ F BF BF F F \
5375BF F BF F F \
5376 F F BF F F \
5377 F F BF F F \
5378 F F BF F F \
5379 F F BF F F \
5380BF BF BF BF BF ]]\
5381imageraw[2] = [[ F BF BF BF F \
5382BF F F F BF \
5383 F F F F BF \
5384 F F F BF F \
5385 F F BF F F \
5386 F BF F F F \
5387BF BF BF BF BF ]]\
5388imageraw[3] = [[ F BF BF BF F \
5389BF F F F BF \
5390 F F F F BF \
5391 F BF BF BF F \
5392 F F F F BF \
5393BF F F F BF \
5394 F BF BF BF F ]]\
5395imageraw[4] = [[ F F F BF F \
5396 F F BF BF F \
5397 F BF F BF F \
5398 F BF F BF F \
5399BF F F BF F \
5400BF BF BF BF BF \
5401 F F F BF F ]]\
5402imageraw[5] = [[BF BF BF BF BF \
5403BF F F F F \
5404BF F F F F \
5405BF BF BF BF F \
5406 F F F F BF \
5407BF F F F BF \
5408 F BF BF BF F ]]\
5409imageraw[6] = [[ F BF BF BF F \
5410BF F F F BF \
5411BF F F F F \
5412BF BF BF BF F \
5413BF F F F BF \
5414BF F F F BF \
5415 F BF BF BF F ]]\
5416imageraw[7] = [[BF BF BF BF BF \
5417 F F F F BF \
5418 F F F BF F \
5419 F F BF F F \
5420 F F BF F F \
5421 F BF F F F \
5422BF F F F F ]]\
5423imageraw[8] = [[ F BF BF BF F \
5424BF F F F BF \
5425BF F F F BF \
5426 F BF BF BF F \
5427BF F F F BF \
5428BF F F F BF \
5429 F BF BF BF F ]]\
5430imageraw[9] = [[ F BF BF BF F \
5431BF F F F BF \
5432BF F F F BF \
5433 F BF BF BF BF \
5434 F F F F BF \
5435 F F F BF F \
5436 F BF BF F F ]]\
5437\
5438local images = { }\
5439for i = 0, 9 do\
5440 images[i] = Image( 5, 7 )\
5441 images[i]:loadstr( imageraw[i] or \"\" )\
5442end\
5443\
5444images.home = Image( 8, 5 ):loadstr [[ F F CF CF CF CF F F \
5445 F CF CF CF CF CF CF F \
5446CF CF CF CF CF CF CF CF \
5447 F 8F 8F 8F 3F 3F 8F F \
5448 F 8F 7F 8F 8F 8F 8F F ]]\
5449images.shutdown = Image( 7, 5 ):loadstr [[ F F F EF F F F \
5450 F EF F EF F EF F \
5451EF F F F F F EF \
5452EF F F F F F EF \
5453 F EF EF EF EF EF F ]]\
5454images.screenshot = Image( 8, 5 ):loadstr [[ F F F F F 7F 7F F \
54557F 7F FF FF 7F 7F 7F 7F \
54567F FF 0F 3F FF 7F EF 7F \
54577F FF 3F 3F FF 7F DF 7F \
54587F 7F FF FF 7F 7F 7F 7F ]]\
5459\
5460images.files = Image( 9, 3 ):loadstr [[1F 1F 1F 1F 1F 1F 1F 1F 1F \
54614F 4F 47F47i47l47e47s4F 4F \
54624F 4F 4F 4F 4F 4F 4F 4F 4F ]]\
5463images.edit = Image( 9, 3 ):loadstr [[78178:77 0F 07E07d07i07t0F \
546478278:77 0F 08-08-08-08-0F \
546578378:77 0F 08-08-08-08-0F ]]\
5466images.paint = Image( 9, 3 ):loadstr [[BF 3F 3B-3B-3B-3B-3B-3F BF \
5467BF 3F 3EP3Ea3Ei3En3Et3F BF \
5468BF 3F 3B-3B-3B-3B-3B-3F BF ]]\
5469images.run = Image( 9, 3 ):loadstr [[F0RF0uF0nF4 F4 F4 F4 F4 F0 \
5470F4 F4CF4rF4aF4fF4tF4OF4SF0 \
5471F4>F4 F4_F0 F0 F0 F0 F0 F0 ]]\
5472images.settings = Image( 9, 4 ):loadstr[[ 7S 7e 7t 7t 7i 7n 7g 7s \
5473 7[ Bx 7] 7. 7. 7. \
5474 7[ 7] 7. 7. 7. \
5475 7[ Bx 7] 7. 7. 7. ]]\
5476\
5477images.folder = Image( 5, 3 ):loadstr [[10 10 10 0F 0F \
547840 40 40 4F 4F \
547940 40 40 40 40 ]]\
5480\
5481local function login( name, pass )\
5482\
5483 if #( fs.list( core.path .. \"/user\" ) or {} ) == 0 then\
5484 kernel.setup( )\
5485 end\
5486\
5487 local waiting = true\
5488 local account\
5489\
5490 local frame = core.UI:newChild( UIFrame( 1, 1, term.getSize( ) ) )\
5491 local input = { }\
5492 local title = { }\
5493\
5494 title = frame:newChild( UIText( 1, 1, frame.w, 3, \"\\n Login\" ) )\
5495 title.bc = colours.grey\
5496 title.tc = colours.white\
5497\
5498 local userlist = frame:newChild( UIMenu( 2, 8, frame.w - 2, 4, \"horizontal\" ) )\
5499 userlist.padding = 1\
5500 local scrollbar = frame:newChild( UIScrollBar( 2, 13, frame.w - 2, 1, userlist, \"horizontal\" ) )\
5501 scrollbar.bc = colours.lightGrey\
5502 scrollbar.tc = colours.grey\
5503\
5504 local password = { }\
5505\
5506 local cols = { colours.blue, colours.red, colours.green, colours.yellow }\
5507 local n = 1\
5508\
5509 local users = fs.list( core.path .. \"/user\" )\
5510 for i = 1, #users do\
5511 local f = userlist:newChild( UIFrame( 0, 0, 10, 4 ) )\
5512 f:newChild( UIButton( 2, 1, f.w - 2, f.h - 1, \"\" ) ).bc = cols[n]\
5513 f:newChild( UIText( 1, f.h, #users[i], 1, users[i] ) ):centreX( )\
5514 c = f:newChild( UIButton( 1, 1, f.w, f.h, \"\" ) )\
5515 c.bc = 0\
5516 c.align = false\
5517 function c:onClick( x )\
5518 if password.input then\
5519 password.input:remove( )\
5520 password.title:remove( )\
5521 end\
5522 password.title = frame:newChild( UIText( 0, 15, 8, 1, \"password\" ) )\
5523 password.title:centreX( )\
5524 password.input = frame:newChild( UIInput( 0, 16, math.floor( term.getSize( ) / 2 ), 1 ) )\
5525 password.input.mask = \"*\"\
5526 password.input:centreX( )\
5527 password.input:focusOn( )\
5528 function password.input:whenUnFocussed( )\
5529 password.input:remove( )\
5530 password.title:remove( )\
5531 password = { }\
5532 end\
5533 function password.input:onEnter( )\
5534 local err\
5535 account, err = Account( users[i], password.input.text )\
5536 if account then\
5537 waiting = false\
5538 else\
5539 Thread( function( )\
5540 local alert = frame:newChild( UIText( 0, ({term.getSize()})[2] - 1, #err, 1, err ) )\
5541 alert.bc = 0\
5542 alert.tc = colours.red\
5543 alert:centreX( )\
5544 sleep( 1 )\
5545 alert:remove( )\
5546 end )\
5547 end\
5548 end\
5549 if type( x ) == \"string\" then\
5550 password.input.text = x\
5551 password.input:onEnter( )\
5552 end\
5553 end\
5554\
5555 if users[i] == name then\
5556 c:onClick( pass )\
5557 end\
5558\
5559 n = n + 1\
5560 if n == 5 then\
5561 n = 1\
5562 end\
5563\
5564 if #users == 1 then\
5565 c:onClick( )\
5566 end\
5567 end\
5568 if #users * 11 <= userlist.w then\
5569 userlist.w = #users * 11 - 1\
5570 userlist:centreX( )\
5571 scrollbar:remove( )\
5572 end\
5573\
5574 local mag_detector = Thread( function( )\
5575 while true do\
5576 local ev = { coroutine.yield( ) }\
5577 if password.input then\
5578 if ev[1] == \"mag_swipe\" then\
5579 password.input.text = ev[2]\
5580 password.input:onEnter( )\
5581 end\
5582 end\
5583 end\
5584 end )\
5585\
5586 while waiting do\
5587 coroutine.yield( )\
5588 end\
5589 frame:remove( )\
5590 mag_detector:stop( )\
5591 return account\
5592end\
5593\
5594Session.public \"account\"\
5595Session.public.account.write = false\
5596Session.public \"UI\"\
5597Session.public.UI.write = false\
5598Session.public \"app_paths\"\
5599Session.public.app_paths.write = false\
5600Session.public \"apps\"\
5601Session.public.apps.write = false\
5602Session.public \"running\"\
5603Session.public.running.write = false\
5604Session.public \"background\"\
5605Session.public.background.write = false\
5606\
5607function Session:Session( name, pass )\
5608 if core.session then\
5609 core.session:logout( )\
5610 end\
5611\
5612 self.account = login( name, pass )\
5613 core.log( \"Starting session\", self.account.username )\
5614\
5615 filesystem.mount( Drive.redirect( self.account.userpath .. \"/data\" ), \"user\" )\
5616\
5617 self.UI = core.UI:newChild( UIFrame( 1, 1, core.UI.w, core.UI.h ) )\
5618 self.apps = { }\
5619 self.app_paths = { }\
5620 self.threads = { }\
5621 self.running = { }\
5622 self.background = { }\
5623\
5624 local files = fs.list( core.path .. \"/apps\" )\
5625 for i = 1, #files do\
5626 local ok, err = self.public:registerApp( core.path .. \"/apps/\" .. files[i] )\
5627 if not ok then\
5628 core.log( err )\
5629 end\
5630 end\
5631\
5632 local t = self.public:newThread( Thread( function( )\
5633 while true do\
5634 for i = #self.threads, 1, -1 do\
5635 if not self.threads[i]:isRunning( ) and not self.threads[i]:isPaused( ) then\
5636 self.threads[i]:stop( ) -- just to make sure it stops\
5637 table.remove( self.threads, i )\
5638 end\
5639 end\
5640 coroutine.yield( )\
5641 end\
5642 end ) )\
5643 function t:onException( err )\
5644 core.log( \"Session error\", err )\
5645 end\
5646\
5647 self.public:showHomepage( )\
5648\
5649 local updateInfo = core.getUpdateInfo( )\
5650 if core.compareVersions( updateInfo ) then\
5651 local response = display.confirm( self.UI, \"Update found, would you like to update?\" )\
5652 if response then\
5653 if core.update( ) then\
5654 if display.confirm( self.UI, \"Updated, would you like to restart?\" ) then\
5655 self.public:logout( )\
5656 core.finish( )\
5657 os.reboot( )\
5658 end\
5659 else\
5660 NovaUI.display.alert( UI, \"Failed to download update\" )\
5661 end\
5662 end\
5663 end\
5664\
5665 core.session = self.public\
5666 return self.public\
5667end\
5668\
5669function Session.public:logout( )\
5670 filesystem.unmount \"user\"\
5671 for i = 1, #self.threads do\
5672 self.threads[i]:stop( )\
5673 end\
5674 for i = #self.running, 1, -1 do\
5675 self.running[i]:close \"logout\"\
5676 end\
5677 for i = #self.background, 1, -1 do\
5678 self.background[i]:close \"logout\"\
5679 end\
5680 self.UI:remove( )\
5681 core.session = false\
5682end\
5683\
5684function Session.public:registerApp( path )\
5685 local app, err = App( self.public, path )\
5686 if app then\
5687 self.apps[app.name] = app\
5688 self.app_paths[path] = app\
5689 end\
5690 return app, err\
5691end\
5692\
5693function Session.public:isRegistered( name )\
5694 return not not self.apps[name]\
5695end\
5696\
5697function Session.public:launch( name, args )\
5698 if not self.apps[name] then\
5699 return false, \"app not found\"\
5700 end\
5701\
5702 if self.apps[name].single then\
5703 for i = 1, #self.running do\
5704 if self.running[i].app.name == name then\
5705 local app = self.running[i]\
5706 self.public:hide( )\
5707 self.public:show( app )\
5708 return true\
5709 end\
5710 end\
5711 end\
5712\
5713 -- create the app instance\
5714 local instance = self.apps[name]:launch( args or { } )\
5715\
5716 -- show the UI\
5717 if self.homepage then\
5718 self.homepage:remove( )\
5719 self.homepage = nil\
5720 elseif self.running[#self.running] then\
5721 self.running[#self.running]:hideUI( )\
5722 end\
5723 if self.overlay then\
5724 self.overlay:remove( )\
5725 self.overlay = nil\
5726 end\
5727\
5728 -- add it to the list\
5729 table.insert( self.running, instance )\
5730\
5731 return instance\
5732end\
5733\
5734function Session.public:launchBackground( name, args )\
5735 if not self.apps[name] then\
5736 return false, \"app not found\"\
5737 end\
5738\
5739 -- create the app instance\
5740 local instance = self.apps[name]:launch( args or { }, true )\
5741\
5742 -- add it to the list\
5743 table.insert( self.background, instance )\
5744\
5745 return instance\
5746end\
5747\
5748function Session.public:hide( )\
5749 local instance = self.running[#self.running]\
5750 if instance then\
5751 table.remove( self.running, #self.running )\
5752 table.insert( self.running, 1, instance )\
5753 instance:hideUI( )\
5754 end\
5755end\
5756\
5757function Session.public:show( instance )\
5758 if not instance then\
5759 instance = self.running[#self.running]\
5760 end\
5761 for i = #self.running, 1, -1 do\
5762 if self.running[i] == instance then\
5763 if self.overlay then self.overlay:remove( ) self.overlay = nil end\
5764 if self.homepage then self.homepage:remove( ) self.homepage = nil end\
5765 instance:showUI( )\
5766 table.insert( self.running, instance )\
5767 table.remove( self.running, i )\
5768 return true\
5769 end\
5770 end\
5771 return false, \"instance not running\"\
5772end\
5773\
5774function Session.public:close( instance, reason )\
5775 if not instance then\
5776 instance = self.running[#self.running]\
5777 end\
5778 for i = #self.running, 1, -1 do\
5779 if self.running[i] == instance then\
5780 table.remove( self.running, i )\
5781 if #self.running == 0 then\
5782 self.public:showHomepage( )\
5783 else\
5784 self.public:show( )\
5785 end\
5786 return true\
5787 end\
5788 end\
5789 for i = #self.background, 1, -1 do\
5790 if self.background[i] == instance then\
5791 table.remove( self.background, i )\
5792 return true\
5793 end\
5794 end\
5795 return false, \"instance not running\"\
5796end\
5797\
5798function Session.public:newThread( thread )\
5799 table.insert( self.threads, thread )\
5800 return thread\
5801end\
5802\
5803function Session.public:showOverlay( )\
5804 if self.overlay then\
5805 self.overlay:remove( )\
5806 self.overlay = nil\
5807 end\
5808 local frame = self.UI:newChild( UIFrame( 1, 1, self.UI.w, self.UI.h ) )\
5809 self.overlay = frame\
5810\
5811 local timethread\
5812\
5813 local close = frame:newChild( UIButton( 1, 1, frame.w, frame.h, \"\" ) )\
5814 close.bc = 0\
5815 close.align = false\
5816 function close:onClick( )\
5817 timethread:stop( )\
5818 frame:remove( )\
5819 end\
5820 frame:newChild( UIButton( 1, 2, self.UI.w, self.UI.h - 1 ) ).bc = colours.white\
5821\
5822 local timebox = frame:newChild( UIFrame( 3, 3, 27, 9 ) )\
5823 timethread = self.public:newThread( Thread( function( )\
5824 while true do\
5825 timebox:clearChildren( )\
5826 local t = tostring( textutils.formatTime( os.time( ) ) )\
5827 pcall( function( )\
5828 local _t = core.getirltime( )\
5829 if _t then\
5830 if _t.m < 10 then _t.m = \"0\" .. _t.m end\
5831 if _t.h < 10 then _t.h = \"0\" .. _t.h end\
5832 t = _t.h .. \":\" .. _t.m\
5833 end\
5834 end )\
5835 local tb1 = timebox:newChild( UIImage( 2, 2, 5, 7, images[tonumber( t:sub( 1, 1 ) )] ) )\
5836 local tb2 = timebox:newChild( UIImage( 8, 2, 5, 7, images[tonumber( t:sub( 2, 2 ) )] ) )\
5837 local tb3 = timebox:newChild( UIImage( 16, 2, 5, 7, images[tonumber( t:sub( 4, 4 ) )] ) )\
5838 local tb4 = timebox:newChild( UIImage( 22, 2, 5, 7, images[tonumber( t:sub( 5, 5 ) )] ) )\
5839 timebox:newChild( UIButton( 14, 3, 1, 1, \" \" ) ).bc = colours.blue\
5840 timebox:newChild( UIButton( 14, 7, 1, 1, \" \" ) ).bc = colours.blue\
5841 coroutine.yield( )\
5842 end\
5843 end ) )\
5844\
5845 local home = frame:newChild( UIImage( 0, 0, 8, 5, images.home ) )\
5846 home:align( \"below\", timebox, 1 )\
5847 home.w = 8\
5848 function home.onClick( )\
5849 if not self.homepage then\
5850 if self.running[#self.running] then\
5851 self.running[#self.running]:hideUI( )\
5852 end\
5853 self.public:showHomepage( )\
5854 end\
5855 end\
5856 local screenshot = frame:newChild( UIImage( 0, 0, 8, 5, images.screenshot ) )\
5857 screenshot:align( \"right\", home, 2 )\
5858 function screenshot.onClick( )\
5859 frame:remove( )\
5860 self.overlay = nil\
5861 Thread( function( )\
5862 coroutine.yield( )\
5863 local w, h = term.getSize( )\
5864 local im = Image( w, h )\
5865 for x = 1, w do\
5866 for y = 1, h do\
5867 local bc, tc, char = buffer.getPixel( x, y )\
5868 if bc then\
5869 im:pixel( x, y, bc, tc, char )\
5870 end\
5871 end\
5872 end\
5873 local h = fs.open( self.account.userpath .. \"/data/screenshots/\" .. os.time( ) .. \".nim\", \"w\" )\
5874 if h then\
5875 h.write( im:savestr( ) )\
5876 h.close( )\
5877 end\
5878 end ).onException = core.log\
5879 end\
5880 local power = frame:newChild( UIImage( 0, 0, 7, 5, images.shutdown ) )\
5881 power:align( \"right\", screenshot, 2 )\
5882 function power.onClick( )\
5883 local dropdown = frame:newChild( UIFrame( 0, 0, 10, 3 ) )\
5884 dropdown:align( \"left\", power )\
5885 local shutdown = dropdown:newChild( UIButton( 1, 2, 10, 1, \"Shutdown\" ) )\
5886 local restart = dropdown:newChild( UIButton( 1, 3, 10, 1, \"Restart\" ) )\
5887 local logout = dropdown:newChild( UIButton( 1, 4, 10, 1, \"Logout\" ) )\
5888 function shutdown:onClick( )\
5889 core.finish( )\
5890 os.shutdown( )\
5891 end\
5892 function restart:onClick( )\
5893 core.finish( )\
5894 os.reboot( )\
5895 end\
5896 function logout.onClick( )\
5897 Thread( function( )\
5898 self.public:logout( )\
5899 core.session = Session( )\
5900 end )\
5901 end\
5902 end\
5903\
5904 if core.platform == \"pocket\" then\
5905 timebox.x = timebox.x - 2\
5906 home.x = home.x - 2\
5907 screenshot.x = screenshot.x - 3\
5908 power.x = power.x - 4\
5909 end\
5910\
5911 -- running apps\
5912 local w, h = term.getSize( )\
5913\
5914 local x, y = timebox.x + timebox.w + 1, timebox.y\
5915\
5916 local list = frame:newChild( UIFrame( x, y, 0, 0, \"vertical\" ) )\
5917 list.w = w - list.x - 1\
5918 list.h = h - list.y\
5919\
5920 if core.platform ~= \"pocket\" then\
5921 local scrollbar = frame:newChild( UIScrollBar( 0, 0, 1, 0, list, \"vertical\" ) )\
5922 scrollbar:align( \"right\", list )\
5923 -- now let's add the list of running apps...\
5924 self.public:newThread( Thread( function( )\
5925 while true do\
5926 -- clear everything in the list\
5927 list:clearChildren( )\
5928 if #self.running == 0 then\
5929 local m = list:newChild( UIText( 0, 2, 15, 1, \"no running apps\" ) ):centreX( )\
5930 m.bc = 0\
5931 m.tc = colours.grey\
5932 end\
5933 -- add each running app\
5934 for i = 1, #self.running do\
5935 local instance = self.running[i]\
5936 local name = instance.app.name\
5937 local icon = list:newChild( UIImage( 1, (i-1)*4+1, 9, 3, self.running[i].app.icon ) )\
5938 icon:centreX( )\
5939 function icon.onClick( )\
5940 frame:remove( )\
5941 if i ~= #self.running then\
5942 self.public:hide( )\
5943 self.public:show( instance )\
5944 end\
5945 end\
5946 -- the x, width and y are auto-generated in a UIMenu\
5947 end\
5948 coroutine.yield( )\
5949 end\
5950 end ) ).onException = core.log -- add a background function that constantly runs\
5951 end\
5952end\
5953\
5954function Session.public:showHomepage( ) -- this is what brings up the homepage UI\
5955 if self.homepage then\
5956 self.homepage:remove( )\
5957 self.homepage = nil\
5958 end\
5959 if self.overlay then\
5960 self.overlay:remove( )\
5961 self.overlay = nil\
5962 end\
5963 local frame = self.UI:newChild( UIFrame( 1, 1, self.UI.w, self.UI.h ) )\
5964 self.homepage = frame\
5965\
5966 -- background\
5967 frame:newChild( UIButton( 1, 1, frame.w, 3 ) ).bc = colours.grey\
5968\
5969 -- nova button\
5970 local nova = frame:newChild( UIButton( 1, 1, 4, 1, \"Nova\" ) )\
5971 nova.bc = 0\
5972 nova.tc = colours.white\
5973 function nova.onClick( )\
5974 self.public:showOverlay( )\
5975 end\
5976 -- nova version label\
5977 local text = core.name:gsub( \"^Nova\", \"\" )\
5978 local name = frame:newChild( UIText( 5, 1, #text, 1, text ) )\
5979 name.bc = 0\
5980 name.tc = colours.lightGrey\
5981\
5982 -- time frame\
5983 local time = frame:newChild( UIText( frame.w - 4, 1, 5, 1, function( )\
5984 local t = tostring( textutils.formatTime( os.time( ) ) )\
5985 pcall( function( )\
5986 local _t = core.getirltime( )\
5987 if _t then\
5988 if _t.m < 10 then _t.m = \"0\" .. _t.m end\
5989 if _t.h < 10 then _t.h = \"0\" .. _t.h end\
5990 t = _t.h .. \":\" .. _t.m\
5991 end\
5992 end )\
5993 return t\
5994 end ) )\
5995 time.bc = 0\
5996 time.tc = colours.lightGrey\
5997\
5998 -- home label\
5999 local home = frame:newChild( UIText( 0, 2, 4, 1, \"Home\" ) )\
6000 home:centreX( )\
6001 home.bc = 0\
6002 home.tc = colours.cyan\
6003\
6004 -- about button\
6005 local about = frame:newChild( UIButton( 3, frame.h - 1, 5, 1, \"About\" ) )\
6006 about.tc = colours.grey\
6007 function about.onClick( )\
6008 self:showAbout( )\
6009 end\
6010\
6011 -- help button\
6012 local help = frame:newChild( UIButton( frame.w - 5, frame.h - 1, 4, 1, \"Help\" ) )\
6013 help.tc = colours.grey\
6014 function help.onClick( )\
6015 Thread( function( )\
6016 display.alert( self.UI, \"Not yet implemented, sorry!\", true )\
6017 display.alert( self.UI, \"You can always ask for help on the forum post.\" )\
6018 end )\
6019 end\
6020\
6021 if core.platform == \"computer\" or core.platform == \"turtle\" then\
6022 -- Files\
6023 local icon1UI = frame:newChild( UIImage( 5, 6, 9, 3, images.files ) )\
6024 function icon1UI.onClick( )\
6025 self.public:launch \"Files\"\
6026 end\
6027 -- Edit\
6028 local icon2UI = frame:newChild( UIImage( 0, 0, 9, 3, images.edit ) )\
6029 icon2UI:align( \"right\", icon1UI, 2 )\
6030 function icon2UI.onClick( )\
6031 self.public:launch \"Edit\"\
6032 end\
6033 -- Paint\
6034 local icon3UI = frame:newChild( UIImage( 0, 0, 9, 3, images.paint ) )\
6035 icon3UI:align( \"right\", icon2UI, 2 )\
6036 function icon3UI.onClick( )\
6037 self.public:launch \"Paint\"\
6038 end\
6039 -- Run\
6040 local icon4UI = frame:newChild( UIImage( 0, 0, 9, 3, images.run ) )\
6041 icon4UI:align( \"right\", icon3UI, 2 )\
6042 function icon4UI.onClick( )\
6043 self.public:launch \"Run\"\
6044 end\
6045\
6046 -- Apps shortcut\
6047 local icon5UI = frame:newChild( UIImage( 0, 0, 5, 4, images.folder ) )\
6048 icon5UI:align( \"below\", icon1UI, 2 )\
6049 icon5UI.x = icon5UI.x + 2\
6050 function icon5UI.onClick( )\
6051 self.public:launch( \"Files\", { \"C:\" .. core.path .. \"/apps\" } )\
6052 end\
6053 local b = icon5UI:newChild( UIButton( 1, 4, 5, 1, \"apps\" ) )\
6054 b.bc, b.tc = 0, colours.grey\
6055 b.onClick = icon5UI.onClick\
6056\
6057 -- Screenshots shortcut\
6058 local icon6UI = frame:newChild( UIImage( 0, 0, 5, 3, images.folder ) )\
6059 icon6UI:align( \"below\", icon2UI, 2 )\
6060 icon6UI.x = icon6UI.x + 2\
6061 function icon6UI.onClick( )\
6062 self.public:launch( \"Files\", { \"user:/screenshots\" } )\
6063 end\
6064 local b = frame:newChild( UIButton( 0, 0, 0, 1, \"screenshots\" ) )\
6065 b.bc, b.tc = 0, colours.grey\
6066 b.onClick = icon6UI.onClick\
6067 b:alignTo( \"below\", icon6UI )\
6068 b.x = b.x - 3\
6069 b.w = 11\
6070\
6071 -- Files shortcut\
6072 local icon7UI = frame:newChild( UIImage( 0, 0, 5, 4, images.folder ) )\
6073 icon7UI:align( \"below\", icon3UI, 2 )\
6074 icon7UI.x = icon7UI.x + 2\
6075 function icon7UI.onClick( )\
6076 self.public:launch( \"Files\", { \"user:\" } )\
6077 end\
6078 local b = icon7UI:newChild( UIButton( 1, 4, 5, 1, \"files\" ) )\
6079 b.bc, b.tc = 0, colours.grey\
6080 b.onClick = icon7UI.onClick\
6081\
6082 local icon8UI = frame:newChild( UIImage( 0, 0, 9, 4, images.settings ) )\
6083 icon8UI:align( \"below\", icon4UI, 2 )\
6084 function icon8UI.onClick( )\
6085 self.public:launch \"Settings\"\
6086 end\
6087 else\
6088 -- Files\
6089 local icon1UI = frame:newChild( UIImage( 4, 6, 9, 3, images.files ) )\
6090 function icon1UI.onClick( )\
6091 self.public:launch \"Files\"\
6092 end\
6093 -- Edit\
6094 local icon2UI = frame:newChild( UIImage( 0, 0, 9, 3, images.paint ) )\
6095 icon2UI:align( \"right\", icon1UI, 2 )\
6096 function icon2UI.onClick( )\
6097 self.public:launch \"Paint\"\
6098 end\
6099 -- Paint\
6100 local icon3UI = frame:newChild( UIImage( 0, 0, 9, 3, images.edit ) )\
6101 icon3UI:align( \"below\", icon2UI, 2 )\
6102 function icon3UI.onClick( )\
6103 self.public:launch \"Edit\"\
6104 end\
6105\
6106 -- Apps shortcut\
6107 local icon5UI = frame:newChild( UIImage( 0, 0, 5, 4, images.folder ) )\
6108 icon5UI:align( \"below\", icon1UI, 2 )\
6109 icon5UI.x = icon5UI.x + 2\
6110 function icon5UI.onClick( )\
6111 self.public:launch( \"Files\", { \"C:\" .. core.path .. \"/apps\" } )\
6112 end\
6113 local b = icon5UI:newChild( UIButton( 1, 4, 5, 1, \"apps\" ) )\
6114 b.bc, b.tc = 0, colours.grey\
6115 b.onClick = icon5UI.onClick\
6116 end\
6117end\
6118\
6119function Session:showAbout( )\
6120 local frame = self.UI:newChild( UIFrame( 1, 1, self.UI.w, self.UI.h ) )\
6121 frame:newChild( UIText( 1, 1, frame.w, frame.h, \"\" ) ).bc = colours.white\
6122\
6123 local close = frame:newChild( UIButton( 0, frame.h - 1, 5, 1, \"Close\" ) ):centreX( )\
6124 close.tc = colours.grey\
6125 function close.onClick( )\
6126 frame:remove( )\
6127 end\
6128\
6129 frame:newChild( UIImage( 0, 3, 27, 4, Image( ):loadstr[[B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 \
6130B0 B0 B0 B0 B0 B0 B0 B0 B0 \
6131B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 \
6132B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 B0 ]] ) ):centreX( )\
6133\
6134 markup.load( [=[\
6135 <text x:centre y:8 width:auto tc:lightGrey>]=] .. core.name .. [=[</text>\
6136 <text x:centre y:9 width:auto tc:lightGrey>]=] .. core.version .. [=[</text>\
6137 <text x:centre y:10 width:auto tc:lightGrey>by ]=] .. core.author .. [=[</text>\
6138 <text x:2 y:13 width:expand height:4 tc:grey>Thanks to Bomb Bloke for BBTetris, and thanks to the CC community for all the feedback!</text>\
6139 ]=], frame, getfenv( ) )\
6140end", meta={
6141 type = "class",
6142}};["Sandbox"]={content="\
6143\
6144Sandbox.public \"environment\"\
6145Sandbox.public.environment.write = false\
6146Sandbox.public \"instance\"\
6147Sandbox.public.instance.write = false\
6148\
6149function Sandbox:Sandbox( instance, args )\
6150 self.instance = instance\
6151 self.environment = { }\
6152 self.environment.ARGS = args\
6153 self.environment._G = self.environment\
6154\
6155 -- Lua stuff\
6156 self.environment._VERSION = _VERSION;\
6157 self.environment.pairs = pairs;\
6158 self.environment.ipairs = ipairs;\
6159 self.environment.select = select;\
6160 self.environment.unpack = unpack;\
6161 self.environment.setfenv = setfenv;\
6162 self.environment.getfenv = getfenv;\
6163 self.environment.setmetatable = setmetatable;\
6164 self.environment.getmetatable = getmetatable;\
6165 self.environment.next = next;\
6166 self.environment.rawset = rawset;\
6167 self.environment.rawget = rawget;\
6168 self.environment.rawequal = rawequal;\
6169 self.environment.type = type;\
6170 self.environment.tostring = tostring;\
6171 self.environment.tonumber = tonumber;\
6172 self.environment.pcall = pcall;\
6173 self.environment.xpcall = xpcall;\
6174 self.environment.loadstring = loadstring;\
6175 self.environment.assert = assert;\
6176 self.environment.error = error;\
6177 self.environment.sleep = sleep;\
6178 self.environment.__inext = __inext;\
6179 self.environment.load = load\
6180 self.environment._CC_VERSION = _CC_VERSION\
6181 self.environment._MC_VERSION = _MC_VERSION\
6182 self.environment.bit32 = bit32\
6183 self.environment._LUAJ_VERSION = _LUAJ_VERSION\
6184\
6185 if instance.app.runmode ~= \"compatibility\" then\
6186 -- classes\
6187 self.environment.class = class;\
6188 self.environment.Drive = Drive;\
6189 self.environment.Archive = Archive;\
6190 self.environment.FileData = FileData;\
6191\
6192 -- NovaUI\
6193 self.environment.NovaUI = { }\
6194 self.environment.NovaUI.Image = Image;\
6195 self.environment.NovaUI.UIButton = UIButton;\
6196 self.environment.NovaUI.UICanvas = UICanvas;\
6197 self.environment.NovaUI.UICode = UICode;\
6198 self.environment.NovaUI.UIElement = UIElement;\
6199 self.environment.NovaUI.UIFrame = UIFrame;\
6200 self.environment.NovaUI.UIImage = UIImage;\
6201 self.environment.NovaUI.UIInput = UIInput;\
6202 self.environment.NovaUI.UIKeyHandler = UIKeyHandler;\
6203 self.environment.NovaUI.UIMenu = UIMenu;\
6204 self.environment.NovaUI.UIScrollBar = UIScrollBar;\
6205 self.environment.NovaUI.UIText = UIText;\
6206 self.environment.NovaUI.UITextbox = UITextbox;\
6207 self.environment.NovaUI.buffer = buffer;\
6208 self.environment.NovaUI.clipboard = clipboard;\
6209 self.environment.NovaUI.display = display;\
6210 self.environment.NovaUI.markup = markup;\
6211 self.environment.NovaUI.stencil = stencil;\
6212 self.environment.NovaUI.Thread = function( f )\
6213 return instance:newThread( Thread( f ) );\
6214 end\
6215\
6216 self.environment.markup = setmetatable( { }, { __index = markup } )\
6217 self.environment.markup.MarkupPage = MarkupPage;\
6218 end\
6219\
6220 -- libs\
6221 self.environment.math = math;\
6222 self.environment.string = string;\
6223 self.environment.table = table;\
6224 self.environment.coroutine = coroutine;\
6225 self.environment.sha256 = sha256;\
6226 self.environment.encryption = encryption;\
6227 self.environment.stringutils = stringutils;\
6228 self.environment.keys = keys;\
6229 self.environment.colours = colours;\
6230 self.environment.colors = colors;\
6231 self.environment.vector = vector;\
6232 self.environment.bit = bit;\
6233 self.environment.http = http;\
6234 self.environment.textutils = textutils;\
6235 self.environment.rednet = rednet;\
6236 self.environment.os = os;\
6237\
6238 if instance.app.runmode ~= \"compatibility\" then\
6239 -- Nova API\
6240 self.environment.Nova = { }\
6241 self.environment.Nova.name = core.name\
6242 self.environment.Nova.version = core.version\
6243 self.environment.Nova.platform = core.platform\
6244 self.environment.Nova.app = { }\
6245 self.environment.Nova.app.UI = instance.content\
6246 self.environment.Nova.user = { }\
6247 self.environment.Nova.filesystem = filesystem\
6248 self.environment.Nova.clipboard = clipboard\
6249 self.environment.Nova.name = core.name\
6250 self.environment.Nova.path = core.path\
6251 self.environment.Nova.author = core.author\
6252 self.environment.Nova.network = {\
6253 open = network.open;\
6254 close = network.close;\
6255 send = network.send;\
6256 receive = network.receive;\
6257 listen = network.listen;\
6258 ping = com.ping;\
6259 }\
6260\
6261 function self.environment.Nova.getUpdateInfo( )\
6262 return core.getUpdateInfo( )\
6263 end\
6264 function self.environment.Nova.compareVersions( )\
6265 return core.compareVersions( )\
6266 end\
6267 function self.environment.Nova.update( )\
6268 return core.update( )\
6269 end\
6270 function self.environment.Nova.shutdown( )\
6271 instance.app.session:logout( )\
6272 core.finish( )\
6273 os.shutdown( )\
6274 end\
6275 function self.environment.Nova.restart( )\
6276 instance.app.session:logout( )\
6277 core.finish( )\
6278 os.reboot( )\
6279 end\
6280 function self.environment.Nova.log( ... )\
6281 return core.log( instance.app.name, ... )\
6282 end\
6283 function self.environment.Nova.time( )\
6284 return os.time( )\
6285 end\
6286 function self.environment.Nova.clock( )\
6287 return os.clock( )\
6288 end\
6289 function self.environment.Nova.irltime( )\
6290 return core.getirltime( )\
6291 end\
6292 function self.environment.Nova.irldate( )\
6293 return core.getirldate( )\
6294 end\
6295 function self.environment.Nova.day( )\
6296 return os.day( )\
6297 end\
6298 function self.environment.Nova.date( )\
6299 return error \"not yet implemented\"\
6300 end\
6301 function self.environment.Nova.sleep( n )\
6302 return sleep( n )\
6303 end\
6304 function self.environment.Nova.pullEvent( )\
6305 local ev = { coroutine.yield( ) }\
6306 if ev[1] == \"terminate\" then\
6307 instance:close \"terminate\"\
6308 end\
6309 return unpack( ev )\
6310 end\
6311 function self.environment.Nova.pullEventRaw( )\
6312 return coroutine.yield( )\
6313 end\
6314 function self.environment.Nova.versionTable( )\
6315 return { major = core.version_major, minor = core.version_minor, patch = core.version_patch }\
6316 end\
6317 function self.environment.Nova.getLabel( )\
6318 return os.getComputerLabel( )\
6319 end\
6320 function self.environment.Nova.getID( )\
6321 return os.getComputerID( )\
6322 end\
6323\
6324 function self.environment.Nova.app.launch( name, ... )\
6325 instance.session:launch( name, { ... } )\
6326 end\
6327 function self.environment.Nova.app.register( path )\
6328 local a, err = instance.session:registerApp( path )\
6329 if not a then\
6330 return false, err\
6331 end\
6332 return true\
6333 end\
6334 function self.environment.Nova.app.isRegistered( path )\
6335 return instance.session.app_paths[path] and instance.session.app_paths[path].name or false\
6336 end\
6337 function self.environment.Nova.app.listNames( )\
6338 local t = { }\
6339 for k, v in pairs( instance.session.apps ) do\
6340 table.insert( t, k )\
6341 end\
6342 return t\
6343 end\
6344 function self.environment.Nova.app.getInstallPath( name )\
6345 if instance.session.apps[name] then\
6346 return instance.session.apps[name].installpath\
6347 end\
6348 return false\
6349 end\
6350 function self.environment.Nova.app.getIcon( name )\
6351 if instance.session.apps[name] then\
6352 return instance.session.apps[name].icon\
6353 end\
6354 return false\
6355 end\
6356 function self.environment.Nova.app.setData( index, value )\
6357 return instance.session.account:setData( instance.app.name, index, value )\
6358 end\
6359 function self.environment.Nova.app.getData( index )\
6360 return instance.session.account:getData( instance.app.name, index )\
6361 end\
6362 function self.environment.Nova.app.close( reason )\
6363 return instance:close( reason or \"internal\" )\
6364 end\
6365 function self.environment.Nova.app.newThread( func )\
6366 return instance:newThread( Thread( func ) )\
6367 end\
6368 function self.environment.Nova.app.setTitle( title )\
6369 instance.title = tostring( title )\
6370 end\
6371 function self.environment.Nova.app.require( file, ... )\
6372 file = file:gsub( \"%.\", \"/\" )\
6373 local content = instance.app:readFile( tostring( file ) .. \".lua\" )\
6374 if content then\
6375 local f, err = loadstring( content, filesystem.getName( file ) )\
6376 if f then\
6377 local t = instance:newThread( Thread( f ) )\
6378 return t\
6379 else\
6380 instance:showError( err:gsub( \"Sandbox.lua:103: \", \"\" ) )\
6381 end\
6382 else\
6383 instance:showError \"no such file\"\
6384 end\
6385 end\
6386 function self.environment.Nova.app.notifyUser( message, data )\
6387 local title = instance.session.UI:newChild( UIButton( instance.session.UI.w + 1, 2, 15, 1, instance.app.name ) )\
6388 local box = instance.session.UI:newChild( UIButton( instance.session.UI.w + 1, 3, 15, 3, message ) )\
6389 title.bc, box.bc = colours.blue, colours.lightBlue\
6390 title.tc, box.tc = colours.white, colours.blue\
6391 box.align = false\
6392 if data then\
6393 function title.onClick( )\
6394 instance.session:launch( instance.app.name, data )\
6395 end\
6396 box.onClick = title.onClick\
6397 end\
6398 instance.session:newThread( Thread( function( )\
6399 for i = 1, 15 do\
6400 title.x, box.x = title.x - 1, box.x - 1\
6401 sleep( .1 )\
6402 end\
6403 sleep( 2 )\
6404 for i = 1, 15 do\
6405 title.x, box.x = title.x + 1, box.x + 1\
6406 sleep( .1 )\
6407 end\
6408 title:remove( )\
6409 box:remove( )\
6410 end ) )\
6411 end\
6412 self.environment.Nova.app.public = setmetatable( { }, { __newindex = function( _, k, v )\
6413 instance.public_variables[k] = v\
6414 end, __index = function( _, k )\
6415 return instance.public_variables[k]\
6416 end } )\
6417 self.environment.Nova.app.event = setmetatable( { }, { __newindex = function( _, k, v )\
6418 if type( v ) == \"function\" then\
6419 instance.events[k] = v\
6420 else\
6421 return error \"expected function\"\
6422 end\
6423 end, __index = instance.events } )\
6424 self.environment.Nova.app.running = setmetatable( { }, {\
6425 __index = function( _, name )\
6426 local t = { }\
6427 local apps = { }\
6428 for i = 1, #instance.session.running do\
6429 if instance.session.running[i].app.name == name then\
6430 table.insert( apps, instance.session.running[i] )\
6431 end\
6432 end\
6433 return setmetatable( t, {\
6434 __index = function( _, k )\
6435 if apps[k] then\
6436 return setmetatable( { }, { __index = apps[k].public_variables } )\
6437 end\
6438 end;\
6439 } )\
6440 end;\
6441 __newindex = { };\
6442 } )\
6443 self.environment.Nova.app.service = setmetatable( { }, {\
6444 __index = function( _, name )\
6445 local t = { }\
6446 local apps = { }\
6447 for i = 1, #instance.session.background do\
6448 if instance.session.background[i].app.name == name then\
6449 table.insert( apps, instance.session.background[i] )\
6450 end\
6451 end\
6452 return setmetatable( t, {\
6453 __index = function( _, k )\
6454 if apps[k] then\
6455 return setmetatable( { }, { __index = apps[k].public_variables } )\
6456 end\
6457 end;\
6458 } )\
6459 end;\
6460 __newindex = { };\
6461 } )\
6462\
6463 function self.environment.Nova.user.getName( )\
6464 return instance.app.session.account.username\
6465 end\
6466 function self.environment.Nova.user.rename( name )\
6467 return instance.app.session.account:rename( name )\
6468 end\
6469 function self.environment.Nova.user.changePassword( pass )\
6470 return instance.app.session.account:changePassword( pass )\
6471 end\
6472 function self.environment.Nova.user.logout( )\
6473 Thread( function( )\
6474 instance.app.session:logout( )\
6475 core.session = Session( )\
6476 end )\
6477 end\
6478 function self.environment.Nova.user.delete( )\
6479 Thread( function( )\
6480 fs.delete( instance.app.session.account.userpath )\
6481 instance.app.session:logout( )\
6482 core.session = Session( )\
6483 end )\
6484 end\
6485 function self.environment.Nova.user.create( name, pass )\
6486 return Account.create( name, pass )\
6487 end\
6488 function self.environment.Nova.user.switch( name, pass )\
6489 Thread( function( )\
6490 instance.app.session:logout( )\
6491 core.session = Session( name, pass )\
6492 end )\
6493 end\
6494 end\
6495\
6496 return self.public\
6497end\
6498\
6499local function a( name )\
6500 -- user lib\
6501 self.environment.user.name = self.session.account.name\
6502 function self.environment.user.checkPassword( pass )\
6503 return self.session.account:getPassword( ) == sha256( pass )\
6504 end\
6505\
6506 -- running apps\
6507 function self.environment.app.focus( )\
6508 self.session:viewApp( self.app )\
6509 end\
6510end", meta={
6511 type = "class",
6512}};["encryption"]={content="\
6513\
6514local function sum( n, ... ) -- number n, number ...additions\
6515 local t = { ... }\
6516 for i = 1, #t do\
6517 n = n + t[i]\
6518 end\
6519 return n\
6520\
6521 -- number sum\
6522end\
6523\
6524local function loop( n, lim ) -- number n, number limit\
6525 while n > lim do\
6526 n = n - lim\
6527 end\
6528 while n < 1 do\
6529 n = n + lim\
6530 end\
6531 return n\
6532 -- number limited\
6533end\
6534\
6535local function shift( str, count )\
6536 local str = { str:byte( 1, #str ) }\
6537 for i = 1, #str do\
6538 str[i] = loop( str[i] + count, 255 )\
6539 end\
6540 for i = 1, #str do\
6541 str[i] = string.char( str[i] )\
6542 end\
6543 return table.concat( str, \"\" )\
6544end\
6545\
6546local function tohex( b ) -- string[4] bits\
6547 local n = 0\
6548 for i = 1, 4 do\
6549 n = n * 2\
6550 n = n + tonumber( b:sub( i, i ) )\
6551 end\
6552 if n >= 10 then\
6553 local hexes = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\" }\
6554 return hexes[n - 9]\
6555 end\
6556 return tostring( n )\
6557\
6558 -- string[1] hex\
6559end\
6560\
6561local function fromhex( h ) -- string[1] hex\
6562 local n = tonumber( h )\
6563 if not n then\
6564 local hexes = { [\"A\"] = 10, [\"B\"] = 11, [\"C\"] = 12, [\"D\"] = 13, [\"E\"] = 14, [\"F\"] = 15 }\
6565 n = hexes[h]\
6566 end\
6567 local str = \"\"\
6568 for i = 1, 4 do\
6569 str = str .. n % 2\
6570 n = math.floor( n / 2 )\
6571 end\
6572 return str:reverse( )\
6573\
6574 -- string[4] bits\
6575end\
6576\
6577local function xor( n1, n2 )\
6578 if n1 > 255 or n2 > 255 or n1 < 0 or n2 < 0 then\
6579 return error \"expected numbers between 0 and 255\"\
6580 end\
6581 local bit1, bit2 = { }, { }\
6582 for i = 1, 8 do\
6583 bit1[9-i] = n1 % 2 == 1\
6584 n1 = math.floor( n1 / 2 )\
6585 end\
6586 for i = 1, 8 do\
6587 bit2[9-i] = n2 % 2 == 1\
6588 n2 = math.floor( n2 / 2 )\
6589 end\
6590 local bits = { }\
6591 for i = 1, 8 do\
6592 bits[i] = ( bit1[i] and not bit2[i] ) or ( not bit1[i] and bit2[i] )\
6593 end\
6594 local n = 0\
6595 for i = 1, 8 do\
6596 n = n * 2\
6597 n = n + ( bits[i] and 1 or 0 )\
6598 end\
6599 return n\
6600end\
6601\
6602local function nand( n1, n2 )\
6603 if n1 > 255 or n2 > 255 or n1 < 0 or n2 < 0 then\
6604 return error \"expected numbers between 0 and 255\"\
6605 end\
6606 local bit1, bit2 = { }, { }\
6607 for i = 1, 8 do\
6608 bit1[9-i] = n1 % 2 == 1\
6609 n1 = math.floor( n1 / 2 )\
6610 end\
6611 for i = 1, 8 do\
6612 bit2[9-i] = n2 % 2 == 1\
6613 n2 = math.floor( n2 / 2 )\
6614 end\
6615 local bits = { }\
6616 for i = 1, 8 do\
6617 bits[i] = not bit1[i] == bit2[i]\
6618 end\
6619 local n = 0\
6620 for i = 1, 8 do\
6621 n = n * 2\
6622 n = n + ( bits[i] and 1 or 0 )\
6623 end\
6624 return n\
6625end\
6626\
6627local function tobits( n )\
6628 local str = \"\"\
6629 for i = 1, 8 do\
6630 str = str .. n % 2\
6631 n = math.floor( n / 2 )\
6632 end\
6633 return str:reverse( )\
6634end\
6635\
6636local function frombits( b )\
6637 local n = 0\
6638 for i = 1, 8 do\
6639 n = n * 2\
6640 n = n + tonumber( b:sub( i, i ) )\
6641 end\
6642 return n\
6643end\
6644\
6645local t = os.clock( )\
6646local function start( )\
6647 t = os.clock( )\
6648end\
6649local function yield( )\
6650 if os.clock( ) - t > .1 then\
6651 coroutine.yield( )\
6652 start( )\
6653 end\
6654end\
6655\
6656function encrypt( str, key ) -- string text, string key\
6657 local enc = \"\"\
6658 start( )\
6659 for i = 1, #str do\
6660 math.randomseed( sum( key:byte( 1, #key ) ) )\
6661 key = shift( key, math.random( 1, 100 ) )\
6662 local ki = loop( i, #key )\
6663 local a = str:sub( i, i ):byte( )\
6664 local b = key:sub( ki, ki ):byte( )\
6665 enc = enc .. tobits( xor( a, b ) )\
6666 yield( )\
6667 end\
6668 local enc2 = \"\"\
6669 for i = 1, #enc / 4 do\
6670 enc2 = enc2 .. tohex( enc:sub( i * 4 - 3, i * 4 ) )\
6671 yield( )\
6672 end\
6673 return enc2\
6674\
6675 -- string cipher\
6676end\
6677\
6678function decrypt( str, key ) -- string cipher, string key\
6679 start( )\
6680 local dec2 = \"\"\
6681 for i = 1, #str do\
6682 dec2 = dec2 .. fromhex( str:sub( i, i ) )\
6683 yield( )\
6684 end\
6685 str = dec2\
6686 local dec = \"\"\
6687 local keys = { }\
6688 for i = 1, #str / 8 do\
6689 math.randomseed( sum( key:byte( 1, #key ) ) )\
6690 keys[i] = shift( key, math.random( 1, 100 ) )\
6691 key = keys[i]\
6692 yield( )\
6693 end\
6694 for i = 1, #str / 8 do\
6695 local ki = loop( i, #key )\
6696 local a = frombits( str:sub( ( i - 1 ) * 8 + 1, i * 8 ) )\
6697 local b = string.byte( keys[i]:sub( ki, ki ) )\
6698 dec = dec .. string.char( nand( a, b ) )\
6699 yield( )\
6700 end\
6701 return dec\
6702\
6703 -- string text\
6704end", meta={
6705 type = "lib",
6706}};["MarkupLoadedObject"]={content="\
6707\
6708MarkupLoadedObject.public \"element\" (UIElement)\
6709\
6710MarkupLoadedObject.public \"mode\" \"string\"\
6711\
6712MarkupLoadedObject.public \"x\" \"number\"\
6713MarkupLoadedObject.public \"y\" \"number\"\
6714MarkupLoadedObject.public \"content\"\
6715MarkupLoadedObject.public.content.write = false\
6716\
6717function MarkupLoadedObject:MarkupLoadedObject( )\
6718 self.mode = \"block\"\
6719\
6720 self.x = 0\
6721 self.y = 0\
6722 self.content = {\
6723 width = 1;\
6724 height = 1;\
6725 }\
6726 self.element = nil\
6727\
6728 return self.public\
6729end\
6730\
6731function MarkupLoadedObject.public:initialise( class, ... )\
6732 self.element = class(\
6733 self.x,\
6734 self.y,\
6735 self.content.width,\
6736 self.content.height,\
6737 ...\
6738 )\
6739end", meta={
6740 type = "class",
6741}};["Image"]={content="\
6742\
6743local colourLookup = {\
6744 [\"0\"] = colours.white;\
6745 [\"1\"] = colours.orange;\
6746 [\"2\"] = colours.magenta;\
6747 [\"3\"] = colours.lightBlue;\
6748 [\"4\"] = colours.yellow;\
6749 [\"5\"] = colours.lime;\
6750 [\"6\"] = colours.pink;\
6751 [\"7\"] = colours.grey;\
6752 [\"8\"] = colours.lightGrey;\
6753 [\"9\"] = colours.cyan;\
6754 [\"A\"] = colours.purple;\
6755 [\"B\"] = colours.blue;\
6756 [\"C\"] = colours.brown;\
6757 [\"D\"] = colours.green;\
6758 [\"E\"] = colours.red;\
6759 [\"F\"] = colours.black;\
6760 [\" \"] = 0;\
6761}\
6762\
6763local colourSave = { }\
6764for k, v in pairs( colourLookup ) do\
6765 colourSave[v] = k\
6766end\
6767\
6768function Image:Image( w, h )\
6769 self.pixels = { }\
6770\
6771 for y = 1, h or 1 do\
6772 self.pixels[y] = { }\
6773 for x = 1, w or 1 do\
6774 self.pixels[y][x] = { bc = 0, tc = 0, char = \"\" }\
6775 end\
6776 end\
6777\
6778 return self.public\
6779end\
6780\
6781function Image.public:getSize( )\
6782 return #( self.pixels[1] or { } ), #self.pixels\
6783end\
6784\
6785function Image.public:foreach( f )\
6786 local w, h = #( self.pixels[1] or { } ), #self.pixels\
6787 local pixels = { }\
6788 for y = 1, h do\
6789 pixels[y] = { }\
6790 for x = 1, w do\
6791 pixels[y][x] = { }\
6792 local bc, tc, char = f( x, y, self.public:getPixel( x, y ) )\
6793 pixels[y][x].bc = bc or self.pixels[y][x].bc\
6794 pixels[y][x].tc = tc or self.pixels[y][x].tc\
6795 pixels[y][x].char = char or self.pixels[y][x].char\
6796 end\
6797 end\
6798 for y = 1, h do\
6799 for x = 1, w do\
6800 self.pixels[y][x].bc = pixels[y][x].bc\
6801 self.pixels[y][x].tc = pixels[y][x].tc\
6802 self.pixels[y][x].char = pixels[y][x].char\
6803 end\
6804 end\
6805end\
6806\
6807function Image.public:pixel( x, y, bc, tc, char )\
6808 if self.pixels[y] and self.pixels[y][x] then\
6809 --if bc == 0 then bc = pixels[y][x].bc end\
6810 --if tc == 0 then char = pixels[y][x].char tc = pixels[y][x].tc end\
6811 self.pixels[y][x] = { bc = bc, tc = tc, char = char }\
6812 return true\
6813 end\
6814 return false\
6815end\
6816\
6817function Image.public:getPixel( x, y )\
6818 if self.pixels[y] and self.pixels[y][x] then\
6819 return self.pixels[y][x].bc, self.pixels[y][x].tc, self.pixels[y][x].char\
6820 end\
6821 return false\
6822end\
6823\
6824function Image.public:savestr( str )\
6825 local w, h = #( self.pixels[1] or { } ), #self.pixels\
6826 local str = \"\"\
6827 for y = 1, h do\
6828 for x = 1, w do\
6829 local bc = colourSave[self.pixels[y][x].bc]\
6830 local tc = colourSave[self.pixels[y][x].tc]\
6831 local char = self.pixels[y][x].char\
6832 if #char == 0 then\
6833 char = \" \"\
6834 bc = colourSave[0]\
6835 end\
6836 str = str .. bc .. tc .. char\
6837 end\
6838 str = str .. \"\\n\"\
6839 end\
6840 return str:sub( 1, -2 )\
6841end\
6842\
6843function Image.public:loadstr( str )\
6844 local lines = { }\
6845 local last = 1\
6846 for i = 1, #str do\
6847 if str:sub( i, i ) == \"\\n\" then\
6848 table.insert( lines, str:sub( last, i - 1 ) )\
6849 last = i + 1\
6850 end\
6851 end\
6852 table.insert( lines, str:sub( last ) )\
6853 local width = #lines[1] / 3\
6854 local height = #lines\
6855 self.public:resize( width, height )\
6856 for i = 1, #lines do\
6857 local x = 1\
6858 for pixel in lines[i]:gmatch( \"[0123456789ABCDEF ][0123456789ABCDEF ].\" ) do\
6859 local bc = pixel:sub( 1, 1 )\
6860 local tc = pixel:sub( 2, 2 )\
6861 local ch = pixel:sub( 3, 3 )\
6862 self.public:pixel( x, i, colourLookup[bc], colourLookup[tc], ch )\
6863 x = x + 1\
6864 end\
6865 end\
6866 return self.public\
6867end\
6868\
6869function Image.public:resize( w, h, bc, tc, char )\
6870 while #self.pixels > h do\
6871 table.remove( self.pixels, #self.pixels )\
6872 end\
6873 while #self.pixels < h do\
6874 local t = { }\
6875 for i = 1, #( self.pixels[1] or { } ) do\
6876 t[i] = { bc = bc or 0, tc = tc or 0, char = char or \"\" }\
6877 end\
6878 table.insert( self.pixels, t )\
6879 end\
6880 if self.pixels[1] then\
6881 local cw = #self.pixels[1]\
6882 while cw > w do\
6883 for y = 1, h do\
6884 table.remove( self.pixels[y], #self.pixels[y] )\
6885 end\
6886 cw = cw - 1\
6887 end\
6888 while cw < w do\
6889 for y = 1, h do\
6890 table.insert( self.pixels[y], { bc = bc or 0, tc = tc or 0, char = char or \"\" } )\
6891 end\
6892 cw = cw + 1\
6893 end\
6894 end\
6895end", meta={
6896 type = "class",
6897}};["MarkupSpace"]={content="\
6898\
6899require \"MarkupGenericObject\"\
6900\
6901MarkupSpace:extends( MarkupGenericObject )\
6902\
6903MarkupSpace.public.etype = \"space\"\
6904\
6905function MarkupSpace:MarkupSpace( ... )\
6906 self:MarkupGenericObject( ... )\
6907\
6908 return self.public\
6909end\
6910\
6911function MarkupSpace.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
6912 if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
6913 if colours[value] or value == \"transparent\" then\
6914 self.bc = colours[value] or 0\
6915 end\
6916 end\
6917 self.page.needsReload = true\
6918end\
6919\
6920function MarkupSpace.public:loadObject( positionHandler )\
6921 self.page:newStyle {\
6922 bc = self.bc;\
6923 }\
6924 local ob = MarkupLoadedObject( )\
6925 ob.mode = \"inline\"\
6926 ob.x, ob.y = positionHandler.x, positionHandler.y\
6927 ob:initialise( UIText, \"\" )\
6928 ob.element.bc = self.page:getStyle \"bc\"\
6929 self.page:removeStyle( )\
6930 return { ob }\
6931 -- generate a list of MarkupLoadedObjects\
6932end", meta={
6933 type = "class",
6934}};["MarkupInput"]={content="\
6935\
6936require \"MarkupGenericObject\"\
6937\
6938MarkupInput:extends( MarkupGenericObject )\
6939\
6940MarkupInput.public.etype = \"input\"\
6941\
6942function MarkupInput:MarkupInput( ... )\
6943 self:MarkupGenericObject( ... )\
6944\
6945 self.mask = false\
6946\
6947 return self.public\
6948end\
6949\
6950function MarkupInput.public:setAttribute( attribute, value )\
6951 if MarkupGenericObject.attributes[attribute] then\
6952 MarkupGenericObject.attributes[attribute]( self, value )\
6953 end\
6954 if attribute == \"tc\" or attribute == \"textColour\" then\
6955 if colours[value] or value == \"transparent\" then\
6956 self.tc = colours[value] or 0\
6957 end\
6958 end\
6959 if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
6960 if colours[value] or value == \"transparent\" then\
6961 self.bc = colours[value] or 0\
6962 end\
6963 end\
6964 if attribute == \"fbc\" or attribute == \"focussedColour\" then\
6965 if colours[value] or value == \"transparent\" then\
6966 self.fbc = colours[value] or 0\
6967 end\
6968 end\
6969 if attribute == \"mask\" then\
6970 if value == false or value == \"false\" then\
6971 self.mask = false\
6972 elseif type( value ) == \"string\" then\
6973 self.mask = value:sub( 1, 1 )\
6974 end\
6975 end\
6976 self.page.needsReposition = true\
6977end\
6978\
6979\
6980function MarkupInput.public:loadObject( positionHandler )\
6981\
6982 local w, h = 0, 1\
6983 if self.width.type == \"text\" and self.width.value == \"auto\" then\
6984 w = positionHandler.w - positionHandler.x + 1\
6985 elseif self.width.type == \"percent\" then\
6986 w = self.width.value / 100 * positionHandler.w\
6987 elseif self.width.type == \"pixel\" then\
6988 w = self.width.value\
6989 end\
6990 local loadedObject = MarkupLoadedObject( )\
6991 loadedObject.x = positionHandler.x\
6992 loadedObject.y = positionHandler.y\
6993 loadedObject.mode = \"inline\"\
6994 loadedObject.content.width = w\
6995 loadedObject.content.height = h\
6996 local input\
6997 if not self.element then\
6998 loadedObject:initialise( UIInput, self.mask )\
6999 input = loadedObject.element\
7000 self.element = loadedObject.element\
7001 else\
7002 self.element.x = loadedObject.x\
7003 self.element.y = loadedObject.y\
7004 self.element.w = loadedObject.content.width\
7005 self.element.h = loadedObject.content.height\
7006 input = self.element\
7007 end\
7008 input.bc = self.bc or self.page:getStyle \"bc\"\
7009 input.tc = self.tc or self.page:getStyle \"tc\"\
7010 input.fbc = self.fbc or self.page:getStyle \"bc\"\
7011 loadedObject.element = input\
7012\
7013 function input:whenFocussed( )\
7014 core.log \"Focussed on the input\"\
7015 end\
7016 function input:onEnter()\
7017 core.log( self.text )\
7018 end\
7019\
7020 return { loadedObject }\
7021 -- generate a list of MarkupLoadedObjects\
7022end\
7023\
7024function MarkupInput.public:scriptObject( )\
7025 return setmetatable( {}, {\
7026 __newindex = function( _, k, v )\
7027 if type( v ) ~= \"string\" then\
7028 v = tostring( v )\
7029 end\
7030 return self.public:setAttribute( k, v )\
7031 end;\
7032 __index = function( _, k )\
7033 if k == \"width\" then\
7034 return ( self.width.type == \"percent\" and self.width.value .. \"%\" ) or ( self.width.type == \"pixel\" and self.width.value .. \"px\" ) or ( self.width.type == \"text\" and self.width.value )\
7035 elseif k == \"height\" then\
7036 return ( self.height.type == \"percent\" and self.height.value .. \"%\" ) or ( self.height.type == \"pixel\" and self.height.value .. \"px\" ) or ( self.height.type == \"text\" and self.height.value )\
7037 elseif k == \"bc\" or k == \"backgroundColour\" or k == \"colour\" then\
7038 return self.bc\
7039 elseif k == \"tc\" or k == \"textColour\" then\
7040 return self.tc\
7041 elseif k == \"mask\" then\
7042 return self.mask\
7043 elseif k == \"text\" then\
7044 return self.element and self.element.text or \"\"\
7045 else\
7046 return error \"cannot access this attribute\"\
7047 end\
7048 end;\
7049 } )\
7050end", meta={
7051 type = "class",
7052}};["handshake"]={content="\
7053-- Handshake\
7054-- Used with permission from 1lann\
7055-- Compressed using Lua Minifier (https://mothereff.in/lua-minifier)\
7056local Handshake={}Handshake.prime=625210769;Handshake.base=-1;Handshake.secret=-1;Handshake.sharedSecret=-1;function Handshake.exponentWithModulo(a,b,c)local d=a;for e=1,b-1 do d=d*d;if d>=c then d=d%c end end;return d end;function Handshake.clear()Handshake.base=-1;Handshake.secret=-1;Handshake.sharedSecret=-1 end;function Handshake.generateInitiatorData()Handshake.base=math.random(10,99999)Handshake.secret=math.random(10,99999)return{type=\"initiate\",prime=Handshake.prime,base=Handshake.base,moddedSecret=Handshake.exponentWithModulo(Handshake.base,Handshake.secret,Handshake.prime)}end;function Handshake.generateResponseData(f)local g=type(f.prime)==\"number\"local h=f.prime==Handshake.prime;local i=type(f.base)==\"number\"local j=f.type==\"initiate\"local k=type(f.moddedSecret)==\"number\"local l=g and i and k;if l and h then if j then Handshake.base=f.base;Handshake.secret=math.random(10,99999)Handshake.sharedSecret=Handshake.exponentWithModulo(f.moddedSecret,Handshake.secret,Handshake.prime)return{type=\"response\",prime=Handshake.prime,base=Handshake.base,moddedSecret=Handshake.exponentWithModulo(Handshake.base,Handshake.secret,Handshake.prime)},Handshake.sharedSecret elseif f.type==\"response\"and Handshake.base>0 and Handshake.secret>0 then Handshake.sharedSecret=Handshake.exponentWithModulo(f.moddedSecret,Handshake.secret,Handshake.prime)return Handshake.sharedSecret else return false end else return false end end;return Handshake", meta={
7057 type = "lib",
7058}};["MarkupNewline"]={content="\
7059\
7060require \"MarkupGenericObject\"\
7061\
7062MarkupNewline:extends( MarkupGenericObject )\
7063\
7064MarkupNewline.public.etype = \"newline\"\
7065\
7066function MarkupNewline:MarkupNewline( ... )\
7067 self:MarkupGenericObject( ... )\
7068\
7069 return self.public\
7070end\
7071\
7072function MarkupNewline.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
7073 if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
7074 if colours[value] or value == \"transparent\" then\
7075 self.bc = colours[value] or 0\
7076 end\
7077 end\
7078 self.page.needsReload = true\
7079end\
7080\
7081function MarkupNewline.public:loadObject( positionHandler )\
7082 self.page:newStyle {\
7083 bc = self.bc;\
7084 }\
7085 local ob = MarkupLoadedObject( )\
7086 ob.x, ob.y = positionHandler.x, positionHandler.y\
7087 ob:initialise( UIText, \"\" )\
7088 ob.element.bc = self.page:getStyle \"bc\"\
7089 self.page:removeStyle( )\
7090 return { ob }\
7091 -- generate a list of MarkupLoadedObjects\
7092end", meta={
7093 type = "class",
7094}};["UICanvas"]={content="\
7095\
7096local colour = term.isColour( )\
7097\
7098require \"UIElement\"\
7099require \"Image\"\
7100UICanvas:extends( UIElement )\
7101\
7102UICanvas.public \"cx\" \"number\"\
7103UICanvas.public \"cy\" \"number\"\
7104UICanvas.public \"cb\" \"boolean\"\
7105UICanvas.public \"bc\" \"number\"\
7106UICanvas.public \"tc\" \"number\"\
7107UICanvas.public \"image\"\
7108UICanvas.public.image.write = false\
7109\
7110UICanvas.handlesKeys = true\
7111UICanvas.handlesScroll = true\
7112UICanvas.handlesTab = true\
7113UICanvas.handlesEnter = true\
7114\
7115local function termObject( canvas )\
7116 local t = { }\
7117 function t.write( str )\
7118 if type( str ) ~= \"string\" and type( str ) ~= \"number\" then return error \"expected string\" end\
7119 str = tostring( str )\
7120 for x = 1, #str do\
7121 canvas.image:pixel( canvas.cx, canvas.cy, canvas.bc, canvas.tc, str:sub( x, x ) )\
7122 canvas.cx = canvas.cx + 1\
7123 end\
7124 end\
7125 function t.clearLine( )\
7126 canvas.image:foreach( function( x, y, bc, tc, char )\
7127 if y == canvas.cy then\
7128 return canvas.bc, canvas.tc, \" \"\
7129 end\
7130 end )\
7131 end\
7132 function t.clear( )\
7133 canvas.image:foreach( function( x, y, bc, tc, char )\
7134 return canvas.bc, canvas.tc, \" \"\
7135 end )\
7136 end\
7137 function t.setCursorPos( x, y )\
7138 if type( x ) ~= \"number\" or type( y ) ~= \"number\" then\
7139 return error \"expected number, number\"\
7140 end\
7141 canvas.cx = x\
7142 canvas.cy = y\
7143 end\
7144 function t.getCursorPos( )\
7145 return canvas.cx, canvas.cy\
7146 end\
7147 function t.setBackgroundColour( col )\
7148 if type( col ) ~= \"number\" then\
7149 return error \"expected number\"\
7150 end\
7151 canvas.bc = col\
7152 end\
7153 function t.getBackgroundColour()\
7154 return canvas.bc\
7155 end\
7156 function t.getTextColour()\
7157 return canvas.tc\
7158 end\
7159 function t.getBackgroundColor()\
7160 return canvas.bc\
7161 end\
7162 function t.getTextColor()\
7163 return canvas.tc\
7164 end\
7165 function t.setTextColour( col )\
7166 if type( col ) ~= \"number\" then\
7167 return error \"expected number\"\
7168 end\
7169 canvas.tc = col\
7170 end\
7171 function t.setBackgroundColor( col )\
7172 if type( col ) ~= \"number\" then\
7173 return error \"expected number\"\
7174 end\
7175 canvas.bc = col\
7176 end\
7177 function t.setTextColor( col )\
7178 if type( col ) ~= \"number\" then\
7179 return error \"expected number\"\
7180 end\
7181 canvas.tc = col\
7182 end\
7183 function t.scroll( dir )\
7184 canvas.image:foreach( function( x, y, bc, tc, char )\
7185 if canvas.image:getPixel( x, y + dir ) then\
7186 return canvas.image:getPixel( x, y + dir )\
7187 else\
7188 return canvas.bc, canvas.tc, \" \"\
7189 end\
7190 end )\
7191 end\
7192 function t.setCursorBlink( state )\
7193 canvas.cb = not not state\
7194 end\
7195 function t.isColour( )\
7196 return colour\
7197 end\
7198 function t.isColor( )\
7199 return colour\
7200 end\
7201 function t.getSize( )\
7202 return canvas.w, canvas.h\
7203 end\
7204\
7205 return t\
7206end\
7207\
7208local function luaEnvironment( canvas )\
7209 local env = { }\
7210 env.fs = fs\
7211 env.term = term\
7212 env._VERSION = _VERSION\
7213 env.pairs = pairs\
7214 env.ipairs = ipairs\
7215 env.select = select\
7216 env.unpack = unpack\
7217 env.setfenv = setfenv\
7218 env.getfenv = getfenv\
7219 env.setmetatable = setmetatable\
7220 env.getmetatable = getmetatable\
7221 env.next = next\
7222 env.rawset = rawset\
7223 env.rawget = rawget\
7224 env.rawequal = rawequal\
7225 env.type = type\
7226 env.tostring = tostring\
7227 env.tonumber = tonumber\
7228 env.pcall = pcall\
7229 env.xpcall = xpcall\
7230 env.loadstring = loadstring\
7231 env.assert = assert\
7232 env.error = error\
7233 env.sleep = sleep\
7234 env.__inext = __inext\
7235 env.math = math\
7236 env.string = string\
7237 env.table = table\
7238 env.coroutine = coroutine\
7239 env.keys = keys\
7240 env.colours = colours\
7241 env.colors = colors\
7242 env.vector = vector\
7243 env.bit = bit\
7244 env.http = http\
7245 env.write = write\
7246 env.print = print\
7247 env.printError = printError\
7248 env.read = read\
7249 env.rednet = rednet\
7250 env.load = load\
7251 env._CC_VERSION = _CC_VERSION\
7252 env._MC_VERSION = _MC_VERSION\
7253 env.bit32 = bit32\
7254 env._LUAJ_VERSION = _LUAJ_VERSION\
7255 local tAPIsLoading = { }\
7256 env.os = setmetatable( {\
7257 pullEventRaw = function( sFilter )\
7258 while true do\
7259 local event = { coroutine.yield( ) }\
7260 if not sFilter or sFilter == event[1] then\
7261 return unpack( event )\
7262 end\
7263 end\
7264 end;\
7265 pullEvent = function( sFilter )\
7266 while true do\
7267 local event = { coroutine.yield( ) }\
7268 if event[1] == \"terminate\" then\
7269 error( \"Terminated\", 0 )\
7270 end\
7271 if not sFilter or sFilter == event[1] then\
7272 return unpack( event )\
7273 end\
7274 end\
7275 end;\
7276 run = function( _tEnv, _sPath, ... )\
7277 local tArgs = { ... }\
7278 local fnFile, err = env.loadfile( _sPath )\
7279 if fnFile then\
7280 local tEnv = _tEnv\
7281 --setmetatable( tEnv, { __index = function(t,k) return _G[k] end } )\
7282 setmetatable( tEnv, { __index = env } )\
7283 setfenv( fnFile, tEnv )\
7284 local ok, err = pcall( function()\
7285 fnFile( unpack( tArgs ) )\
7286 end )\
7287 if not ok then\
7288 if err and err ~= \"\" then\
7289 printError( err )\
7290 end\
7291 return false\
7292 end\
7293 return true\
7294 end\
7295 if err and err ~= \"\" then\
7296 printError( err )\
7297 end\
7298 return false\
7299 end;\
7300 loadAPI = function( _sPath )\
7301 local sName = fs.getName( _sPath )\
7302 if tAPIsLoading[sName] == true then\
7303 printError( \"API \"..sName..\" is already being loaded\" )\
7304 return false\
7305 end\
7306 tAPIsLoading[sName] = true\
7307 \
7308 local tEnv = {}\
7309 setmetatable( tEnv, { __index = env } )\
7310 local fnAPI, err = loadfile( _sPath )\
7311 if fnAPI then\
7312 setfenv( fnAPI, tEnv )\
7313 fnAPI()\
7314 else\
7315 printError( err )\
7316 tAPIsLoading[sName] = nil\
7317 return false\
7318 end\
7319 \
7320 local tAPI = {}\
7321 for k,v in pairs( tEnv ) do\
7322 tAPI[k] = v\
7323 end\
7324 \
7325 env[sName] = tAPI \
7326 tAPIsLoading[sName] = nil\
7327 return true\
7328 end;\
7329 unloadAPI = function( _sName )\
7330 if _sName ~= \"_G\" and type(env[_sName]) == \"table\" then\
7331 env[_sName] = nil\
7332 end\
7333 end;\
7334 }, { __index = os } );\
7335 env.help = help\
7336 env.io = io\
7337 env.parallel = parallel\
7338\
7339 env.loadfile = function( _sFile )\
7340 local file = env.fs.open( _sFile, \"r\" )\
7341 if file then\
7342 local func, err = loadstring( file.readAll(), env.fs.getName( _sFile ) )\
7343 file.close()\
7344 return func, err\
7345 end\
7346 return nil, \"File not found\"\
7347 end\
7348\
7349 env.dofile = function( _sFile )\
7350 local fnFile, e = env.loadfile( _sFile )\
7351 if fnFile then\
7352 setfenv( fnFile, env )\
7353 return fnFile()\
7354 else\
7355 error( e, 2 )\
7356 end\
7357 end\
7358\
7359 env.shell = shell\
7360 env.multishell = multishell\
7361 env.redstone = redstone -- change in future, use device system\
7362 env.rs = rs -- change in future, use device system\
7363 env.gps = gps -- change in future, use Nova gps system\
7364 env.peripheral = peripheral -- change in future, use device system\
7365 env.disk = disk -- change in future, use filesystem\
7366 env.window = window -- oh crap...\
7367 env.textutils = textutils\
7368 env.paintutils = paintutils\
7369 env.term = term\
7370\
7371 env._G = env\
7372\
7373 return env\
7374end\
7375\
7376function UICanvas:UICanvas( x, y, w, h )\
7377 self:UIElement( x, y, w, h )\
7378 self.image = Image( w, h )\
7379 self.image:foreach( function( x, y, bc, tc, char )\
7380 return colours.black, colours.white, \" \"\
7381 end )\
7382 self.term = termObject( self.public )\
7383 self.running = false\
7384 self.co = false\
7385 self.cx = 1\
7386 self.cy = 1\
7387 self.bc = colours.black\
7388 self.tc = colours.white\
7389 self.cb = false\
7390 self.environment = luaEnvironment( )\
7391 self.thread = Thread( function( )\
7392 while true do\
7393 local event = { coroutine.yield( ) }\
7394 if event[1] ~= \"mouse_click\"\
7395 and event[1] ~= \"mouse_drag\"\
7396 and event[1] ~= \"mouse_scroll\"\
7397 and event[1] ~= \"key\"\
7398 and event[1] ~= \"char\"\
7399 and event[1] ~= \"update\" then\
7400 self.public:passEvent( unpack( event ) )\
7401 end\
7402 end\
7403 end, true )\
7404 return self.public\
7405end\
7406\
7407function UICanvas.public:setTask( func )\
7408 setfenv( func, self.environment )\
7409 self.co = coroutine.create( func )\
7410 self.running = true\
7411end\
7412\
7413function UICanvas.public:passEvent( ... )\
7414 if not self.running then return end\
7415 local prev = term.redirect( self.term )\
7416 local ok, err = coroutine.resume( self.co, ... )\
7417 term.redirect( prev )\
7418 if not ok then\
7419 self.running = false\
7420 local prev = term.redirect( self.term )\
7421 self.environment.printError( err )\
7422 term.redirect( prev )\
7423 end\
7424 if coroutine.status( self.co ) == \"dead\" then\
7425 self.running = false\
7426 end\
7427end\
7428\
7429function UICanvas.public:draw( x, y )\
7430 local layer = stencil.addLayer( x, y, self.w, self.h )\
7431 self.image:resize( self.w, self.h )\
7432 self.image:foreach( function( px, py, bc, tc, char )\
7433 stencil.pixel( x + px - 1, y + py - 1, bc, tc, char )\
7434 return bc, tc, char\
7435 end )\
7436 if self.cb then\
7437 stencil.setCursorBlink( x + self.cx - 1, y + self.cy - 1, self.tc )\
7438 end\
7439 local c = { }\
7440 for i, child in ipairs( self.children ) do\
7441 c[i] = child\
7442 end\
7443 for i, child in ipairs( c ) do\
7444 child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
7445 end\
7446 stencil.closeLayer( layer )\
7447end\
7448\
7449function UICanvas.public:onMouseClick( x, y, button )\
7450 self.public:passEvent( \"mouse_click\", button, x, y )\
7451end\
7452\
7453function UICanvas.public:onMouseDrag( x, y, cx, cy, button )\
7454 self.public:passEvent( \"mouse_drag\", button, x, y )\
7455end\
7456\
7457function UICanvas.public:onMouseScroll( x, y, dir )\
7458 self.public:passEvent( \"mouse_scroll\", dir, x, y )\
7459end\
7460\
7461function UICanvas.public:onKeyPress( key, lastkey )\
7462 self.public:passEvent( \"key\", key )\
7463end\
7464\
7465function UICanvas.public:onTextInput( text, lastkey )\
7466 self.public:passEvent( \"char\", text )\
7467end", meta={
7468 type = "class",
7469}};["clipboard"]={content="\
7470local a=false;function set(b,c)a={mode=b,data=c}end;function get()if a then return a.mode,a.data end end", meta={
7471 type = "lib",
7472}};["ttf"]={content="\
7473local pack = {\
7474Run={\
7475[\"icon.nim\"]=\"F0RF0uF0nF4 F4 F4 F4 F4 F0 \\nF4 F4CF4rF4aF4fF4tF4OF4SF0 \\nF4>F4 F4_F0 F0 F0 F0 F0 F0 \",\
7476[\"main.lua\"]=\"\\nif not Nova then\\n error( \\\"Cannot run outside Nova\\\", 0 )\\nend\\n\\nlocal Nova = Nova\\n\\nlocal handler = Nova.app.UI:getHandler( )\\n\\nlocal canvas = Nova.app.UI:newChild( NovaUI.UICanvas( 1, 1, Nova.app.UI.w, Nova.app.UI.h ) )\\nif ARGS[1] then\\n local data, err = Nova.filesystem.readfile( ARGS[1] )\\n if data then\\n local f, err = loadstring( data.content, Nova.filesystem.getName( ARGS[1] ) )\\n if f then\\n canvas:setTask( function( ... )\\n setfenv( f, getfenv( ) )\\n local ok, err = pcall( f, ... )\\n if not ok then\\n term.setBackgroundColour( colours.black )\\n term.setTextColour( colours.red )\\n print( err )\\n end\\n print \\\"App has finished, press any key to exit.\\\"\\n os.pullEvent \\\"key\\\"\\n Nova.app.close( )\\n end )\\n handler:setFocus( canvas )\\n canvas:passEvent( unpack( ARGS, 2 ) )\\n else\\n error( err:gsub( \\\"main:12: \\\", \\\"\\\" ), 0 )\\n end\\n else\\n error( err, 0 )\\n end\\nelse\\n local data, err = Nova.filesystem.readfile( \\\"rom/programs/shell\\\" )\\n if data then\\n local f, err = loadstring( data.content, \\\"shell\\\" )\\n if f then\\n canvas:setTask( function( )\\n setfenv( f, getfenv( ) )\\n local ok, err = pcall( f )\\n if not ok then\\n term.setBackgroundColour( colours.black )\\n term.setTextColour( colours.red )\\n print( err )\\n end\\n print \\\"App has finished, press any key to exit.\\\"\\n os.pullEvent \\\"key\\\"\\n Nova.app.close( )\\n end )\\n handler:setFocus( canvas )\\n canvas:passEvent( )\\n else\\n error( err:gsub( \\\"main:34: \\\", \\\"\\\" ), 0 )\\n end\\n else\\n error( err, 0 )\\n end\\nend\",\
7477[\"appconfig.txt\"]=\"handles = {\\n \\\"unknown\\\";\\n \\\"lua\\\";\\n};\",\
7478}\
7479,\
7480NetServer={\
7481[\"responder.lua\"]=\"\\nlocal channels = { }\\n\\nlocal function findFinish( str, pos )\\n local escape = false\\n for i = pos, #str do\\n if not escape then\\n if str:sub( i, i ) == \\\"\\\\\\\\\\\" then\\n escape = true\\n end\\n if str:sub( i, i + 1 ) == \\\"?>\\\" then\\n return i + 1\\n end\\n end\\n end\\nend\\n\\nlocal function loadPluginEnvironment( args, rootpath )\\n local env = { }\\n env.ARGS = args\\n env._G = env\\n\\n env._VERSION = _VERSION;\\n env.pairs = pairs;\\n env.ipairs = ipairs;\\n env.select = select;\\n env.unpack = unpack;\\n env.setfenv = setfenv;\\n env.getfenv = getfenv;\\n env.setmetatable = setmetatable;\\n env.getmetatable = getmetatable;\\n env.next = next;\\n env.rawset = rawset;\\n env.rawget = rawget;\\n env.rawequal = rawequal;\\n env.type = type;\\n env.tostring = tostring;\\n env.tonumber = tonumber;\\n env.pcall = pcall;\\n env.xpcall = xpcall;\\n env.loadstring = loadstring;\\n env.assert = assert;\\n env.error = error;\\n env.sleep = sleep;\\n env.__inext = __inext;\\n\\n env.math = math;\\n env.string = string;\\n env.table = table;\\n env.coroutine = coroutine;\\n env.sha256 = sha256;\\n env.encryption = encryption;\\n env.stringutils = stringutils;\\n env.keys = keys;\\n env.colours = colours;\\n env.colors = colors;\\n env.vector = vector;\\n env.bit = bit;\\n env.http = http;\\n env.textutils = textutils;\\n env.rednet = rednet;\\n env.os = os;\\n\\n env.class = class;\\n env.Drive = Drive;\\n env.Archive = Archive;\\n env.FileData = FileData;\\n\\n env.filesystem = Nova.filesystem\\n env.clipboard = Nova.clipboard\\n env.network = Nova.network\\n\\n env.document = { }\\n function env.document.write() end -- document.write is given in the readFile function\\n env.rootpath = rootpath\\n\\n return env\\nend\\n\\nlocal function parseContent( content, args )\\n local stream = \\\"\\\"\\n local plugins = Nova.app.getData \\\"plugins\\\" or { }\\n for k, v in pairs( plugins ) do\\n v.loadContent = loadstring( v.loadContent or \\\"\\\" )\\n end\\n local escape\\n local i = 1\\n while i <= #content do\\n if escape then\\n stream = stream .. content:sub( i, i )\\n else\\n if content:sub( i, i ) == \\\"\\\\\\\\\\\" then\\n escape = true\\n end\\n if content:sub( i, i + 1 ) == \\\"<?\\\" then\\n local plugin = content:sub( i + 2 ):match \\\"^(%w+)\\\"\\n if plugin then\\n local finish = findFinish( content, i + 3 + #(plugin or \\\"\\\") )\\n if not finish then\\n stream = stream .. \\\"<text tc:red width:auto>End of <? expected</text>\\\"\\n elseif plugins[plugin] then\\n local environment = loadPluginEnvironment( args, Nova.app.getData \\\"rootpath\\\" or \\\"user:\\\" )\\n environment.document.write = function( str )\\n stream = stream .. tostring( str )\\n end\\n if type( plugins[plugin].loadContent ) == \\\"function\\\" then\\n setfenv( plugins[plugin].loadContent, environment )\\n local ok, err = pcall( plugins[plugin].loadContent, content:sub( i + 2 + #plugin, finish - 2 ) )\\n if not ok then\\n stream = stream .. \\\"<text tc:red width:auto>\\\" .. err .. \\\"</text>\\\"\\n end\\n else\\n stream = stream .. \\\"<text tc:red width:auto>Plugin \\\\\\\"\\\" .. plugin .. \\\"\\\\\\\" failed to load</text>\\\"\\n end\\n else\\n stream = stream .. \\\"<text tc:red width:auto>No such plugin \\\\\\\"\\\" .. plugin .. \\\"\\\\\\\"</text>\\\"\\n end\\n if finish then\\n i = finish\\n end\\n else\\n stream = stream .. \\\"<\\\"\\n end\\n else\\n stream = stream .. content:sub( i, i )\\n end\\n end\\n i = i + 1\\n end\\n return stream\\nend\\n\\nlocal function readFile( file, args, p )\\n local filedata, err = Nova.filesystem.readfile( file )\\n if not filedata then\\n return false, err\\n end\\n if p then\\n return parseContent( filedata.content, args )\\n end\\n return filedata.content\\nend\\n\\nlocal function addWebHandler( channel )\\n table.insert( channels, channel )\\n Nova.app.newThread( function( )\\n while true do\\n local ok, data = Nova.network.receive( channel )\\n if not ok and data == \\\"closed\\\" then\\n for i = #channels, 1, -1 do\\n if channels[i] == channel then\\n table.remove( channels, i )\\n break\\n end\\n end\\n break\\n end\\n if Nova.app.getData \\\"online\\\" and ok and type( data ) == \\\"table\\\" then\\n if data.request == \\\"read\\\" then\\n local rootpath = Nova.app.getData \\\"rootpath\\\" or \\\"user:\\\"\\n if type( data.file ) == \\\"string\\\" then\\n if Nova.filesystem.isDirectory( Nova.filesystem.merge( rootpath, data.file ) ) then\\n data.file = Nova.filesystem.merge( data.file, \\\"index.nml\\\" )\\n end\\n if not Nova.filesystem.isFile( Nova.filesystem.merge( rootpath, data.file ) ) then\\n if Nova.filesystem.isFile( Nova.filesystem.merge( rootpath, Nova.filesystem.getDirectory( data.file ), \\\"noindex.nml\\\" ) ) then\\n data.args = type( data.args ) == \\\"table\\\" and data.args or { }\\n data.args.file = data.file\\n data.file = Nova.filesystem.merge( Nova.filesystem.getDirectory( data.file ), \\\"noindex.nml\\\" )\\n else\\n Nova.network.send( channel, {\\n success = false;\\n reason = \\\"no such file or directory\\\";\\n } )\\n end\\n end\\n local path = Nova.filesystem.merge( rootpath, data.file )\\n local content, err = readFile( path, type( data.args ) == \\\"table\\\" and data.args or { }, Nova.filesystem.getExtension( path ) == \\\"nml\\\" )\\n if content then\\n Nova.network.send( channel, {\\n success = true;\\n content = content;\\n endpath = data.file;\\n } )\\n else\\n Nova.network.send( channel, {\\n success = false;\\n reason = err;\\n } )\\n end\\n else\\n Nova.network.send( channel, {\\n success = false;\\n reason = \\\"malformed packet\\\";\\n } )\\n end\\n end\\n end\\n end\\n end )\\nend\\n\\nfunction Nova.app.canClose( )\\n for i = 1, #channels do\\n Nova.network.close( channels[i] )\\n end\\nend\\n\\nwhile true do\\n addWebHandler( Nova.network.listen \\\"NetServer:Request\\\" )\\nend\",\
7482[\"main.lua\"]=\"\\nlocal UI = Nova.app.UI\\nNova.app.require \\\"LuaPlugin\\\"\\n\\nlocal function registerPlugin( path )\\n if Nova.filesystem.isFile( path ) then\\n local pass\\n if Nova.filesystem.isProtected( path ) then\\n pass = NovaUI.display.response( UI, \\\"File is protected, please input a password\\\" )\\n if not pass then return end\\n end\\n local filedata, err = Nova.filesystem.readfile( path, pass )\\n if not filedata then\\n NovaUI.display.alert( UI, err, true )\\n return\\n end\\n local name = Nova.filesystem.getName( path, true )\\n local f, err = loadstring( filedata.content, name )\\n if not f then\\n NovaUI.display.help( UI, err:gsub( \\\"main:17: \\\", \\\"\\\" ), true )\\n return\\n end\\n local plugins = Nova.app.getData \\\"plugins\\\" or { }\\n plugins[name] = { loadContent = filedata.content }\\n Nova.app.setData( \\\"plugins\\\", plugins )\\n NovaUI.display.alert( UI, \\\"Plugin registered!\\\", true )\\n else\\n NovaUI.display.alert( UI, \\\"Plugin doesn't exist\\\" )\\n end\\nend\\n\\nif ARGS[1] and ARGS[2] == \\\"NetServerPlugin\\\" then\\n registerPlugin( ARGS[1] )\\n Nova.app.close( )\\nend\\n\\nlocal _, _, document = NovaUI.markup.load( [[\\n <text x:2 y:2>Server control panel.</text>\\n <text x:2 y:4>Your server is currently</text>\\n <text x:2 y:6>Root path of server:</text>\\n <input x:2 y:7 id:rootpath align:below width:expand>\\n <text x:2 y:9>Plugins (run a .nsp file to add the plugin)</text>\\n]], UI, getfenv( ) )\\n\\nlocal running = UI:newChild( NovaUI.UIButton( 27, 4, 7, 1, function( self )\\n local online = Nova.app.getData \\\"online\\\"\\n self.bc = online and colours.green or colours.red\\n return online and \\\"online\\\" or \\\"offline\\\"\\nend ) )\\nrunning.tc = colours.black\\n\\nfunction running:onClick( )\\n Nova.app.setData( \\\"online\\\", not Nova.app.getData \\\"online\\\" )\\nend\\n\\nlocal rootpath = document.getElementById \\\"rootpath\\\"\\nrootpath.w = rootpath.w - 1\\nrootpath.text = Nova.app.getData \\\"rootpath\\\" or \\\"user:\\\"\\nfunction rootpath:onEnter( )\\n Nova.app.setData( \\\"rootpath\\\", self.text )\\nend\\n\\nlocal plugins = UI:newChild( NovaUI.UIFrame( 3, 10, UI.w - 3, UI.h - 12 ) )\\n\\nlocal plugin_list = Nova.app.getData \\\"plugins\\\" or { }\\nif #plugin_list > plugins.h then\\n plugins.w = plugins.w - 1\\n UI:newChild( NovaUI.UIScrollbar( 0, 0, 1, 0, plugins ) ):alignTo( plugins )\\nend\\n\\nlocal y = 1\\nfor k, v in pairs( plugin_list ) do\\n local button = plugins:newChild( NovaUI.UIButton( 1, y, plugins.w, 1, k ) )\\n button.tc = colours.grey\\n button.align = false\\n\\n y = y + 1\\nend\\n\\nlocal function installLua( )\\n local f, err = loadstring( LuaPlugin, \\\"lua\\\" )\\n if not f then\\n NovaUI.display.help( UI, err:gsub( \\\"main:81: \\\", \\\"\\\" ) )\\n return\\n end\\n local plugins = Nova.app.getData \\\"plugins\\\" or { }\\n plugins.lua = { loadContent = LuaPlugin }\\n Nova.app.setData( \\\"plugins\\\", plugins )\\n NovaUI.display.alert( UI, \\\"Plugin registered!\\\" )\\nend\\n\\nlocal button = UI:newChild( NovaUI.UIButton( 2, UI.h - 1, UI.w - 2, 1, \\\"install Lua plugin\\\" ) )\\nfunction button:onClick( )\\n installLua( )\\nend\",\
7483[\"LuaPlugin.lua\"]=\"LuaPlugin = [[local content = ...\\n\\nlocal env = { }\\nenv.ARGS = ARGS\\nenv._G = env\\n\\nenv._VERSION = _VERSION;\\nenv.pairs = pairs;\\nenv.ipairs = ipairs;\\nenv.select = select;\\nenv.unpack = unpack;\\nenv.setfenv = setfenv;\\nenv.getfenv = getfenv;\\nenv.setmetatable = setmetatable;\\nenv.getmetatable = getmetatable;\\nenv.next = next;\\nenv.rawset = rawset;\\nenv.rawget = rawget;\\nenv.rawequal = rawequal;\\nenv.type = type;\\nenv.tostring = tostring;\\nenv.tonumber = tonumber;\\nenv.pcall = pcall;\\nenv.xpcall = xpcall;\\nenv.loadstring = loadstring;\\nenv.assert = assert;\\nenv.error = error;\\nenv.sleep = sleep;\\nenv.__inext = __inext;\\n\\nenv.math = math;\\nenv.string = string;\\nenv.table = table;\\nenv.coroutine = coroutine;\\nenv.sha256 = sha256;\\nenv.encryption = encryption;\\nenv.stringutils = stringutils;\\nenv.keys = keys;\\nenv.colours = colours;\\nenv.colors = colors;\\nenv.vector = vector;\\nenv.bit = bit;\\nenv.http = http;\\nenv.textutils = textutils;\\nenv.rednet = rednet;\\n\\nfunction env.write( ... )\\n local args = { ... }\\n local s = \\\"\\\"\\n for i = 1, #args do\\n s = s .. tostring( args[i] )\\n end\\n document.write( s )\\nend\\nfunction env.print( ... )\\n local args = { ... }\\n local s = \\\"\\\"\\n for i = 1, #args do\\n s = s .. tostring( args[i] )\\n end\\n document.write( s .. \\\"\\\\n\\\" )\\nend\\n\\nlocal function formatPath( path )\\n path = filesystem.merge( rootpath, path )\\n local parts = { }\\n local last = 1\\n for i = 1, #path do\\n if path:sub( i, i ) == \\\"/\\\" then\\n table.insert( parts, path:sub( last, i - 1 ) )\\n last = i + 1\\n end\\n end\\n table.insert( parts, path:sub( last ) )\\n local i = 1\\n while i <= #parts do\\n if parts[i] == \\\"..\\\" then\\n table.remove( parts, i )\\n table.remove( parts, i - 1 )\\n else\\n i = i + 1\\n end\\n end\\n if #parts == 0 then\\n parts[1] = \\\"\\\"\\n end\\n return filesystem.merge( unpack( parts ) )\\nend\\n\\nfunction env.readfile( path )\\n local filedata, err = filesystem.readfile( formatPath( path ) )\\n if filedata then\\n return filedata.content\\n end\\n return nil\\nend\\n\\nlocal f, err = loadstring( content, \\\"script\\\" )\\nif f then\\n setfenv( f, env )\\n f, err = pcall( f )\\nend\\nif not f then\\n err = err:gsub( \\\"string:97: \\\", \\\"\\\" )\\n document.write( \\\"<t tc:red>\\\" .. err .. \\\"</t>\\\" )\\nend\\n]]\",\
7484[\"icon.nim\"]=\"--[[\\n type = \\\"nova_image\\\",\\n]]07N07e07t07S07e07r07v07e07r\\n08<08p08>08H08i08<08/08p08>\\n08<08p08>08.08.08<08/08p08>\",\
7485[\"appconfig.txt\"]=\"handles = {\\n \\\"NetServerPlugin\\\";\\n};\\nextensions = {\\n [\\\"nsp\\\"] = \\\"NetServerPlugin\\\";\\n};\\nfiletypes = {\\n [\\\"NetServerPlugin\\\"] = \\\"NetServer plugin file\\\";\\n};\\nservices = {\\n \\\"responder\\\";\\n};\",\
7486}\
7487,\
7488Files={\
7489[\"icon.nim\"]=\"1F 1F 1F 1F 1F 1F 1F 1F 1F \\n4F 4F 47F47i47l47e47s4F 4F \\n4F 4F 4F 4F 4F 4F 4F 4F 4F \",\
7490[\"main.lua\"]=\"\\nif not Nova then\\n error( \\\"Cannot run outside Nova\\\", 0 )\\nend\\n\\nlocal icons = { }\\nicons.app = NovaUI.Image( 5, 3 ):loadstr [[BF BF BF BF BF \\n3F 3Ea3Ep3Ep3F \\n9F 9F 9F 9F 9F ]]\\nicons.archive = NovaUI.Image( 5, 3 ):loadstr [[4F 4F 4F 0F 0F \\n1F 1F 1F 1F 1F \\n1F 1F 1F 1F 1F ]]\\nicons.class = NovaUI.Image( 5, 3 ):loadstr [[01c01l01a01s01s\\n00 08-08-08-00 \\n00 08-08-08-00 ]]\\nicons.design_file = NovaUI.Image( 5, 3 ):loadstr [[ F BF BF F F \\n F BF F BF F \\n F BF BF F F ]]\\nicons.design_project = NovaUI.Image( 5, 3 ):loadstr [[ F BF BF F F \\n F BF 3 BF F \\n 3PB3rB3j 3c 3t]]\\nicons.folder = NovaUI.Image( 5, 3 ):loadstr [[10 10 10 0F 0F \\n40 40 40 4F 4F \\n40 40 40 40 40 ]]\\nicons.lib = NovaUI.Image( 5, 3 ):loadstr [[0F 09l09i09b0F \\n00 08-08-08-00 \\n00 08-08-08-00 ]]\\nicons.lua = NovaUI.Image( 5, 3 ):loadstr [[0F 03l03u03a0F \\n00 08-08-08-00 \\n00 08-08-08-00 ]]\\nicons.nova_image = NovaUI.Image( 5, 3 ):loadstr [[3Bi3Bm3B 3B 4B \\n3B 3B 3B 3B 3B \\nDB DB DB DB DB ]]\\nicons.shortcut = NovaUI.Image( 5, 3 ):loadstr [[08s08h08o08r08t\\n03 08c08u08t08 \\n08-08-08-08-08-]]\\nicons.text = NovaUI.Image( 5, 3 ):loadstr [[0F 0Ft0Fx0Ft08 \\n0F 08-08-08-08 \\n0F 08-08-08-08 ]]\\nicons.unknown = NovaUI.Image( 5, 3 ):loadstr [[0F 08-08-08-0F \\n0F 08-08-08-0F \\n0F 08-08-08-0F ]]\\n\\nlocal UI = Nova.app.UI\\n\\nlocal topbar = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, 3 ) )\\ntopbar:newChild( NovaUI.UIText( 1, 1, topbar.w, topbar.h, \\\"\\\" ) ).bc = colours.grey\\n\\nlocal sidebar = UI:newChild( NovaUI.UIFrame( 1, 5, 13, UI.h - 5 ) )\\nlocal sidebarborder = UI:newChild( NovaUI.UIText( sidebar.x + sidebar.w + 1, 4, 1, UI.h - 3, \\\"\\\" ) )\\nsidebarborder.bc = colours.lightGrey\\n\\nif Nova.platform ~= \\\"computer\\\" then\\n sidebar.w = 0\\n sidebarborder.x = 0\\nend\\n\\nlocal background = UI:newChild( NovaUI.UIButton( sidebar.x + sidebar.w, topbar.y + topbar.h, 0, 0, \\\"\\\" ) )\\nbackground.bc = 0\\nbackground.align = false\\nbackground.w = UI.w - background.x + 1\\nbackground.h = UI.h - background.y + 1\\n\\nlocal filecontent = UI:newChild( NovaUI.UIFrame( sidebarborder.x + sidebarborder.w + 1, topbar.y + topbar.h + 1, 0, 0 ) )\\nfilecontent.w = UI.w - filecontent.x - 1\\nfilecontent.h = UI.h - filecontent.y\\n\\nlocal scrollbar = UI:newChild( NovaUI.UIScrollBar( 0, 0, 1, 0, filecontent ) )\\nscrollbar:align( \\\"right\\\", filecontent )\\n\\nlocal stage = 0\\nlocal loading\\nlocal dir = 1\\nlocal ltime = 0\\nlocal loader = UI:newChild( NovaUI.UIText( UI.w - 4, UI.h, 4, 1, function( )\\n if not loading then return \\\"\\\" end\\n if os.clock( ) - ltime > 0.2 then\\n stage = stage + dir\\n if stage == 4 then dir = -1 end\\n if stage == 1 then dir = 1 end\\n ltime = os.clock( )\\n end\\n if stage == 1 then return \\\"o...\\\"\\n elseif stage == 2 then return \\\".o..\\\"\\n elseif stage == 3 then return \\\"..o.\\\"\\n elseif stage == 4 then return \\\"...o\\\"\\n end\\nend ) )\\n\\nlocal filepath = \\\"C:\\\"\\nlocal history = { }\\nlocal historyindex = 0\\nlocal favourites = Nova.app.getData \\\"favourites\\\" or { }\\n\\nlocal sortorder = Nova.app.getData \\\"sortorder\\\" or { mode = \\\"name\\\", direction = \\\"ascending\\\" }\\nlocal viewhidden = Nova.app.getData \\\"viewhidden\\\"\\nif viewhidden == nil then\\n viewhidden = false\\nend\\nlocal defaults = Nova.app.getData \\\"defaults\\\" or { }\\nfor k, v in pairs( defaults ) do\\n Nova.filesystem.setDefaultHandler( k, v )\\nend\\n\\nlocal mount = false\\n\\nlocal function charweight( char )\\n return string.byte( char:lower( ) )\\nend\\nlocal function compstring( s1, s2 ) -- s1 > s2\\n for i = 1, #s1 do\\n if #s2 < i then\\n return true\\n end\\n local c1, c2 = s1:sub( i, i ), s2:sub( i, i )\\n local w1, w2 = charweight( c1 ), charweight( c2 )\\n if w1 > w2 then\\n return true\\n elseif w1 < w2 then\\n return false\\n end\\n end\\n return false\\nend\\n\\nlocal function formatSize( s )\\n if s < 1024 then\\n return s .. \\\" bytes\\\"\\n end\\n s = s / 1024\\n if s < 1024 then\\n return math.floor( s ) .. \\\"KB\\\"\\n end\\n s = s / 1024\\n if s < 1024 then\\n return math.floor( s ) .. \\\"MB\\\"\\n end\\nend\\n\\nlocal activedropdown\\nlocal function dropdown( x, y, options )\\n local frame = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n local close = frame:newChild( NovaUI.UIButton( 1, 1, frame.w, frame.h, \\\"\\\" ) )\\n close.bc = 0\\n close.align = false\\n function close:onClick( )\\n activedropdown = nil\\n frame:remove( )\\n end\\n local f = frame:newChild( NovaUI.UIFrame( x, y, 15, 10 ) )\\n activedropdown = frame\\n NovaUI.display.menu( f, options )\\n if f.x + f.w > UI.w then\\n f.x = UI.w - f.w + 1\\n end\\n if f.y + f.h > UI.h then\\n f.y = UI.h - f.h + 1\\n end\\nend\\n\\nfunction Nova.app.canClose( reason )\\n if mount then\\n Nova.filesystem.unmount( mount )\\n end\\nend\\n\\nlocal showFolderOptions, showFileOptions\\n\\nlocal openPath\\nlocal function listFiles( path, openAs )\\n local l = loading\\n loading = true\\n filecontent:clearChildren( )\\n\\n if mount and path:sub( 1, #mount ) ~= mount then\\n Nova.filesystem.unmount( mount )\\n mount = nil\\n end\\n\\n local _dirs, _files = { }, { }\\n local files = Nova.filesystem.listFiles( path, function( files )\\n for i = 1, #files do\\n if files[i]:sub( 1, 1 ) ~= \\\".\\\" or viewhidden then\\n if Nova.filesystem.isDirectory( Nova.filesystem.merge( path, files[i] ) ) then\\n table.insert( _dirs, { name = files[i], size = Nova.filesystem.getSize( Nova.filesystem.merge( path, files[i] ) ) } )\\n else\\n table.insert( _files, { name = files[i], size = Nova.filesystem.getSize( Nova.filesystem.merge( path, files[i] ) ) } )\\n end\\n end\\n end\\n end )\\n\\n if not files then\\n loading = false\\n if not openAs then\\n openAs = Nova.filesystem.getType( path )\\n end\\n if openAs == \\\"shortcut\\\" then\\n local ok, data = Nova.filesystem.getFileMetaValue( path, \\\"destination\\\" )\\n if ok then\\n openPath( data )\\n else\\n Nova.app.newThread( function( )\\n NovaUI.display.alert( UI, \\\"Malformed shorcut\\\", true )\\n Nova.app.close( )\\n end )\\n end\\n return\\n elseif openAs == \\\"archive\\\" then\\n local archive = Archive( path )\\n local pass\\n if Nova.filesystem.isProtected( path ) then\\n local r = NovaUI.display.response( UI, \\\"Please enter archive password\\\" )\\n if r then\\n pass = r\\n else\\n return\\n end\\n end\\n archive:loadFile( path, pass )\\n mount = Nova.filesystem.getName( path, true )\\n Nova.filesystem.mount( archive:createDrive( ), mount )\\n openPath( mount .. \\\":\\\" )\\n return\\n end\\n if Nova.filesystem.exists( path ) then\\n local handlers = Nova.filesystem.getHandlers( openAs )\\n if handlers[#handlers] then\\n Nova.app.close( )\\n Nova.app.launch( handlers[#handlers], path, openAs )\\n else\\n Nova.app.newThread( function( )\\n NovaUI.display.alert( UI, \\\"Unknown file type.\\\", true )\\n Nova.app.close( )\\n end )\\n end\\n else\\n Nova.app.newThread( function( )\\n NovaUI.display.alert( UI, \\\"File doesn't exist\\\", true )\\n Nova.app.close( )\\n end )\\n end\\n return\\n end\\n\\n if #files == 0 then\\n filecontent:newChild( NovaUI.UIText( 0, 2, 9, 1, \\\"No files!\\\" ) ):centreX( )\\n end\\n\\n -- sort based on name or size\\n table.sort( _dirs, function( i1, i2 )\\n if sortorder.mode == \\\"name\\\" then\\n return not compstring( i1.name, i2.name )\\n elseif sortorder.mode == \\\"size\\\" then\\n return i1.size < i2.size\\n end\\n end )\\n table.sort( _files, function( i1, i2 )\\n if sortorder.mode == \\\"name\\\" then\\n return not compstring( i1.name, i2.name )\\n elseif sortorder.mode == \\\"size\\\" then\\n return i1.size < i2.size\\n end\\n end )\\n\\n -- give each item a full path\\n for i = 1, #_dirs do\\n _dirs[i].path = Nova.filesystem.merge( path, _dirs[i].name )\\n end\\n for i = 1, #_files do\\n _files[i].path = Nova.filesystem.merge( path, _files[i].name )\\n end\\n\\n -- reverse order if neccessary\\n if sortorder.direction == \\\"descending\\\" then\\n for i = 1, math.ceil( #_dirs / 2 ) do\\n _dirs[i], _dirs[#_dirs-i + 1] = _dirs[#_dirs-i + 1], _dirs[i]\\n end\\n for i = 1, math.ceil( #_files / 2 ) do\\n _files[i], _files[#_files-i + 1] = _files[#_files-i + 1], _files[i]\\n end\\n end\\n\\n local y = 1\\n\\n local function loadDirs( )\\n for i = 1, #_dirs do\\n local frame = filecontent:newChild( NovaUI.UIFrame( 1, y, filecontent.w, 3 ) )\\n local app_registered = not viewhidden and Nova.app.isRegistered( _dirs[i].path:gsub( \\\"^C:/?\\\", \\\"\\\", 1 ) )\\n if app_registered then\\n local icon = frame:newChild( NovaUI.UIImage( 1, 1, 5, 3, icons.app ) )\\n else\\n local icon = frame:newChild( NovaUI.UIImage( 1, 1, 5, 3, icons.folder ) )\\n end\\n local name = frame:newChild( NovaUI.UIText( 7, 1, frame.w - 6, 1, Nova.filesystem.getName( _dirs[i].path ) ) )\\n local size = frame:newChild( NovaUI.UIText( 7, 2, frame.w - 7, 1, formatSize( _dirs[i].size ) ) )\\n size.tc = colours.lightGrey\\n local desc = frame:newChild( NovaUI.UIText( 7, 3, frame.w - 7, 1, app_registered and \\\"Nova app\\\" or \\\"Folder\\\" ) )\\n desc.tc = colours.lightGrey\\n\\n local overlay = frame:newChild( NovaUI.UIButton( 1, 1, frame.w, frame.h, \\\"\\\" ) )\\n overlay.bc = 0\\n overlay.align = false\\n function overlay:onClick( x, y, button )\\n if button == 1 or button == \\\"enter\\\" then\\n if app_registered then\\n Nova.app.launch( app_registered )\\n else\\n openPath( _dirs[i].path )\\n end\\n else\\n showFolderOptions( filecontent.x + frame.x + x - 2, filecontent.y + frame.y + y - 2 + filecontent.cy, _dirs[i].path, frame )\\n end\\n end\\n y = y + 4\\n end\\n end\\n if sortorder.direction == \\\"ascending\\\" then\\n loadDirs( )\\n end\\n for i = 1, #_files do\\n local filetype = Nova.filesystem.getType( _files[i].path )\\n\\n local app_registered = not viewhidden and Nova.app.isRegistered( _files[i].path:gsub( \\\"^C:/?\\\", \\\"\\\", 1 ) )\\n local frame = filecontent:newChild( NovaUI.UIFrame( 1, y, filecontent.w, 3 ) )\\n if app_registered then\\n local icon = frame:newChild( NovaUI.UIImage( 1, 1, 5, 3, icons.app ) )\\n else\\n local icon = frame:newChild( NovaUI.UIImage( 1, 1, 5, 3, icons[filetype] or icons.unknown ) )\\n end\\n local name = frame:newChild( NovaUI.UIText( 7, 1, frame.w - 6, 1, Nova.filesystem.getName( _files[i].path, true ) ) )\\n local size = frame:newChild( NovaUI.UIText( 7, 2, frame.w - 7, 1, formatSize( _files[i].size ) ) )\\n size.tc = colours.lightGrey\\n local desc = frame:newChild( NovaUI.UIText( 7, 3, frame.w - 7, 1, app_registered and \\\"Nova app\\\" or Nova.filesystem.getTypeDescription( filetype ) or filetype ) )\\n desc.tc = colours.lightGrey\\n\\n local overlay = frame:newChild( NovaUI.UIButton( 1, 1, frame.w, frame.h, \\\"\\\" ) )\\n overlay.bc = 0\\n overlay.align = false\\n function overlay:onClick( x, y, button )\\n if button == 1 or button == \\\"enter\\\" then\\n if app_registered then\\n Nova.app.launch( app_registered )\\n else\\n if filetype == \\\"shortcut\\\" then\\n local ok, data = Nova.filesystem.getFileMetaValue( _files[i].path, \\\"destination\\\" )\\n if ok then\\n if Nova.filesystem.isDirectory( data ) then\\n openPath( data )\\n else\\n Nova.app.launch( \\\"Files\\\", data )\\n end\\n else\\n Nova.app.newThread( function( )\\n NovaUI.display.alert( UI, \\\"Malformed shorcut\\\", true )\\n Nova.app.close( )\\n end )\\n end\\n return\\n elseif filetype == \\\"archive\\\" then\\n Nova.app.newThread( function( )\\n loading = true\\n local archive = Archive( _files[i].path )\\n local pass\\n if Nova.filesystem.isProtected( _files[i].path ) then\\n local r = NovaUI.display.response( UI, \\\"Please enter archive password\\\" )\\n if r then\\n pass = r\\n else\\n return\\n end\\n end\\n archive:loadFile( _files[i].path, pass )\\n mount = Nova.filesystem.getName( _files[i].path, true )\\n Nova.filesystem.mount( archive:createDrive( ), mount )\\n openPath( mount .. \\\":\\\" )\\n loading = false\\n end )\\n return\\n else\\n Nova.app.launch( \\\"Files\\\", _files[i].path, filetype )\\n end\\n end\\n else\\n showFileOptions( filecontent.x + frame.x + x - 2, filecontent.y + frame.y + y - 2 + filecontent.cy, _files[i].path, frame )\\n end\\n end\\n\\n y = y + 4\\n end\\n if sortorder.direction == \\\"descending\\\" then\\n loadDirs( )\\n end\\n loading = l\\nend\\n\\nlocal function refresh( )\\n listFiles( filepath )\\nend\\n\\nlocal pathinput\\n\\nfunction openPath( path, openAs )\\n if not viewhidden and Nova.app.isRegistered( path:gsub( \\\"^C:/?\\\", \\\"\\\", 1 ) ) then\\n Nova.app.launch( Nova.app.isRegistered( path:gsub( \\\"^C:/?\\\", \\\"\\\", 1 ) ) )\\n return\\n end\\n filecontent.cy = 0\\n historyindex = historyindex + 1\\n while history[historyindex] do\\n table.remove( history, historyindex )\\n end\\n table.insert( history, path )\\n filepath = path\\n pathinput.text = path\\n listFiles( path, openAs )\\nend\\n\\nlocal backbutton = topbar:newChild( NovaUI.UIButton( 2, 2, 3, 1, \\\"<\\\" ) )\\nbackbutton.bc = colours.lightGrey\\nbackbutton.tc = colours.black\\nfunction backbutton:onClick( )\\n if historyindex > 1 then\\n historyindex = historyindex - 1\\n listFiles( history[historyindex] )\\n filepath = history[historyindex]\\n pathinput.text = history[historyindex]\\n end\\nend\\n\\nlocal forwardbutton = topbar:newChild( NovaUI.UIButton( 6, 2, 3, 1, \\\">\\\" ) )\\nforwardbutton.bc = colours.lightGrey\\nforwardbutton.tc = colours.black\\nfunction forwardbutton:onClick( )\\n if historyindex < #history then\\n historyindex = historyindex + 1\\n listFiles( history[historyindex] )\\n filepath = history[historyindex]\\n pathinput.text = history[historyindex]\\n end\\nend\\n\\nlocal upbutton = topbar:newChild( NovaUI.UIButton( 10, 2, 3, 1, \\\"^\\\" ) )\\nupbutton.bc = colours.lightGrey\\nupbutton.tc = colours.black\\nfunction upbutton:onClick( )\\n local up = ( filepath:match \\\"(.+/)\\\" or filepath .. \\\" \\\" ):sub( 1, -2 )\\n if up == filepath and filepath:find \\\":.\\\" then\\n up = ( filepath:match \\\"(.+:.)\\\" or filepath .. \\\" \\\" ):sub( 1, -2 )\\n end\\n openPath( up )\\nend\\n\\npathinput = topbar:newChild( NovaUI.UIInput( 14, 2, topbar.w - 14, 1 ) )\\npathinput.bc = colours.lightGrey\\npathinput.fbc = colours.white\\n\\nfunction pathinput:onEnter( )\\n openPath( self.text )\\nend\\n\\nlocal sidebarcontent\\n\\nNova.app.newThread( function( )\\n while true do\\n local cy = sidebarcontent and sidebarcontent.cy or 0\\n local t = {\\n { type = \\\"label\\\", name = \\\"Drives\\\" };\\n }\\n local drives = Nova.filesystem.listmounted( )\\n for i = 1, #drives do\\n t[#t + 1] = {\\n type = \\\"button\\\";\\n name = drives[i] .. \\\":\\\";\\n onClick = function( self, _, button )\\n if button == 1 then\\n openPath( drives[i] .. \\\":\\\" )\\n else\\n local x, y = self:positionIn( UI )\\n dropdown( x, y, {\\n spacing = false;\\n shadow = colours.grey;\\n height = 5;\\n \\\"space\\\";\\n { type = \\\"button\\\", name = \\\"open\\\", onClick = function( _, frame )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n openPath( drives[i] .. \\\":\\\" )\\n end };\\n { type = \\\"button\\\", name = \\\"unmount\\\", onClick = function( _, frame )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n if drives[i] ~= \\\"C\\\" then\\n Nova.filesystem.unmount( drives[i] )\\n else\\n NovaUI.display.alert( UI, \\\"Cannot unmount this drive.\\\" )\\n end\\n end };\\n } )\\n end\\n end;\\n }\\n end\\n table.insert( t, \\\"space\\\" )\\n table.insert( t, { type = \\\"label\\\", name = \\\"Favourites\\\" } )\\n for k, v in pairs( favourites ) do\\n table.insert( t, { type = \\\"button\\\", name = Nova.filesystem.getName( k, true ), onClick = function( self, frame, button )\\n if button == 1 then\\n openPath( k )\\n else\\n local x, y = self:positionIn( UI )\\n dropdown( x, y, {\\n spacing = false;\\n shadow = colours.grey;\\n height = 5;\\n \\\"space\\\";\\n { type = \\\"button\\\", name = \\\"open\\\", onClick = function( _, frame )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n openPath( k )\\n end };\\n { type = \\\"button\\\", name = \\\"remove\\\", onClick = function( _, frame )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n favourites[k] = nil;\\n Nova.app.setData( \\\"favourites\\\", favourites )\\n end };\\n } )\\n end\\n end } )\\n end\\n sidebarcontent = NovaUI.display.menu( sidebar, t )\\n sidebarcontent.cy = cy\\n local t = os.clock( )\\n while os.clock( ) - t < 0.5 do\\n coroutine.yield( )\\n end\\n end\\nend )\\n\\nfunction background:onClick( x, y, button )\\n if button == 2 then\\n dropdown( background.x + x - 1, background.y + y - 1, {\\n spacing = false;\\n shadow = colours.grey;\\n height = 11;\\n { type = \\\"button\\\", name = \\\"new file\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local p = Nova.filesystem.merge( filepath, \\\"New File\\\" )\\n if Nova.filesystem.exists( p ) then\\n local i = 1\\n while Nova.filesystem.exists( p .. \\\" (\\\" .. i .. \\\")\\\" ) do\\n i = i + 1\\n end\\n p = p .. \\\" (\\\" .. i .. \\\")\\\"\\n end\\n Nova.filesystem.newFile( p )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"new folder\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local p = Nova.filesystem.merge( filepath, \\\"New Folder\\\" )\\n if Nova.filesystem.exists( p ) then\\n local i = 1\\n while Nova.filesystem.exists( p .. \\\" (\\\" .. i .. \\\")\\\" ) do\\n i = i + 1\\n end\\n p = p .. \\\" (\\\" .. i .. \\\")\\\"\\n end\\n Nova.filesystem.newDirectory( p )\\n refresh( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"paste\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.paste( filepath )\\n refresh( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"refresh\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n refresh( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = viewhidden and \\\"hide hidden\\\" or \\\"show hidden\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n viewhidden = not viewhidden\\n Nova.app.setData( \\\"viewhidden\\\", viewhidden )\\n refresh( )\\n end };\\n { type = \\\"menu\\\", name = \\\"sort by\\\", options = {\\n spacing = false;\\n shadow = colours.grey;\\n width = 7;\\n height = 3;\\n { type = \\\"button\\\", name = \\\"name\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n sortorder.mode = \\\"name\\\"\\n Nova.app.setData( \\\"sortorder\\\", sortorder )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"size\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n sortorder.mode = \\\"size\\\"\\n Nova.app.setData( \\\"sortorder\\\", sortorder )\\n refresh( )\\n end };\\n } };\\n { type = \\\"menu\\\", name = \\\"sort\\\", options = {\\n spacing = false;\\n shadow = colours.grey;\\n width = 13;\\n height = 3;\\n { type = \\\"button\\\", name = \\\"ascending\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n sortorder.direction = \\\"ascending\\\"\\n Nova.app.setData( \\\"sortorder\\\", sortorder )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"descending\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n sortorder.direction = \\\"descending\\\"\\n Nova.app.setData( \\\"sortorder\\\", sortorder )\\n refresh( )\\n end };\\n } };\\n } )\\n end\\nend\\n\\nlocal renaming = false\\n\\nfunction showFileOptions( x, y, path, frame )\\n local archive = Nova.filesystem.getType( path ) == \\\"archive\\\"\\n local _type = Nova.filesystem.getType( path )\\n dropdown( x, y, {\\n spacing = false;\\n shadow = colours.grey;\\n height = 14;\\n { type = \\\"button\\\", name = \\\"open\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.app.launch( \\\"Files\\\", path, _type )\\n end };\\n { type = \\\"button\\\", name = archive and \\\"extract\\\" or \\\"open with\\\", onClick = function( )\\n if archive then\\n Nova.app.newThread( function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local p = path:gsub( \\\".nac$\\\", \\\"\\\", 1 )\\n local archive = Archive( )\\n local pass\\n if Nova.filesystem.isProtected( path ) then\\n pass = NovaUI.display.response( UI, \\\"Please enter password\\\" )\\n if not pass then\\n Nova.display.response( UI, \\\"Archive is password protected\\\" )\\n return\\n end\\n end\\n archive:loadFile( path, pass )\\n local i = 1\\n while Nova.filesystem.exists( p ) do\\n p = path:gsub( \\\".nac$\\\", \\\"\\\", 1 ) .. \\\" (\\\" .. i .. \\\")\\\"\\n i = i + 1\\n end\\n archive:saveDirectory( p )\\n refresh( )\\n end )\\n else\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local _, _, document = NovaUI.markup.load( [[\\n <script>\\n function close( )\\n document.getElementById( \\\"main\\\" ):remove( )\\n end\\n function toggleDefault( )\\n local d = document.getElementById \\\"default\\\"\\n d.text = d.text == \\\"@\\\" and \\\" \\\" or \\\"@\\\"\\n end\\n </script>\\n <frame x:1 y:1 id:main width:expand height:expand>\\n <button width:expand height:expand tc:0 bc:0 onClick:\\\"close()\\\"></button>\\n <frame width:25 height:12 x:centre y:centre>\\n <text x:2 y:2 width:24 height:11 bc:grey></text>\\n <text x:1 y:1 width:24 height:11 bc:white></text>\\n <text x:centre y:2 width:auto>Open with...</text>\\n <text x:2 y:3 width:auto tc:lightGrey>----------------------</text>\\n <text id:applist x:2 y:4 width:22 height:5>This is where the app list will be!</text>\\n <text x:2 y:9 width:auto tc:lightGrey>----------------------</text>\\n <button x:2 y:10 width:3 tc:lightGrey onClick:\\\"toggleDefault()\\\">[ ]</button>\\n <button align:right spacing:1 width:auto tc:grey onClick=\\\"toggleDefault()\\\">Default</button>\\n <button id:ok x:20 y:10 width:4 bc:lightGrey tc:black>ok</button>\\n <button id:default x:3 y:10 width:1 onClick:\\\"toggleDefault()\\\">@</button>\\n </frame>\\n </frame>\\n ]], UI, getfenv( ) )\\n local frame = document.getElementById \\\"main\\\"\\n local a = document.getElementById \\\"applist\\\"\\n local default = document.getElementById \\\"default\\\"\\n local ok = document.getElementById \\\"ok\\\"\\n local applist = a.parent:newChild( NovaUI.UIFrame( a.x, a.y, a.w, a.h ) )\\n local handlers = Nova.filesystem.getHandlers( _type )\\n if #handlers == 0 then\\n handlers = Nova.filesystem.getAllHandlers( )\\n end\\n if #handlers + 1 > applist.h then\\n applist.w = applist.w - 1\\n a.parent:newChild( NovaUI.UIScrollBar( 0, 0, 1, 0, applist ) ):align( \\\"right\\\", applist )\\n end\\n a:remove( )\\n local current\\n for i = 1, #handlers do\\n local button = applist:newChild( NovaUI.UIButton( 1, i, applist.w, 1, handlers[i] ) )\\n button.tc = colours.grey\\n button.align = false\\n function button:onClick( )\\n if current == button then\\n button.bc = colours.white ok.bc = colours.lightGrey current = nil return\\n end\\n ok.bc = colours.lightBlue\\n if current then current.bc = colours.white end\\n button.bc = colours.lightBlue\\n current = button\\n end\\n end\\n local showall = applist:newChild( NovaUI.UIButton( 1, #handlers + 1, applist.w, 1, \\\"Show all\\\" ) )\\n showall.align = false\\n function showall:onClick( )\\n current = nil\\n applist:clearChildren( )\\n local handlers = Nova.filesystem.getAllHandlers( )\\n for i = 1, #handlers do\\n local button = applist:newChild( NovaUI.UIButton( 1, i, applist.w, 1, handlers[i] ) )\\n button.tc = colours.grey\\n button.align = false\\n function button:onClick( )\\n if current == button then\\n button.bc = colours.white ok.bc = colours.lightGrey current = nil return\\n end\\n ok.bc = colours.lightBlue\\n if current then current.bc = colours.white end\\n button.bc = colours.lightBlue\\n current = button\\n end\\n end\\n end\\n function ok:onClick( )\\n if not current then return end\\n local app = current.text\\n if default.text == \\\"@\\\" then\\n if defaults[_type] ~= app then\\n defaults[_type] = app\\n Nova.app.setData( \\\"defaults\\\", defaults )\\n end\\n Nova.filesystem.setDefaultHandler( _type, app )\\n end\\n frame:remove( )\\n Nova.app.launch( app, path, _type )\\n end\\n end\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"copy\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.copy( path )\\n end };\\n { type = \\\"button\\\", name = \\\"cut\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.cut( path )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"delete\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.delete( path )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"rename\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n renaming = true\\n local input = filecontent:newChild( NovaUI.UIInput( frame.x + 6, frame.y, frame.w - 7, 1 ) )\\n input.bc = 0\\n input.fbc = 0\\n input.text = Nova.filesystem.getName( path )\\n input:focusOn( )\\n input:select( 1, #Nova.filesystem.getName( path, true ) )\\n\\n function input:onEnter( )\\n local p = Nova.filesystem.merge( filepath, self.text )\\n if p == path then return end\\n if Nova.filesystem.exists( p ) then\\n NovaUI.display.alert( UI, \\\"File already exists\\\" )\\n else\\n pcall( Nova.filesystem.move, path, p )\\n refresh( )\\n end\\n end\\n function input:whenUnFocussed( )\\n Nova.log \\\"unfocussed\\\"\\n input:remove( )\\n renaming = false\\n end\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"shortcut\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local fd = FileData \\\"\\\"\\n fd.meta.destination = path\\n fd.meta.type = \\\"shortcut\\\"\\n local p = Nova.filesystem.merge( Nova.filesystem.getDirectory( path ), Nova.filesystem.getName( path, true ) ) .. \\\" - Shortcut\\\"\\n if Nova.filesystem.exists( p ) then\\n local i = 1\\n while Nova.filesystem.exists( p .. \\\" (\\\" .. i .. \\\")\\\" ) do\\n i = i + 1\\n end\\n p = p .. \\\" (\\\" .. i .. \\\")\\\"\\n end\\n Nova.filesystem.writefile( p, fd )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"properties\\\" };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = Nova.filesystem.isProtected( path ) and \\\"unprotect\\\" or \\\"protect\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.app.newThread( function( )\\n local r\\n local p = Nova.filesystem.isProtected( path )\\n if p then\\n r = NovaUI.display.response( UI, \\\"Password to decrypt file with.\\\" )\\n else\\n r = NovaUI.display.response( UI, \\\"Password to encrypt file with.\\\" )\\n end\\n loading = true\\n if r then\\n local data, err = Nova.filesystem.readfile( path, p and r )\\n if not p then\\n data.password = r\\n end\\n data.meta.password = not p and true or nil\\n Nova.filesystem.writefile( path, data )\\n refresh( )\\n end\\n loading = false\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"favourite\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n if not favourites[path] then\\n favourites[path] = true\\n Nova.app.setData( \\\"favourites\\\", favourites )\\n end\\n end };\\n } )\\nend\\n\\nfunction showFolderOptions( x, y, path, frame )\\n dropdown( x, y, {\\n spacing = false;\\n shadow = colours.grey;\\n height = 14;\\n { type = \\\"button\\\", name = \\\"open\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n openPath( path )\\n end };\\n { type = \\\"button\\\", name = \\\"open in new\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.app.launch( \\\"Files\\\", path )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"copy\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.copy( path )\\n end };\\n { type = \\\"button\\\", name = \\\"cut\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.cut( path )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"delete\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n Nova.filesystem.delete( path )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"rename\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n renaming = true\\n local input = filecontent:newChild( NovaUI.UIInput( frame.x + 6, frame.y, frame.w - 7, 1 ) )\\n input.bc = 0\\n input.fbc = 0\\n input.text = Nova.filesystem.getName( path )\\n input:focusOn( )\\n input:select( 1, #Nova.filesystem.getName( path, true ) )\\n\\n function input:onEnter( )\\n local p = Nova.filesystem.merge( filepath, self.text )\\n if p == path then return end\\n if Nova.filesystem.exists( p ) then\\n NovaUI.display.alert( UI, \\\"File already exists\\\" )\\n else\\n pcall( Nova.filesystem.move, path, p )\\n refresh( )\\n end\\n end\\n function input:whenUnFocussed( ) \\n input:remove( )\\n renaming = false\\n end\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"shortcut\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local fd = FileData \\\"\\\"\\n fd.meta.destination = path\\n fd.meta.type = \\\"shortcut\\\"\\n local p = Nova.filesystem.merge( Nova.filesystem.getDirectory( path ), Nova.filesystem.getName( path, true ) ) .. \\\" - Shortcut\\\"\\n if Nova.filesystem.exists( p ) then\\n local i = 1\\n while Nova.filesystem.exists( p .. \\\" (\\\" .. i .. \\\")\\\" ) do\\n i = i + 1\\n end\\n p = p .. \\\" (\\\" .. i .. \\\")\\\"\\n end\\n Nova.filesystem.writefile( p, fd )\\n refresh( )\\n end };\\n { type = \\\"button\\\", name = \\\"properties\\\" };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"archive\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n local archive = Archive( )\\n loading = true\\n archive:loadDirectory( path )\\n local p = path .. \\\".nac\\\"\\n local i = 1\\n while Nova.filesystem.exists( p ) do\\n p = path .. \\\" (\\\" .. i .. \\\").nac\\\"\\n i = i + 1\\n end\\n archive:saveFile( p )\\n refresh( )\\n loading = false\\n end };\\n { type = \\\"button\\\", name = \\\"favourite\\\", onClick = function( )\\n if activedropdown then activedropdown:remove( ) activedropdown = nil end\\n if not favourites[path] then\\n favourites[path] = true\\n Nova.app.setData( \\\"favourites\\\", favourites )\\n end\\n end };\\n } )\\nend\\n\\nNova.app.newThread( function( )\\n while true do\\n local t = os.clock( )\\n while os.clock( ) - t < 4 do\\n coroutine.yield( )\\n end\\n if not renaming then\\n refresh( )\\n end\\n end\\nend )\\n\\nlocal keyhandler = Nova.app.UI:newChild( NovaUI.UIKeyHandler( ) )\\nfunction keyhandler:onKey( key )\\n if key == keys.backspace then\\n if historyindex > 1 then\\n historyindex = historyindex - 1\\n listFiles( history[historyindex] )\\n filepath = history[historyindex]\\n pathinput.text = history[historyindex]\\n end\\n end\\nend\\n\\nif ARGS[1] then\\n openPath( ARGS[1], ARGS[2] )\\nelse\\n openPath \\\"C:\\\"\\nend\",\
7491}\
7492,\
7493Browser={\
7494[\"icon.nim\"]=\"--[[\\n type = \\\"nova_image\\\",\\n]]B0 40 40 40 \\nB0 D0 7N 7e 7t \\nE0 E0 E0 D0 \",\
7495[\"main.lua\"]=\"\\nlocal downloadpath\\nif Nova.filesystem.isDirectory \\\"user:\\\" then\\n if not Nova.filesystem.isDirectory \\\"user:/downloads\\\" then\\n Nova.filesystem.newDirectory \\\"user:/downloads\\\"\\n end\\n downloadpath = \\\"user:/downloads\\\"\\nelse\\n if not Nova.filesystem.isDirectory \\\"C:/downloads\\\" then\\n Nova.filesystem.newDirectory \\\"C:/downloads\\\"\\n end\\n downloadpath = \\\"C:/downloads\\\"\\nend\\n\\nlocal UI = Nova.app.UI\\n\\nlocal topbar = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, 3 ) )\\ntopbar:newChild( NovaUI.UIText( 1, 1, topbar.w, topbar.h, \\\"\\\" ) ).bc = colours.grey\\n\\nlocal backbutton = topbar:newChild( NovaUI.UIButton( 2, 2, 3, 1, \\\"<\\\" ) )\\nbackbutton.bc = colours.lightGrey\\nbackbutton.tc = colours.black\\n\\nlocal forwardbutton = topbar:newChild( NovaUI.UIButton( 6, 2, 3, 1, \\\">\\\" ) )\\nforwardbutton.bc = colours.lightGrey\\nforwardbutton.tc = colours.black\\n\\nlocal refresh = topbar:newChild( NovaUI.UIButton( 10, 2, 3, 1, \\\"R\\\" ) )\\nrefresh.bc = colours.lightGrey\\nrefresh.tc = colours.black\\n\\nlocal pathinput = topbar:newChild( NovaUI.UIInput( 14, 2, topbar.w - 18, 1 ) )\\npathinput.bc = colours.lightGrey\\npathinput.fbc = colours.white\\n\\nlocal options = topbar:newChild( NovaUI.UIButton( topbar.w - 3, 2, 3, 1, \\\"=\\\" ) )\\noptions.bc = colours.lightGrey\\noptions.tc = colours.black\\n\\nlocal contentframe = UI:newChild( NovaUI.UIFrame( 1, 4, UI.w, UI.h - 3 ) )\\n\\nlocal browser = { }\\nbrowser.history = { }\\nbrowser.historyindex = 0\\nbrowser.page = markup.MarkupPage( Nova.app.UI.w, Nova.app.UI.h - 3 )\\nbrowser.frame = false\\nbrowser.settingframe = false\\nbrowser.id = false\\nbrowser.connection = false\\nbrowser.loading = false\\nbrowser.log = { }\\n\\nfunction Nova.app.canClose( )\\n for i = 1, #browser.page.threads do\\n browser.page.threads[i]:stop( )\\n end\\n if browser.connection then\\n network.close( browser.connection )\\n end\\nend\\n\\nfunction browser:clearCurrent( )\\n if self.frame then\\n self.frame:clearChildren( )\\n self.frame:remove( )\\n end\\n for i = 2, #self.page.threads do\\n self.page.threads[i]:stop( )\\n end\\n while self.page.errors[1] do\\n table.remove( self.page.errors, 1 )\\n end\\n self.log = { }\\nend\\nfunction browser:addContent( content )\\n self.page:loadMarkup( content )\\n local frame = self.page:repositionElements( )\\n self.frame = frame\\n contentframe:newChild( frame )\\nend\\nfunction browser:goto( href, noHistory )\\n while self.loading do\\n coroutine.yield( )\\n end\\n self.loading = true\\n pathinput.text = \\\"loading...\\\"\\n local newhref = href\\n if not noHistory then\\n while self.history[self.historyindex + 1] do\\n table.remove( self.history, self.historyindex + 1 )\\n end\\n end\\n local content\\n if href:sub( 1, 5 ) == \\\"file:\\\" then\\n local history = Nova.app.getData \\\"history\\\" or { }\\n table.insert( history, { time = Nova.irltime(), date = Nova.irldate(), url = href:sub( 6 ) } )\\n Nova.app.setData( \\\"history\\\", history )\\n local pass\\n if Nova.filesystem.isProtected( href:sub( 6 ) ) then\\n pass = NovaUI.display.response( UI, \\\"File is password protected\\\" )\\n if not pass then\\n content = \\\"<t tc:red>file is password protected</t>\\\"\\n end\\n end\\n if not content then\\n local filedata, err = Nova.filesystem.readfile( href:sub( 6 ), pass )\\n if filedata then\\n content = filedata.content\\n else\\n content = \\\"<t tc:red>\\\" .. err .. \\\"</t>\\\"\\n end\\n end\\n if not noHistory then\\n self.historyindex = self.historyindex + 1\\n table.insert( self.history, {\\n href = href:sub( 6 );\\n content = content;\\n } )\\n end\\n end\\n if href == \\\"home\\\" then -- or href == ... in the future?\\n if href == \\\"home\\\" then\\n content = [[\\n <br>\\n <p align:centre>Home</p>\\n <br>\\n <p tc:lightGrey>Welcome to Nova Browser!<br>Nova Browser is a website browser which loads NML (Nova Markup Language) and displays it.<br>You can type in the name of the computer ID you want to connect to in the bar at the top and press enter, and it will load that webpage.</p>\\n ]]\\n end\\n if not noHistory then\\n self.historyindex = self.historyindex + 1\\n table.insert( self.history, {\\n href = href;\\n content = content;\\n } )\\n end\\n elseif not content then\\n local id = href:match \\\"^(%d+)/?\\\" or href:match \\\"^(local)/?\\\"\\n if tonumber( id ) or id == \\\"local\\\" then\\n local path = href:gsub( id, \\\"\\\" ):gsub( \\\"^/\\\", \\\"\\\", 1 )\\n local args = path:match( \\\"?(.+)\\\" )\\n local rawargs = args\\n path = path:gsub( \\\"?(.+)\\\", \\\"\\\" )\\n local history = Nova.app.getData \\\"history\\\" or { }\\n table.insert( history, { time = Nova.irltime(), date = Nova.irldate(), url = id .. \\\"/\\\" .. path } )\\n Nova.app.setData( \\\"history\\\", history )\\n if args then\\n local t = { }\\n local last = 1\\n for i = 1, #args do\\n if args:sub( i, i ) == \\\"&\\\" then\\n table.insert( t, args:sub( last, i - 1 ) )\\n last = i + 1\\n end\\n end\\n table.insert( t, args:sub( last ) )\\n for i = 1, #t do\\n local index, value = t[i]:match \\\"(%w+)=(.+)\\\"\\n if index then\\n t[index] = value\\n end\\n t[i] = nil\\n end\\n args = t\\n end\\n if id == \\\"local\\\" then\\n id = os.getComputerID( )\\n else\\n id = tonumber( id )\\n end\\n if id ~= self.id and self.connection then\\n Nova.network.close( self.connection )\\n self.connection = false\\n end\\n local channel = self.connection\\n self.id = id\\n if not channel then\\n channel = Nova.network.open( self.id, \\\"NetServer:Request\\\", true )\\n end\\n if channel then\\n self.connection = channel\\n Nova.network.send( channel, {\\n request = \\\"read\\\";\\n file = path;\\n args = args or { };\\n } )\\n local ok, response = Nova.network.receive( channel, 5 )\\n if ok then\\n if response.success then\\n if response.endpath then\\n newhref = Nova.filesystem.merge( id, response.endpath )\\n if rawargs then\\n newhref = newhref .. \\\"?\\\" .. rawargs\\n end\\n end\\n if not noHistory then\\n self.historyindex = self.historyindex + 1\\n table.insert( self.history, {\\n href = newhref;\\n content = response.content;\\n } )\\n end\\n content = response.content\\n else\\n content = \\\"<t tc:red>\\\" .. tostring( response.reason ) .. \\\"</t>\\\"\\n end\\n else\\n content = \\\"<t tc:red>Server didn't respond. This usually means the server is offline at the moment.</t>\\\"\\n end\\n else\\n content = \\\"<t tc:red>Couldn't connect. This usually means the server isn't running at the moment, or you entered the wrong ID.</t>\\\"\\n end\\n else\\n content = \\\"<t tc:red>invalid href, no computer id given</t>\\\"\\n end\\n end\\n self:clearCurrent( )\\n self:addContent( content )\\n pathinput.text = newhref\\n self.loading = false\\nend\\nfunction browser:back( )\\n while self.loading do coroutine.yield( ) end\\n if self.history[self.historyindex - 1] then\\n self.historyindex = self.historyindex - 1\\n self:clearCurrent( )\\n self:addContent( self.history[self.historyindex].content )\\n pathinput.text = self.history[self.historyindex].href\\n end\\nend\\nfunction browser:forward( )\\n while self.loading do coroutine.yield( ) end\\n if self.history[self.historyindex + 1] then\\n self.historyindex = self.historyindex + 1\\n self:clearCurrent( )\\n self:addContent( self.history[self.historyindex].content )\\n pathinput.text = self.history[self.historyindex].href\\n end\\nend\\nfunction browser:refresh( )\\n while self.loading do coroutine.yield( ) end\\n local h = self.history[self.historyindex].href\\n while self.history[self.historyindex] do\\n table.remove( self.history, self.historyindex )\\n end\\n self.historyindex = self.historyindex - 1\\n self:goto( h )\\nend\\n\\nfunction backbutton:onClick( )\\n Nova.app.newThread( function( )\\n browser:back( )\\n end )\\nend\\nfunction backbutton:text( )\\n if browser.history[browser.historyindex - 1] then\\n self.tc = colours.black\\n else\\n self.tc = colours.grey\\n end\\n return \\\"<\\\"\\nend\\nfunction forwardbutton:onClick( )\\n Nova.app.newThread( function( )\\n browser:forward( )\\n end )\\nend\\nfunction forwardbutton:text( )\\n if browser.history[browser.historyindex + 1] then\\n self.tc = colours.black\\n else\\n self.tc = colours.grey\\n end\\n return \\\">\\\"\\nend\\nfunction refresh:onClick( )\\n Nova.app.newThread( function( )\\n browser:refresh( )\\n end )\\nend\\nfunction pathinput:onEnter( )\\n Nova.app.newThread( function( )\\n browser:goto( self.text )\\n end )\\nend\\nfunction pathinput:whenFocussed( )\\n Nova.app.newThread( function( )\\n self:select( 1, #self.text )\\n end )\\nend\\nfunction pathinput:whenUnFocussed( )\\n self.scroll = 0\\nend\\n\\nlocal menu\\n\\nlocal function showBookmarks( )\\n\\nend\\n\\nlocal function addBookmark( )\\n\\nend\\n\\nlocal function showHistory( )\\n\\nend\\n\\nlocal function showDownloads( )\\n\\nend\\n\\nlocal function showSettings( )\\n\\nend\\n\\nlocal function showDevelopMenu( )\\n local frame = UI:newChild( NovaUI.UIFrame( 1, UI.h - 10, UI.w, 11 ) )\\n menu = frame\\n frame:newChild( NovaUI.UIText( 1, 1, frame.w, frame.h, \\\"\\\" ) )\\n local title = frame:newChild( NovaUI.UIText( 1, 1, frame.w - 1, 1, \\\"Develop\\\" ) )\\n title.bc = colours.grey\\n title.tc = colours.white\\n local close = frame:newChild( NovaUI.UIButton( frame.w, 1, 1, 1, \\\"x\\\" ) )\\n close.bc = colours.red\\n close.tc = colours.black\\n function close:onClick( )\\n frame:remove( )\\n menu = nil\\n end\\n local tabs = {\\n console = frame:newChild( NovaUI.UIButton( 1, 2, 7, 1, \\\"console\\\" ) );\\n exceptions = frame:newChild( NovaUI.UIButton( 9, 2, 10, 1, \\\"exceptions\\\" ) );\\n nml = frame:newChild( NovaUI.UIButton( 20, 2, 3, 1, \\\"NML\\\" ) );\\n }\\n local currentdisplay\\n local currentcontent\\n local onLoad = {\\n console = function( )\\n local f = frame:newChild( NovaUI.UIFrame( 1, 3, frame.w, frame.h - 2 ) )\\n local f2 = f:newChild( NovaUI.UIFrame( 1, 1, f.w - 1, f.h ) )\\n local s = f:newChild( NovaUI.UIScrollBar( f.w, 1, 1, f.h, f2 ) )\\n Nova.app.newThread( function( )\\n while true do\\n for i = 1, #browser.log do\\n local message = f2:newChild( NovaUI.UIText( 1, i, f2.w, 1, browser.log[i] ) )\\n end\\n coroutine.yield( )\\n if currentcontent ~= f or menu ~= frame then\\n error()\\n end\\n f2:clearChildren( )\\n end\\n end )\\n return f\\n end;\\n exceptions = function( )\\n local f = frame:newChild( NovaUI.UIFrame( 1, 3, frame.w, frame.h - 2 ) )\\n local f2 = f:newChild( NovaUI.UIFrame( 1, 1, f.w - 1, f.h, \\\"horizontal\\\" ) )\\n local s = f:newChild( NovaUI.UIScrollBar( f.w, 1, 1, f.h, f2 ) )\\n Nova.app.newThread( function( )\\n while true do\\n for i = 1, #browser.page.errors do\\n local err = browser.page.errors[i]\\n local c = f2:newChild( NovaUI.UIFrame( 1, i, f2.w, 1 ) )\\n local source = c:newChild( NovaUI.UIText( 1, 1, #tostring( err.source ) + 1, 1, err.source ) )\\n local level = c:newChild( NovaUI.UIText( 0, 1, #err.level + 4, 1, \\\"[\\\" .. err.level .. \\\"]\\\" ) )\\n level:alignTo( \\\"right\\\", source )\\n level.tc = colours.lightGrey\\n c:newChild( NovaUI.UIText( level.x + level.w - 2, 1, 1, 1, \\\":\\\" ) )\\n local message = c:newChild( NovaUI.UIText( 0, 1, #err.message, 1, err.message ) )\\n message.tc = colours.red\\n message:alignTo( \\\"right\\\", level )\\n end\\n coroutine.yield( )\\n if currentcontent ~= f or menu ~= frame then\\n error()\\n end\\n f2:clearChildren( )\\n end\\n end )\\n return f\\n end;\\n nml = function( )\\n\\n end;\\n }\\n for k, v in pairs( tabs ) do\\n v.tc = colours.grey\\n function v:onClick( )\\n if currentdisplay then\\n currentdisplay.bc = colours.white\\n end\\n if currentcontent then\\n currentcontent:remove( )\\n end\\n currentdisplay = self\\n self.bc = colours.lightBlue\\n currentcontent = onLoad[k]( )\\n end\\n end\\n tabs.console:onClick( )\\nend\\n\\nlocal function showHelp( )\\n\\nend\\n\\nfunction options:onClick( )\\n local menu = {\\n width = 19;\\n height = 12;\\n spacing = false;\\n shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"bookmarks\\\", onClick = function( )\\n showBookmarks( )\\n browser.settingframe:remove( )\\n end };\\n { type = \\\"button\\\", name = \\\"add to bookmarks\\\", onClick = function( )\\n addBookmark( )\\n browser.settingframe:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"history\\\", onClick = function( )\\n showHistory( )\\n browser.settingframe:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"downloads\\\", onClick = function( )\\n showDownloads( )\\n browser.settingframe:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"settings\\\", onClick = function( )\\n showSettings( )\\n browser.settingframe:remove( )\\n end };\\n { type = \\\"button\\\", name = \\\"develop\\\", onClick = function( )\\n showDevelopMenu( )\\n browser.settingframe:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"help\\\", onClick = function( )\\n showHelp( )\\n browser.settingframe:remove( )\\n end };\\n }\\n local s = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n local b = s:newChild( NovaUI.UIButton( UI.w - 3, 2, 3, 1, \\\"-\\\" ) )\\n b.bc = colours.lightBlue\\n b.tc = colours.black\\n local close = s:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\n close.bc = 0\\n close.align = false\\n function close:onClick( )\\n s:remove( )\\n end\\n local frame = s:newChild( NovaUI.UIFrame( UI.w - 18, 3, 19, 12 ) )\\n NovaUI.display.menu( frame, menu )\\n browser.settingframe = s\\nend\\n\\nlocal env = browser.page.environment\\n\\nenv.browser = { }\\nfunction env.browser.redirect( href, hidden )\\n if not browser.id then return false end\\n if type( href ) == \\\"string\\\" then\\n Nova.app.newThread( function( )\\n browser:goto( Nova.filesystem.merge( browser.id, href ), hidden )\\n end )\\n return true\\n else\\n return error \\\"expected string href\\\"\\n end\\nend\\nfunction env.browser.goto( href, hidden )\\n if type( href ) == \\\"string\\\" then\\n browser:goto( href, hidden )\\n return true\\n else\\n return error \\\"expected string href\\\"\\n end\\nend\\nfunction env.browser.reload( )\\n browser:refresh( )\\nend\\nfunction env.browser.back( )\\n browser:back( )\\nend\\nfunction env.browser.forward( )\\n browser:forward( )\\nend\\nfunction env.browser.download( name, content )\\n if Nova.filesystem.exists( Nova.filesystem.merge( downloadpath, name ) ) then\\n local n = 1\\n while Nova.filesystem.exists( Nova.filesystem.merge( downloadpath, name .. \\\" (\\\" .. n .. \\\")\\\" ) ) do\\n n = n + 1\\n end\\n name = name .. \\\" (\\\" .. n .. \\\")\\\"\\n end\\n Nova.filesystem.writefile( Nova.filesystem.merge( downloadpath, name ), FileData( content ) )\\n local downloads = Nova.app.getData \\\"downloads\\\" or { }\\n table.insert( downloads, { time = Nova.irltime(), date = Nova.irldate(), name = name } )\\n Nova.app.setData( \\\"downloads\\\", downloads )\\nend\\nfunction env.browser.cookie( name, value )\\n local cookies = Nova.app.getData \\\"cookies\\\" or { }\\n cookies[name] = value\\n Nova.app.setData( \\\"cookies\\\", cookies )\\nend\\nfunction env.browser.getCookie( name )\\n local cookies = Nova.app.getData \\\"cookies\\\"\\n return cookies[name]\\nend\\nfunction env.browser.log( message )\\n table.insert( browser.log, message )\\nend\\n\\nif type( ARGS[1] ) == \\\"string\\\" then\\n browser:goto( \\\"file:\\\" .. ARGS[1] )\\nelse\\n browser:goto \\\"home\\\"\\nend\",\
7496[\"appconfig.txt\"]=\"extensions = {\\n nml = \\\"NovaMarkupLanguage\\\";\\n};\\nfiletypes = {\\n NovaMarkupLanguage = \\\"Nova Markup Language file\\\";\\n};\\nhandles = {\\n \\\"NovaMarkupLanguage\\\";\\n};\",\
7497}\
7498,\
7499Settings={\
7500[\"icon.nim\"]=\"--[[\\n type = \\\"nova_image\\\",\\n]]07S07e07t07t07i07n07g07s00 \\n07[0Bx07]00 07.07.07.00 00 \\n07[03 07]00 07.07.07.00 00 \",\
7501[\"main.lua\"]=\"\\nlocal f = Nova.app.UI:newChild( NovaUI.UIFrame( 1, 2, Nova.app.UI.w - 1, Nova.app.UI.h - 2 ) )\\n\\nlocal function formatSpace( n )\\n if n < 1024 then\\n return tostring( n ) .. \\\" bytes\\\"\\n end\\n n = n / 1024\\n if n < 1024 then\\n return tostring( math.floor( n ) ) .. \\\"KB\\\"\\n end\\n return tostring( math.floor( n / 1024 ) ) .. \\\"MB\\\"\\nend\\n\\nNovaUI.display.menu( f, {\\n { type = \\\"label\\\", name = \\\"Computer\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"Computer ID: \\\", getDisplay = function( )\\n return tostring( os.getComputerID( ) )\\n end };\\n { type = \\\"display\\\", name = \\\"Computer label: \\\", getDisplay = function( )\\n return os.getComputerLabel( ) or \\\"<No label>\\\"\\n end };\\n \\\"space\\\";\\n { type = \\\"input\\\", label = \\\"Change label\\\", onEnter = function( input )\\n os.setComputerLabel( input.text )\\n end };\\n \\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"Nova\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"Version: \\\", getDisplay = function( )\\n return Nova.name .. \\\" (v\\\" .. Nova.version .. \\\")\\\"\\n end };\\n \\\"space\\\";\\n { type = \\\"button\\\", name = \\\"force update\\\", blue = true, onClick = function( )\\n Nova.app.newThread( function( )\\n if Nova.update( ) then\\n if NovaUI.display.confirm( Nova.app.UI, \\\"Updated, would you like to restart?\\\" ) then\\n Nova.restart( )\\n end\\n else\\n NovaUI.display.alert( Nova.app.UI, \\\"Failed to download update\\\" )\\n end\\n end )\\n end };\\n \\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"Files\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"Mounted drives: \\\", getDisplay = function( )\\n local drives = Nova.filesystem.listmounted( )\\n return tostring( #drives )\\n end };\\n { type = \\\"display\\\", name = \\\"Free space: \\\", getDisplay = function( )\\n return formatSpace( Nova.filesystem.getSpace \\\"C:\\\" )\\n end };\\n \\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"User\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"Logged in as: \\\", getDisplay = function( )\\n return Nova.user.getName( )\\n end };\\n \\\"space\\\";\\n { type = \\\"button\\\", name = \\\"change username\\\", blue = true, onClick = function( )\\n Nova.app.newThread( function( )\\n local name = NovaUI.display.response( Nova.app.UI, \\\"Your new username:\\\" )\\n if name then\\n local ok, err = Nova.user.rename( name )\\n if ok then\\n NovaUI.display.alert( Nova.app.UI, \\\"Username changed\\\" )\\n elseif err == \\\"user exists\\\" then\\n NovaUI.display.alert( Nova.app.UI, \\\"User already exists!\\\" )\\n end\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"change password\\\", blue = true, onClick = function( )\\n Nova.app.newThread( function( )\\n local pass = NovaUI.display.response( Nova.app.UI, \\\"Your new password:\\\", \\\"*\\\" )\\n if pass then\\n if Nova.user.changePassword( pass ) then\\n NovaUI.display.alert( Nova.app.UI, \\\"Password changed\\\" )\\n end\\n end\\n end )\\n end };\\n \\\"space\\\";\\n { type = \\\"button\\\", name = \\\"create user\\\", blue = true, onClick = function( )\\n Nova.app.newThread( function( )\\n local username = NovaUI.display.response( Nova.app.UI, \\\"New username\\\" )\\n if username then\\n local password = NovaUI.display.response( Nova.app.UI, \\\"New password\\\" )\\n if password then\\n if Nova.user.create( username, password ) then\\n if NovaUI.display.confirm( Nova.app.UI, \\\"Would you like to log in as this user?\\\" ) then\\n Nova.user.switch( username, password )\\n end\\n else\\n NovaUI.display.alert( Nova.app.UI, \\\"Could not create account\\\" )\\n end\\n return\\n end\\n end\\n NovaUI.display.alert( Nova.app.UI, \\\"Cancelled\\\" )\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"delete this account\\\", blue = true, onClick = function( )\\n Nova.app.newThread( function( )\\n if NovaUI.display.confirm( Nova.app.UI, \\\"Are you sure you want to delete your account?\\\" ) then\\n Nova.user.delete( )\\n end\\n end )\\n end };\\n \\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"Apps\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"not yet implemented\\\" };\\n \\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"Network\\\" };\\n \\\"space\\\";\\n { type = \\\"display\\\", name = \\\"not yet implemented\\\" };\\n} )\",\
7502}\
7503,\
7504Paint={\
7505[\"icon.nim\"]=\"BF 3F 3B-3B-3B-3B-3B-3F BF \\nBF 3F 3EP3Ea3Ei3En3Et3F BF \\nBF 3F 3B-3B-3B-3B-3B-3F BF \",\
7506[\"main.lua\"]=\"\\nlocal ARGS = ARGS\\nif not Nova then\\n ARGS = { ... }\\nend\\n\\nif not NovaUI then\\n os.loadAPI \\\"NovaUI\\\"\\nend\\nlocal UI = Nova and Nova.app.UI or NovaUI.UIHandler( )\\nlocal running = true\\n\\nif not Nova then\\n NovaUI.buffer.reset( )\\nelse\\n UI.scrolls = false\\nend\\n\\nlocal taskbar = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, 1 ) )\\ntaskbar:newChild( NovaUI.UIButton( 1, 1, taskbar.w, 1, \\\"\\\" ) ).bc = colours.grey\\nlocal imagebody = UI:newChild( NovaUI.UIFrame( 1, 2, UI.w, UI.h - 1 ) )\\nimagebody.scrolls = false\\n\\nlocal background = NovaUI.Image( imagebody.w, imagebody.h )\\nbackground:foreach( function( ) return 32768, 1, \\\" \\\" end )\\nimagebody:newChild( NovaUI.UIImage( 1, 1, imagebody.w, imagebody.h, background ) )\\n\\nlocal image = imagebody:newChild( NovaUI.UIFrame( 1, 1, 1, 1 ) )\\nlocal imageeventh = image:newChild( NovaUI.UIButton( 1, 1, 1, 1, \\\"\\\" ) )\\nimageeventh.bc = 0\\nimageeventh.align = false\\n\\nlocal keyhandler = imagebody:newChild( NovaUI.UIKeyHandler( ) )\\n\\nlocal activedropdown\\nlocal activemenu\\nlocal cx, cy, bc, tc = 1, 1, 1, 1\\n\\nlocal menus\\n\\nlocal savepath\\nlocal changed = false\\nlocal asterix\\nlocal function saved( )\\n if changed then\\n local result = NovaUI.display.confirm( UI, \\\"You have unsaved changes, would you like to save?\\\" )\\n if result then\\n savefile( true )\\n end\\n if result == nil then\\n return false\\n end\\n end\\n return true\\nend\\nif Nova then\\n function Nova.app.canClose( reason )\\n if reason == \\\"user\\\" and changed then\\n NovaUI.Thread( function( )\\n if saved( ) then\\n Nova.app.close( )\\n end\\n end )\\n return false\\n end\\n return true\\n end\\nend\\nlocal function contentChanged( )\\n if changed then return end\\n changed = true\\n if Nova then\\n Nova.app.setTitle \\\"Paint*\\\"\\n else\\n for k, v in pairs( menus ) do v.x = v.x + 2 end\\n asterix = taskbar:newChild( NovaUI.UIText( 1, 1, 1, 1, \\\"*\\\" ) )\\n asterix.bc = 0\\n asterix.tc = 1\\n end\\nend\\nlocal function contentSaved( )\\n if not changed then return end\\n if asterix then asterix:remove( ) end\\n changed = false\\n if Nova then\\n Nova.app.setTitle \\\"Paint\\\"\\n else\\n for k, v in pairs( menus ) do v.x = v.x - 2 end\\n end\\nend\\nlocal savefile\\nlocal openfile\\n\\nlocal layers = { }\\nlocal layer = 0\\nlocal function newLayer( name, im )\\n table.insert( layers, { image = im, active = true, name = name, element = NovaUI.UIImage( 1, 1, image.w, image.h, im ) } )\\n for i = 1, #layers do\\n layers[i].element:remove( )\\n if layers[i].active then\\n image:newChild( layers[i].element )\\n end\\n end\\n im:resize( image.w, image.h )\\n imageeventh:remove( )\\n image:newChild( imageeventh )\\n layer = layer + 1\\nend\\nlocal function setActive( layer, state )\\n layers[layer].active = state == nil and not layers[layer.active] or state or false\\n for i = 1, #layers do\\n layers[i].element:remove( )\\n if layers[i].active then\\n image:newChild( layers[i].element )\\n end\\n end\\n imageeventh:remove( )\\n image:newChild( imageeventh )\\nend\\nlocal function resize( w, h )\\n if w == image.w and h == image.h then return end\\n for i = 1, #layers do\\n layers[i].image:resize( w, h, bc, tc, \\\" \\\" )\\n layers[i].element.w = w\\n layers[i].element.h = h\\n end\\n imageeventh.w = w\\n imageeventh.h = h\\n image.w = w\\n image.h = h\\nend\\n\\nfunction savefile( )\\n if not savepath then\\n savepath = NovaUI.display.response( UI, \\\"Where would you like to save?\\\" )\\n if savepath then\\n savepath = savepath .. \\\".nim\\\"\\n else\\n return false\\n end\\n end\\n local savestr\\n if #layers > 1 then\\n savestr = \\\"\\\"\\n for i = 1, #layers do\\n savestr = savestr .. \\\"---[\\\" .. layers[i].name .. \\\"]\\\\n\\\" .. layers[i].image:savestr( )\\n if i ~= #layers then savestr = savestr .. \\\"\\\\n\\\" end\\n end\\n elseif layers[1] then\\n savestr = layers[1].image:savestr( )\\n else\\n NovaUI.display.alert( UI, \\\"No layers!\\\" )\\n return\\n end\\n if Nova then\\n local fd = FileData( savestr )\\n fd.meta.type = \\\"nova_image\\\"\\n Nova.filesystem.writefile( savepath, fd )\\n else\\n local h = fs.open( savepath, \\\"w\\\" )\\n if h then\\n h.write( savestr )\\n h.close( )\\n else\\n NovaUI.display.alert( UI, \\\"Could not save file\\\" )\\n return\\n end\\n end\\n contentSaved( )\\nend\\nlocal function openlayer( str )\\n if not str then return end\\n local lt = str:sub( 1, str:find \\\"%-%-%-\\\" ) -- returns nil if it can't find it, causing it to sub to the end\\n local remaining = str:find \\\"%-%-%-\\\" and str:sub( str:find \\\"%-%-%-\\\" + 3 ) or false\\n local name = \\\"Un-named Layer\\\"\\n if lt:sub( 1, 1 ) == \\\"[\\\" and lt:sub( 2 ):find \\\"]\\\" then\\n name = lt:sub( 2, lt:sub( 2 ):find \\\"]\\\" )\\n lt = lt:sub( lt:sub( 2 ):find \\\"]\\\" + 2 )\\n if lt:sub( 1, 1 ) == \\\"\\\\n\\\" then lt = lt:sub( 2 ) end\\n end\\n local im = NovaUI.Image( )\\n im:loadstr( lt )\\n if remaining and remaining:sub( 1, 1 ) == \\\"\\\\n\\\" then remaining = remaining:sub( 2 ) end\\n return name, im, remaining\\nend\\nfunction openfile( path )\\n savepath = path\\n contentSaved( )\\n local content\\n if Nova then\\n local pass\\n if Nova.filesystem.isProtected( path ) then\\n pass = NovaUI.display.response( UI, \\\"Password for file\\\" )\\n if not pass then\\n return\\n end\\n end\\n local filedata, err = Nova.filesystem.readfile( path, pass )\\n if filedata then content = filedata.content else NovaUI.display.alert( UI, err, true ) return end\\n else\\n local h = fs.open( path, \\\"r\\\" )\\n if h then content = h.readAll( ) h.close( ) else NovaUI.display.alert( UI, \\\"could not open file\\\", true ) return end\\n if content:find \\\"^--%[%[.-%]%]\\\" then\\n content = content:gsub( \\\"^--%[%[.-%]%]\\\", \\\"\\\", 1 )\\n end\\n end\\n for i = #layers, 1, -1 do\\n layers[i].element:remove( )\\n end\\n layers = { }\\n image.x, image.y = 1, 1\\n if content:sub( 1, 3 ) == \\\"---\\\" then\\n local name, im, content = openlayer( content:sub( 4 ) )\\n while name do\\n resize( im:getSize( ) )\\n newLayer( name, im )\\n name, im, content = openlayer( content )\\n end\\n else\\n local im = NovaUI.Image( ):loadstr( content )\\n resize( im:getSize( ) )\\n newLayer( \\\"Layer 1\\\", im )\\n end\\n layer = 1\\n imageeventh:remove( )\\n image:newChild( imageeventh )\\n image:centre( )\\n return true\\nend\\n\\nlocal function copy( )\\n local im = layers[layer]\\n NovaUI.clipboard.set( \\\"image\\\", im.image )\\nend\\nlocal function cut( )\\n if #layers > 1 then\\n local im = layers[layer]\\n NovaUI.clipboard.set( \\\"image\\\", im.image )\\n im.element:remove( )\\n table.remove( layers, layer )\\n layer = math.max( layer - 1, 1 )\\n contentChanged( )\\n end\\nend\\nlocal function paste( )\\n local mode, data = NovaUI.clipboard.get( )\\n if mode ~= \\\"image\\\" then\\n return\\n end\\n local im = NovaUI.Image( image.w, image.h )\\n im:foreach( function( x, y )\\n local b, t, char = data:getPixel( x, y )\\n if b then return b, t, char end\\n return bc, tc, \\\" \\\"\\n end )\\n newLayer( \\\"Pasted layer\\\", im )\\n contentChanged( )\\nend\\n\\nlocal view = {\\n colours = { active = true, element = UI:newChild( NovaUI.UIFrame( 1, 2, 11, 5 ) ) };\\n layers = { active = true, element = UI:newChild( NovaUI.UIFrame( UI.w - 14, 11, 15, 10 ) ) };\\n tools = { active = true, element = UI:newChild( NovaUI.UIFrame( UI.w - 14, 2, 15, 9 ) ) };\\n}\\nif Nova and Nova.platform ~= \\\"computer\\\" then\\n for k, v in pairs( view ) do\\n v.active = false\\n v.element:remove( )\\n end\\nend\\nlocal title = view.colours.element:newChild( NovaUI.UIButton( 1, 1, 10, 1, \\\"Colour\\\" ) )\\ntitle.bc = colours.grey\\ntitle.tc = colours.white\\nfunction title:onDrag( _, _, cx, cy )\\n view.colours.element.x = view.colours.element.x + cx\\n view.colours.element.y = view.colours.element.y + cy\\nend\\nlocal close = view.colours.element:newChild( NovaUI.UIButton( 11, 1, 1, 1, \\\"x\\\" ) )\\nclose.bc = colours.grey\\nclose.tc = colours.yellow\\nfunction close:onClick( )\\n view.colours.active = false\\n view.colours.element:remove( )\\nend\\nlocal x, y = 1, 1\\nfor i = 0, 15 do\\n local button = view.colours.element:newChild( NovaUI.UIButton( x * 2 + 2, y + 1, 2, 2, \\\"\\\" ) )\\n button.bc = 2 ^ i\\n function button:onClick( _, _, button )\\n if button == 1 then bc = 2 ^ i\\n elseif button == 2 then tc = 2 ^ i\\n end\\n end\\n x = x + 1\\n if x > 4 then x = 1 y = y + 1 end\\nend\\nview.colours.element:newChild( NovaUI.UIButton( 1, 2, 3, 2, function( self )\\n self.bc = bc\\n return \\\"\\\"\\nend ) )\\nview.colours.element:newChild( NovaUI.UIButton( 1, 4, 3, 2, function( self )\\n self.bc = tc\\n return \\\"\\\"\\nend ) )\\n\\nlocal title = view.layers.element:newChild( NovaUI.UIButton( 1, 1, 14, 1, \\\"Layers\\\" ) )\\ntitle.bc = colours.grey\\ntitle.tc = colours.white\\nfunction title:onDrag( _, _, cx, cy )\\n view.layers.element.x = view.layers.element.x + cx\\n view.layers.element.y = view.layers.element.y + cy\\nend\\nlocal close = view.layers.element:newChild( NovaUI.UIButton( 15, 1, 1, 1, \\\"x\\\" ) )\\nclose.bc = colours.grey\\nclose.tc = colours.yellow\\nfunction close:onClick( )\\n view.layers.active = false\\n view.layers.element:remove( )\\nend\\nlocal current = view.layers.element:newChild( NovaUI.UIText( 1, 2, 15, 1, function( )\\n return layers[layer] and layers[layer].name or \\\"\\\"\\nend ) )\\ncurrent.bc = colours.grey\\ncurrent.tc = colours.white\\nlocal layerlist = view.layers.element:newChild( NovaUI.UIFrame( 1, 3, 15, 8 ) )\\n\\nNovaUI.Thread( function( )\\n while true do\\n layerlist:clearChildren( )\\n for i = 1, #layers do\\n local l = layers[i]\\n local button = layerlist:newChild( NovaUI.UIButton( 3, i, 15, 1, layers[i].name ) )\\n button.bc = colours.lightGrey\\n button.tc = colours.black\\n function button:onClick( x, y, b )\\n if b == 1 then\\n layer = i\\n else\\n local close = UI:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\n close.bc, close.align = 0, false\\n local f = UI:newChild( NovaUI.UIFrame( view.layers.element.x + view.layers.element.w, view.layers.element.y + i + layerlist.cy + 1, 10, 5 ) )\\n NovaUI.display.menu( f, {\\n width = 11, height = 5, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"delete\\\", onClick = function( )\\n if #layers > 1 then\\n table.remove( layers, i )\\n if layer == i then\\n layer = math.max( i - 1, 1 )\\n elseif layer > #layers then\\n layer = layer - 1\\n end\\n setActive( layer, layers[layer].active ) -- update\\n close:remove( )\\n f:remove( )\\n contentChanged( )\\n else\\n NovaUI.display.alert( UI, \\\"Cannot delete last layer\\\" )\\n close:remove( )\\n f:remove( )\\n end\\n end };\\n { type = \\\"button\\\", name = layers[i] and layers[i].active and \\\"hide\\\" or \\\"show\\\", onClick = function( )\\n if layers[i] then\\n setActive( i, not layers[i].active ) -- update\\n close:remove( )\\n f:remove( )\\n end\\n end };\\n { type = \\\"button\\\", name = \\\"rename\\\", onClick = function( )\\n NovaUI.Thread( function( )\\n close:remove( )\\n f:remove( )\\n local r = NovaUI.display.response( UI, \\\"New layer name\\\" )\\n if r then\\n layers[i].name = r\\n contentChanged( )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"merge up\\\", onClick = function( )\\n close:remove( )\\n f:remove( )\\n if layers[i - 1] then\\n local im = layers[i-1].image\\n local im2 = layers[i].image\\n im:foreach( function( x, y, bc, tc, char )\\n local b, t, c = im2:getPixel( x, y )\\n if b == 0 then\\n b = bc\\n end\\n if t == 0 or c == \\\"\\\" then\\n t, c = tc, char\\n end\\n return b, t, c\\n end )\\n layers[i].element:remove( )\\n table.remove( layers, i )\\n if layer >= i then\\n layer = layer - 1\\n end\\n contentChanged( )\\n else\\n NovaUI.display.alert( UI, \\\"Nothing to merge to (top layer)\\\" )\\n end\\n end };\\n } )\\n if f.x + f.w > UI.w then\\n f.x = UI.w - f.w + 1\\n end\\n if f.y + f.h > UI.h then\\n f.y = UI.h - f.h + 1\\n end\\n function close:onClick( )\\n close:remove( )\\n f:remove( )\\n end\\n end\\n end\\n function button:onDrag( _, y, _, _, b )\\n if b == 1 then\\n for i = 1, #layers do\\n if layers[i] == l then\\n local pos = math.min( math.max( y + i - 1, 1 ), #layers )\\n table.remove( layers, i )\\n table.insert( layers, pos, l )\\n Nova.log( y, i, pos )\\n if layer == i then\\n layer = pos\\n elseif layer == pos then\\n layer = math.min( math.max( i - y + 1, 1 ), #layers )\\n end\\n self.y = self.y + y - 1\\n setActive( 1, layers[1].active )\\n end\\n end\\n end\\n end\\n local active = layerlist:newChild( NovaUI.UIButton( 1, i, 2, 1, layers[i].active and \\\" @\\\" or \\\" O\\\" ) )\\n active.bc = colours.lightGrey\\n active.tc = colours.black\\n function active:onClick( )\\n setActive( i, not layers[i].active )\\n end\\n end\\n coroutine.yield( )\\n end\\nend )\\n\\nlocal title = view.tools.element:newChild( NovaUI.UIButton( 1, 1, 14, 1, \\\"Tools\\\" ) )\\ntitle.bc = colours.grey\\ntitle.tc = colours.white\\nfunction title:onDrag( _, _, cx, cy )\\n view.tools.element.x = view.tools.element.x + cx\\n view.tools.element.y = view.tools.element.y + cy\\nend\\nlocal close = view.tools.element:newChild( NovaUI.UIButton( 15, 1, 1, 1, \\\"x\\\" ) )\\nclose.bc = colours.grey\\nclose.tc = colours.yellow\\nfunction close:onClick( )\\n view.tools.active = false\\n view.tools.element:remove( )\\nend\\n\\nlocal t = {\\n { name = \\\"Brush\\\", tool = \\\"brush\\\" };\\n { name = \\\"Rectangle\\\", tool = \\\"rectangle\\\" };\\n { name = \\\"Ellipse\\\", tool = \\\"ellipse\\\" };\\n { name = \\\"Line\\\", tool = \\\"line\\\" };\\n { name = \\\"Fill\\\", tool = \\\"fill\\\" };\\n { name = \\\"Pick colour\\\", tool = \\\"pick\\\" };\\n { name = \\\"Move\\\", tool = \\\"move\\\" };\\n { name = \\\"Eraser\\\", tool = \\\"eraser\\\" };\\n}\\n\\nlocal tool = { name = \\\"brush\\\", size = 1, data = { } }\\nlocal function setTool( name )\\n tool.name = name\\n tool.data = { }\\nend\\n\\nfor i = 1, #t do\\n local b = view.tools.element:newChild( NovaUI.UIButton( 1, i + 1, 15, 1, function( self )\\n if tool.name == t[i].tool then\\n self.tc = colours.white\\n else\\n self.tc = colours.grey\\n end\\n return t[i].name\\n end ) )\\n b.bc = colours.lightGrey\\n b.align = false\\n function b:onClick( )\\n setTool( t[i].tool )\\n end\\nend\\n\\nlocal tools = { }\\n\\ntools.brush = { }\\nfunction tools.brush.onClick( x, y )\\n cx = x\\n cy = y\\n layers[layer].image:pixel( x, y, bc, tc, \\\" \\\" )\\n if tool.size > 1 then\\n local r = tool.size ^ 2\\n for xx = x - tool.size + 1, x + tool.size - 1 do\\n for yy = y - tool.size + 1, y + tool.size - 1 do\\n if ( xx - x ) ^ 2 + ( yy - y ) ^ 2 < r then\\n layers[layer].image:pixel( xx, yy, bc, tc, \\\" \\\" )\\n end\\n end\\n end\\n end\\n cx, cy = x, y\\n contentChanged( )\\nend\\ntools.brush.onDrag = tools.brush.onClick\\nfunction tools.brush.onChar( char )\\n layers[layer].image:pixel( cx, cy, bc, tc, char )\\n cx = cx + 1\\n contentChanged( )\\nend\\n\\ntools.rectangle = { }\\nfunction tools.rectangle.onClick( x, y )\\n tool.data.x = x\\n tool.data.y = y\\n tool.data.x2 = x\\n tool.data.y2 = y\\n tool.data.button = 1\\n tool.data.layer = layers[layer]\\n tool.data.image = NovaUI.Image( image.w, image.h )\\n tool.data.image:foreach( function( x, y ) return tool.data.layer.image:getPixel( x, y ) end )\\n tool.data.layer.image:pixel( x, y, bc, 1, \\\" \\\" )\\n contentChanged( )\\nend\\nlocal function drawRectangle( im, x, y, x2, y2, char, button )\\n local minx, miny, maxx, maxy = math.min( x, x2 ), math.min( y, y2 ), math.max( x, x2 ), math.max( y, y2 )\\n if button == 1 then\\n for x = minx, maxx do\\n for y = miny, maxy do\\n im:pixel( x, y, bc, 1, char )\\n end\\n end\\n else\\n for x = minx, maxx do\\n im:pixel( x, miny, bc, 1, char )\\n im:pixel( x, maxy, bc, 1, char )\\n end\\n for y = miny + 1, maxy - 1 do\\n im:pixel( minx, y, bc, 1, char )\\n im:pixel( maxx, y, bc, 1, char )\\n end\\n end\\n contentChanged( )\\nend\\nfunction tools.rectangle.onDrag( x, y, _, _, button )\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n tool.data.x2 = x\\n tool.data.y2 = y\\n tool.data.button = button\\n drawRectangle( im, x, y, tool.data.x, tool.data.y, \\\" \\\", button )\\nend\\nfunction tools.rectangle.onChar( char )\\n if not tool.data.x then return end\\n drawRectangle( im, tool.data.x2, tool.data.y2, tool.data.x, tool.data.y, char, tool.data.button )\\nend\\nfunction tools.rectangle.onKey( key )\\n if not tool.data.x then return end\\n if key == keys.backspace then\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n end\\nend\\n\\ntools.ellipse = { }\\nfunction tools.ellipse.onClick( x, y )\\n tool.data.x = x\\n tool.data.y = y\\n tool.data.x2 = x\\n tool.data.y2 = y\\n tool.data.layer = layers[layer]\\n tool.data.image = NovaUI.Image( image.w, image.h )\\n tool.data.image:foreach( function( x, y ) return tool.data.layer.image:getPixel( x, y ) end )\\nend\\nlocal function drawEllipse( im, originX, originY, w, h, char )\\n local hh = h * h\\n local ww = w * w\\n local hhww = hh*ww\\n local x0 = w\\n local dx = 0\\n for x = -w, w do\\n im:pixel(originX + x, originY, bc, 1, char)\\n end\\n for y = 1, h do\\n local x1 = x0 - (dx - 1)\\n for i = x1, 0, -1 do\\n if (x1*x1*hh + y*y*ww <= hhww) then\\n break\\n end\\n x1 = x1 - 1\\n end\\n dx = x0 - x1\\n x0 = x1\\n for x = -x0, x0 do\\n im:pixel(originX + x, originY - y, bc, 1, char)\\n im:pixel(originX + x, originY + y, bc, 1, char)\\n end\\n end\\n contentChanged( )\\nend\\nfunction tools.ellipse.onDrag( x, y, _, _, button )\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n local minx, miny, maxx, maxy = math.min( x, tool.data.x ), math.min( y, tool.data.y ), math.max( x, tool.data.x ), math.max( y, tool.data.y )\\n tool.data.x2 = x\\n tool.data.y2 = y\\n local originX = minx + math.floor( ( maxx - minx ) / 2 + .5 )\\n local originY = miny + math.floor( ( maxy - miny ) / 2 + .5 )\\n drawEllipse( im, originX, originY, maxx - originX + 1, maxy - originY + 1, \\\" \\\" )\\nend\\nfunction tools.ellipse.onChar( char )\\n if tool.data.x then\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n local minx, miny, maxx, maxy = math.min( tool.data.x2, tool.data.x ), math.min( tool.data.y2, tool.data.y ), math.max( tool.data.x2, tool.data.x ), math.max( tool.data.y2, tool.data.y )\\n local originX = minx + math.floor( ( maxx - minx ) / 2 + .5 )\\n local originY = miny + math.floor( ( maxy - miny ) / 2 + .5 )\\n drawEllipse( im, originX, originY, maxx - originX + 1, maxy - originY + 1, char )\\n end\\nend\\n\\ntools.line = { }\\nfunction tools.line.onClick( x, y )\\n tool.data.x = x\\n tool.data.y = y\\n tool.data.x2 = x\\n tool.data.y2 = y\\n tool.data.layer = layers[layer]\\n tool.data.image = NovaUI.Image( image.w, image.h )\\n tool.data.image:foreach( function( x, y ) return tool.data.layer.image:getPixel( x, y ) end )\\n tool.data.layer.image:pixel( x, y, bc, tc, \\\" \\\" )\\n contentChanged( )\\nend\\nlocal function drawLine( im, char )\\n local x1, y1 = tool.data.x, tool.data.y\\n local x2, y2 = tool.data.x2, tool.data.y2\\n if math.abs( x2 - x1 ) > math.abs( y2 - y1 ) then\\n if x2 < x1 then\\n x1, x2 = x2, x1\\n y1, y2 = y2, y1\\n end\\n local g = ( y2 - y1 ) / ( x2 - x1 )\\n for x = x1, x2 do\\n local y = math.floor( y1 + g * ( x - x1 ) + 0.5 )\\n im:pixel( x, y, bc, tc, char )\\n end\\n else\\n if y2 < y1 then\\n x1, x2 = x2, x1\\n y1, y2 = y2, y1\\n end\\n local g = ( x2 - x1 ) / ( y2 - y1 )\\n for y = y1, y2 do\\n local x = math.floor( x1 + g * ( y - y1 ) + 0.5 )\\n im:pixel( x, y, bc, tc, char )\\n end\\n end\\n contentChanged( )\\nend\\nfunction tools.line.onDrag( x, y, _, _, button )\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n tool.data.x2 = x\\n tool.data.y2 = y\\n drawLine( im, \\\" \\\" )\\nend\\nfunction tools.line.onKey( key )\\n if not tool.data.x then return end\\n if key == keys.backspace then\\n local im = tool.data.layer.image\\n local imc = tool.data.image\\n im:foreach( function( x, y )\\n return imc:getPixel( x, y )\\n end )\\n end\\nend\\nfunction tools.line.onChar( char )\\n if not tool.data.x then return end\\n drawLine( tool.data.layer.image, char )\\nend\\n\\ntools.fill = { }\\nlocal function fillarea( x, y, save )\\n local im = layers[layer].image\\n local _bc = im:getPixel( x, y )\\n if _bc then\\n local pixels = { }\\n local function pixel( x, y )\\n if pixels[x] and pixels[x][y] then\\n return\\n end\\n pixels[x] = pixels[x] or { }\\n pixels[x][y] = true\\n local b, t, c = im:getPixel( x, y )\\n if b == _bc then\\n im:pixel( x, y, bc, tc, save and c or \\\" \\\" )\\n pixel( x - 1, y )\\n pixel( x + 1, y )\\n pixel( x, y - 1 )\\n pixel( x, y + 1 )\\n end\\n end\\n pixel( x, y )\\n contentChanged( )\\n end\\nend\\nfunction tools.fill.onClick( x, y, button )\\n NovaUI.Thread( function( )\\n fillarea( x, y, button == 2 )\\n end )\\nend\\n\\ntools.pick = { }\\nfunction tools.pick.onClick( x, y, button )\\n local b, t = layers[layer].image:getPixel( x, y )\\n bc = button == 1 and b or t\\n tc = button == 1 and t or b\\nend\\n\\ntools.move = { }\\nfunction tools.move.onDrag( _, _, cx, cy )\\n image.x = image.x + cx\\n image.y = image.y + cy\\nend\\ntools.eraser = { }\\nfunction tools.eraser.onClick( x, y )\\n layers[layer].image:pixel( x, y, 0, 0, \\\"\\\" )\\n if tool.size > 1 then\\n local r = tool.size ^ 2\\n for xx = x - tool.size + 1, x + tool.size - 1 do\\n for yy = y - tool.size + 1, y + tool.size - 1 do\\n if ( xx - x ) ^ 2 + ( yy - y ) ^ 2 < r then\\n layers[layer].image:pixel( xx, yy, 0, 0, \\\"\\\" )\\n end\\n end\\n end\\n end\\n cx, cy = x, y\\n contentChanged( )\\nend\\ntools.eraser.onDrag = tools.eraser.onClick\\n\\nmenus = { }\\nmenus.file = taskbar:newChild( NovaUI.UIButton( 1, 1, 4, 1, \\\"File\\\" ) )\\nmenus.edit = taskbar:newChild( NovaUI.UIButton( 7, 1, 4, 1, \\\"Edit\\\" ) )\\nmenus.view = taskbar:newChild( NovaUI.UIButton( 13, 1, 4, 1, \\\"View\\\" ) )\\nmenus.tool = taskbar:newChild( NovaUI.UIButton( 19, 1, 4, 1, \\\"Tool\\\" ) )\\nmenus.image = taskbar:newChild( NovaUI.UIButton( 25, 1, 5, 1, \\\"Image\\\" ) )\\nlocal menuoptions = { }\\n\\nmenuoptions.file = {\\n width = 10, height = 8, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"New\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n for i = 1, #layers do\\n layers[i].element:remove( )\\n end\\n local im = NovaUI.Image( 5, 5 )\\n im:foreach( function( ) return 1, 1, \\\" \\\" end )\\n resize( 5, 5 )\\n layers = { { image = im, name = \\\"Layer 1\\\", element = image:newChild( NovaUI.UIImage( 1, 1, 5, 5, im ) ) } }\\n layer = 1\\n imageeventh:remove( )\\n image:newChild( imageeventh )\\n contentChanged( )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Open\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n local path = NovaUI.display.response( UI, \\\"Path to open from\\\" )\\n if path then\\n openfile( path .. \\\".nim\\\" )\\n end\\n end\\n end )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Save\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n savefile( )\\n end ).onException = function( _, err ) term.setTextColour( colours.black ) print( err ) running = false end\\n end };\\n { type = \\\"button\\\", name = \\\"Save As\\\", onClick = function( )\\n activedropdown:remove( )\\n NovaUI.Thread( function( )\\n local path = NovaUI.display.response( UI, \\\"Path to save as\\\" )\\n if path then\\n savepath = path .. \\\".nim\\\"\\n savefile( )\\n end\\n end )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Exit\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n if Nova then\\n Nova.app.close( )\\n else\\n running = false\\n end\\n end\\n end )\\n end };\\n}\\nmenuoptions.edit = {\\n width = 14, height = 5, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Copy layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n copy( )\\n end };\\n { type = \\\"button\\\", name = \\\"Cut layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n cut( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Paste layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n paste( )\\n end };\\n}\\nmenuoptions.view = {\\n width = 10, height = 4, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Layers\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n if view.layers.active then view.layers.element:remove( ) else UI:newChild( view.layers.element ) end\\n view.layers.active = not view.layers.active\\n end };\\n { type = \\\"button\\\", name = \\\"Colours\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n if view.colours.active then view.colours.element:remove( ) else UI:newChild( view.colours.element ) end\\n view.colours.active = not view.colours.active\\n end };\\n { type = \\\"button\\\", name = \\\"Tools\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n if view.tools.active then view.tools.element:remove( ) else UI:newChild( view.tools.element ) end\\n view.tools.active = not view.tools.active\\n end };\\n}\\nmenuoptions.tool = {\\n width = 15, height = 13, spacing = false, shadow = colours.grey;\\n { type = \\\"display\\\", name = \\\"\\\", getDisplay = function( )\\n return tool.name\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Brush\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"brush\\\" end };\\n { type = \\\"button\\\", name = \\\"Rectangle\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"rectangle\\\" end };\\n { type = \\\"button\\\", name = \\\"Ellipse\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"ellipse\\\" end };\\n { type = \\\"button\\\", name = \\\"Line\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"line\\\" end };\\n { type = \\\"button\\\", name = \\\"Fill\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"fill\\\" end };\\n { type = \\\"button\\\", name = \\\"Pick colour\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"pick\\\" end };\\n { type = \\\"button\\\", name = \\\"Move\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"move\\\" end };\\n { type = \\\"button\\\", name = \\\"Eraser\\\", onClick = function( )\\n activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n setTool \\\"eraser\\\" end };\\n \\\"rule\\\";\\n { type = \\\"menu\\\", name = \\\"size\\\", options = {\\n shadow = colours.grey, width = 4, height = 6, spacing = false;\\n { type = \\\"button\\\", name = \\\"1\\\", onClick = function( ) activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end tool.size = 1 end };\\n { type = \\\"button\\\", name = \\\"2\\\", onClick = function( ) activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end tool.size = 2 end }; { type = \\\"button\\\", name = \\\"3\\\", onClick = function( ) activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end tool.size = 3 end };\\n { type = \\\"button\\\", name = \\\"4\\\", onClick = function( ) activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end tool.size = 4 end };\\n { type = \\\"button\\\", name = \\\"5\\\", onClick = function( ) activedropdown:remove( ) if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end tool.size = 5 end };\\n } }\\n}\\nmenuoptions.image = {\\n width = 18, height = 11, spacing = false, shadow = colours.grey;\\n { type = \\\"menu\\\", name = \\\"Background style\\\", options = {\\n width = 10, height = 5, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Checker\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n background:foreach( function( x, y )\\n x = math.floor( ( x + 1 ) / 2 )\\n y = math.floor( ( y + 1 ) / 2 )\\n local xm, ym = x % 2 == 1, y % 2 == 1\\n if ( xm and ym ) or ( not xm and not ym ) then\\n return colours.lightGrey, 1, \\\" \\\"\\n end\\n return 1, colours.lightGrey, \\\" \\\"\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"White\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n background:foreach( function( x, y )\\n return 1, 1, \\\" \\\"\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Black\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n background:foreach( function( x, y )\\n return 32768, 1, \\\" \\\"\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Lines\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n background:foreach( function( x, y )\\n if x % 2 == 1 or y % 2 == 1 then\\n return 1, colours.lightGrey, \\\" \\\"\\n end\\n return colours.lightGrey, 1, \\\" \\\"\\n end )\\n end };\\n } };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Resize\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n local closer = UI:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\n closer.bc = 0\\n closer.align = false\\n local f = UI:newChild( NovaUI.UIFrame( 1, math.ceil( UI.h / 2 ) - 5, UI.w, 10 ) )\\n f:newChild( NovaUI.UIText( 1, 1, f.w, f.h, \\\"\\\" ) ).bc = colours.blue\\n local title = f:newChild( NovaUI.UIText( 0, 2, 6, 1, \\\"Resize\\\" ) )\\n title:centreX( )\\n title.bc = 0\\n title.tc = 1\\n local inputs = f:newChild( NovaUI.UIFrame( 0, 4, 30, 1 ) )\\n inputs:centreX( )\\n local buttons = f:newChild( NovaUI.UIFrame( 0, 6, 35, 3 ) )\\n buttons:centreX( )\\n\\n local wt = inputs:newChild( NovaUI.UIText( 1, 1, 7, 1, \\\"Width:\\\" ) )\\n wt.bc = 0\\n wt.tc = 1\\n local w = inputs:newChild( NovaUI.UIInput( 0, 1, 5, 1 ) )\\n w:align( \\\"right\\\", wt )\\n w.bc, w.fbc = colours.lightBlue, colours.lightBlue\\n w.text = tostring( image.w )\\n local ht = inputs:newChild( NovaUI.UIText( 1, 1, 8, 1, \\\"Height:\\\" ) )\\n ht:align( \\\"right\\\", w, 5 )\\n ht.bc = 0\\n ht.tc = 1\\n local h = inputs:newChild( NovaUI.UIInput( 0, 1, 5, 1 ) )\\n h:align( \\\"right\\\", ht )\\n h.bc, h.fbc = colours.lightBlue, colours.lightBlue\\n h.text = tostring( image.h )\\n\\n function w:onEnter( )\\n h:focusOn( )\\n end\\n\\n local function done( )\\n if tonumber( w.text ) and tonumber( h.text ) then\\n resize( tonumber( w.text ), tonumber( h.text ) )\\n contentChanged( )\\n else\\n NovaUI.display.alert( UI, \\\"Please enter numbers\\\" )\\n end \\n end\\n\\n function h:onEnter( )\\n done( )\\n end\\n\\n local confirm = buttons:newChild( NovaUI.UIButton( 1, 1, 17, 3, \\\"Confirm\\\" ) )\\n confirm.bc = colours.lightBlue\\n function confirm:onClick( )\\n f:remove( )\\n closer:remove( )\\n done( )\\n end\\n local cancel = buttons:newChild( NovaUI.UIButton( 0, 1, 16, 3, \\\"Cancel\\\" ) )\\n cancel.bc = colours.lightBlue\\n cancel:alignTo( \\\"right\\\", confirm, 2 )\\n function cancel:onClick( )\\n f:remove( )\\n closer:remove( )\\n end\\n function closer:onClick( )\\n f:remove( )\\n closer:remove( )\\n end\\n end };\\n { type = \\\"display\\\", name = \\\"current: \\\", getDisplay = function( )\\n return image.w .. \\\"x\\\" .. image.h\\n end };\\n \\\"rule\\\";\\n { type = \\\"menu\\\", name = \\\"Effects\\\", options = {\\n width = 12, height = 4, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Greyscale\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n local c = {\\n [0] = 4, [colours.white] = 0, [colours.orange] = 1, [colours.magenta] = 1, [colours.lightBlue] = 1;\\n [colours.yellow] = 0, [colours.lime] = 1, [colours.pink] = 0, [colours.grey] = 2, [colours.lightGrey] = 1;\\n [colours.cyan] = 2, [colours.purple] = 2, [colours.blue] = 3, [colours.brown] = 3, [colours.green] = 2;\\n [colours.red] = 2, [colours.black] = 3;\\n }\\n local d = { [0] = colours.white, [1] = colours.lightGrey, [2] = colours.grey, [3] = colours.black, [4] = 0 }\\n layers[layer].image:foreach( function( x, y, bc, tc, char )\\n return d[c[bc]], d[c[tc]], char\\n end )\\n contentChanged( )\\n end };\\n { type = \\\"button\\\", name = \\\"Inverse\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.display.alert( UI, \\\"Not yet implemented!\\\" )\\n end };\\n { type = \\\"button\\\", name = \\\"Sepia\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n local c = {\\n [0] = 4, [colours.white] = 0, [colours.orange] = 2, [colours.magenta] = 1, [colours.lightBlue] = 1;\\n [colours.yellow] = 1, [colours.lime] = 1, [colours.pink] = 0, [colours.grey] = 2, [colours.lightGrey] = 1;\\n [colours.cyan] = 2, [colours.purple] = 2, [colours.blue] = 3, [colours.brown] = 3, [colours.green] = 2;\\n [colours.red] = 2, [colours.black] = 3;\\n }\\n local d = { [0] = colours.white, [1] = colours.yellow, [2] = colours.orange, [3] = colours.brown, [4] = 0 }\\n layers[layer].image:foreach( function( x, y, bc, tc, char )\\n return d[c[bc]], d[c[tc]], char\\n end )\\n contentChanged( )\\n end };\\n } };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"New layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n local r = NovaUI.display.response( UI, \\\"New layer name\\\" )\\n if r then\\n local im = NovaUI.Image( image.w, image.h )\\n im:foreach( function( ) return 0, 0, \\\"\\\" end )\\n newLayer( r, im )\\n contentChanged( )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Remove layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n if #layers > 1 then\\n table.remove( layers, layer )\\n layer = math.max( layer - 1, 1 )\\n setActive( layer, layers[layer].active ) -- update\\n contentChanged( )\\n else\\n NovaUI.display.alert( UI, \\\"Cannot delete last layer\\\" )\\n end\\n end };\\n { type = \\\"button\\\", name = \\\"Rename layer\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.lightBlue activemenu = nil end\\n NovaUI.Thread( function( )\\n local r = NovaUI.display.response( UI, \\\"New layer name\\\" )\\n if r then\\n layers[layer].name = r\\n contentChanged( )\\n end\\n end )\\n end };\\n}\\n\\nfunction imageeventh:onClick( x, y, button )\\n if tools[tool.name].onClick then\\n tools[tool.name].onClick( x, y, button )\\n end\\nend\\nfunction imageeventh:onDrag( rx, ry, cx, cy, button )\\n if tools[tool.name].onDrag then\\n tools[tool.name].onDrag( rx, ry, cx, cy, button )\\n end\\nend\\n\\nfunction keyhandler:onKey( key, lastkey )\\n if lastkey == 29 then\\n if key == keys.s then\\n NovaUI.Thread( function( )\\n savefile( )\\n end )\\n elseif key == keys.o then\\n NovaUI.Thread( function( )\\n if saved( ) then\\n local response = NovaUI.display.response( UI, \\\"Path to open from\\\" )\\n if response then\\n openfile( response .. \\\".nim\\\" )\\n end\\n end\\n end )\\n elseif key == keys.c then\\n copy( )\\n elseif key == keys.x then\\n cut( )\\n elseif key == keys.b then\\n paste( )\\n end\\n else\\n if tools[tool.name].onKey then\\n tools[tool.name].onKey( key )\\n end\\n end\\nend\\nfunction keyhandler:onChar( char )\\n if tools[tool.name].onChar then\\n tools[tool.name].onChar( char )\\n end\\nend\\n\\nfor k, v in pairs( menus ) do\\n v.bc, v.tc = 0, colours.lightBlue\\n function v:onClick( )\\n if activemenu == v then activedropdown:remove( ) activemenu.tc = colours.lightBlue activemenu = nil return end\\n activemenu = v\\n v.tc = colours.white\\n activedropdown = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n local close = activedropdown:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\n close.bc, close.align = 0, false\\n function close:onClick( x, y, button )\\n v.tc = colours.lightBlue\\n activedropdown:remove( )\\n if y == 1 then if Nova then os.queueEvent( \\\"mouse_click\\\", button, x, 2 ) else os.queueEvent( \\\"mouse_click\\\", button, x, 1 ) end else activemenu = nil end\\n end\\n local miniframe = activedropdown:newChild( NovaUI.UIFrame( v.x, v.y + 1, 0, 0 ) )\\n NovaUI.display.menu( miniframe, menuoptions[k] )\\n if miniframe.x + miniframe.w > activedropdown.w then\\n miniframe.x = activedropdown.w - miniframe.w + 1\\n end\\n end\\nend\\n\\nif ARGS[1] then\\n if not openfile( ARGS[1] ) then\\n if Nova then\\n Nova.app.close( )\\n else\\n running = false\\n end\\n end\\nelse\\n image.w = 15\\n image.h = 10\\n imageeventh.w = 15\\n imageeventh.h = 10\\n local im = NovaUI.Image( 15, 10 )\\n im:foreach( function( ) return 1, 1, \\\" \\\" end )\\n newLayer( \\\"Layer 1\\\", im )\\n image:centre( )\\nend\\n\\nif not Nova then\\n local function update( event, dt )\\n if event[1] ~= \\\"update\\\" then\\n NovaUI.Thread.update( event )\\n UI:event( event )\\n else\\n NovaUI.Thread.update { \\\"update\\\", dt }\\n UI:update( event[2] )\\n UI:draw( )\\n NovaUI.buffer.drawChanges( )\\n NovaUI.buffer.clear( )\\n end\\n end\\n\\n local ok, err = pcall( function( )\\n local time = os.clock( )\\n local timer = os.startTimer( 0 )\\n os.queueEvent \\\"start\\\"\\n while running do\\n local ev = { coroutine.yield( ) }\\n local dt = os.clock( ) - time\\n time = os.clock( )\\n if ev[1] == \\\"timer\\\" and ev[2] == timer then\\n update( { \\\"update\\\" }, dt )\\n timer = os.startTimer( 0.05 )\\n else\\n update( ev, dt )\\n end\\n end\\n end )\\n if not ok then\\n print( err )\\n end\\n term.setBackgroundColour( colours.black )\\n term.scroll( 1 )\\n term.setCursorPos( 1, ({ term.getSize( ) })[2] )\\n term.setTextColour( colours.blue )\\n print \\\"Thank you for using Nova Paint\\\"\\nend\",\
7507[\"appconfig.txt\"]=\"handles = {\\n \\\"nova_image\\\";\\n};\",\
7508}\
7509,\
7510Edit={\
7511[\"icon.nim\"]=\"78178:77 0F 07E07d07i07t0F \\n78278:77 0F 08-08-08-08-0F \\n78378:77 0F 08-08-08-08-0F \",\
7512[\"main.lua\"]=\"\\nlocal ARGS = ARGS\\nif not Nova then\\n ARGS = { ... }\\nend\\n\\nif not NovaUI then\\n os.loadAPI \\\"NovaUI\\\"\\nend\\nlocal UI = Nova and Nova.app.UI or NovaUI.UIHandler( )\\nlocal running = true\\n\\nif not Nova then\\n NovaUI.buffer.reset( )\\nend\\n\\nlocal syntax = {\\n lua = {\\n blocks = {\\n { start = \\\"--[[\\\", finish = \\\"]]\\\", tc = colours.green };\\n { start = \\\"--\\\", finish = \\\"\\\\n\\\", tc = colours.green };\\n };\\n words = {\\n [\\\"while\\\"] = { tc = colours.blue };\\n [\\\"do\\\"] = { tc = colours.blue };\\n [\\\"if\\\"] = { tc = colours.blue };\\n [\\\"elseif\\\"] = { tc = colours.blue };\\n [\\\"else\\\"] = { tc = colours.blue };\\n [\\\"then\\\"] = { tc = colours.blue };\\n [\\\"for\\\"] = { tc = colours.blue };\\n [\\\"in\\\"] = { tc = colours.blue };\\n [\\\"end\\\"] = { tc = colours.blue };\\n [\\\"local\\\"] = { tc = colours.blue };\\n [\\\"not\\\"] = { tc = colours.blue };\\n [\\\"and\\\"] = { tc = colours.blue };\\n [\\\"or\\\"] = { tc = colours.blue };\\n [\\\"function\\\"] = { tc = colours.blue };\\n [\\\"return\\\"] = { tc = colours.blue };\\n [\\\"for\\\"] = { tc = colours.blue };\\n [\\\"break\\\"] = { tc = colours.blue };\\n\\n [\\\"true\\\"] = { tc = colours.blue };\\n [\\\"false\\\"] = { tc = colours.blue };\\n [\\\"nil\\\"] = { tc = colours.blue };\\n };\\n default = { bc = colours.white, tc = colours.grey };\\n linen = { bc = colours.grey, tc = colours.lightGrey };\\n string = { tc = colours.red, escape = { tc = colours.brown } };\\n tab = { tc = colours.lightGrey, char = \\\":\\\" };\\n symbols = {\\n [\\\"=\\\"] = { tc = colours.purple };\\n [\\\"+\\\"] = { tc = colours.purple };\\n [\\\"*\\\"] = { tc = colours.purple };\\n [\\\"/\\\"] = { tc = colours.purple };\\n [\\\"^\\\"] = { tc = colours.purple };\\n [\\\"%\\\"] = { tc = colours.purple };\\n [\\\">\\\"] = { tc = colours.purple };\\n [\\\"<\\\"] = { tc = colours.purple };\\n [\\\"#\\\"] = { tc = colours.purple };\\n };\\n selection = { bc = colours.blue, tc = colours.white };\\n };\\n default = {\\n blocks = { };\\n words = { a = { colours.orange } };\\n default = { bc = colours.white, tc = colours.grey };\\n linen = { bc = colours.grey, tc = colours.lightGrey };\\n string = { };\\n symbols = { };\\n selection = { bc = colours.blue, tc = colours.white };\\n };\\n}\\n\\nlocal taskbar = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, 1 ) )\\ntaskbar:newChild( NovaUI.UIButton( 1, 1, taskbar.w, 1, \\\"\\\" ) ).bc = colours.grey\\n\\nlocal codefield = UI:newChild( NovaUI.UICode( 1, 2, UI.w - 1, UI.h - 2, syntax.default ) )\\ncodefield:focusOn( )\\n\\nlocal scrollv = UI:newChild( NovaUI.UIScrollBar( UI.w, 2, 1, UI.h - 2, codefield, \\\"vertical\\\" ) )\\nlocal scrollh = UI:newChild( NovaUI.UIScrollBar( 1, UI.h, UI.w, 1, codefield, \\\"horizontal\\\" ) )\\n\\nlocal savepath\\nlocal activedropdown\\nlocal activemenu\\nlocal history = { \\\"\\\" }\\nlocal historyindex = 1\\nlocal changed = false\\nlocal function final( )\\n while history[historyindex + 1] do\\n table.remove( history, historyindex + 1 )\\n end\\nend\\n\\nlocal menus = { }\\nlocal asterix\\n\\nlocal function contentChanged( )\\n if changed then return end\\n changed = true\\n if Nova then\\n Nova.app.setTitle \\\"Edit*\\\"\\n else\\n for k, v in pairs( menus ) do\\n v.x = v.x + 2\\n end\\n asterix = taskbar:newChild( NovaUI.UIText( 1, 1, 1, 1, \\\"*\\\" ) )\\n asterix.bc = 0\\n asterix.tc = 1\\n end\\nend\\nlocal function contentSaved( )\\n if not changed then return end\\n if asterix then\\n asterix:remove( )\\n end\\n changed = false\\n if Nova then\\n Nova.app.setTitle \\\"Edit\\\"\\n else\\n for k, v in pairs( menus ) do\\n v.x = v.x - 2\\n end\\n end\\nend\\n\\nlocal function copy( )\\n if codefield.selected then\\n local text = codefield:getSelection( )\\n NovaUI.clipboard.set( \\\"plaintext\\\", text )\\n end\\nend\\nlocal function cut( )\\n if codefield.selected then\\n local text = codefield:getSelection( )\\n NovaUI.clipboard.set( \\\"plaintext\\\", text )\\n codefield:setSelection \\\"\\\"\\n table.insert( history, codefield:getCode( ) )\\n historyindex = #history\\n final( )\\n contentChanged( )\\n end\\nend\\nlocal function paste( )\\n local mode, data = NovaUI.clipboard.get( )\\n if mode ~= \\\"plaintext\\\" then\\n return\\n end\\n if codefield.selected then\\n codefield:setSelection( data )\\n else\\n codefield:write( data )\\n end\\n table.insert( history, codefield:getCode( ) )\\n historyindex = #history\\n final( )\\n contentChanged( )\\nend\\n\\nfunction codefield:onChange( mode )\\n if mode == \\\" \\\" or mode == \\\"\\\\n\\\" or mode == \\\" \\\" or mode == \\\"paste\\\" or mode == \\\"cut\\\" or mode == \\\"backspace\\\" or mode == \\\"delete\\\" then\\n table.insert( history, self:getCode( ) )\\n else\\n history[math.max( #history, 1 )] = self:getCode( )\\n end\\n historyindex = #history\\n final( )\\n contentChanged( )\\nend\\nlocal function undo( )\\n if historyindex > 1 then\\n historyindex = historyindex - 1\\n codefield:setCode( history[historyindex] )\\n contentChanged( )\\n end\\nend\\nlocal function redo( )\\n if historyindex < #history then\\n historyindex = historyindex + 1\\n codefield:setCode( history[historyindex] )\\n contentChanged( )\\n end\\nend\\n\\nlocal function savefile( wait )\\n local t = NovaUI.Thread( function( )\\n if not savepath then\\n savepath = NovaUI.display.response( UI, \\\"Where would you like to save?\\\" )\\n end\\n if not savepath then\\n return\\n end\\n if Nova then\\n Nova.filesystem.writefile( savepath, FileData( codefield:getCode( ) ) )\\n else\\n local h = fs.open( savepath, \\\"w\\\" )\\n if h then\\n h.write( codefield:getCode( ) )\\n h.close( )\\n else\\n NovaUI.display.alert( UI, \\\"Could not save file!\\\" )\\n end\\n end\\n contentSaved( )\\n end )\\n if wait then\\n while t:isRunning( ) do\\n coroutine.yield( )\\n end\\n end\\nend\\n\\nlocal function saved( )\\n if changed then\\n local result = NovaUI.display.confirm( UI, \\\"You have unsaved changes, would you like to save?\\\" )\\n if result then\\n savefile( true )\\n end\\n if result == nil then\\n return false\\n end\\n end\\n return true\\nend\\n\\nif Nova then\\n function Nova.app.canClose( reason )\\n if reason == \\\"user\\\" and changed then\\n NovaUI.Thread( function( )\\n if saved( ) then\\n Nova.app.close( )\\n end\\n end )\\n return false\\n end\\n return true\\n end\\nend\\n\\nlocal function openfile( path, mode )\\n if not mode and Nova then\\n mode = Nova.filesystem.getType( path )\\n elseif not mode then\\n mode = \\\"lua\\\"\\n end\\n savepath = path\\n local content\\n if Nova then\\n local filedata, err = Nova.filesystem.readfile( path )\\n if filedata then\\n content = filedata.content\\n else\\n NovaUI.display.alert( UI, err )\\n return\\n end\\n else\\n local h = fs.open( path, \\\"r\\\" )\\n if h then\\n content = h.readAll( )\\n h.close( )\\n else\\n NovaUI.display.alert( UI, \\\"could not open file\\\" )\\n return\\n end\\n end\\n if mode == \\\"lua\\\" or mode == \\\"class\\\" or mode == \\\"lib\\\" then\\n codefield.syntax = syntax.lua\\n else\\n codefield.syntax = syntax.default\\n end\\n codefield:setCode( content )\\n contentSaved( )\\n history = { content }\\n historyindex = 1\\nend\\n\\nmenus.file = taskbar:newChild( NovaUI.UIButton( 1, 1, 4, 1, \\\"File\\\" ) )\\nmenus.edit = taskbar:newChild( NovaUI.UIButton( 7, 1, 4, 1, \\\"Edit\\\" ) )\\nmenus.syntax = taskbar:newChild( NovaUI.UIButton( 13, 1, 6, 1, \\\"Syntax\\\" ) )\\nmenus.debug = taskbar:newChild( NovaUI.UIButton( 21, 1, 5, 1, \\\"Debug\\\" ) )\\nlocal menuoptions = { }\\nmenuoptions.file = {\\n width = 10;\\n height = 8;\\n spacing = false;\\n shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"New\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n codefield:setCode \\\"\\\"\\n history = { \\\"\\\", \\\"\\\" }\\n historyindex = 1\\n contentChanged( )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Open\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n local path = NovaUI.display.response( UI, \\\"Path to open from\\\" )\\n if path then\\n openfile( path )\\n end\\n end\\n end )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Save\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n savefile( )\\n end };\\n { type = \\\"button\\\", name = \\\"Save As\\\", onClick = function( )\\n activedropdown:remove( )\\n NovaUI.Thread( function( )\\n local path = NovaUI.display.response( UI, \\\"Path to save as\\\" )\\n if path then\\n savepath = path\\n savefile( )\\n end\\n end )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Exit\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n if saved( ) then\\n if Nova then\\n Nova.app.close( )\\n else\\n running = false\\n end\\n end\\n end )\\n end };\\n}\\nmenuoptions.edit = {\\n width = 9;\\n height = 8;\\n spacing = false;\\n shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Undo\\\", onClick = function( )\\n undo( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n { type = \\\"button\\\", name = \\\"Redo\\\", onClick = function( )\\n redo( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Copy\\\", onClick = function( )\\n copy( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n { type = \\\"button\\\", name = \\\"Cut\\\", onClick = function( )\\n cut( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Paste\\\", onClick = function( )\\n paste( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n}\\nmenuoptions.syntax = {\\n width = 12;\\n height = 3;\\n spacing = false;\\n shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Plain text\\\", onClick = function( )\\n codefield.syntax = syntax.default\\n codefield:updateCharacters( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n { type = \\\"button\\\", name = \\\"Lua\\\", onClick = function( )\\n codefield.syntax = syntax.lua\\n codefield:updateCharacters( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n end };\\n}\\nmenuoptions.debug = {\\n width = 18;\\n height = 5;\\n spacing = false;\\n shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"Run\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n local f, err = loadstring( codefield:getCode( ), \\\"program\\\" )\\n if f then\\n local frame = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n local title = frame:newChild( NovaUI.UIText( 1, 1, UI.w - 5, 1, \\\"Running...\\\" ) ) title.bc = colours.black title.tc = colours.white\\n local close = frame:newChild( NovaUI.UIButton( UI.w - 4, 1, 5, 1, \\\"close\\\" ) ) close.bc = colours.black close.tc = colours.red\\n function close:onClick( ) frame:remove( ) end\\n local canvas = frame:newChild( NovaUI.UICanvas( 1, 2, UI.w, UI.h - 1 ) ) canvas:setTask( f ) canvas:passEvent( )\\n else\\n NovaUI.display.help( UI, \\\"Syntax error\\\", err:gsub( \\\"main:401: \\\", \\\"\\\" ) )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"Run with params\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n local r = NovaUI.display.response( UI, \\\"Please enter params, separated by a space.\\\" )\\n local p = { }\\n if r then\\n local last = 1\\n for i = 1, #r do\\n if r:sub( i, i ) == \\\" \\\" then\\n p[#p+1] = r:sub( last, i - 1 )\\n last = i + 1\\n end\\n end\\n p[#p+1] = r:sub( last )\\n end\\n local f, err = loadstring( codefield:getCode( ), \\\"program\\\" )\\n if f then\\n local frame = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n local title = frame:newChild( NovaUI.UIText( 1, 1, UI.w - 5, 1, \\\"Running...\\\" ) ) title.bc = colours.black title.tc = colours.white\\n local close = frame:newChild( NovaUI.UIButton( UI.w - 4, 1, 5, 1, \\\"close\\\" ) ) close.bc = colours.black close.tc = colours.red\\n function close:onClick( ) frame:remove( ) end\\n local canvas = frame:newChild( NovaUI.UICanvas( 1, 2, UI.w, UI.h - 1 ) ) canvas:setTask( f ) canvas:passEvent( unpack( p ) )\\n else\\n NovaUI.display.help( UI, \\\"Syntax error\\\", err:gsub( \\\"main:429: \\\", \\\"\\\" ) )\\n end\\n end )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"Check syntax\\\", onClick = function( )\\n activedropdown:remove( )\\n if activemenu then activemenu.tc = colours.cyan activemenu = nil end\\n NovaUI.Thread( function( )\\n local f, err = loadstring( codefield:getCode( ), \\\"program\\\" )\\n if f then\\n NovaUI.display.alert( UI, \\\"Everything looks good!\\\" )\\n else\\n NovaUI.display.help( UI, \\\"Syntax error\\\", err:gsub( \\\"main:446: \\\", \\\"\\\" ) )\\n end\\n end )\\n end };\\n}\\n\\nfor k, v in pairs( menus ) do\\n v.bc = 0\\n v.tc = colours.cyan\\n function v:onClick( )\\n if activemenu == v then\\n activedropdown:remove( )\\n activemenu.tc = colours.cyan\\n activemenu = nil\\n return\\n end\\n activemenu = v\\n v.tc = colours.lightBlue\\n local frame = UI:newChild( NovaUI.UIFrame( 1, 1, UI.w, UI.h ) )\\n activedropdown = frame\\n local close = frame:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\n close.bc = 0\\n close.align = false\\n function close:onClick( x, y, button )\\n v.tc = colours.cyan\\n frame:remove( )\\n if y == 1 then\\n if Nova then\\n os.queueEvent( \\\"mouse_click\\\", button, x, 2 )\\n else\\n os.queueEvent( \\\"mouse_click\\\", button, x, 1 )\\n end\\n else\\n activemenu = nil\\n end\\n end\\n local miniframe = frame:newChild( NovaUI.UIFrame( v.x, v.y + 1, 0, 0 ) )\\n NovaUI.display.menu( miniframe, menuoptions[k] )\\n if miniframe.x + miniframe.w > frame.w then\\n miniframe.x = frame.w - miniframe.w + 1\\n end\\n end\\nend\\n\\nlocal keyhandler = UI:newChild( NovaUI.UIKeyHandler( 0, 0, 0, 0 ) )\\n\\nfunction keyhandler:onKey( key, lastkey )\\n if lastkey == 29 then\\n if key == 31 then -- ctrl-s\\n savefile( )\\n elseif key == 24 then -- ctrl-o\\n NovaUI.Thread( function( )\\n if saved( ) then\\n local path = NovaUI.display.response( UI, \\\"Path to open from\\\" )\\n if path then\\n openfile( path )\\n end\\n end\\n end )\\n elseif key == keys.z then\\n undo( )\\n elseif key == keys.y then\\n redo( )\\n end\\n end\\nend\\nfunction codefield:onCtrlKey( key )\\n if key == 31 then -- ctrl-s\\n savefile( )\\n elseif key == 24 then -- ctrl-o\\n NovaUI.Thread( function( )\\n if saved( ) then\\n local path = NovaUI.display.response( UI, \\\"Path to open from\\\" )\\n if path then\\n openfile( path )\\n end\\n end\\n end )\\n elseif key == keys.z then\\n undo( )\\n elseif key == keys.y then\\n redo( )\\n end\\nend\\n\\nif ARGS[1] then\\n openfile( ARGS[1], ARGS[2] )\\nend\\n\\nif not Nova then\\n local function update( event, dt )\\n if event[1] ~= \\\"update\\\" then\\n NovaUI.Thread.update( event )\\n UI:event( event )\\n else\\n NovaUI.Thread.update { \\\"update\\\", dt }\\n UI:update( event[2] )\\n UI:draw( )\\n NovaUI.buffer.drawChanges( )\\n NovaUI.buffer.clear( )\\n end\\n end\\n\\n local ok, err = pcall( function( )\\n local time = os.clock( )\\n local timer = os.startTimer( 0 )\\n os.queueEvent \\\"start\\\"\\n while running do\\n local ev = { coroutine.yield( ) }\\n local dt = os.clock( ) - time\\n time = os.clock( )\\n if ev[1] == \\\"timer\\\" and ev[2] == timer then\\n update( { \\\"update\\\" }, dt )\\n timer = os.startTimer( 0.05 )\\n else\\n update( ev, dt )\\n end\\n end\\n end )\\n if not ok then\\n print( err )\\n end\\n term.setBackgroundColour( colours.black )\\n term.scroll( 1 )\\n term.setCursorPos( 1, ({ term.getSize( ) })[2] )\\n term.setTextColour( colours.blue )\\n print \\\"Thank you for using Nova Edit\\\"\\nend\",\
7513[\"appconfig.txt\"]=\"handles = {\\n \\\"text\\\";\\n \\\"lua\\\";\\n \\\"unknown\\\";\\n \\\"lib\\\";\\n \\\"class\\\";\\n};\",\
7514}\
7515,\
7516Test={\
7517[\"main.lua\"]=\"\\nlocal random = Nova.app.service.Desktop[1].misc\\nNovaUI.display.alert( Nova.app.UI, random, true )\\nNova.app.close( )\",\
7518}\
7519,\
7520BBTetris={\
7521[\"appconfig.txt\"]=\"runmode = \\\"compatibility\\\";\\nfullscreen = true;\",\
7522[\"main.lua\"]=\"-- +---------------------+------------+---------------------+\\n-- | ## # # | | ## # ## |\\n-- | ## # ## | BBTetris | ## ## # |\\n-- | ## # | #### | # # |\\n-- +---------------------+------------+---------------------+\\n\\nlocal version = \\\"Version 1.0.4\\\"\\n\\n-- Yet another version of Tetris, by Jeffrey Alexander (aka Bomb Bloke).\\n-- Heavily based on the old \\\"Brick Game\\\"s you may've once seen.\\n-- http://www.computercraft.info/forums2/index.php?/topic/15878-bbtetris\\n\\n---------------------------------------------\\n------------Variable Declarations------------\\n---------------------------------------------\\n\\n-- Seven regular blocks, six \\\"advanced\\\" blocks, three \\\"trick\\\" blocks.\\nlocal block = {\\n{{1,1,0},{0,1,1}},{{0,1,0},{0,1,0},{0,1,0},{0,1,0}},{{0,1,1},{1,1,0}},{{1,1},{1,1}},{{1,1,1},{0,1,0},\\n{0,0,0}},{{1,1,1},{0,0,1}},{{1,1,1},{1,0,0}},{{1,1,0},{0,1,0},{0,1,1}},{{1,1},{0,1}},{{1,0,0},{1,1,1},\\n{1,0,0}},{{0,1,0},{1,1,1},{0,1,0}},{{1},{1}},{{1,1,1},{1,0,1}},{{1}},{{1},{1}},{{1},{1},{1}}}\\n\\n-- The menu numerals. Eight in all.\\nlocal number = {\\n{{5,1},{4,1,1},{5,1},{5,1},{5,1},{5,1},{4,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{7,1},{6,1},{5,1},{4,1},\\n{3,1,1,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{7,1},{4,1,1,1},{7,1},{3,1,0,0,0,1},{4,1,1,1}},{{6,1},{5,1,1},\\n{4,1,0,1},{3,1,0,0,1},{3,1,0,0,1},{3,1,1,1,1,1},{6,1}},{{3,1,1,1,1,1},{3,1},{3,1,1,1,1},{7,1},{7,1},\\n{3,1,0,0,0,1},{4,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{3,1},{3,1,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},\\n{4,1,1,1}},{{3,1,1,1,1,1},{7,1},{6,1},{5,1},{5,1},{4,1},{4,1}},{{4,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},\\n{4,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},{4,1,1,1}}}\\n\\nlocal gamemode, speed, level, running, grid, saves, canSave, playMusic = 1, 0, 0, true, {}, {}, true, true\\nlocal highScore, screenx, screeny, myEvent, mon, OGeventPuller, skipIntro, skipMonitor, defaultMonitor\\n\\nlocal musicFile = \\\"rom/songs/Tetris A Theme.nbs\\\"\\n\\n---------------------------------------------\\n------------Function Declarations------------\\n---------------------------------------------\\n\\n-- Return to shell.\\nlocal function exitGame(erroring)\\n if canSave then\\n myEvent = fs.open(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.dat\\\", \\\"w\\\")\\n myEvent.writeLine(\\\"Save data file for Bomb Bloke's Tetris game.\\\")\\n myEvent.writeLine(string.gsub(textutils.serialize(highScore),\\\"\\\\n%s*\\\",\\\"\\\"))\\n myEvent.writeLine(string.gsub(textutils.serialize(saves),\\\"\\\\n%s*\\\",\\\"\\\"))\\n myEvent.close()\\n end\\n\\n term.setBackgroundColor(colours.black)\\n term.setTextColor(colours.white)\\n \\n if mon then\\n term.clear()\\n if term.restore then term.restore() else term.redirect(mon.restoreTo) end\\n end\\n \\n if note then os.unloadAPI(\\\"note\\\") end\\n \\n term.clear()\\n term.setCursorPos(1,1)\\n if erroring then print(erroring..\\\"\\\\n\\\") end \\n print(\\\"Thanks for playing!\\\")\\n \\n if (shell.resolve(\\\".\\\") == \\\"disk\\\") and not fs.exists(\\\"\\\\\\\\bbtetris\\\") then\\n print(\\\"\\\\nIf you wish to copy me to your internal drive (for play without the disk - this allows saving of score data etc), type:\\\\n\\\\ncp \\\\\\\\disk\\\\\\\\bbtetris \\\\\\\\bbtetris\\\\n\\\")\\n end\\n\\n os.pullEvent = OGeventPuller\\n error()\\nend\\n\\n-- Writes regular text at the specified location on the screen.\\nlocal function writeAt(text, x , y)\\n term.setBackgroundColor(colours.black)\\n term.setTextColor(colours.white)\\n term.setCursorPos(screenx+x,screeny+y)\\n term.write(text)\\nend\\n\\n-- Returns whether a given event was a touch event this program should listen to.\\nlocal function touchedMe()\\n if myEvent[1] == \\\"mouse_click\\\" then return true\\n elseif myEvent[1] ~= \\\"monitor_touch\\\" or not mon then return false\\n else return mon.side == myEvent[2] end\\nend\\n\\n-- Returns whether one of a given set of keys was pressed.\\nlocal function pressedKey(...)\\n if myEvent[1] ~= \\\"key\\\" then return false end\\n for i=1,#arg do if arg[i] == myEvent[2] then return true end end\\n return false\\nend\\n\\n-- Returns whether a click was performed at a given location.\\n-- If two parameters are passed, it checks to see if x is [1] and y is [2].\\n-- If three parameters are passed, it checks to see if x is [1] and y is between [2]/[3] (non-inclusive).\\n-- If four paramaters are passed, it checks to see if x is between [1]/[2] and y is between [3]/[4] (non-inclusive).\\nlocal function clickedAt(...)\\n if not touchedMe() then return false end\\n if #arg == 2 then return (myEvent[3] == arg[1]+screenx and myEvent[4] == arg[2]+screeny)\\n elseif #arg == 3 then return (myEvent[3] == arg[1]+screenx and myEvent[4] > arg[2]+screeny and myEvent[4] < arg[3]+screeny)\\n else return (myEvent[3] > arg[1]+screenx and myEvent[3] < arg[2]+screenx and myEvent[4] > arg[3]+screeny and myEvent[4] < arg[4]+screeny) end\\nend\\n\\n-- Ensures the wrapped monitor is suitable for play.\\nlocal function enforceScreenSize()\\n term.setBackgroundColor(colours.black)\\n term.setTextColor(colours.white)\\n \\n while true do\\n local scale = 5\\n mon.setTextScale(scale)\\n screenx,screeny = term.getSize()\\n\\n term.clear()\\n term.setCursorPos(1,1)\\n\\n while (screenx < 50 or screeny < 19) and scale > 0.5 do\\n scale = scale - 0.5\\n mon.setTextScale(scale)\\n screenx,screeny = term.getSize()\\n end\\n\\n if screenx > 49 and screeny > 18 then\\n screenx,screeny = math.floor(screenx/2)-10, math.floor(screeny/2)-9\\n return\\n else print(\\\"Make this display out of at least three by two monitor blocks, or tap me to quit.\\\") end\\n \\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if myEvent[1] == \\\"monitor_resize\\\" then break\\n elseif myEvent[1] == \\\"key\\\" or touchedMe() or myEvent[1] == \\\"terminate\\\" then exitGame()\\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n elseif myEvent[1] == \\\"musicFinished\\\" then os.queueEvent(\\\"musicPlay\\\",musicFile) end\\n end\\n end\\nend\\n\\n-- Draws the frame around the playing area, along with other static stuff.\\nlocal function drawBorder()\\n term.setBackgroundColor(colours.black)\\n term.clear()\\n \\n writeAt(\\\"High\\\",14,11)\\n writeAt(\\\"Level\\\",14,14)\\n writeAt(\\\"Speed\\\",14,17)\\n \\n writeAt(\\\"[H]elp\\\",-6,2)\\n writeAt(\\\"[Q]uit\\\",22,2)\\n \\n writeAt(\\\"[ ] Advanced\\\",22,14)\\n writeAt(\\\"[ ] Tricks\\\",22,16)\\n writeAt(\\\"[ ] Quota\\\",22,18)\\n \\n term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n \\n term.setCursorPos(screenx+1,screeny)\\n term.write(string.rep(\\\"L\\\",20))\\n term.setCursorPos(screenx+1,screeny+20)\\n term.write(string.rep(\\\"L\\\",20))\\n term.setCursorPos(screenx+13,screeny+6)\\n term.write(string.rep(\\\"L\\\",7))\\n \\n for i=1,19 do\\n term.setCursorPos(screenx+1,screeny+i)\\n term.write(\\\"L\\\")\\n term.setCursorPos(screenx+12,screeny+i)\\n term.write(\\\"L\\\")\\n term.setCursorPos(screenx+20,screeny+i)\\n term.write(\\\"L\\\")\\n end\\nend\\n\\n-- Draws the big numbers indicating the game mode on the main menu (plus associated data).\\nlocal function drawNumeral()\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n \\n for i=1,7 do\\n term.setCursorPos(screenx+2,screeny+i+6)\\n term.setBackgroundColor(colours.black)\\n term.write(string.rep(\\\" \\\",10))\\n term.setBackgroundColor(term.isColor() and colours.blue or colours.white)\\n \\n for j=2,#number[gamemode][i] do if number[gamemode][i][j] == 1 then\\n term.setCursorPos(screenx+number[gamemode][i][1]+j,screeny+i+6)\\n term.write(\\\"L\\\")\\n end end\\n end\\n \\n term.setBackgroundColor(colours.black)\\n term.setTextColor(term.isColor() and colours.green or colours.white)\\n \\n for i=0,2 do\\n term.setCursorPos(screenx+23,screeny+14+i*2)\\n term.write(bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and \\\"O\\\" or \\\" \\\")\\n end\\n \\n writeAt(string.rep(\\\" \\\",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)\\nend\\n\\n-- Fill the grid with random blocks according to the chosen level.\\nlocal function fillGrid()\\n term.setBackgroundColor(bit.band(gamemode-1,4)==4 and colours.red or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n \\n for i=1,level+(bit.band(gamemode-1,4)==4 and 1 or 0) do\\n grid[i] = {}\\n for j=1,10 do if math.random(2) > 1 then\\n term.setCursorPos(screenx+j+1,screeny+20-i)\\n term.write(\\\"L\\\")\\n grid[i][j] = bit.band(gamemode-1,4)==4 and 14 or 32\\n end end\\n end\\n \\n if bit.band(gamemode-1,4)==4 then for i=level+2,19 do grid[i] = {} end end\\nend\\n\\n-- Do the game over animation.\\nlocal function gameover()\\n term.setBackgroundColor(term.isColor() and colours.black or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n for i=19,1,-1 do\\n term.setCursorPos(screenx+2,screeny+i)\\n term.write(string.rep(\\\"L\\\",10))\\n blockTimer = os.startTimer(0.1)\\n while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent(\\\"timer\\\")} end\\n end\\n \\n term.setBackgroundColor(colours.black)\\n for i=1,19 do\\n term.setCursorPos(screenx+2,screeny+i)\\n term.write(string.rep(\\\" \\\",10))\\n blockTimer = os.startTimer(0.1)\\n while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent(\\\"timer\\\")} end\\n end\\nend\\n\\n-- Renders the block (or clears that area of the screen if not \\\"drawing\\\").\\nlocal function drawBlock(thisBlock,rotation,xpos,ypos,drawing,flicker)\\n if thisBlock > 13 and not drawing then\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n for y=1,#block[thisBlock] do\\n term.setCursorPos(screenx+xpos+1,screeny+ypos+y-1)\\n if grid[21-ypos-y][xpos] ~= nil then\\n term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[20-ypos][xpos]) or colours.white)\\n term.write(\\\"L\\\")\\n else\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n end\\n end\\n return\\n end\\n \\n if drawing and thisBlock > 13 then\\n term.setBackgroundColor(term.isColor() and (flicker and colours.white or colours.black) or (flicker and colours.black or colours.white))\\n else\\n term.setBackgroundColor(drawing and (term.isColor() and bit.blshift(1,thisBlock) or colours.white) or colours.black)\\n end\\n \\n term.setTextColor(term.isColor() and colours.lightGrey or (flicker and colours.white or colours.black))\\n \\n for y=1,#block[thisBlock] do for x=1,#block[thisBlock][1] do if block[thisBlock][y][x] == 1 then\\n if rotation == 0 then\\n term.setCursorPos(screenx+xpos+x,screeny+ypos+y-1)\\n elseif rotation == 1 then\\n term.setCursorPos(screenx+xpos+#block[thisBlock]+1-y,screeny+ypos+x-1)\\n elseif rotation == 2 then\\n term.setCursorPos(screenx+xpos+#block[thisBlock][1]+1-x,screeny+ypos+#block[thisBlock]-y)\\n elseif rotation == 3 then\\n term.setCursorPos(screenx+xpos+y,screeny+ypos+#block[thisBlock][1]-x)\\n end\\n \\n term.write(drawing and \\\"L\\\" or \\\" \\\")\\n end end end\\nend\\n\\n-- Returns whether the block can move into the specified position.\\nlocal function checkBlock(thisBlock,rotation,xpos,ypos)\\n if thisBlock == 14 then\\n if xpos < 1 or xpos > 10 then return false end\\n for y = 1,20-ypos do if grid[y][xpos] == nil then return true end end\\n return false\\n end \\n \\n local checkX, checkY\\n \\n for y=1,#block[thisBlock] do for x=1,#block[thisBlock][1] do if block[thisBlock][y][x] == 1 then\\n if rotation == 0 then\\n checkX, checkY = xpos+x-1, ypos+y-1\\n elseif rotation == 1 then\\n checkX, checkY = xpos+#block[thisBlock]-y, ypos+x-1\\n elseif rotation == 2 then\\n checkX, checkY = xpos+#block[thisBlock][1]-x, ypos+#block[thisBlock]-y\\n elseif rotation == 3 then\\n checkX, checkY = xpos+y-1, ypos+#block[thisBlock][1]-x\\n end\\n \\n if checkX < 1 or checkX > 10 or checkY < 1 or checkY > 19 or grid[20-checkY][checkX] ~= nil then return false end\\n end end end\\n \\n return true\\nend\\n\\n-- Redraw the game view after a monitor re-size.\\nlocal function redrawGame(score)\\n drawBorder()\\n \\n writeAt(\\\"[P]ause\\\",22,4)\\n if note then writeAt(\\\"[M]usic\\\",22,6) end\\n writeAt(\\\"Next\\\",14,1)\\n writeAt(\\\"Score\\\",14,8)\\n writeAt(string.rep(\\\" \\\",6-#tostring(score))..tostring(score),13,9)\\n writeAt(string.rep(\\\" \\\",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n writeAt(tostring(speed),18,18)\\n \\n term.setBackgroundColor(colours.black)\\n term.setTextColor(term.isColor() and colours.green or colours.white)\\n \\n for i=0,2 do\\n term.setCursorPos(screenx+23,screeny+14+i*2)\\n term.write(bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and \\\"O\\\" or \\\" \\\")\\n end\\n \\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n for yy=1,19 do\\n term.setCursorPos(screenx+2,screeny+yy)\\n for xx=1,10 do if grid[20-yy][xx] ~= nil then\\n term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[20-yy][xx]) or colours.white)\\n term.write(\\\"L\\\")\\n else\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n end end\\n end\\nend\\n\\nlocal function intro()\\n local temp\\n \\n local introBlock = {\\n {11,2,4},{12,2,4},{13,2,4},{14,2,4},{15,2,4},{16,2,4},{17,2,4},{18,2,4},{19,2,4},{20,2,4},{21,2,4},{22,2,4},{23,2,4},\\n {24,2,4},{25,2,4},{8,3,4},{9,3,4},{10,3,4},{26,3,4},{27,3,4},{29,3,11},{5,4,4},{6,4,4},{7,4,4},{13,4,11},{14,4,11},\\n {15,4,11},{16,4,11},{17,4,11},{18,4,11},{29,4,11},{31,4,4},{32,4,4},{33,4,4},{46,4,4},{9,5,11},{10,5,11},{11,5,11},\\n {12,5,11},{14,5,11},{29,5,11},{34,5,4},{44,5,4},{45,5,4},{14,6,11},{25,6,11},{26,6,11},{27,6,11},{29,6,11},{35,6,4},\\n {36,6,4},{37,6,4},{43,6,4},{14,7,11},{20,7,11},{21,7,11},{22,7,11},{23,7,11},{28,7,11},{29,7,11},{38,7,4},{39,7,4},\\n {40,7,4},{41,7,4},{42,7,4},{15,8,11},{19,8,11},{24,8,11},{28,8,11},{30,8,11},{31,8,11},{8,9,14},{9,9,14},{15,9,11},\\n {19,9,11},{20,9,11},{25,9,11},{28,9,11},{45,9,11},{46,9,11},{47,9,11},{4,10,14},{5,10,14},{7,10,14},{10,10,14},\\n {15,10,11},{19,10,11},{21,10,11},{22,10,11},{23,10,11},{24,10,11},{28,10,11},{35,10,11},{36,10,11},{37,10,11},\\n {44,10,11},{48,10,11},{3,11,14},{6,11,14},{8,11,14},{9,11,14},{10,11,14},{11,11,14},{14,11,11},{19,11,11},{29,11,11},\\n {32,11,11},{34,11,11},{38,11,11},{41,11,11},{44,11,11},{4,12,14},{5,12,14},{6,12,14},{9,12,14},{12,12,14},{14,12,11},\\n {20,12,11},{23,12,11},{29,12,11},{33,12,11},{44,12,11},{45,12,11},{4,13,14},{7,13,14},{9,13,14},{10,13,14},\\n {11,13,14},{21,13,11},{22,13,11},{29,13,11},{34,13,11},{39,13,11},{40,13,11},{46,13,11},{47,13,11},{4,14,14},\\n {5,14,14},{6,14,14},{7,14,14},{29,14,11},{34,14,11},{40,14,11},{48,14,11},{10,15,4},{11,15,4},{12,15,4},{13,15,4},\\n {14,15,4},{15,15,4},{40,15,11},{44,15,11},{48,15,11},{9,16,4},{16,16,4},{17,16,4},{18,16,4},{19,16,4},{20,16,4},\\n {21,16,4},{22,16,4},{40,16,11},{45,16,11},{46,16,11},{47,16,11},{6,17,4},{7,17,4},{8,17,4},{23,17,4},{24,17,4},\\n {25,17,4},{26,17,4},{42,17,4},{43,17,4},{44,17,4},{27,18,4},{28,18,4},{29,18,4},{30,18,4},{31,18,4},{32,18,4},\\n {33,18,4},{34,18,4},{35,18,4},{36,18,4},{37,18,4},{38,18,4},{39,18,4},{40,18,4},{41,18,4}}\\n\\n term.setBackgroundColor(colours.black)\\n term.clear()\\n \\n writeAt(version,36-#version,19)\\n \\n term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n \\n term.setCursorPos(screenx-15,screeny)\\n term.write(string.rep(\\\"L\\\",53))\\n term.setCursorPos(screenx-15,screeny+20)\\n term.write(string.rep(\\\"L\\\",53))\\n \\n for i=1,19 do\\n term.setCursorPos(screenx-15,screeny+i)\\n term.write(\\\"L\\\")\\n term.setCursorPos(screenx+37,screeny+i)\\n term.write(\\\"L\\\")\\n end\\n\\n while #introBlock > 0 do\\n for i=1,5 do if #introBlock > 0 then\\n temp = math.random(#introBlock)\\n if term.isColor() then term.setBackgroundColor(math.pow(2,introBlock[temp][3])) end\\n term.setCursorPos(screenx-15+introBlock[temp][1],screeny+introBlock[temp][2])\\n term.write(\\\"L\\\")\\n table.remove(introBlock,temp)\\n end end\\n \\n temp = os.startTimer(0)\\n \\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if myEvent[1] == \\\"timer\\\" and myEvent[2] == temp then\\n break\\n elseif myEvent[1] ~= \\\"timer\\\" then\\n return\\n end\\n end \\n end\\n \\n temp = os.startTimer(2)\\n os.pullEvent()\\nend\\n\\n---------------------------------------------\\n------------ Help Pages ------------\\n---------------------------------------------\\n\\n-- Draw the frame for the help screen.\\nlocal function prepareHelp()\\n term.setBackgroundColor(colours.black)\\n term.clear()\\n \\n writeAt(\\\"^\\\",35,2)\\n writeAt(\\\"|\\\",35,3)\\n writeAt(\\\"X\\\",35,10)\\n writeAt(\\\"|\\\",35,17)\\n writeAt(\\\"v\\\",35,18)\\n \\n term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n \\n term.setCursorPos(screenx-15,screeny)\\n term.write(string.rep(\\\"L\\\",53))\\n term.setCursorPos(screenx-15,screeny+20)\\n term.write(string.rep(\\\"L\\\",53))\\n \\n for i=1,19 do\\n term.setCursorPos(screenx-15,screeny+i)\\n term.write(\\\"L\\\")\\n term.setCursorPos(screenx+37,screeny+i)\\n term.write(\\\"L\\\")\\n end\\nend\\n\\n-- Write a given page of the manual.\\nlocal function writeHelp(page)\\n local manual\\n \\n if page == 1 then\\n manual =\\n {\\\"BBTetris (\\\"..version..\\\")\\\",\\n string.rep(\\\"-\\\",11+#version),\\n \\\"\\\",\\n \\\"By Jeffrey Alexander, aka Bomb Bloke.\\\",\\n \\\"\\\",\\n \\\"Yet another Tetris clone, this one for\\\",\\n \\\"ComputerCraft.\\\",\\n \\\"\\\",\\n \\\"Playable via mouse when used with a touch-\\\",\\n \\\"capable display. If using an external\\\",\\n \\\"monitor, at least 3x2 blocks is required,\\\",\\n \\\"while 5x3 is recommended.\\\"}\\n elseif page == 2 then\\n manual =\\n {\\\"Basic Controls\\\",\\n \\\"--------------\\\",\\n \\\"\\\",\\n \\\"Use your arrow keys (or WSAD) for most\\\",\\n \\\"actions.\\\",\\n \\\"\\\",\\n \\\"On the main menu, left alters your level,\\\",\\n \\\"whilst right alters your speed. Use up/down\\\",\\n \\\"to change your game mode and space to start.\\\",\\n \\\"\\\",\\n \\\"Mouse-users may instead click the speed /\\\",\\n \\\"level / game mode toggles to change them,\\\",\\n \\\"then click the play area to begin.\\\"}\\n elseif page == 3 then\\n manual =\\n {\\\"Playing\\\",\\n \\\"-------\\\",\\n \\\"\\\",\\n \\\"Blocks fall according to the current speed,\\\",\\n \\\"which increases every 10k points. You win\\\",\\n \\\"if you somehow manage to reach 100k.\\\",\\n \\\"\\\",\\n \\\"Use the down arrow to drop the block\\\",\\n \\\"quickly, or the up arrow to rotate it.\\\",\\n \\\"\\\",\\n \\\"Space / enter can be used to rotate in the\\\",\\n \\\"opposite direction.\\\",\\n \\\"\\\",\\n \\\"100 points for a line, 300 for two, 700 for\\\",\\n \\\"three and 1500 for a Tetris (four lines)!\\\"}\\n elseif page == 4 then\\n manual =\\n {\\\"Playing (Mouse)\\\",\\n \\\"---------------\\\",\\n \\\"\\\",\\n \\\"Clicking the left side of the grid moves the\\\",\\n \\\"block to the left, and clicking the right\\\",\\n \\\"functions in a similar manner.\\\",\\n \\\"\\\",\\n \\\"Click the bottom of the grid to move the\\\",\\n \\\"block down quickly, or on the block itself\\\",\\n \\\"to rotate. You may click outside the main\\\",\\n \\\"play area to rotate the other way.\\\",\\n \\\"\\\",\\n \\\"The scroll-wheel can also be used to rotate\\\",\\n \\\"or move downwards quickly.\\\"}\\n elseif page == 5 then\\n manual =\\n {\\\"Alternate Modes\\\",\\n \\\"---------------\\\",\\n \\\"\\\",\\n \\\"There are three optional game modes\\\",\\n \\\"available, which can be combined together to\\\",\\n \\\"make your game easier/harder as is your\\\",\\n \\\"whim.\\\",\\n \\\"\\\",\\n \\\"Advanced mode increases the block pool to\\\",\\n \\\"nearly double, making for a more complex\\\",\\n \\\"game. Tricks come in the form of one of\\\",\\n \\\"three rare blocks, each of which has a\\\",\\n \\\"unique power. Quota mode tasks you to clear\\\",\\n \\\"pre-filled grids in order to advance levels.\\\"}\\n end\\n \\n for i=1,17 do\\n if manual[i] then\\n writeAt(manual[i]..string.rep(\\\" \\\",44-#manual[i]),-13,i+1)\\n else\\n writeAt(string.rep(\\\" \\\",44),-13,i+1)\\n end\\n end\\nend\\n\\n-- Explain stuff.\\nlocal function help()\\n local page = 1\\n prepareHelp()\\n writeHelp(page)\\n \\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if clickedAt(35,10) or pressedKey(keys.h,keys.x,keys.q) then\\n return\\n elseif myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n prepareHelp()\\n writeHelp(page)\\n end\\n elseif clickedAt(35,1,4) or pressedKey(keys.w,keys.up) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == -1) then\\n page = (page==1) and 5 or (page-1)\\n writeHelp(page)\\n elseif clickedAt(35,16,19) or pressedKey(keys.s,keys.down) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == 1) then\\n page = (page==5) and 1 or (page+1)\\n writeHelp(page)\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame()\\n elseif myEvent[1] == \\\"musicFinished\\\" then\\n os.queueEvent(\\\"musicPlay\\\",musicFile)\\n end \\n end \\nend\\n\\n---------------------------------------------\\n------------ Main Menu ------------\\n---------------------------------------------\\n\\nlocal function menu()\\n running = true\\n drawNumeral()\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n writeAt(tostring(speed),18,18)\\n \\n while running do\\n myEvent = {os.pullEvent()}\\n \\n if pressedKey(keys.left,keys.a) or clickedAt(13,19,13,16) then\\n level = (level == 12) and 0 or (level+1)\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n elseif pressedKey(keys.right,keys.d) or clickedAt(13,19,16,19) then\\n speed = (speed == 9) and 0 or (speed+1)\\n writeAt(tostring(speed),18,18)\\n elseif pressedKey(keys.space,keys.enter) or clickedAt(1,12,0,20) then\\n running = false\\n elseif (touchedMe() and myEvent[3] < screenx and myEvent[4] == screeny + 2) or pressedKey(keys.h) then\\n help()\\n drawBorder()\\n drawNumeral()\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n writeAt(tostring(speed),18,18)\\n elseif pressedKey(keys.up,keys.w) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == -1) then\\n gamemode = (gamemode == 1) and 8 or (gamemode-1)\\n drawNumeral()\\n elseif pressedKey(keys.down,keys.s) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == 1) then\\n gamemode = (gamemode == 8) and 1 or (gamemode+1)\\n drawNumeral()\\n elseif pressedKey(keys.x,keys.q) then\\n os.pullEvent(\\\"char\\\")\\n exitGame()\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame()\\n elseif touchedMe() and myEvent[3] > screenx+21 then\\n term.setTextColor(term.isColor() and colours.red or colours.white)\\n term.setCursorPos(myEvent[3],myEvent[4])\\n for i=0,2 do if myEvent[4] == screeny + 14 + i * 2 then\\n gamemode = gamemode + (bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and (-bit.blshift(1,i)) or bit.blshift(1,i))\\n drawNumeral()\\n break\\n end end\\n if myEvent[4] == screeny + 2 then exitGame() end\\n elseif myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n drawBorder()\\n drawNumeral()\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n writeAt(tostring(speed),18,18)\\n end\\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n end\\n end\\nend\\n\\n---------------------------------------------\\n------------Primary Game Function------------\\n---------------------------------------------\\n\\nlocal function game()\\n local falling, knocked, score, curBlock, nextSpeed, startSpeed, startLevel, loaded, rotation = true, false, 0, 1, 9999, speed, level, false, 0\\n local nextBlock = math.random(bit.band(gamemode,1)==1 and 7 or 13)\\n local x, y, blockTimer, lines, held, flicker, flickerTimer\\n \\n os.queueEvent(\\\"musicPlay\\\",musicFile)\\n os.queueEvent(playMusic and \\\"musicResume\\\" or \\\"musicPause\\\")\\n \\n for i=1,19 do grid[i] = {} end\\n \\n -- Resume an old game?\\n if saves[gamemode] then\\n writeAt(\\\" Load old \\\",2,9)\\n writeAt(\\\" game \\\",2,10)\\n writeAt(\\\"[Y]es [N]o\\\",2,11)\\n \\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if pressedKey(keys.y) or clickedAt(3,11) then\\n running = true\\n break\\n elseif pressedKey(keys.n) or clickedAt(9,11) then\\n running = false\\n break\\n elseif myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n redrawGame(score)\\n \\n writeAt(\\\" Load old \\\",2,9)\\n writeAt(\\\" game \\\",2,10)\\n writeAt(\\\"[Y]es [N]o\\\",2,11)\\n end\\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame() \\n end\\n end\\n \\n if running then\\n x = saves[gamemode][\\\"x\\\"]\\n y = saves[gamemode][\\\"y\\\"]\\n rotation = saves[gamemode][\\\"rotation\\\"]\\n curBlock = saves[gamemode][\\\"curBlock\\\"]\\n nextBlock = saves[gamemode][\\\"nextBlock\\\"]\\n score = saves[gamemode][\\\"score\\\"]\\n speed = saves[gamemode][\\\"speed\\\"]\\n nextSpeed = saves[gamemode][\\\"nextSpeed\\\"]\\n startSpeed = saves[gamemode][\\\"startSpeed\\\"]\\n level = saves[gamemode][\\\"level\\\"]\\n startLevel = saves[gamemode][\\\"startLevel\\\"]\\n grid = saves[gamemode][\\\"grid\\\"]\\n saves[gamemode] = nil\\n loaded = true\\n end\\n end \\n\\n running = true\\n\\n -- Clear off the menu.\\n term.setBackgroundColor(colours.black)\\n for i=1,7 do\\n term.setCursorPos(screenx+2,screeny+i+6)\\n term.write(string.rep(\\\" \\\",10))\\n end \\n \\n if loaded then\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n for yy=1,19 do\\n term.setCursorPos(screenx+2,screeny+yy)\\n for xx=1,10 do if grid[20-yy][xx] ~= nil then\\n term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[20-yy][xx]) or colours.white)\\n term.write(\\\"L\\\")\\n else\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n end end\\n end \\n else fillGrid() end\\n\\n writeAt(\\\"[P]ause\\\",22,4)\\n if note then writeAt(\\\"[M]usic\\\",22,6) end\\n writeAt(\\\"Next\\\",14,1)\\n writeAt(\\\"Score\\\",14,8)\\n writeAt(string.rep(\\\" \\\",6-#tostring(score))..tostring(score),13,9)\\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n writeAt(tostring(speed),18,18)\\n \\n -- Primary game loop. \\n while running do\\n if rotation < 4 then -- The last type of block \\\"may\\\" persist after line checks.\\n if loaded then\\n loaded = false\\n else\\n curBlock = nextBlock\\n \\n -- Change the \\\"next block\\\" display.\\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), false, false)\\n nextBlock = math.random(bit.band(gamemode,1)==1 and 7 or 13)\\n \\n -- Trick block will be next!\\n if bit.band(gamemode-1,2)==2 and math.random(20) == 20 then nextBlock = 13 + math.random(3) end\\n \\n -- Prepare a new falling block.\\n x, y, rotation = (#block[curBlock][1] < 4 and 5 or 4), 1, 0\\n end\\n \\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)\\n drawBlock(curBlock,rotation,x,y,true,false)\\n \\n -- Can play continue?\\n if checkBlock(curBlock,rotation,x,y) then\\n falling, knocked, flicker, blockTimer, flickerTimer = true, false, false, os.startTimer((10-speed)/10), os.startTimer(0.3)\\n else running = false end\\n else\\n falling, rotation, flickerTimer = true, 0, os.startTimer(0)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n end\\n \\n -- Stuff that happens while a block falls.\\n while falling do\\n myEvent = {os.pullEvent()}\\n \\n -- Is the user still holding down \\\"down\\\" after the last block fell?\\n if held then\\n if not ((myEvent[1] == \\\"timer\\\" and myEvent[2] ~= blockTimer)\\n or pressedKey(keys.down,keys.s) or myEvent[1] == \\\"char\\\")\\n then held = false end\\n end\\n \\n -- Trick blocks flash.\\n if myEvent[1] == \\\"timer\\\" and myEvent[2] == flickerTimer and curBlock > 13 then\\n flicker = not flicker\\n flickerTimer = os.startTimer(0.3)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n\\n elseif curBlock > 14 and (clickedAt(x,x+1+(bit.band(rotation,1)==1 and #block[curBlock] or #block[curBlock][1]),y-1,y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])) or pressedKey(keys.up,keys.w,keys.space,keys.enter)) then\\n \\n -- Erasure!\\n if curBlock == 15 then\\n for i=18-y,1,-1 do if grid[i][x] ~= nil then\\n term.setCursorPos(screenx+1+x,screeny+20-i)\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n grid[i][x] = nil\\n break\\n end end\\n \\n if bit.band(gamemode-1,4)==4 then\\n -- Possible to fulfil the quota here without creating a line.\\n lines = 0\\n for yy=1,19 do for xx=1,10 do if grid[xx][yy] == 14 then\\n yy=19\\n xx=10\\n lines = 1\\n end end end\\n \\n if lines ~= 1 then\\n score = score + 10000\\n level = level + 1\\n rotation = 0\\n falling = false\\n gameover()\\n fillGrid()\\n \\n writeAt(string.rep(\\\" \\\",6-#tostring(score))..tostring(score),13,9)\\n \\n if score > 99999 then running = false end\\n \\n -- Check for a score record. \\n if score > highScore[gamemode] then\\n highScore[gamemode] = score\\n writeAt(string.rep(\\\" \\\",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)\\n end\\n \\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n \\n -- Increment speed.\\n nextSpeed = nextSpeed + 10000\\n speed = (speed==9) and 9 or (speed+1)\\n writeAt(tostring(speed),18,18)\\n end\\n end\\n \\n -- Fill!\\n elseif curBlock == 16 and grid[17-y][x] == nil then\\n rotation = 1\\n for i=1,16-y do if grid[i][x] ~= nil then rotation = i+1 end end\\n term.setCursorPos(screenx+1+x,screeny+20-rotation)\\n term.setBackgroundColor(colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n term.write(\\\"L\\\")\\n grid[rotation][x] = 32\\n falling, rotation = false, 20 + rotation -- Rotation used here as a flag to indicate we're still actually falling.\\n end\\n \\n -- Block rotation.\\n elseif (clickedAt(x,x+1+(bit.band(rotation,1)==1 and #block[curBlock] or #block[curBlock][1]),y-1,y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])) or pressedKey(keys.up,keys.w) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == -1)) and checkBlock(curBlock,rotation==3 and 0 or (rotation+1),x,y) then\\n drawBlock(curBlock,rotation,x,y,false,flicker)\\n rotation = (rotation==3) and 0 or (rotation+1)\\n drawBlock(curBlock,rotation,x,y,true,flicker) \\n \\n elseif (myEvent[1] == \\\"timer\\\" and myEvent[2] == blockTimer) or clickedAt(1,12,17,20) or (pressedKey(keys.down,keys.s) and not held) or (myEvent[1] == \\\"mouse_scroll\\\" and myEvent[2] == 1) then\\n \\n -- Move the block down if we can.\\n if checkBlock(curBlock,rotation,x,y+1) then\\n drawBlock(curBlock,rotation,x,y,false,flicker)\\n y = y + 1\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n knocked = false\\n \\n -- Brick's stopped moving down, add it to the grid.\\n elseif knocked then\\n if curBlock < 15 then\\n for yy=1,#block[curBlock] do for xx=1,#block[curBlock][1] do if block[curBlock][yy][xx] == 1 then\\n if rotation == 0 then\\n grid[21-y-yy][x+xx-1] = (curBlock == 14 and 15 or curBlock)\\n elseif rotation == 1 then\\n grid[21-y-xx][x+#block[curBlock]-yy] = (curBlock == 14 and 15 or curBlock)\\n elseif rotation == 2 then\\n grid[20-y-#block[curBlock]+yy][x+#block[curBlock][1]-xx] = (curBlock == 14 and 15 or curBlock)\\n elseif rotation == 3 then\\n grid[20-y-#block[curBlock][1]+xx][x+yy-1] = (curBlock == 14 and 15 or curBlock)\\n end\\n end end end\\n end\\n \\n if curBlock > 13 then drawBlock(curBlock,rotation,x,y,curBlock==14,false) end\\n falling = false\\n held = true -- This is used to stop the NEXT block being pelted downwards if you were holding a \\\"down\\\" button.\\n \\n -- Brick has \\\"knocked\\\" other bricks, but hasn't yet locked in place.\\n else knocked = true end\\n \\n if falling then blockTimer = os.startTimer((10-speed)/10) end\\n \\n -- Pause the game.\\n elseif (myEvent[1] == \\\"monitor_resize\\\" and mon) or pressedKey(keys.p) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+4) then\\n -- Something altered my monitor!\\n if myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n redrawGame(score)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)\\n end\\n end \\n \\n writeAt(\\\" PAUSED \\\",2,10)\\n blockTimer = os.startTimer(1)\\n \\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if myEvent[1] == \\\"key\\\" or touchedMe() then\\n break\\n elseif myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n redrawGame(score)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)\\n \\n falling = not falling\\n writeAt(falling and \\\" PAUSED \\\" or string.rep(\\\" \\\",10),2,10)\\n blockTimer = os.startTimer(1)\\n end\\n elseif myEvent[1] == \\\"timer\\\" and myEvent[2] == blockTimer then\\n falling = not falling\\n writeAt(falling and \\\" PAUSED \\\" or string.rep(\\\" \\\",10),2,10)\\n blockTimer = os.startTimer(1)\\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame()\\n elseif myEvent[1] == \\\"musicFinished\\\" then\\n os.queueEvent(\\\"musicPlay\\\",musicFile)\\n end\\n end\\n \\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n term.setCursorPos(screenx+2,screeny+10)\\n for xx=1,10 do if grid[10][xx] ~= nil then\\n term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[10][xx]) or colours.white)\\n term.write(\\\"L\\\")\\n else\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n end end\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n blockTimer, flickerTimer, falling = os.startTimer((10-speed)/10), os.startTimer(0.3), true\\n \\n -- Toggle music playback.\\n elseif pressedKey(keys.m) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+6) then\\n playMusic = not playMusic\\n os.queueEvent(playMusic and \\\"musicResume\\\" or \\\"musicPause\\\")\\n \\n -- Repeat music.\\n elseif myEvent[1] == \\\"musicFinished\\\" then\\n os.queueEvent(\\\"musicPlay\\\",musicFile)\\n \\n -- Display the help screen.\\n elseif (touchedMe() and myEvent[3] < screenx and myEvent[4] == screeny + 2) or pressedKey(keys.h) then\\n help()\\n redrawGame(score)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)\\n blockTimer, flickerTimer = os.startTimer((10-speed)/10), os.startTimer(0.3)\\n \\n -- User is attempting to end game.\\n elseif pressedKey(keys.x,keys.q) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+2) then\\n if canSave then\\n writeAt(\\\" Save the \\\",2,9)\\n writeAt(\\\" game \\\",2,10)\\n writeAt(\\\"[Y]es [N]o\\\",2,11)\\n\\n while true do\\n myEvent = {os.pullEvent()}\\n \\n if pressedKey(keys.y) or clickedAt(3,11) then\\n running = true\\n break\\n elseif pressedKey(keys.n) or clickedAt(9,11) then\\n running = false\\n break\\n elseif myEvent[1] == \\\"monitor_resize\\\" and mon then\\n if myEvent[2] == mon.side then\\n enforceScreenSize()\\n redrawGame(score)\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)\\n \\n writeAt(\\\" Save the \\\",2,9)\\n writeAt(\\\" game \\\",2,10)\\n writeAt(\\\"[Y]es [N]o\\\",2,11)\\n end\\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame()\\n end\\n end\\n else running = false end\\n\\n -- User wants to save current progress.\\n if running then\\n saves[gamemode] = {}\\n saves[gamemode][\\\"x\\\"] = x\\n saves[gamemode][\\\"y\\\"] = y\\n saves[gamemode][\\\"rotation\\\"] = rotation\\n saves[gamemode][\\\"curBlock\\\"] = curBlock\\n saves[gamemode][\\\"nextBlock\\\"] = nextBlock\\n saves[gamemode][\\\"score\\\"] = score\\n saves[gamemode][\\\"speed\\\"] = speed\\n saves[gamemode][\\\"nextSpeed\\\"] = nextSpeed\\n saves[gamemode][\\\"startSpeed\\\"] = startSpeed\\n saves[gamemode][\\\"level\\\"] = level\\n saves[gamemode][\\\"startLevel\\\"] = startLevel\\n saves[gamemode][\\\"grid\\\"] = {}\\n for yy=1,19 do\\n saves[gamemode][\\\"grid\\\"][yy] = {}\\n for a,b in pairs(grid[yy]) do saves[gamemode][\\\"grid\\\"][yy][a] = b end\\n end\\n end\\n \\n falling, running, rotation = false, false, 10 -- Rotation used here as a flag to bypass the \\\"game over\\\" animation.\\n \\n -- User is trying to out-right quit the game.\\n elseif myEvent[1] == \\\"terminate\\\" then\\n exitGame()\\n \\n -- Move block left.\\n elseif (clickedAt(1,7,0,18) or pressedKey(keys.left,keys.a)) and checkBlock(curBlock,rotation,x-1,y) then\\n drawBlock(curBlock,rotation,x,y,false,flicker)\\n x = x - 1\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n \\n -- Move block right.\\n elseif (clickedAt(6,12,0,18) or pressedKey(keys.right,keys.d)) and checkBlock(curBlock,rotation,x+1,y) then\\n drawBlock(curBlock,rotation,x,y,false,flicker)\\n x = x + 1\\n drawBlock(curBlock,rotation,x,y,true,flicker)\\n \\n -- Rotating the other way.\\n elseif ((touchedMe() and (myEvent[3] < screenx+1 or myEvent[3] > screenx+12)) or pressedKey(keys.space,keys.enter)) and checkBlock(curBlock,rotation==0 and 3 or (rotation-1),x,y) and curBlock < 14 then\\n drawBlock(curBlock,rotation,x,y,false,flicker)\\n rotation = (rotation==0) and 3 or (rotation-1)\\n drawBlock(curBlock,rotation,x,y,true,flicker) \\n \\n elseif myEvent[1] == \\\"peripheral_detach\\\" and mon then\\n if myEvent[2] == mon.side then exitGame(\\\"I've lost my monitor - your turtle didn't mine it, I hope?\\\") end\\n end \\n end\\n \\n -- Look for complete lines.\\n if running then\\n lines = {}\\n for yy=((rotation>3) and (40-rotation) or y),((rotation>3) and (40-rotation) or y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])-1) do\\n falling = true\\n \\n for xx=1,10 do if yy < 20 then\\n if grid[20-yy][xx] == nil then\\n falling = false\\n break\\n end\\n else falling = false end end\\n \\n if falling then\\n lines[#lines+1] = yy\\n table.remove(grid,20-yy)\\n grid[19] = {}\\n end\\n end\\n \\n if #lines > 0 then\\n \\n -- Blink the complete lines a few times.\\n falling = true\\n term.setBackgroundColor(term.isColor() and colours.black or colours.white)\\n term.setTextColor(term.isColor() and colours.lightGrey or colours.black)\\n for i=1,6 do\\n if not term.isColor() then term.setBackgroundColor(falling and colours.white or colours.black) end\\n for j=1,#lines do\\n term.setCursorPos(screenx+2,screeny+lines[j])\\n term.write(string.rep(falling and \\\"L\\\" or \\\" \\\",10))\\n end\\n falling = not falling\\n \\n blockTimer = os.startTimer(0.1)\\n while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent(\\\"timer\\\")} end\\n end\\n \\n -- Collapse the on-screen grid.\\n for yy=1,lines[#lines] do\\n term.setCursorPos(screenx+2,screeny+yy)\\n for xx=1,10 do if grid[20-yy][xx] ~= nil then\\n term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[20-yy][xx]) or colours.white)\\n term.write(\\\"L\\\")\\n else\\n term.setBackgroundColor(colours.black)\\n term.write(\\\" \\\")\\n end end\\n end\\n \\n if bit.band(gamemode-1,4)==4 then\\n -- Quota reached?\\n for yy=1,19 do for xx=1,10 do if grid[xx][yy] == 14 then\\n yy=19\\n xx=10\\n lines = 1\\n end end end\\n \\n if lines ~= 1 then\\n score = score + 10000\\n level = level + 1\\n rotation = 0\\n falling = false\\n gameover()\\n fillGrid()\\n end\\n else\\n -- Increment score in the usual way.\\n if #lines == 1 then\\n score = score + 100\\n elseif #lines == 2 then\\n score = score + 300\\n elseif #lines == 3 then\\n score = score + 700\\n elseif #lines == 4 then\\n score = score + 1500\\n end\\n end\\n \\n writeAt(string.rep(\\\" \\\",6-#tostring(score))..tostring(score),13,9)\\n \\n if score > 99999 then running = false end\\n \\n -- Check for a score record. \\n if score > highScore[gamemode] then\\n highScore[gamemode] = score\\n writeAt(string.rep(\\\" \\\",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)\\n end\\n \\n writeAt((level>9 and \\\"\\\" or \\\" \\\")..tostring(level),17,15)\\n \\n -- Increment speed?\\n if score > nextSpeed then\\n nextSpeed = nextSpeed + 10000\\n speed = (speed==9) and 9 or (speed+1)\\n writeAt(tostring(speed),18,18)\\n end\\n \\n blockTimer = os.startTimer((10-speed)/10)\\n end\\n end\\n end\\n\\n -- Game over.\\n if rotation < 4 then\\n writeAt(score < 100000 and \\\"GAME OVER\\\" or \\\"!!WINNER!!\\\",2,10)\\n \\n -- Assign bonus points.\\n score = score + (100*startSpeed) + startLevel\\n writeAt(string.rep(\\\" \\\",6-#tostring(score))..tostring(score),13,9)\\n \\n -- Check for a score record. \\n if score > highScore[gamemode] then\\n highScore[gamemode] = score\\n writeAt(string.rep(\\\" \\\",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)\\n end\\n\\n gameover()\\n end\\n\\n speed = startSpeed\\n level = startLevel\\nend\\n\\n---------------------------------------------\\n------------ Init ------------\\n---------------------------------------------\\n\\n-- Override the event-puller.\\nOGeventPuller = os.pullEvent\\nos.pullEvent = os.pullEventRaw\\n\\n-- Load the INI file.\\nif fs.exists(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.ini\\\") then\\n local readIn, readTable \\n myEvent = fs.open(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.ini\\\", \\\"r\\\")\\n readIn = myEvent.readLine() \\n \\n while readIn do\\n readTable = {}\\n readIn = readIn:gsub(\\\"=\\\",\\\" \\\"):lower()\\n for i in readIn:gmatch(\\\"%S+\\\") do readTable[#readTable+1] = i end \\n \\n if readTable[1] == \\\"nointro\\\" and readTable[2] then\\n if readTable[2] == \\\"yes\\\" or readTable[2] == \\\"y\\\" or readTable[2] == \\\"true\\\" or readTable[2] == \\\"on\\\" or readTable[2] == \\\"1\\\" then skipIntro = true end\\n elseif readTable[1] == \\\"nomonitor\\\" and readTable[2] then\\n if readTable[2] == \\\"yes\\\" or readTable[2] == \\\"y\\\" or readTable[2] == \\\"true\\\" or readTable[2] == \\\"on\\\" or readTable[2] == \\\"1\\\" then skipMonitor = true end\\n elseif readTable[1] == \\\"defaultmonitorside\\\" and readTable[2] then\\n defaultMonitor = readTable[2]\\n elseif readTable[1] == \\\"defaultspeed\\\" and tonumber(readTable[2]) then\\n if tonumber(readTable[2]) > -1 and tonumber(readTable[2]) < 10 then speed = tonumber(readTable[2]) end\\n elseif readTable[1] == \\\"defaultlevel\\\" and tonumber(readTable[2]) then\\n if tonumber(readTable[2]) > -1 and tonumber(readTable[2]) < 13 then level = tonumber(readTable[2]) end\\n elseif readTable[1] == \\\"defaultmode\\\" and tonumber(readTable[2]) then\\n if tonumber(readTable[2]) > 0 and tonumber(readTable[2]) < 9 then gamemode = tonumber(readTable[2]) end\\n elseif readTable[1] == \\\"nomusic\\\" and readTable[2] then\\n if readTable[2] == \\\"yes\\\" or readTable[2] == \\\"y\\\" or readTable[2] == \\\"true\\\" or readTable[2] == \\\"on\\\" or readTable[2] == \\\"1\\\" then playMusic = false end \\n end\\n \\n readIn = myEvent.readLine()\\n end\\n \\n myEvent.close()\\nelse\\n myEvent = fs.open(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.ini\\\", \\\"w\\\")\\n \\n if myEvent then\\n myEvent.writeLine(\\\"NoIntro = \\\")\\n myEvent.writeLine(\\\"NoMonitor = \\\")\\n myEvent.writeLine(\\\"NoMusic = \\\")\\n myEvent.writeLine(\\\"DefaultMonitorSide = \\\")\\n myEvent.writeLine(\\\"DefaultSpeed = \\\")\\n myEvent.writeLine(\\\"DefaultLevel = \\\")\\n myEvent.writeLine(\\\"DefaultMode = \\\")\\n myEvent.close()\\n else canSave = false end\\nend\\n\\n-- Load saved data.\\nif fs.exists(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.dat\\\") then\\n myEvent = fs.open(shell.resolve(\\\".\\\")..\\\"\\\\\\\\bbtetris.dat\\\", \\\"r\\\")\\n highScore = myEvent.readLine()\\n highScore = textutils.unserialize(myEvent.readLine())\\n saves = textutils.unserialize(myEvent.readLine())\\n myEvent.close()\\nend\\n\\nif type(highScore) == \\\"table\\\" then\\n for i=#highScore+1,8 do highScore[i] = 0 end\\nelse\\n highScore = {}\\n for i=1,8 do highScore[i] = 0 end\\nend\\n\\n-- Look for a monitor to use.\\nif not skipMonitor then\\n if defaultMonitor then\\n if peripheral.getType(defaultMonitor) == \\\"monitor\\\" and peripheral.call(defaultMonitor, \\\"isColour\\\") then\\n term.clear()\\n term.setCursorPos(1,1)\\n print(\\\"Game in progress on attached display...\\\")\\n \\n mon = peripheral.wrap(defaultMonitor)\\n mon.side = defaultMonitor\\n if not term.restore then mon.restoreTo = term.current() end\\n term.redirect(mon)\\n enforceScreenSize()\\n else \\n exitGame(\\\"The \\\\\\\"monitor\\\\\\\" at location \\\\\\\"\\\"..defaultMonitor..\\\"\\\\\\\" (specified in \\\\\\\"bbtetris.ini\\\\\\\") is invalid. Please fix that.\\\")\\n end\\n else\\n local sides = peripheral.getNames()\\n\\n for i=1,#sides do if peripheral.getType(sides[i]) == \\\"monitor\\\" and peripheral.call(sides[i], \\\"isColour\\\") then\\n print(\\\"\\\")\\n print(\\\"I see a colour monitor attached - do you wish to use it (y/n)?\\\")\\n \\n while true do\\n myEvent = {os.pullEvent(\\\"char\\\")}\\n \\n if myEvent[2]:lower() == \\\"y\\\" then\\n term.clear()\\n term.setCursorPos(1,1)\\n print(\\\"Game in progress on attached display...\\\")\\n \\n mon = peripheral.wrap(sides[i])\\n mon.side = sides[i]\\n if not term.restore then mon.restoreTo = term.current() end\\n term.redirect(mon)\\n enforceScreenSize()\\n break\\n elseif myEvent[2]:lower() == \\\"n\\\" then\\n break\\n end\\n end\\n\\n break\\n end end\\n end\\nend\\n\\n-- Ensure the display is suitable.\\nscreenx,screeny = term.getSize()\\n\\nif screenx < 50 or screeny < 19 then error(\\\"\\\\nA minimum display resolution of 50 columns by 19 rows is required.\\\\n\\\\nIf you're trying to run me on eg a turtle, please try a regular computer.\\\\n\\\",0) end\\n\\nscreenx,screeny = math.floor(screenx/2)-10, math.floor(screeny/2)-9\\n\\n-- Load the music player, if possible.\\nif peripheral.find and peripheral.find(\\\"iron_note\\\") and fs.exists(\\\"rom/programs/note\\\") and fs.exists(musicFile) then os.loadAPI(\\\"rom/programs/note\\\") end\\n\\n-- Show the splash screen.\\nif not skipIntro then intro() end\\n\\n---------------------------------------------\\n------------ Main Program Loop ------------\\n---------------------------------------------\\n\\nwhile true do\\n drawBorder()\\n menu()\\n if note then parallel.waitForAny(game, note.songEngine) else game() end\\nend\",\
7523[\"credit.txt\"]=\"All credit for this program goes to Bomb Bloke\",\
7524}\
7525,\
7526Desktop={\
7527[\"appconfig.txt\"]=\"services = {\\n \\\"launcher\\\";\\n}\",\
7528[\"main.lua\"]=\"\\nlocal backgrounds = {\\n computer = [[00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 00 B0 00 00 B0 B0 B0 00 00 B0 00 00 00 B0 00 00 00 B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 B0 B0 00 00 B0 00 B0 00 00 00 B0 00 B0 00 00 00 B0 00 00 B0 00 B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 B0 00 B0 00 B0 00 00 00 B0 00 B0 00 00 00 B0 00 B0 00 00 00 B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 B0 B0 00 B0 00 00 00 B0 00 00 B0 00 B0 00 00 B0 B0 B0 B0 B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 00 B0 00 00 B0 B0 B0 00 00 00 00 B0 00 00 00 B0 00 00 00 B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ]];\\n turtle = [[]];\\n pocket = [[]];\\n}\\n\\nlocal folder = [[10 10 10 \\n40 40 40 40 40 \\n40 40 40 40 4F>]]\\nlocal appicon = [[ BF BF BF BF BF \\n 3F 3Ea3Ep3Ep3F \\n 9F 9F 9F 9F 9F ]]\\n\\nlocal UI = Nova.app.UI\\nlocal icons = Nova.app.getData \\\"icons\\\" or { }\\n\\nlocal changed = false\\n\\nfunction Nova.app.canClose( )\\n Nova.app.setData( \\\"icons\\\", icons )\\nend\\n\\nlocal function showIconMenu( x, y, data, icon, bframe )\\n local close = UI:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h ) )\\n close.bc = 0\\n close.align = false\\n local frame = UI:newChild( NovaUI.UIFrame( x, y, 0, 0 ) )\\n local menu = {\\n width = 9, height = 4, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"open\\\", onClick = function( )\\n Nova.app.newThread( function( )\\n if data.mode == \\\"app\\\" then\\n Nova.app.launch( data.name )\\n else\\n Nova.app.launch( \\\"Files\\\", data.path )\\n end\\n end )\\n close:remove( )\\n frame:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"delete\\\", onClick = function( )\\n bframe:remove( )\\n for i = 1, #icons do\\n if icons[i] == icon then\\n table.remove( icons, i )\\n break\\n end\\n end\\n close:remove( )\\n frame:remove( )\\n end };\\n }\\n NovaUI.display.menu( frame, menu )\\n function close:onClick( )\\n frame:remove( )\\n close:remove( )\\n end\\nend\\n\\nlocal function newIcon( x, y, data, icon )\\n changed = true\\n local frame = UI:newChild( NovaUI.UIFrame( x, y, data.mode == \\\"app\\\" and 9 or 5, 3 ) )\\n local image = NovaUI.Image( frame.w, frame.h ):loadstr( data.icon )\\n frame:newChild( NovaUI.UIImage( 1, 1, frame.w, frame.h, image ) )\\n local button = frame:newChild( NovaUI.UIButton( 1, 1, frame.w, frame.h, \\\"\\\" ) )\\n button.bc = 0\\n button.tc = 0\\n function button:onClick( x, y, button, double )\\n if button == 1 and double then\\n Nova.app.newThread( function( )\\n if data.mode == \\\"app\\\" then\\n Nova.app.launch( data.name )\\n else\\n Nova.app.launch( \\\"Files\\\", data.path )\\n end\\n end )\\n elseif button == 2 then\\n showIconMenu( x + frame.x - 1, y + frame.y - 1, data, icon, frame )\\n end\\n end\\n local label\\n function button:onDrag( x, y, cx, cy, button )\\n if button == 1 then\\n frame.x = frame.x + cx\\n frame.y = frame.y + cy\\n label.x = label.x + cx\\n label.y = label.y + cy\\n icon.x = frame.x\\n icon.y = frame.y\\n changed = true\\n end\\n end\\n label = UI:newChild( NovaUI.UIText( 0, 0, #data.display, 1, function( self )\\n self.x = frame.x + math.ceil( frame.w / 2 - self.w / 2 )\\n self.y = frame.y + frame.h\\n if not frame.parent then\\n self:remove( )\\n end\\n return data.display\\n end ) )\\n label.bc = 0\\nend\\n\\nlocal function showBlankMenu( x, y )\\n local close = UI:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h ) )\\n close.bc = 0\\n close.align = false\\n local frame = UI:newChild( NovaUI.UIFrame( x, y, 0, 0 ) )\\n local r = Nova.app.getData \\\"startup\\\"\\n local menu = {\\n width = 23, height = 7, spacing = false, shadow = colours.grey;\\n { type = \\\"button\\\", name = \\\"change background\\\", onClick = function( )\\n frame:remove( )\\n close:remove( )\\n NovaUI.display.alert( UI, \\\"Not yet implemented, sorry\\\" )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = ( r or r == nil ) and \\\"don't run on startup\\\" or \\\"run on startup\\\", onClick = function( )\\n Nova.app.setData( \\\"startup\\\", not Nova.app.getData \\\"startup\\\" )\\n frame:remove( )\\n close:remove( )\\n end };\\n \\\"rule\\\";\\n { type = \\\"button\\\", name = \\\"new shortcut\\\", onClick = function( )\\n frame:remove( )\\n close:remove( )\\n Nova.app.newThread( function( )\\n local path = NovaUI.display.response( UI, \\\"Path for shortcut\\\" )\\n if path then\\n local t = { x = x, y = y, data = {\\n mode = \\\"file\\\";\\n path = path;\\n display = Nova.filesystem.getName( path, true );\\n icon = folder;\\n } }\\n table.insert( icons, t )\\n newIcon( x, y, t.data, t )\\n end\\n end )\\n end };\\n { type = \\\"button\\\", name = \\\"new app\\\", onClick = function( )\\n frame:remove( )\\n close:remove( )\\n Nova.app.newThread( function( )\\n local _, _, document = NovaUI.markup.load( [[\\n <script>\\n function close( )\\n document.getElementById( \\\"main\\\" ):remove( )\\n end\\n function toggleDefault( )\\n local d = document.getElementById \\\"default\\\"\\n d.text = d.text == \\\"@\\\" and \\\" \\\" or \\\"@\\\"\\n end\\n </script>\\n <frame x:1 y:1 id:main width:expand height:expand>\\n <button width:expand height:expand tc:0 bc:0 onClick:\\\"close()\\\"></button>\\n <frame width:25 height:12 x:centre y:centre>\\n <text x:2 y:2 width:24 height:11 bc:grey></text>\\n <text x:1 y:1 width:24 height:11 bc:white></text>\\n <text x:centre y:2 width:auto>Select app</text>\\n <text x:2 y:3 width:auto tc:lightGrey>----------------------</text>\\n <text id:applist x:2 y:4 width:22 height:5>This is where the app list will be!</text>\\n <text x:2 y:9 width:auto tc:lightGrey>----------------------</text>\\n <button id:ok x:20 y:10 width:4 bc:lightGrey tc:black>ok</button>\\n </frame>\\n </frame>\\n ]], UI, getfenv( ) )\\n local frame = document.getElementById \\\"main\\\"\\n local a = document.getElementById \\\"applist\\\"\\n local ok = document.getElementById \\\"ok\\\"\\n local applist = a.parent:newChild( NovaUI.UIFrame( a.x, a.y, a.w, a.h ) )\\n local handlers = Nova.app.listNames( )\\n if #handlers == 0 then\\n handlers = Nova.filesystem.getAllHandlers( )\\n end\\n if #handlers + 1 > applist.h then\\n applist.w = applist.w - 1\\n a.parent:newChild( NovaUI.UIScrollBar( 0, 0, 1, 0, applist ) ):align( \\\"right\\\", applist )\\n end\\n a:remove( )\\n local current\\n for i = 1, #handlers do\\n local button = applist:newChild( NovaUI.UIButton( 1, i, applist.w, 1, handlers[i] ) )\\n button.tc = colours.grey\\n button.align = false\\n function button:onClick( )\\n if current == button then\\n button.bc = colours.white ok.bc = colours.lightGrey current = nil return\\n end\\n ok.bc = colours.lightBlue\\n if current then current.bc = colours.white end\\n button.bc = colours.lightBlue\\n current = button\\n end\\n end\\n function ok:onClick( )\\n if not current then return end\\n local app = current.text\\n frame:remove( )\\n local p = Nova.app.getIcon( app )\\n local icon = appicon\\n if p then\\n icon = p:savestr( )\\n end\\n local t = { x = x, y = y, data = {\\n mode = \\\"app\\\";\\n name = app;\\n display = app;\\n icon = icon;\\n } }\\n table.insert( icons, t )\\n newIcon( x, y, t.data, t )\\n end\\n end )\\n end };\\n }\\n NovaUI.display.menu( frame, menu )\\n function close:onClick( )\\n frame:remove( )\\n close:remove( )\\n end\\nend\\n\\nlocal image = NovaUI.Image( UI.w, UI.h )\\nlocal background = Nova.app.getData \\\"background\\\"\\nif background then\\n image:loadstr( background )\\nelse\\n if Nova.platform == \\\"computer\\\" or Nova.platform == \\\"unknown\\\" then\\n image:loadstr( backgrounds.computer )\\n elseif Nova.platform == \\\"turtle\\\" then\\n image:loadstr( backgrounds.turtle )\\n elseif Nova.platform == \\\"pocket\\\" then\\n image:loadstr( backgrounds.pocket )\\n end\\nend\\n\\nlocal w, h = image:getSize( )\\n\\nlocal back1 = UI:newChild( NovaUI.UIButton( 1, 1, UI.w, UI.h, \\\"\\\" ) )\\nlocal back2 = UI:newChild( NovaUI.UIImage( 1, 1, w, h, image ) )\\nback2:centre( )\\n\\nfunction back1:onClick( x, y, button )\\n if button == 2 then\\n showBlankMenu( x, y )\\n end\\nend\\nfunction back2:onClick( x, y, button )\\n if button == 2 then\\n showBlankMenu( x + self.x - 1, y + self.y - 1 )\\n end\\nend\\n\\nfor i = 1, #icons do\\n newIcon( icons[i].x, icons[i].y, icons[i].data, icons[i] )\\nend\\n\\nchanged = false\\n\\nlocal t = 0\\n\\nwhile true do\\n if changed and os.clock( ) - t >= 0.5 then\\n Nova.app.setData( \\\"icons\\\", icons )\\n changed = false\\n t = os.clock( )\\n end\\n coroutine.yield( )\\nend\",\
7529[\"launcher.lua\"]=\"\\nlocal s = Nova.app.getData \\\"startup\\\"\\nif s ~= false then\\n Nova.app.launch \\\"Desktop\\\"\\nend\",\
7530}\
7531,\
7532}\
7533 \
7534function ttf( table, dir )\
7535 if not fs.isDir( dir ) then\
7536 fs.makeDir( dir )\
7537 end\
7538 for k, v in pairs( table ) do\
7539 if type( v ) == \"table\" then\
7540 ttf( v, dir..\"/\"..k )\
7541 elseif type( v ) == \"string\" then\
7542 local f = fs.open( dir..\"/\"..k, \"w\" )\
7543 f.write( v )\
7544 f.close( )\
7545 end\
7546 end\
7547end\
7548return function( path )\
7549 ttf( pack, path )\
7550end", meta={
7551 type = "lib",
7552}};["UIMenu"]={content="\
7553\
7554require \"UIElement\"\
7555\
7556UIMenu:extends( UIElement )\
7557\
7558UIMenu.handlesMouse = false\
7559UIMenu.handlesScroll = true\
7560UIMenu.isScrollTarget = true\
7561UIMenu.scrollDirection = \"vertical\"\
7562UIMenu.public \"padding\" \"number\"\
7563\
7564function UIMenu:UIMenu( x, y, w, h, direction )\
7565 self:UIElement( x, y, w, h )\
7566 self.scrollDirection = direction or \"vertical\"\
7567 self.padding = 0\
7568 return self.public\
7569end\
7570\
7571function UIMenu.public:update( dt )\
7572 local max = self.scrollDirection == \"vertical\" and self.h or self.w\
7573 if self.scrollDirection == \"vertical\" then\
7574 for i = 1, #self.children do\
7575 max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
7576 end\
7577 else\
7578 for i = 1, #self.children do\
7579 max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
7580 end\
7581 end\
7582 if self.scrollDirection == \"vertical\" then\
7583 if max - self.h < -self.cy then\
7584 self.cy = -max + self.h\
7585 end\
7586 else\
7587 if max - self.w < -self.cx then\
7588 self.cx = -max + self.w\
7589 end\
7590 end\
7591 if max == ( self.scrollDirection == \"vertical\" and self.h or self.w ) then\
7592 self.handlesScroll = false\
7593 else\
7594 self.handlesScroll = true\
7595 end\
7596 local c = { }\
7597 for i, child in ipairs( self.children ) do\
7598 c[i] = child\
7599 end\
7600 for i, child in ipairs( c ) do\
7601 child:update( dt )\
7602 end\
7603 local x, y = 1, 1\
7604 for i, child in ipairs( self.children ) do\
7605 child.x = x\
7606 child.y = y\
7607 if self.scrollDirection == \"vertical\" then\
7608 child.w = self.w\
7609 y = y + child.h + self.padding\
7610 else\
7611 child.h = self.h\
7612 x = x + child.w + self.padding\
7613 end\
7614 end\
7615end\
7616\
7617function UIMenu.public:onMouseScroll( rx, ry, dir )\
7618 if self.scrollDirection == \"vertical\" then\
7619 self.cy = self.cy - dir\
7620 local max = 0\
7621 for i = 1, #self.children do\
7622 max = math.max( max, self.children[i].y + self.children[i].h )\
7623 end\
7624 max = max - self.h - 1\
7625 if self.cy < -max then\
7626 self.cy = -max\
7627 end\
7628 if self.cy > 0 then self.cy = 0 end\
7629 else\
7630 self.cx = self.cx - dir\
7631 local max = 0\
7632 for i = 1, #self.children do\
7633 max = math.max( max, self.children[i].x + self.children[i].w )\
7634 end\
7635 max = max - self.w - 1\
7636 if self.cx < -max then\
7637 self.cx = -max\
7638 end\
7639 if self.cx > 0 then self.cx = 0 end\
7640 end\
7641end\
7642\
7643function UIMenu.public:getDisplaySizeH( )\
7644 return self.w\
7645end\
7646function UIMenu.public:getDisplaySizeV( )\
7647 return self.h\
7648end\
7649\
7650function UIMenu.public:getContentSizeH( )\
7651 local max = self.w\
7652 for i = 1, #self.children do\
7653 max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
7654 end\
7655 return max\
7656end\
7657function UIMenu.public:getContentSizeV( )\
7658 local max = self.h\
7659 for i = 1, #self.children do\
7660 max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
7661 end\
7662 return max\
7663end\
7664\
7665function UIMenu.public:getContentScrollH( )\
7666 return -self.cx\
7667end\
7668function UIMenu.public:getContentScrollV( )\
7669 return -self.cy\
7670end\
7671\
7672function UIMenu.public:setContentScrollH( scroll )\
7673 self.cx = -scroll\
7674end\
7675function UIMenu.public:setContentScrollV( scroll )\
7676 self.cy = -scroll\
7677end", meta={
7678 type = "class",
7679}};["MarkupText"]={content="\
7680\
7681require \"MarkupGenericObject\"\
7682\
7683MarkupText:extends( MarkupGenericObject )\
7684\
7685MarkupText.public.etype = \"text\"\
7686\
7687function MarkupText:MarkupText( ... )\
7688 self:MarkupGenericObject( ... )\
7689\
7690 self.innerNML = { }\
7691\
7692 return self.public\
7693end\
7694\
7695function MarkupText.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
7696 if MarkupGenericObject.attributes[attribute] then\
7697 MarkupGenericObject.attributes[attribute]( self, value )\
7698 end\
7699 if attribute == \"tc\" or attribute == \"textColour\" then\
7700 if colours[value] or value == \"transparent\" then\
7701 self.tc = colours[value] or 0\
7702 end\
7703 end\
7704 if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
7705 if colours[value] or value == \"transparent\" then\
7706 self.bc = colours[value] or 0\
7707 end\
7708 end\
7709 if attribute == \"align\" then\
7710 if value == \"left\" or value == \"right\" or value == \"centre\" then\
7711 self.align = value\
7712 end\
7713 end\
7714 if attribute == \"innerNML\" then\
7715 self.innerNML = self.page:loadAST( value )\
7716 end\
7717 self.page.needsReposition = true\
7718end\
7719\
7720function MarkupText.public:loadObject( positionHandler )\
7721\
7722 self.page:newStyle {\
7723 bc = self.bc;\
7724 tc = self.tc;\
7725 align = self.align;\
7726 }\
7727 for i = #self.innerNML, 1, -1 do\
7728 if self.innerNML[i].remove then\
7729 table.remove( self.objects, i )\
7730 end\
7731 end\
7732 local objects = { }\
7733 if self.width.type == \"text\" and self.width.value == \"auto\" and self.height.type == \"text\" and self.height.value == \"auto\" then\
7734 for i = 1, #self.innerNML do\
7735 local o = positionHandler:newObject( self.innerNML[i], self.page )\
7736 for i = 1, #o do\
7737 table.insert( objects, o[i] )\
7738 end\
7739 end\
7740 else\
7741 local w, h = 0, 0\
7742 if self.width.type == \"text\" and self.width.value == \"auto\" then\
7743 w = positionHandler.w - positionHandler.x + 1\
7744 elseif self.width.type == \"percent\" then\
7745 w = self.width.value / 100 * positionHandler.w\
7746 elseif self.width.type == \"pixel\" then\
7747 w = self.width.value\
7748 end\
7749 if self.height.type == \"text\" and self.height.value == \"auto\" then\
7750 h = positionHandler.h - positionHandler.y + 1\
7751 elseif self.height.type == \"percent\" then\
7752 h = self.height.value / 100 * positionHandler.h\
7753 elseif self.height.type == \"pixel\" then\
7754 h = self.height.value\
7755 end\
7756 local pos = MarkupPositionHandler( w, h )\
7757 for i = 1, #self.innerNML do\
7758 local o = pos:newObject( self.innerNML[i], self.page )\
7759 for i = 1, #o do\
7760 table.insert( objects, o[i] )\
7761 end\
7762 end\
7763 if self.height.type == \"text\" and self.height.value == \"auto\" then\
7764 h = 0\
7765 for i = 1, #objects do\
7766 if objects[i].y + objects[i].content.height - 1 > h then\
7767 h = objects[i].y + objects[i].content.height - 1\
7768 end\
7769 end\
7770 end\
7771 local rw, rh = w, h\
7772 local scrollbarv, scrollbarh = false, false\
7773 for i = 1, #objects do\
7774 if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
7775 w = w - 1\
7776 end\
7777 if objects[i].x + objects[i].content.width - 1 > w and not scrollbarh then\
7778 h = h - 1\
7779 scrollbarh = true\
7780 end\
7781 if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
7782 scrollbarv = true\
7783 end\
7784 if scrollbarv and scrollbarh then\
7785 break\
7786 end\
7787 end\
7788 local loadedObject = MarkupLoadedObject( )\
7789 loadedObject.x = positionHandler.x\
7790 loadedObject.y = positionHandler.y\
7791 loadedObject.mode = \"inline\"\
7792 loadedObject.content.width = rw\
7793 loadedObject.content.height = rh\
7794 loadedObject:initialise( UIFrame )\
7795 local f = loadedObject.element\
7796 f:newChild( UIText( 1, 1, rw, rh, \"\" ) ).bc = self.page:getStyle \"bc\"\
7797 local cf\
7798 if not scrollbarv and not scrollbarh then\
7799 cf = f\
7800 else\
7801 cf = f:newChild( UIFrame( 1, 1, w, h ) )\
7802 end\
7803 for i = 1, #objects do\
7804 cf:newChild( objects[i].element )\
7805 end\
7806 if scrollbarv then\
7807 f:newChild( UIScrollBar( rw, 1, 1, h, cf ) )\
7808 end\
7809 if scrollbarh then\
7810 f:newChild( UIScrollBar( rh, 1, rw, 1, cf, \"horizontal\" ) )\
7811 end\
7812 objects = { loadedObject }\
7813 end\
7814\
7815 self.page:removeStyle( )\
7816 return objects\
7817 -- generate a list of MarkupLoadedObjects\
7818end\
7819\
7820function MarkupText.public:scriptObject( )\
7821 return setmetatable( {}, {\
7822 __newindex = function( _, k, v )\
7823 if type( v ) ~= \"string\" then\
7824 v = tostring( v )\
7825 end\
7826 if k == \"innerNML\" then\
7827 v = v:gsub( \"<!%-%-*.-%-%->\", \"\" )\
7828 v = markup_parse.parse( v, self.page )\
7829 end\
7830 return self.public:setAttribute( k, v )\
7831 end;\
7832 __index = function( _, k )\
7833 if k == \"width\" then\
7834 return ( self.width.type == \"percent\" and self.width.value .. \"%\" ) or ( self.width.type == \"pixel\" and self.width.value .. \"px\" ) or ( self.width.type == \"text\" and self.width.value )\
7835 elseif k == \"height\" then\
7836 return ( self.height.type == \"percent\" and self.height.value .. \"%\" ) or ( self.height.type == \"pixel\" and self.height.value .. \"px\" ) or ( self.height.type == \"text\" and self.height.value )\
7837 elseif k == \"bc\" or k == \"backgroundColour\" or k == \"colour\" then\
7838 return self.bc\
7839 elseif k == \"tc\" or k == \"textColour\" then\
7840 return self.tc\
7841 elseif k == \"align\" then\
7842 return self.align\
7843 else\
7844 return error \"cannot access this attribute\"\
7845 end\
7846 end;\
7847 } )\
7848end", meta={
7849 type = "class",
7850}};["main"]={content="\
7851require \"clipboard\"\
7852require \"com\"\
7853require \"core\"\
7854require \"encryption\"\
7855require \"filesystem\"\
7856require \"network\"\
7857require \"sha256\"\
7858require \"stringutils\"\
7859require \"Thread\"\
7860require \"UI\"\
7861\
7862local w, h = term.getSize( )\
7863\
7864if _G.NovaRunning then\
7865 return\
7866end\
7867_G.NovaRunning = true\
7868\
7869if not term.isColour( ) then\
7870 error( \"Nova requires an advanced computer\", 0 )\
7871end\
7872\
7873term.setBackgroundColour( colours.black )\
7874term.clear( )\
7875sleep( 0.1 )\
7876term.setBackgroundColour( colours.grey )\
7877term.clear( )\
7878sleep( 0.1 )\
7879term.setBackgroundColour( colours.lightGrey )\
7880term.clear( )\
7881sleep( 0.1 )\
7882\
7883Thread( function( )\
7884 require \"kernel\"\
7885end ).onException = function( _, err )\
7886 core.error( err )\
7887end\
7888\
7889com.updateModems( )\
7890\
7891local function update( event, dt )\
7892 if event[1] == \"update\" then\
7893 Thread.update( { \"update\", dt } )\
7894 buffer.drawChanges( )\
7895 buffer.clear( )\
7896 else\
7897 com.listen( event )\
7898 com.clear( )\
7899 Thread.update( { unpack( event ) }, true )\
7900 network.clearBuffers( )\
7901 end\
7902 filesystem.clearCache( )\
7903end\
7904\
7905ok, err = pcall( function( )\
7906 local time = os.clock( )\
7907 local timer = os.startTimer( 0 )\
7908 os.queueEvent \"start\"\
7909 while core.running and not core.error_message do\
7910 local ev = { coroutine.yield( ) }\
7911 local dt = os.clock( ) - time\
7912 time = os.clock( )\
7913 if ev[1] == \"timer\" and ev[2] == timer then\
7914 update( { \"update\" }, dt )\
7915 timer = os.startTimer( 0.05 )\
7916 else\
7917 update( ev, dt )\
7918 end\
7919 end\
7920end )\
7921\
7922core.finish( )\
7923\
7924if not ok or core.error_message then\
7925 core.log( \"fatal error\", core.error_message or err )\
7926 local w, h = term.getSize( )\
7927 term.setBackgroundColour( colours.blue )\
7928 term.clear( )\
7929 term.setTextColour( colours.white )\
7930 term.setCursorPos( 1, 1 )\
7931 term.write \"Nova has encountered a fatal error.\"\
7932 term.setCursorPos( 1, 2 )\
7933 print \"Please report this message to the creator\\n\"\
7934 term.setTextColour( colours.white )\
7935 print( core.error_message or err )\
7936 parallel.waitForAny( function( )\
7937 os.pullEvent \"key\"\
7938 end, function( )\
7939 os.pullEvent \"mouse_click\"\
7940 end )\
7941end", meta={}};["MarkupFrame"]={content="\
7942\
7943require \"MarkupGenericObject\"\
7944\
7945MarkupFrame:extends( MarkupGenericObject )", meta={
7946 type = "class",
7947}};["core"]={content="\
7948\
7949require \"ttf\"\
7950\
7951name = \"Nova Horizon\"\
7952version_major = 1\
7953version_minor = 2\
7954version_patch = 6\
7955version = (\"%d.%d.%02d\"):format( version_major, version_minor, version_patch )\
7956author = \"Benedict Allen\"\
7957\
7958platform = \"computer\"\
7959local w, h = term.getSize( )\
7960if turtle then\
7961 platform = \"turtle\"\
7962elseif pocket or w < 39 then\
7963 platform = \"pocket\"\
7964end\
7965\
7966running = true\
7967path = \"Nova\"\
7968\
7969session = false\
7970UI = false\
7971\
7972if not fs.isDir( path ) then\
7973 fs.makeDir( path )\
7974end\
7975if not fs.isDir( path .. \"/apps\" ) then\
7976 fs.makeDir( path .. \"/apps\" )\
7977 ttf( path .. \"/apps\" )\
7978end\
7979if not fs.isDir( path .. \"/user\" ) then\
7980 fs.makeDir( path .. \"/user\" )\
7981end\
7982\
7983start_time = os.clock( )\
7984\
7985error_message = false\
7986\
7987function error( message )\
7988 core.error_message = message\
7989end\
7990\
7991local file = path .. \"/log.txt\"\
7992local h = fs.open( file, \"w\" )\
7993if h then\
7994 h.close( )\
7995end\
7996\
7997function log( pre, ... )\
7998 local data\
7999 if ... then\
8000 local t = { ... }\
8001 data = tostring( t[1] )\
8002 for i = 2, #t do\
8003 data = data .. \", \" .. tostring( t[i] )\
8004 end\
8005 end\
8006 local h = fs.open( file, \"a\" )\
8007 if h then\
8008 local time = \"[\" .. os.clock( ) - start_time .. \"] \"\
8009 if data then\
8010 h.write( time .. tostring( pre ) .. \": \" .. textutils.serialize( data ) .. \"\\n\" )\
8011 else\
8012 h.write( time .. tostring( pre ) .. \"\\n\" )\
8013 end\
8014 h.close( )\
8015 return true\
8016 end\
8017 return false\
8018end\
8019\
8020function getRunTime( )\
8021 return os.clock( ) - start_time\
8022end\
8023\
8024function finish( )\
8025 if core.session then\
8026 core.session:logout( )\
8027 end\
8028 core.log \"stopping\"\
8029 _G.NovaRunning = false\
8030end\
8031\
8032function getUpdateInfo( )\
8033 local t = {\
8034 name = \"Nova Horizon\";\
8035 version_major = version_major;\
8036 version_minor = version_minor;\
8037 version_patch = version_patch;\
8038 }\
8039 pcall( function( )\
8040 local h = http.get \"http://pastebin.com/raw.php?i=K8MKteNu\"\
8041 if h then\
8042 t = textutils.unserialize( h.readAll( ) ) or t\
8043 h.close( )\
8044 end\
8045 end )\
8046 return t\
8047end\
8048\
8049function compareVersions( t )\
8050 t = t or getUpdateInfo( )\
8051 if t.version_major > version_major then\
8052 return true\
8053 elseif t.version_major == version_major then\
8054 if t.version_minor > version_minor then\
8055 return true\
8056 elseif t.version_minor == version_minor then\
8057 return t.version_patch > version_patch\
8058 end\
8059 end\
8060 return false\
8061end\
8062\
8063function update( updateInfo )\
8064 updateInfo = updateInfo or getUpdateInfo( )\
8065 local h = http.get( \"http://pastebin.com/raw.php?i=\" .. updateInfo.link )\
8066 if h then\
8067 if type( updateInfo.pre_update ) == \"function\" then\
8068 setfenv( updateInfo.pre_update, getfenv( ) )\
8069 updateInfo.pre_update( )\
8070 end\
8071 local content = h.readAll( )\
8072 h.close( )\
8073 filesystem.delete( \"C:/\" .. path .. \"/apps\" )\
8074 filesystem.delete \"C:/startup\"\
8075 filesystem.writefile( \"startup\", FileData( content ) )\
8076 if type( updateInfo.post_update ) == \"function\" then\
8077 setfenv( updateInfo.post_update, getfenv( ) )\
8078 updateInfo.post_update( )\
8079 end\
8080 return true\
8081 end\
8082 return false\
8083end\
8084\
8085-- Credits to lbphacker for code below\
8086local a=math.floor;local b=24*60*60;local c=365*b;local d=c+b;local e=4*c+b;local f=4;local g=1970;local h={-1,30,58,89,119,150,180,211,242,272,303,333,364}local i={}for j=1,2 do i[j]=h[j]end;for j=3,13 do i[j]=h[j]+1 end;local function gmtime(k)local m,n,o,p,q,r,s,t;local u=h;t=k;m=a(t/e)t=t-m*e;m=m*4+g;if t>=c then m=m+1;t=t-c;if t>=c then m=m+1;t=t-c;if t>=d then m=m+1;t=t-d else u=i end end end;n=a(t/b)t=t-n*b;local o=1;while u[o]<n do o=o+1 end;o=o-1;local p=n-u[o]q=(a(k/b)+f)%7;r=a(t/3600)t=t-r*3600;s=a(t/60)t=t-s*60;return m,n+1,o,p,q,r,s,t end\
8087--time since last update\
8088local firstepoch, firstclock, tslu\
8089function getirltime(gmtoffset)\
8090 if not firstepoch or os.clock( ) - tslu > 300 then\
8091 pcall( function( )\
8092 local httpResponseHandle = http.get \"http://lbphacker.hu/cctime.php\"\
8093 if not httpResponseHandle then\
8094 return false\
8095 end\
8096 firstepoch = tonumber(httpResponseHandle.readAll())\
8097 if not firstepoch then\
8098 return false\
8099 end\
8100 firstclock = os.clock()\
8101 httpResponseHandle.close()\
8102 tslu = os.clock( )\
8103 end )\
8104 end\
8105 if firstepoch then\
8106 local y, j, m, d, w, h, n, s = gmtime(firstepoch + math.floor(os.clock() - firstclock) + (gmtoffset or 0) * 3600)\
8107 return {h = h, m = n, s = s}\
8108 end\
8109 return { h = math.floor( os.time( ) / 60 ), m = os.time( ) % 60, s = 0 }\
8110end\
8111\
8112local months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }\
8113\
8114function getirldate(gmtoffset)\
8115 if not firstepoch or os.clock( ) - tslu > 300 then\
8116 pcall( function( )\
8117 local httpResponseHandle = http.get \"http://lbphacker.hu/cctime.php\"\
8118 if not httpResponseHandle then\
8119 return false\
8120 end\
8121 firstepoch = tonumber(httpResponseHandle.readAll())\
8122 if not firstepoch then\
8123 return false\
8124 end\
8125 firstclock = os.clock()\
8126 httpResponseHandle.close()\
8127 tslu = os.clock( )\
8128 end )\
8129 end\
8130 if firstepoch then\
8131 local y, j, m, d, w, h, n, s = gmtime(firstepoch + math.floor(os.clock() - firstclock) + (gmtoffset or 0) * 3600)\
8132 return {y = y, j = j, m = m, d = d, w = w}\
8133 end\
8134 local day = os.day( )\
8135 local month = 12\
8136 local year = math.floor( day / 365 )\
8137 day = day - year * 365\
8138 for i = 1, #months do\
8139 if day < months[i] then\
8140 month = i\
8141 break\
8142 else\
8143 day = day - months[i]\
8144 end\
8145 end\
8146 return { y = year, j = 0, m = month, d = day, w = 0 }\
8147end", meta={
8148 type = "lib",
8149}};["stencil"]={content="\
8150\
8151local function linewrap( str, w )\
8152 for i = 1, w + 1 do\
8153 if str:sub( i, i ) == \"\\n\" then\
8154 return str:sub( 1, i - 1 ), str:sub( i + 1 )\
8155 end\
8156 end\
8157 if #str <= w then\
8158 return str, \"\"\
8159 end\
8160 if str:sub( w + 1, w + 1 ):find \"%s\" then\
8161 return str:sub( 1, w ), str:sub( w + 1 ):gsub( \"^%s+\", \"\" )\
8162 end\
8163 for i = w, 1, -1 do\
8164 if str:sub( i, i ):find \"%s\" then\
8165 return str:sub( 1, i ), str:sub( i + 1 )\
8166 end\
8167 end\
8168 return str:sub( 1, w ), str:sub( w + 1 )\
8169end\
8170\
8171local function wordwrap( str, w, h )\
8172 if w < 1 then return { } end\
8173 local lines = { }\
8174 local line\
8175 while #str > 0 do\
8176 line, str = linewrap( str, w )\
8177 table.insert( lines, line )\
8178 end\
8179 while #lines > h do\
8180 lines[#lines] = nil\
8181 end\
8182 return lines\
8183end\
8184\
8185local function bbox( x1, y1, w1, h1, x2, y2, w2, h2 )\
8186 local x, y, w, h\
8187 if x1 + w1 <= x2 or y1 + h1 <= y2 or x2 + w2 <= x1 or y2 + h2 <= y1 then\
8188 return false\
8189 end\
8190 x = math.max( x1, x2 )\
8191 w = math.min( x1 + w1, x2 + w2 ) - x\
8192 y = math.max( y1, y2 )\
8193 h = math.min( y1 + h1, y2 + h2 ) - y\
8194 return x, y, w, h\
8195end\
8196\
8197local layers = { }\
8198local w, h = term.getSize( )\
8199local limit = { x = 1, y = 1, w = w, h = h }\
8200\
8201function pixel( x, y, bc, tc, char )\
8202 if limit and x >= limit.x and x < limit.x + limit.w and y >= limit.y and y < limit.y + limit.h then\
8203 buffer.setPixel( x, y, bc, tc, char )\
8204 return true\
8205 end\
8206 return false\
8207end\
8208\
8209function textLine( x, y, bc, tc, text )\
8210 for i = 1, #text do\
8211 pixel( x + i - 1, y, bc, tc, text:sub( i, i ) )\
8212 end\
8213end\
8214\
8215function rectangle( x, y, w, h, bc, tc, char )\
8216 for i = 0, h - 1 do\
8217 textLine( x, y + i, bc, tc, ( char or \" \" ):rep( w ) )\
8218 end\
8219end\
8220\
8221function text( x, y, w, h, bc, tc, text, pad )\
8222 local lines = wordwrap( text, w, h )\
8223 if pad then\
8224 pad( lines )\
8225 end\
8226 if bc ~= 0 then\
8227 while #lines < h do\
8228 table.insert( lines, string.rep( \" \", math.max( w, 0 ) ) )\
8229 end\
8230 end\
8231 for i = 1, #lines do\
8232 local line = lines[i]:sub( 1, w )\
8233 if bc ~= 0 then\
8234 line = line .. string.rep( \" \", math.max( w - #line, 0 ) )\
8235 end\
8236 textLine( x, y + i - 1, bc, tc, line )\
8237 end\
8238end\
8239\
8240function setCursorBlink( x, y, col )\
8241 if limit and x >= limit.x and x < limit.x + limit.w and y >= limit.y and y < limit.y + limit.h then\
8242 buffer.setCursorBlink( x, y, col )\
8243 return true\
8244 end\
8245 return false\
8246end\
8247\
8248function addLayer( x, y, w, h )\
8249 table.insert( layers, { x = x, y = y, w = w, h = h } )\
8250 if limit then\
8251 x, y, w, h = bbox( x, y, w, h, limit.x, limit.y, limit.w, limit.h )\
8252 if x then\
8253 limit = { x = x, y = y, w = w, h = h }\
8254 else\
8255 limit = false\
8256 end\
8257 end\
8258 return #layers\
8259end\
8260\
8261function closeLayer( layer )\
8262 while layers[layer] do\
8263 table.remove( layers, layer )\
8264 end\
8265 limit = { x = 1, y = 1, w = w, h = h }\
8266 for i, l in ipairs( layers ) do\
8267 local x, y, w, h = bbox( limit.x, limit.y, limit.w, limit.h, l.x, l.y, l.w, l.h )\
8268 if x then\
8269 limit = { x = x, y = y, w = w, h = h }\
8270 else\
8271 limit = false\
8272 break\
8273 end\
8274 end\
8275end\
8276\
8277function layerCount( )\
8278 return #layers\
8279end\
8280\
8281function getDrawArea( )\
8282 if limit then\
8283 return limit.x, limit.y, limit.w, limit.h\
8284 end\
8285 return 0, 0, 0, 0\
8286end", meta={
8287 type = "lib",
8288}};["Account"]={content="\
8289\
8290Account.public \"username\"\
8291Account.public.username.write = false\
8292Account.public \"password\"\
8293Account.public.password.write = false\
8294Account.public \"shaPassword\"\
8295Account.public.shaPassword.write = false\
8296Account.public \"userpath\"\
8297Account.public.userpath.write = false\
8298\
8299function Account:Account( name, pass )\
8300 if not fs.isDir( core.path .. \"/user/\" .. name ) then\
8301 return false, \"no such user\"\
8302 end\
8303\
8304 -- try to login\
8305 local userpath = core.path .. \"/user/\" .. name .. \"/\"\
8306 local h = fs.open( userpath .. \"passcheck.txt\", \"r\" )\
8307 if h then\
8308 local passcheck = h.readAll( )\
8309 h.close( )\
8310 if encryption.decrypt( passcheck, sha256( pass ) ) ~= name then\
8311 return false, \"incorrect password\"\
8312 end\
8313 else\
8314 return false, \"corrupt user\"\
8315 end\
8316\
8317 self.username = name\
8318 self.password = pass\
8319 self.shaPassword = sha256( pass )\
8320 self.userpath = userpath\
8321\
8322 return self.public\
8323end\
8324\
8325function Account.public:rename( name )\
8326 if fs.exists( core.path .. \"/user/\" .. name ) then\
8327 return false, \"user exists\"\
8328 end\
8329 local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
8330 if h then\
8331 h.write( encryption.encrypt( name, self.shaPassword ) )\
8332 h.close( )\
8333 if not pcall( function( )\
8334 fs.move( self.userpath, core.path .. \"/user/\" .. name )\
8335 end ) then\
8336 local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
8337 if h then\
8338 h.write( encryption.encrypt( self.username, self.shaPassword ) )\
8339 h.close( )\
8340 end\
8341 return false\
8342 end\
8343 self.username = name\
8344 self.userpath = core.path .. \"/user/\" .. name .. \"/\"\
8345 return true\
8346 end\
8347 return false\
8348end\
8349\
8350function Account.public:changePassword( pass )\
8351 local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
8352 if h then\
8353 self.password = pass\
8354 self.shaPassword = sha256( pass )\
8355 h.write( encryption.encrypt( self.username, self.shaPassword ) )\
8356 h.close( )\
8357 end\
8358 return false\
8359end\
8360\
8361function Account.public:setData( index, entry, value )\
8362 local data\
8363 local h = fs.open( self.userpath .. index, \"r\" )\
8364 if h then\
8365 data = textutils.unserialize( h.readAll( ) ) or { }\
8366 h.close( )\
8367 else\
8368 data = { }\
8369 end\
8370 data[entry] = value\
8371 local h = fs.open( self.userpath .. index, \"w\" )\
8372 if h then\
8373 h.write( textutils.serialize( data ) )\
8374 h.close( )\
8375 return true\
8376 end\
8377 return false\
8378end\
8379\
8380function Account.public:getData( index, entry )\
8381 local data\
8382 local h = fs.open( self.userpath .. index, \"r\" )\
8383 if h then\
8384 data = textutils.unserialize( h.readAll( ) ) or { }\
8385 h.close( )\
8386 return data[entry]\
8387 end\
8388end\
8389\
8390function Account.public:hasAccess( request ) -- things like root filesystem access, settings, etc\
8391 return true\
8392end\
8393\
8394function Account.static.create( name, pass )\
8395 if fs.exists( core.path .. \"/user/\" .. name ) then\
8396 return false, \"account already exists\"\
8397 end\
8398 fs.makeDir( core.path .. \"/user/\" .. name )\
8399 local h = fs.open( core.path .. \"/user/\" .. name .. \"/passcheck.txt\", \"w\" )\
8400 if h then\
8401 h.write( encryption.encrypt( name, sha256( pass ) ) )\
8402 h.close( )\
8403 else\
8404 fs.delete( core.path .. \"/user/\" .. name )\
8405 end\
8406 fs.makeDir( core.path .. \"/user/\" .. name .. \"/data\" )\
8407 fs.makeDir( core.path .. \"/user/\" .. name .. \"/data/screenshots\" )\
8408end", meta={
8409 type = "class",
8410}};["MarkupPage"]={content="\
8411\
8412MarkupPage.public \"errors\"\
8413MarkupPage.public.errors.write = false\
8414MarkupPage.public \"ids\"\
8415MarkupPage.public.ids.write = false\
8416MarkupPage.public \"environment\"\
8417MarkupPage.public.environment.write = false\
8418MarkupPage.public \"threads\"\
8419MarkupPage.public.threads.write = false\
8420\
8421MarkupPage.public \"needsReposition\" \"boolean\"\
8422\
8423function MarkupPage:MarkupPage( w, h, content )\
8424\
8425 self.w = w\
8426 self.h = h\
8427 self.errors = { }\
8428 self.ids = { }\
8429 self.objects = { }\
8430 self.threads = { }\
8431 self.scriptelements = { }\
8432\
8433 self.styles = {\
8434 { bc = 1, tc = colours.grey, align = \"left\" };\
8435 }\
8436\
8437 self.needsReposition = false\
8438\
8439 self.environment = DocumentEnvironment( self.public, self )\
8440\
8441 if content then\
8442 self.public:loadMarkup( tostring( content ) )\
8443 end\
8444\
8445 self.threads[1] = Thread( function( )\
8446 while true do\
8447 if self.needsReposition then\
8448 self.public:repositionElements( )\
8449 end\
8450 for i = #self.threads, 1, -1 do\
8451 if not self.threads[i]:isRunning( ) and not self.threads[i]:isPaused( ) then\
8452\
8453 end\
8454 end\
8455 coroutine.yield( )\
8456 end\
8457 end )\
8458 self.threads[1].onException = function( _, err )\
8459 table.insert( self.errors, { source = \"internal\", level = \"fatal\", message = err } )\
8460 self.threads[1]:restart( )\
8461 end\
8462\
8463 return self.public\
8464end\
8465\
8466function MarkupPage.public:loadMarkup( content )\
8467 content = content:gsub( \"<!%-%-*.-%-%->\", \"\" )\
8468 local ast = markup_parse.parse( content, self.public )\
8469 self.objects = self.public:loadAST( ast )\
8470 self.needsReposition = true\
8471end\
8472\
8473function MarkupPage.public:loadAST( ast )\
8474 local t = { }\
8475 for i = 1, #ast do\
8476 if type( ast[i] ) == \"table\" then\
8477 if ast[i].type == \"script\" then\
8478 if not ast[i].loaded then\
8479 local s = ast[i].attributes.innerNML[1]\
8480 local source = ast[i].attributes.name and ( \"script \\\"\" .. ast[i].attributes.name .. \"\\\"\" ) or ( \"script on line \" .. ast[i].source )\
8481 local f, err = loadstring( s, \"\" )\
8482 if f then\
8483 local t = Thread( f )\
8484 function t.onException( _, err )\
8485 table.insert( self.errors, { source = source, level = \"error\", message = err } )\
8486 end\
8487 t.environment = self.environment\
8488 table.insert( self.threads, t )\
8489 else\
8490 table.insert( self.errors, { source = source, level = \"error\", message = err } )\
8491 end\
8492 ast[i].loaded = true\
8493 end\
8494 else\
8495 local _type = ast[i].type\
8496 if nova_markup.objects[_type] then\
8497 local object = nova_markup.objects[_type]( ast[i].source ) -- MarkupGenericObject\
8498 object.page = self.public\
8499 for k, v in pairs( ast[i].attributes ) do\
8500 object:setAttribute( k, v, ast[i].source, true )\
8501 end\
8502 table.insert( t, object )\
8503 else\
8504 table.insert( self.errors, { source = ast[i].source, level = \"warning\", message = \"no such element \\\"\" .. _type .. \"\\\"\" } )\
8505 end\
8506 end\
8507 else\
8508 ast[i] = ast[i]:gsub( \"^%s+\", \"\" ):gsub( \"%s+$\", \"\" )\
8509 if #ast[i] > 0 then\
8510 table.insert( t, ast[i] )\
8511 end\
8512 end\
8513 end\
8514 return t\
8515end\
8516\
8517function MarkupPage.public:repositionElements( )\
8518 local elementPositionHandler = MarkupPositionHandler( self.w, self.h )\
8519 local frame = UIFrame( 1, 1, self.w, self.h )\
8520 for i = #self.objects, 1, -1 do\
8521 if self.objects[i].remove then\
8522 table.remove( self.objects, i )\
8523 end\
8524 end\
8525 for i = 1, #self.objects do\
8526 local loadedObjects = elementPositionHandler:newObject( self.objects[i], self.public )\
8527 -- MarkupLoadedObject list\
8528 for i = 1, #loadedObjects do\
8529 frame:newChild( loadedObjects[i].element )\
8530 end\
8531 end\
8532 if self.frame then\
8533 self.frame:clearChildren( )\
8534 local c = frame:getChildren( )\
8535 for i = 1, #c do\
8536 self.frame:newChild( c[i] )\
8537 end\
8538 else\
8539 self.frame = frame\
8540 end\
8541 self.needsReposition = false\
8542 return self.frame\
8543end\
8544\
8545function MarkupPage.public:resize( w, h )\
8546 self.w = w\
8547 self.h = h\
8548 self.public:repositionElements( )\
8549end\
8550\
8551MarkupPage.static.objects = ob\
8552\
8553function MarkupPage.public:newStyle( t )\
8554 table.insert( self.styles, t )\
8555end\
8556\
8557function MarkupPage.public:removeStyle( )\
8558 table.remove( self.styles, #self.styles )\
8559end\
8560\
8561function MarkupPage.public:getStyle( index, default )\
8562 for i = #self.styles, 1, -1 do\
8563 if self.styles[i][index] ~= nil then\
8564 return self.styles[i][index]\
8565 end\
8566 end\
8567 return default\
8568end", meta={
8569 type = "class",
8570}};["UIHandler"]={content="\
8571\
8572require \"stencil\"\
8573\
8574UIHandler.public.w, UIHandler.public.h = term.getSize( )\
8575\
8576UIHandler.public \"focus\"\
8577UIHandler.public.focus.write = false\
8578\
8579function UIHandler:UIHandler( )\
8580 self.children = { }\
8581 self.focus = false\
8582 self.lastx = 0\
8583 self.lasty = 0\
8584 self.lastt = false\
8585 self.lastkey = nil\
8586 self.lastkeytime = false\
8587 return self.public\
8588end\
8589\
8590function UIHandler.public:draw( )\
8591 local c = { }\
8592 for i, child in ipairs( self.children ) do\
8593 c[i] = child\
8594 end\
8595 for i, child in ipairs( c ) do\
8596 child:draw( child.x, child.y )\
8597 end\
8598end\
8599\
8600function UIHandler.public:update( dt )\
8601 local c = { }\
8602 for i, child in ipairs( self.children ) do\
8603 c[i] = child\
8604 end\
8605 for i, child in ipairs( c ) do\
8606 child:update( dt )\
8607 end\
8608 if self.lastkeytime then\
8609 if os.clock( ) - self.lastkeytime > 0.3 then\
8610 self.lastkey = nil\
8611 self.lastkeytime = false\
8612 end\
8613 end\
8614end\
8615\
8616local function findMouseTarget( children, parent, handler, x, y )\
8617 x = x - parent.cx\
8618 y = y - parent.cy\
8619 for i = #children, 1, -1 do\
8620 local child = children[i]\
8621 if x >= child.x and y >= child.y and x < child.x + child.w and y < child.y + child.h then\
8622 local ok, c, data = findMouseTarget( child:getChildren( ), child, handler, x - child.x + 1, y - child.y + 1 )\
8623 if ok then\
8624 return true, c, data\
8625 end\
8626 local ok, data = handler( child, parent, x, y )\
8627 if ok then\
8628 return true, child, data\
8629 end\
8630 end\
8631 end\
8632 return false\
8633end\
8634\
8635local function screenCoords( child )\
8636 local x, y = child.x, child.y\
8637 while child.parent and child.parent:type( ) ~= \"UIHandler\" do -- everything but a UIHandler will have a parent\
8638 child = child.parent\
8639 x = x + child.x - 1 + child.cx\
8640 y = y + child.y - 1 + child.cy\
8641 end\
8642 return x, y\
8643end\
8644\
8645local function merge( t1, t2 )\
8646 local t = { }\
8647 for i, v in ipairs( t1 ) do\
8648 table.insert( t, v )\
8649 end\
8650 for i, v in ipairs( t2 ) do\
8651 table.insert( t, v )\
8652 end\
8653 return t\
8654end\
8655\
8656function UIHandler.public:event( event )\
8657 if event[1] == \"mouse_click\" then\
8658 self.lastx = event[3]\
8659 self.lasty = event[4]\
8660 local found, child, data = findMouseTarget( self.children, {cx=0, cy=0}, function( child, parent, x, y )\
8661 if child.handlesMouse then\
8662 return true, { rx = x - child.x + 1, ry = y - child.y + 1 }\
8663 end\
8664 end, event[3], event[4] )\
8665 if found then\
8666 if child ~= self.focus and self.focus then\
8667 self.focus:onUnFocus( )\
8668 end\
8669 if child ~= self.focus then\
8670 child:onFocus( )\
8671 end\
8672 self.focus = child\
8673 child:onMouseClick( data.rx, data.ry, event[2] )\
8674 local sx, sy = screenCoords( child )\
8675 self.lastt = { child = child, sx = sx, sy = sy, x = child.x, y = child.y }\
8676 else\
8677 self.lastt = false\
8678 if self.focus then\
8679 self.focus:onUnFocus( )\
8680 end\
8681 self.focus = false\
8682 end\
8683 elseif event[1] == \"mouse_scroll\" then\
8684 local found, child, data = findMouseTarget( self.children, {cx=0, cy=0}, function( child, parent, x, y )\
8685 if child.handlesScroll then\
8686 return true, { rx = x - child.x + 1, ry = y - child.y + 1 }\
8687 end\
8688 end, event[3], event[4] )\
8689 if found then\
8690 child:onMouseScroll( data.rx, data.ry, event[2] )\
8691 end\
8692 elseif event[1] == \"mouse_drag\" then\
8693 if self.lastt then\
8694 local cx, cy = event[3] - self.lastx, event[4] - self.lasty\
8695 local sx, sy = self.lastt.sx + ( self.lastt.child.x - self.lastt.x ), self.lastt.sy + ( self.lastt.child.y - self.lastt.y )\
8696 self.lastt.child:onMouseDrag( event[3] - sx + 1, event[4] - sy + 1, cx, cy, event[2] )\
8697 self.lastx = event[3]\
8698 self.lasty = event[4]\
8699 end\
8700 elseif event[1] == \"key\" then\
8701 if event[2] == keys.tab then\
8702\
8703 if self.focus and self.focus.handlesTab then\
8704 self.focus:onKeyPress( keys.tab, self.lastkey )\
8705 else\
8706 local tabs = { }\
8707 for i = 1, #self.children do\
8708 local t2 = self.children[i]:getTabIndexes( )\
8709 if #t2 > 0 then\
8710 tabs = merge( tabs, t2 )\
8711 end\
8712 end\
8713 local of = self.focus\
8714 local found = false\
8715 for i = 1, #tabs do\
8716 if tabs[i] == self.focus then\
8717 if self.lastkey == keys.leftShift or self.lastkey == keys.rightShift then\
8718 if tabs[i-1] then\
8719 self.focus = tabs[i-1]\
8720 else\
8721 self.focus = tabs[#tabs]\
8722 end\
8723 else\
8724 if tabs[i+1] then\
8725 self.focus = tabs[i+1]\
8726 else\
8727 self.focus = tabs[1]\
8728 end\
8729 end\
8730 found = true\
8731 break\
8732 end\
8733 end\
8734 if not found then\
8735 self.focus = tabs[1]\
8736 end\
8737 if of ~= self.focus then\
8738 if of then\
8739 of:onUnFocus( )\
8740 end\
8741 if self.focus then\
8742 self.focus:onFocus( )\
8743 end\
8744 end\
8745 end\
8746 else\
8747 if self.focus and ( self.focus.handlesKeys or ( self.focus.handlesEnter and event[2] == keys.enter ) ) then\
8748 self.focus:onKeyPress( event[2], self.lastkey )\
8749 else\
8750 local function find( c )\
8751 for i = #c, 1, -1 do\
8752 local ok, obj = find( c[i]:getChildren( ) )\
8753 if ok then\
8754 return true, obj\
8755 end\
8756 if c[i].handlesKeys then\
8757 c[i]:onKeyPress( event[2], self.lastkey )\
8758 return true, c[i]\
8759 end\
8760 end\
8761 end\
8762 find( self.children )\
8763 end\
8764 end\
8765 elseif event[1] == \"char\" then\
8766 if self.focus and self.focus.handlesKeys then\
8767 self.focus:onTextInput( event[2], self.lastkey )\
8768 else\
8769 local function find( c )\
8770 for i = #c, 1, -1 do\
8771 local ok, obj = find( c[i]:getChildren( ) )\
8772 if ok then\
8773 return true, obj\
8774 end\
8775 if c[i].handlesKeys then\
8776 c[i]:onTextInput( event[2], self.lastkey )\
8777 return true, c[i]\
8778 end\
8779 end\
8780 end\
8781 find( self.children )\
8782 end\
8783 end\
8784 if event[1] == \"key\" or ( event[1] == \"char\" and self.lastkey ) then\
8785 if event[1] == \"key\" then\
8786 self.lastkey = event[2]\
8787 end\
8788 self.lastkeytime = os.clock( )\
8789 else\
8790 self.lastkey = nil\
8791 self.lastkeytime = false\
8792 end\
8793end\
8794\
8795function UIHandler.public:newChild( child )\
8796 if class.typeOf( child, UIElement ) then\
8797 table.insert( self.children, child )\
8798 child.parent = self.public\
8799 return child\
8800 else\
8801 error( \"expected UIElement child\", 2 )\
8802 end\
8803end\
8804\
8805function UIHandler.public:removeChild( child )\
8806 local removed = false\
8807 for i = #self.children, 1, -1 do\
8808 if self.children[i] == child then\
8809 table.remove( self.children, i )\
8810 removed = true\
8811 end\
8812 end\
8813 return removed\
8814end\
8815\
8816function UIHandler.public:unFocus( )\
8817 if self.focus then\
8818 self.focus:onUnFocus( )\
8819 self.focus = false\
8820 end\
8821end\
8822\
8823function UIHandler.public:setFocus( child )\
8824 if class.typeOf( child, UIElement ) then\
8825 if self.focus ~= child then\
8826 if self.focus then\
8827 self.focus:onUnFocus( )\
8828 end\
8829 child:onFocus( )\
8830 self.focus = child\
8831 end\
8832 else\
8833 error( \"expected UIElement child\", 3 )\
8834 end\
8835end", meta={
8836 type = "class",
8837}};}
8838--/end of files
8839local autorun = {"App";"Thread";"UICode";"MarkupGenericObject";"UIKeyHandler";"UIButton";"MarkupButton";"UIImage";"FileData";"AppInstance";"Archive";"UIText";"MarkupParagraph";"DocumentEnvironment";"UITextbox";"UIScrollBar";"Drive";"MarkupPositionHandler";"UIFrame";"UIInput";"UIElement";"Session";"Sandbox";"MarkupLoadedObject";"Image";"MarkupSpace";"MarkupInput";"MarkupNewline";"UICanvas";"UIMenu";"MarkupText";"MarkupFrame";"Account";"MarkupPage";"UIHandler";"main"}
8840--[[type="lib"]]
8841
8842-- Swift Class Library
8843-- Made by awsumben13
8844-- Feel free to use this in any of your projects but keep this at the top and give credit where necessary
8845
8846--[[
8847 This class library offers:
8848 Inheritance "Class:extends( other class )"
8849 Initialiser methods "function ClassName:ClassName( )"
8850 Public / private variables with customisable read/write access "Class.public.x.write = false"
8851 Static variables ( accessed from the class object ) "Class.static.x = 5"
8852 Custom metamethods including __index and __newindex "Class.meta.index = { }"
8853]]
8854
8855local class = { }
8856
8857function class.new( name )
8858
8859 local object = { }
8860 local public = { }
8861
8862 object.public = { }
8863 object.private = { }
8864 object.static = { }
8865 object.name = name
8866 object.extends = false
8867 object.class = public
8868
8869 public.name = name
8870
8871 local customindex = false
8872 local customnewindex = false
8873 local objectmeta = { }
8874
8875 function public:new( ... )
8876
8877 local ob = { }
8878 local pb = { }
8879
8880 ob.class = public
8881 ob.public = pb
8882
8883 setmetatable( ob, {
8884 __index = object.private;
8885 } )
8886
8887 function pb:type( full )
8888 return ob.class:type( full )
8889 end
8890
8891 function pb:typeOf( class )
8892 return ob.class:typeOf( class )
8893 end
8894
8895 local obmeta = { }
8896 obmeta.__index = function( _, k )
8897 if object.public[k] then
8898 if object.public[k].read then
8899 local val
8900 if customindex then
8901 if type( customindex ) == "function" then
8902 return customindex( ob, k )
8903 else
8904 return customindex[k]
8905 end
8906 elseif type( object.public[k].read ) == "function" then
8907 val = object.public[k].read( ob )
8908 elseif object.public[k].value ~= nil then
8909 val = object.public[k].value
8910 else
8911 val = ob[k]
8912 end
8913 if type( val ) == "function" then
8914 return function( self, ... )
8915 if self == pb then
8916 return val( ob, ... )
8917 end
8918 return val( self, ... )
8919 end
8920 end
8921 return val
8922 else
8923 error( "variable has no read access", 2 )
8924 end
8925 elseif customindex then
8926 if type( customindex ) == "function" then
8927 return customindex( ob, k )
8928 else
8929 return customindex[k]
8930 end
8931 else
8932 error( "no such variable \"" .. tostring( k ) .. "\"", 2 )
8933 end
8934 end;
8935 obmeta.__newindex = function( _, k, v )
8936 if object.public[k] then
8937 if object.public[k].write then
8938 if customnewindex then
8939 if type( customnewindex ) == "function" then
8940 return customnewindex( ob, k, v )
8941 else
8942 customnewindex[k] = v
8943 end
8944 elseif type( object.public[k].write ) == "function" then
8945 object.public[k].write( ob, v )
8946 else
8947 ob[k] = v
8948 end
8949 else
8950 error( "variable has no write access", 2 )
8951 end
8952 else
8953 error( "no such variable \"" .. tostring( k ) .. "\"", 2 )
8954 end
8955 end;
8956 obmeta.__tostring = function( )
8957 return object.name
8958 end;
8959 obmeta.__metatable = "SwiftClassObject";
8960
8961 for k, v in pairs( objectmeta ) do
8962 obmeta["__" .. tostring( k )] = v
8963 end
8964
8965 setmetatable( pb, obmeta )
8966
8967 local c = object
8968 while true do
8969 if type( c.private[c.name] ) == "function" then
8970 return c.private[c.name]( ob, ... )
8971 end
8972 if c.extends then
8973 c = c.extends
8974 else
8975 break
8976 end
8977 end
8978
8979 return pb
8980 end
8981
8982 function public:type( full )
8983 local str = ""
8984 if full then
8985 local c = object.extends
8986 while c do
8987 str = c.name .. "." .. str
8988 c = c.extends
8989 end
8990 end
8991 return str .. object.name
8992 end
8993
8994 function public:typeOf( other )
8995 if type( other ) == "table" then
8996 if getmetatable( other ) == "SwiftClass" then
8997 local ob = object
8998 while ob do
8999 if ob.class == other then
9000 return true
9001 end
9002 ob = ob.extends
9003 end
9004 end
9005 end
9006 return false
9007 end
9008
9009 function public:extends( ob )
9010 ob:extend( object )
9011 end
9012
9013 function public:extend( ob )
9014 setmetatable( ob.static, { __index = object.static } )
9015 setmetatable( ob.public, { __index = object.public } )
9016 setmetatable( ob.private, { __index = object.private } )
9017 ob.extends = object
9018 end
9019
9020 local meta = { }
9021 meta.__index = function( _, k )
9022 if k == "static" then
9023 return setmetatable( { }, {
9024 __newindex = function( _, k, v )
9025 object.static[k] = v
9026 end;
9027 __metatable = { };
9028 } )
9029 elseif k == "public" then
9030 return setmetatable( { }, {
9031 __newindex = function( _, k, v )
9032 object.public[k] = {
9033 read = true;
9034 write = false;
9035 value = v;
9036 }
9037 end;
9038 __call = function( _, k )
9039 object.public[k] = {
9040 read = true;
9041 write = true;
9042 value = nil;
9043 }
9044 return function( _type )
9045 object.public[k].write = function( ob, value )
9046 if type( value ) == _type or class.typeOf( value, _type ) then
9047 ob[k] = value
9048 else
9049 if class.type( _type ) == "Class" then
9050 error( "expected " .. _type:type( ) .. " " .. k, 3 )
9051 else
9052 error( "expected " .. _type .. " " .. k, 3 )
9053 end
9054 end
9055 end
9056 end
9057 end;
9058 __index = function( _, k )
9059 if object.public[k] then
9060 return setmetatable( { }, {
9061 __newindex = function( _, name, v )
9062 if name == "read" then
9063 if type( v ) == "boolean" or type( v ) == "function" then
9064 object.public[k].read = v
9065 else
9066 error( "invalid modifier value", 2 )
9067 end
9068 elseif name == "write" then
9069 if type( v ) == "boolean" or type( v ) == "function" then
9070 object.public[k].write = v
9071 else
9072 error( "invalid modifier value", 2 )
9073 end
9074 else
9075 error( "invalid modifier name", 2 )
9076 end
9077 end;
9078 __metatable = { };
9079 } )
9080 else
9081 error( "public index " .. tostring( k ) .. " not found", 2 )
9082 end
9083 end;
9084 __metatable = { };
9085 } )
9086 elseif k == "meta" then
9087 return setmetatable( { }, {
9088 __index = function( _, k )
9089 if k == "index" then
9090 return customindex
9091 elseif k == "newindex" then
9092 return customnewindex
9093 else
9094 return objectmeta[k]
9095 end
9096 end;
9097 __newindex = function( _, k, v )
9098 if k == "metatable" then
9099 error( "cannot change this metamethod", 2 )
9100 elseif k == "index" then
9101 if type( v ) == "function" or type( v ) == "table" or v == nil then
9102 customindex = v
9103 else
9104 error( "cannot use type " .. type( v ) .. " for index metamethod", 2 )
9105 end
9106 elseif k == "newindex" then
9107 if type( v ) == "function" or type( v ) == "table" or v == nil then
9108 customnewindex = v
9109 else
9110 error( "cannot use type " .. type( v ) .. " for newindex metamethod", 2 )
9111 end
9112 else
9113 objectmeta[k] = v
9114 end
9115 end;
9116 __metatable = { };
9117 } )
9118 else
9119 return object.static[k]
9120 end
9121 end
9122 meta.__newindex = function( _, k, v )
9123 object.private[k] = v
9124 end;
9125 meta.__call = function( self, ... )
9126 return self:new( ... )
9127 end;
9128 meta.__totring = function( )
9129 return "Class"
9130 end;
9131 meta.__metatable = "SwiftClass"
9132
9133 setmetatable( public, meta )
9134
9135 return public
9136end
9137
9138function class.public( name )
9139 local object = class.new( name )
9140 getfenv( )[name] = object
9141end
9142
9143function class.type( object )
9144 if type( object ) == "table" then
9145 if getmetatable( object ) == "SwiftClass" then
9146 return "Class"
9147 end
9148 if getmetatable( object ) == "SwiftClassObject" then
9149 return object:type( )
9150 end
9151 end
9152 return type( object )
9153end
9154
9155function class.typeOf( object, ... )
9156 local types = { ... }
9157 for i = 1, #types do
9158 if type( object ) == "table" and getmetatable( object ) == "SwiftClassObject" then
9159 if object:typeOf( types[i] ) then
9160 return types[i]
9161 end
9162 elseif type( object ) == "table" and getmetatable( object ) == "SwiftClass" then
9163 if types[i] == "Class" then
9164 return "Class"
9165 end
9166 else
9167 if type( object ) == types[i] then
9168 return types[i]
9169 end
9170 end
9171 end
9172 return false
9173end
9174
9175setmetatable( class, { __call = function( _, ... ) return class.new( ... ) end } )
9176local args = { ... }
9177local global = getfenv( )
9178local custom = setmetatable( { class = class, ARGS = args }, { __index = global } )
9179function custom.export( variable, value )
9180 if custom[variable] ~= nil or value then
9181 global[variable] = custom[variable] == nil and value or custom[variable]
9182 end
9183end
9184local required = { }
9185local function require( file, ... )
9186 if not required[file] and files[file] then
9187 required[file] = true
9188 if files[file].meta.type == "class" then
9189 custom[file] = class( file )
9190 end
9191 local fenv = setmetatable( { }, { __index = custom } )
9192 local f, err = loadstring( files[file].content, file .. ".lua" )
9193 if f then
9194 setfenv( f, fenv )
9195 local ok, data = pcall( f )
9196 if ok then
9197 if files[file].meta.type == "lib" then
9198 if data ~= nil then
9199 custom[file] = data
9200 return data
9201 else
9202 local t = { }
9203 for k, v in pairs( fenv ) do
9204 t[k] = v
9205 end
9206 custom[file] = t
9207 return t
9208 end
9209 end
9210 return data
9211 else
9212 error( data, 0 )
9213 end
9214 else
9215 error( err, 0 )
9216 end
9217 end
9218end
9219custom.require = require
9220
9221for i = 1, #autorun do
9222 require( autorun[i] )
9223end