Advertisement
Andy73

Nova OS Installer [clone] by awsumben13

Apr 21st, 2015
276
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[type="executable_package", name="n"]]
  2. local files = {["App"]={content="\
  3. \
  4. App.public \"name\"\
  5. App.public.name.write = false\
  6. App.public \"session\"\
  7. App.public.session.write = false\
  8. App.public \"runmode\"\
  9. App.public.runmode.write = false\
  10. App.public \"conf\"\
  11. App.public.conf.write = false\
  12. App.public \"icon\"\
  13. App.public.icon.write = false\
  14. App.public \"installpath\"\
  15. App.public.installpath.write = false\
  16. App.public \"single\"\
  17. App.public.single.write = false\
  18. \
  19. function 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\
  121. end\
  122. \
  123. function App.public:launch( args, noGUI )\
  124.     return AppInstance( self.public, self.session, args, noGUI )\
  125. end\
  126. \
  127. function App.public:readFile( path )\
  128.     return self.readfile( path )\
  129. end", meta={
  130.   type = "class",
  131. }};["Thread"]={content="\
  132. \
  133. local threads = { }\
  134. \
  135. Thread.public \"environment\"\
  136. function Thread.public.environment:read( )\
  137.     return self.env\
  138. end\
  139. function Thread.public.environment:write( value )\
  140.     if type( value ) ~= \"table\" then\
  141.         error( \"expected table\", 3 )\
  142.     end\
  143.     self.env = value\
  144. end\
  145. \
  146. Thread.public \"onException\" \"function\"\
  147. Thread.public \"onFinish\" \"function\"\
  148. \
  149. function 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\
  159. end\
  160. \
  161. function Thread.public:pause( )\
  162.     if self.state == \"running\" then\
  163.         self.state = \"paused\"\
  164.         return true\
  165.     end\
  166.     return false\
  167. end\
  168. \
  169. function Thread.public:resume( )\
  170.     if self.state == \"paused\" then\
  171.         self.state = \"running\"\
  172.         return true\
  173.     end\
  174.     return false\
  175. end\
  176. \
  177. function Thread.public:restart( )\
  178.     self.co = coroutine.create( self.thread )\
  179.     self.state = \"running\"\
  180. end\
  181. \
  182. function Thread.public:stop( )\
  183.     self.state = \"stopped\"\
  184. end\
  185. \
  186. function Thread.public:isRunning( )\
  187.     return self.state == \"running\"\
  188. end\
  189. \
  190. function Thread.public:isPaused( )\
  191.     return self.state == \"paused\"\
  192. end\
  193. \
  194. function 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\
  222. end\
  223. \
  224. function 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\
  231. end", meta={
  232.   type = "class",
  233. }};["UICode"]={content="\
  234. \
  235. require \"UIElement\"\
  236. \
  237. UICode:extends( UIElement )\
  238. \
  239. UICode.handlesKeys = true\
  240. UICode.handlesScroll = true\
  241. UICode.isScrollTarget = true\
  242. UICode.handlesTab = true\
  243. UICode.scrollDirection = \"vertical\"\
  244. \
  245. UICode.public \"showLines\" \"boolean\"\
  246. UICode.public \"syntax\" \"table\"\
  247. \
  248. UICode.public \"onChange\" \"function\"\
  249. UICode.public \"onCtrlKey\" \"function\"\
  250. \
  251. UICode.public \"selected\"\
  252. UICode.public.selected.write = false\
  253. function UICode.public.selected:read( )\
  254.     return not not self.selection\
  255. end\
  256. \
  257. function 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\
  287. end\
  288. \
  289. function 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\
  388. end\
  389. \
  390. function 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\
  404. end\
  405. \
  406. local tabsize = 4\
  407. \
  408. function 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 )\
  518. end\
  519. \
  520. function 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\
  544. end\
  545. \
  546. function 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\
  558. end\
  559. \
  560. function 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\
  581. end\
  582. \
  583. function 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\
  602. end\
  603. \
  604. function 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 )\
  617. end\
  618. \
  619. function 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\
  630. end\
  631. \
  632. function 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\
  645. end\
  646. \
  647. function UICode.public:setSelection( str )\
  648.     if self.selection then\
  649.         self:setSelection( str )\
  650.         self.public:updateCharacters( )\
  651.     end\
  652. end\
  653. \
  654. function UICode.public:write( str )\
  655.     self:write( str )\
  656.     self.public:updateCharacters( )\
  657. end\
  658. \
  659. function 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\
  719. end\
  720. \
  721. function 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\
  729. end\
  730. \
  731. function 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\
  737. end\
  738. \
  739. function 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\
  913. end\
  914. \
  915. function 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\
  929. end\
  930. \
  931. function 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( )\
  942. end\
  943. \
  944. function UICode.public:getCode( )\
  945.     return table.concat( self.lines, \"\\n\" )\
  946. end\
  947. \
  948. function 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\
  954. end\
  955. \
  956. function 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\
  962. end\
  963. \
  964. function UICode.public:focusOn( )\
  965.     local handler = self.public:getHandler( )\
  966.     handler:setFocus( self.public )\
  967. end\
  968. \
  969. function UICode.public:getDisplaySizeH( )\
  970.     return self.w - self.lineWidth\
  971. end\
  972. function UICode.public:getDisplaySizeV( )\
  973.     return self.h\
  974. end\
  975. function 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\
  981. end\
  982. function UICode.public:getContentSizeV( )\
  983.     return #self.lines\
  984. end\
  985. function UICode.public:getContentScrollH( )\
  986.     return self.scrollx\
  987. end\
  988. function UICode.public:getContentScrollV( )\
  989.     return self.scrolly\
  990. end\
  991. function UICode.public:setContentScrollH( scroll )\
  992.     self.scrollx = scroll\
  993. end\
  994. function UICode.public:setContentScrollV( scroll )\
  995.     self.scrolly = scroll\
  996. end", meta={
  997.   type = "class",
  998. }};["kernel"]={content="\
  999. \
  1000. -- initiate UI\
  1001. \
  1002. require \"core\"\
  1003. require \"nova_markup\"\
  1004. require \"Thread\"\
  1005. require \"UIHandler\"\
  1006. \
  1007. core.UI = UIHandler( 1, 1, term.getSize( ) )\
  1008. \
  1009. local 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\
  1020. end )\
  1021. function UIThread:onException( err )\
  1022.     core.log( \"UI error\", err )\
  1023.     self:restart( )\
  1024. end\
  1025. \
  1026. -- sessions\
  1027. \
  1028. function 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 )\
  1090. end\
  1091. \
  1092. if #fs.list( core.path .. \"/user\" ) == 0 then\
  1093.     setup( true )\
  1094. end\
  1095. \
  1096. core.session = Session( )", meta={
  1097.   type = "lib",
  1098. }};["MarkupGenericObject"]={content="\
  1099. \
  1100. require \"MarkupPage\"\
  1101. \
  1102. MarkupGenericObject.public \"page\" (MarkupPage)\
  1103. MarkupGenericObject.public.etype = \"unknown\"\
  1104. MarkupGenericObject.public \"static\"\
  1105. MarkupGenericObject.public.static.write = false\
  1106. MarkupGenericObject.public \"remove\" \"boolean\"\
  1107. \
  1108. function MarkupGenericObject:MarkupGenericObject( source )\
  1109.     self.width = { type = \"text\", value = \"auto\" }\
  1110.     self.height = { type = \"text\", value = \"auto\" }\
  1111. \
  1112.     self.source = source\
  1113. end\
  1114. \
  1115. function 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\
  1121. end\
  1122. \
  1123. function 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\
  1130. end\
  1131. \
  1132. function MarkupGenericObject.public:loadObject( positionHandler )\
  1133.     -- generate a list of MarkupLoadedObjects\
  1134. end\
  1135. \
  1136. function 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\
  1147. end\
  1148. \
  1149. function 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.     } )\
  1167. end\
  1168. \
  1169. MarkupGenericObject.static.attributes = { }\
  1170. \
  1171. function 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\
  1181. end\
  1182. \
  1183. function 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\
  1193. end\
  1194. \
  1195. function 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\
  1203. end\
  1204. \
  1205. function 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\
  1211. end", meta={
  1212.   type = "class",
  1213. }};["markup_parse"]={content="\
  1214. \
  1215. local parse_element\
  1216. \
  1217. local 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\
  1225. end\
  1226. \
  1227. local 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. }\
  1240. local s_tags = {\
  1241.     rule = true;\
  1242.     newline = true;\
  1243.     image = true;\
  1244.     input = true;\
  1245.     space = true;\
  1246.     iframe = true;\
  1247. }\
  1248. \
  1249. function 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\
  1319. end\
  1320. \
  1321. function 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\
  1364. end", meta={
  1365.   type = "lib",
  1366. }};["UIKeyHandler"]={content="\
  1367. \
  1368. require \"UIElement\"\
  1369. UIKeyHandler:extends( UIElement )\
  1370. \
  1371. UIKeyHandler.handlesMouse = false\
  1372. UIKeyHandler.handlesKeys = true\
  1373. \
  1374. UIKeyHandler.onKey = false\
  1375. UIKeyHandler.public \"onKey\" \"function\"\
  1376. UIKeyHandler.onChar = false\
  1377. UIKeyHandler.public \"onChar\" \"function\"\
  1378. \
  1379. function UIKeyHandler:UIKeyHandler( )\
  1380.     self:UIElement( 0, 0, 0, 0 )\
  1381.     return self.public\
  1382. end\
  1383. \
  1384. function UIKeyHandler.public:onKeyPress( key, lastkey )\
  1385.     if self.onKey then\
  1386.         self.onKey( self.public, key, lastkey )\
  1387.     end\
  1388. end\
  1389. function UIKeyHandler.public:onTextInput( char, lastkey )\
  1390.     if self.onChar then\
  1391.         self.onChar( self.public, char, lastkey )\
  1392.     end\
  1393. end", meta={
  1394.   type = "class",
  1395. }};["UIButton"]={content="\
  1396. \
  1397. require \"UIElement\"\
  1398. \
  1399. UIButton:extends( UIElement )\
  1400. \
  1401. UIButton.text = \"\"\
  1402. UIButton.public \"text\"\
  1403. \
  1404. UIButton.public \"bc\" \"number\"\
  1405. UIButton.public \"tc\" \"number\"\
  1406. \
  1407. UIButton.onClick = false\
  1408. UIButton.public \"onClick\" \"function\"\
  1409. UIButton.onDrag = false\
  1410. UIButton.public \"onDrag\" \"function\"\
  1411. \
  1412. UIButton.align = true\
  1413. UIButton.public \"align\" \"boolean\"\
  1414. \
  1415. UIButton.handlesEnter = true\
  1416. \
  1417. function 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\
  1423. end\
  1424. \
  1425. function 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 )\
  1455. end\
  1456. \
  1457. function 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.     }\
  1466. end\
  1467. \
  1468. function 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\
  1472. end\
  1473. \
  1474. function UIButton.public:onKeyPress( key )\
  1475.     if key == keys.enter and self.onClick then\
  1476.         self.onClick( self.public, 1, 1, \"enter\" )\
  1477.     end\
  1478. end", meta={
  1479.   type = "class",
  1480. }};["MarkupButton"]={content="\
  1481. \
  1482. require \"MarkupGenericObject\"\
  1483. \
  1484. MarkupButton:extends( MarkupGenericObject )", meta={
  1485.   type = "class",
  1486. }};["com"]={content="\
  1487. \
  1488. require \"encryption\"\
  1489. require \"network\"\
  1490. \
  1491. local modem\
  1492. local mside\
  1493. local messages = { }\
  1494. \
  1495. function 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\
  1502. end\
  1503. \
  1504. function 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\
  1511. end\
  1512. \
  1513. function 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\
  1540. end\
  1541. \
  1542. function 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\
  1585. end\
  1586. \
  1587. function 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\
  1609. end\
  1610. \
  1611. function 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\
  1637. end\
  1638. \
  1639. function 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\
  1650. end", meta={
  1651.   type = "lib",
  1652. }};["UIImage"]={content="\
  1653. \
  1654. require \"Image\"\
  1655. require \"UIElement\"\
  1656. \
  1657. UIImage.public \"image\" (Image)\
  1658. UIImage.public \"onClick\" \"function\"\
  1659. UIImage.public \"onDrag\" \"function\"\
  1660. UIImage.public \"onScroll\" \"function\"\
  1661. \
  1662. UIImage:extends( UIElement )\
  1663. \
  1664. function UIImage:UIImage( x, y, w, h, image ) -- Image object\
  1665.     self:UIElement( x, y, w, h )\
  1666.     self.image = image\
  1667.     return self.public\
  1668. end\
  1669. \
  1670. function 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 )\
  1691. end\
  1692. \
  1693. function UIImage.public:onMouseClick( rx, ry, button )\
  1694.     if self.onClick then\
  1695.         self.onClick( self.public, rx, ry, button )\
  1696.     end\
  1697. end\
  1698. \
  1699. function 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\
  1703. end\
  1704. \
  1705. function UIImage.public:onMouseScroll( rx, ry, dir )\
  1706.     if self.onScroll then\
  1707.         self.onScroll( self.public, rx, ry, dir )\
  1708.     end\
  1709. end", meta={
  1710.   type = "class",
  1711. }};["nova_markup"]={content="\
  1712. \
  1713. require \"markup_parse\"\
  1714. \
  1715. objects = {\
  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. \
  1732. FileData.public \"content\" \"string\"\
  1733. FileData.public \"meta\"\
  1734. FileData.public \"password\" \"string\"\
  1735. \
  1736. function 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\
  1742. end\
  1743. function FileData.public.meta:read( )\
  1744.     return self.metadata\
  1745. end\
  1746. \
  1747. function 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\
  1767. end\
  1768. \
  1769. function 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\
  1780. end", meta={
  1781.   type = "class",
  1782. }};["AppInstance"]={content="\
  1783. \
  1784. AppInstance.public \"app\"\
  1785. AppInstance.public.app.write = false\
  1786. AppInstance.public \"session\"\
  1787. AppInstance.public.session.write = false\
  1788. AppInstance.public \"UI\"\
  1789. AppInstance.public.UI.write = false\
  1790. AppInstance.public \"events\"\
  1791. AppInstance.public.events.write = false\
  1792. AppInstance.public \"content\"\
  1793. AppInstance.public.content.write = false\
  1794. AppInstance.public \"sandbox\"\
  1795. AppInstance.public.sandbox.write = false\
  1796. AppInstance.public \"public_variables\"\
  1797. AppInstance.public.public_variables.write = false\
  1798. AppInstance.public \"title\" \"string\"\
  1799. \
  1800. function 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\
  1893. end\
  1894. \
  1895. function 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\
  1904. end\
  1905. \
  1906. function 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\"\
  1912. end\
  1913. \
  1914. function AppInstance.public:hideUI( )\
  1915.     self.public:event \"onUnFocusPre\"\
  1916.     if self.UI then\
  1917.         self.UI:remove( )\
  1918.     end\
  1919.     self.public:event \"onUnFocusPost\"\
  1920. end\
  1921. \
  1922. function 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\
  1936. end\
  1937. \
  1938. function 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\
  1949. end\
  1950. \
  1951. function 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\
  1959. end\
  1960. \
  1961. function 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\
  1971. end", meta={
  1972.   type = "class",
  1973. }};["Archive"]={content="\
  1974. \
  1975. require \"Drive\"\
  1976. \
  1977. local rfs = fs\
  1978. \
  1979. function Archive:Archive( savepath )\
  1980.     self.files = { }\
  1981.     self.savepath = savepath\
  1982. \
  1983.     return self.public\
  1984. end\
  1985. \
  1986. function 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 )\
  2006. end\
  2007. \
  2008. function 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\"\
  2025. end\
  2026. \
  2027. function 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\
  2044. end\
  2045. \
  2046. function 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 )\
  2056. end\
  2057. \
  2058. function 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 )\
  2289. end", meta={
  2290.   type = "class",
  2291. }};["markup"]={content="\
  2292. \
  2293. require \"parser\"\
  2294. \
  2295. local 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\
  2368. end\
  2369. \
  2370. local 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\
  2383. end\
  2384. \
  2385. parser.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\
  2393. end, nil, nil )\
  2394. \
  2395. parser.registerTag( \"frame\", true, true, function( parent, attributes, content, scripts, ids, errors )\
  2396.     local ob = loadStandardAttributes( UIFrame, attributes, scripts, ids, errors, parent )\
  2397.     return ob\
  2398. end, nil, nil )\
  2399. \
  2400. parser.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\
  2417. end, nil, nil )\
  2418. \
  2419. parser.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\
  2432. end, nil, nil )\
  2433. \
  2434. parser.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\
  2457. end, nil, nil )\
  2458. \
  2459. function 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\
  2494. end", meta={
  2495.   type = "lib",
  2496. }};["UIText"]={content="\
  2497. \
  2498. require \"UIElement\"\
  2499. \
  2500. UIText:extends( UIElement )\
  2501. \
  2502. UIText.text = \"\"\
  2503. UIText.public \"text\"\
  2504. \
  2505. UIText.public \"bc\" \"number\"\
  2506. UIText.public \"tc\" \"number\"\
  2507. \
  2508. function 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\
  2514. end\
  2515. \
  2516. function 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 )\
  2531. end", meta={
  2532.   type = "class",
  2533. }};["network"]={content="\
  2534. \
  2535. require \"com\"\
  2536. require \"handshake\"\
  2537. \
  2538. local buffers = {\
  2539.     established = { }; -- .listen()\
  2540. }\
  2541. local channels = { }\
  2542. local opening = false\
  2543. local channelID = 0\
  2544. \
  2545. function 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\
  2552. end\
  2553. \
  2554. function 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\
  2590. end\
  2591. \
  2592. function 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\
  2604. end\
  2605. \
  2606. function 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 )\
  2611. end\
  2612. \
  2613. function 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\
  2625. end\
  2626. \
  2627. function 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\
  2640. end\
  2641. \
  2642. -- internal functions\
  2643. \
  2644. function addToBuffer( buffer, data )\
  2645.     buffers[buffer] = buffers[buffer] or { }\
  2646.     table.insert( buffers[buffer], data )\
  2647. end\
  2648. \
  2649. function 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 } )\
  2657. end\
  2658. \
  2659. function clearBuffers( )\
  2660.     for key in pairs( buffers ) do\
  2661.         buffers[key] = { }\
  2662.     end\
  2663. end", meta={
  2664.   type = "lib",
  2665. }};["MarkupParagraph"]={content="\
  2666. \
  2667. require \"MarkupGenericObject\"\
  2668. \
  2669. MarkupParagraph:extends( MarkupGenericObject )\
  2670. \
  2671. MarkupParagraph.public.etype = \"paragraph\"\
  2672. \
  2673. function MarkupParagraph:MarkupParagraph( ... )\
  2674.     self:MarkupGenericObject( ... )\
  2675. \
  2676.     self.innerNML = { }\
  2677. \
  2678.     return self.public\
  2679. end\
  2680. \
  2681. function 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\
  2704. end\
  2705. \
  2706. function 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\
  2808. end", meta={
  2809.   type = "class",
  2810. }};["DocumentEnvironment"]={content="\
  2811. \
  2812. function 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\
  2933. end", meta={
  2934.   type = "class",
  2935. }};["buffer"]={content="\
  2936. \
  2937. local screen = { }\
  2938. local last = { }\
  2939. local w, h = term.getSize( )\
  2940. local bgc = colours.white\
  2941. local cb\
  2942. \
  2943. for 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\
  2950. end\
  2951. \
  2952. function 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\
  2968. end\
  2969. \
  2970. function 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\
  2975. end\
  2976. \
  2977. function 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\
  2982. end\
  2983. \
  2984. function setCursorBlink( x, y, col )\
  2985.     cb = { x = x, y = y, col = col }\
  2986. end\
  2987. \
  2988. function 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\
  3032. end\
  3033. \
  3034. function 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\
  3042. end\
  3043. \
  3044. function setBackgroundColour( col )\
  3045.     bgc = col\
  3046. end\
  3047. \
  3048. function reset( )\
  3049.     for y = 1, h do\
  3050.         last[y] = { }\
  3051.         for x = 1, w do\
  3052.             last[y][x] = { }\
  3053.         end\
  3054.     end\
  3055. end", 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)\
  3063. local 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. \
  3067. local 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\
  3097. end\
  3098. \
  3099. local 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\
  3128. end\
  3129. \
  3130. local 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\
  3151. end\
  3152. \
  3153. local 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\
  3172. end\
  3173. \
  3174. local _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. \
  3198. function registerTag( name, content, children, whenloaded, parents, childrenallowed )\
  3199.     _tags[name] = { content = content, children = children, whenloaded = whenloaded, parents = parents, childrenallowed = childrenallowed }\
  3200. end\
  3201. \
  3202. local loadString\
  3203. \
  3204. local 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\
  3279. end\
  3280. \
  3281. function loadString( str, parent )\
  3282.     return groupTags( readTags( findTags( str ) ), str, parent or \"frame\" )\
  3283. end\
  3284. \
  3285. local loadElements\
  3286. function 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\
  3299. end\
  3300. \
  3301. function load( str, frame )\
  3302.     local tags = loadString( str )\
  3303.     return loadElements( tags, frame )\
  3304. end", meta={
  3305.   type = "lib",
  3306. }};["UITextbox"]={content="\
  3307. \
  3308. local 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 )\
  3326. end\
  3327. \
  3328. local 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\
  3341. end\
  3342. \
  3343. require \"UIElement\"\
  3344. UITextbox:extends( UIElement )\
  3345. \
  3346. UITextbox.handlesKeys = true\
  3347. UITextbox.tabIndex =  true\
  3348. \
  3349. UITextbox.public \"text\"\
  3350. \
  3351. UITextbox.public \"onChange\" \"function\"\
  3352. function 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\
  3363. end\
  3364. \
  3365. UITextbox.bc = colours.lightGrey\
  3366. UITextbox.fbc = colours.white\
  3367. UITextbox.tc = colours.black\
  3368. \
  3369. UITextbox.public \"bc\" \"number\"\
  3370. UITextbox.public \"fbc\" \"number\"\
  3371. UITextbox.public \"tc\" \"number\"\
  3372. \
  3373. UITextbox.onEnter = false\
  3374. UITextbox.public \"onEnter\" \"function\"\
  3375. \
  3376. function 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\
  3383. end\
  3384. \
  3385. function 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\
  3402. end\
  3403. \
  3404. function 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\
  3420. end\
  3421. \
  3422. function 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 )\
  3442. end\
  3443. \
  3444. function UITextbox.public:onMouseClick( rx, ry, button )\
  3445.     local pos = self:getCursorPos( rx, ry )\
  3446.     self.cursor = pos\
  3447. end\
  3448. \
  3449. function 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\
  3476. end\
  3477. \
  3478. function 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\
  3496. end\
  3497. \
  3498. function 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\
  3504. end\
  3505. \
  3506. function 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\
  3512. end\
  3513. \
  3514. function UITextbox.public:focusOn( )\
  3515.     local handler = self.public:getHandler( )\
  3516.     handler:setFocus( self.public )\
  3517. end", meta={
  3518.   type = "class",
  3519. }};["UIScrollBar"]={content="\
  3520. \
  3521. require \"UIElement\"\
  3522. \
  3523. UIScrollBar:extends( UIElement )\
  3524. \
  3525. UIScrollBar.public \"bc\"  \"number\"\
  3526. UIScrollBar.public \"tc\"  \"number\"\
  3527. \
  3528. UIScrollBar.handlesScroll = true\
  3529. \
  3530. function 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\
  3549. end\
  3550. \
  3551. function 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\
  3561. end\
  3562. \
  3563. --[[\
  3564.     barsize/traysize = framesize/contentsize\
  3565.     barposition = traysize * contentscroll / contentsize\
  3566. ]]\
  3567. \
  3568. function 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\
  3593. end\
  3594. \
  3595. function 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 )\
  3616. end\
  3617. \
  3618. function 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\
  3639. end\
  3640. \
  3641. function 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\
  3658. end\
  3659. \
  3660. function 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\
  3675. end", meta={
  3676.   type = "class",
  3677. }};["Drive"]={content="\
  3678. \
  3679. function 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\
  3692. end\
  3693. \
  3694. function Drive.meta:index( key )\
  3695.     return self.methods[key]\
  3696. end\
  3697. \
  3698. for 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\
  3704. end\
  3705. \
  3706. local function combine( p1, p2 )\
  3707.     if type( p2 ) ~= \"string\" then\
  3708.         return error \"expected string\"\
  3709.     end\
  3710.     return fs.combine( p1, p2 )\
  3711. end\
  3712. \
  3713. function 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. \
  3767. end", meta={
  3768.   type = "class",
  3769. }};["MarkupPositionHandler"]={content="\
  3770. \
  3771. MarkupPositionHandler.public \"x\"\
  3772. MarkupPositionHandler.public.x.write = false\
  3773. MarkupPositionHandler.public \"y\"\
  3774. MarkupPositionHandler.public.y.write = false\
  3775. MarkupPositionHandler.public \"w\" \"number\"\
  3776. MarkupPositionHandler.public \"h\" \"number\"\
  3777. \
  3778. function 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\
  3788. end\
  3789. \
  3790. local inline = {\
  3791.     text = true;\
  3792.     image = true;\
  3793.     frame = true;\
  3794.     button = true;\
  3795.     input = true;\
  3796.     space = true;\
  3797.     newline = true;\
  3798. }\
  3799. \
  3800. function 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\
  3844. end\
  3845. \
  3846. function 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 )\
  3894. end", meta={
  3895.   type = "class",
  3896. }};["UI"]={content="\
  3897. require \"buffer\"\
  3898. require \"display\"\
  3899. require \"markup\"\
  3900. require \"parser\"\
  3901. require \"stencil\"", meta={}};["display"]={content="\
  3902. \
  3903. local 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\
  3908. end\
  3909. \
  3910. function 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\
  3939. end\
  3940. \
  3941. function 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\
  3972. end\
  3973. \
  3974. function 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\
  4009. end\
  4010. \
  4011. function sframe( frame, x, y, w, h ) -- scrolling frame (auto scrollbar)\
  4012. \
  4013. end\
  4014. \
  4015. function 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\
  4038. end\
  4039. \
  4040. function 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\
  4146. end", meta={
  4147.   type = "lib",
  4148. }};["UIFrame"]={content="\
  4149. \
  4150. require \"UIElement\"\
  4151. \
  4152. UIFrame:extends( UIElement )\
  4153. \
  4154. UIFrame.handlesMouse = false\
  4155. UIFrame.handlesScroll = true\
  4156. UIFrame.isScrollTarget = true\
  4157. UIFrame.scrollDirection = \"vertical\"\
  4158. \
  4159. UIFrame.scrolls = true\
  4160. UIFrame.public \"scrolls\" \"boolean\"\
  4161. \
  4162. function UIFrame:UIFrame( x, y, w, h, direction )\
  4163.     self:UIElement( x, y, w, h )\
  4164.     self.scrollDirection = direction or \"vertical\"\
  4165.     return self.public\
  4166. end\
  4167. \
  4168. function 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\
  4200. end\
  4201. \
  4202. function 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\
  4226. end\
  4227. \
  4228. function UIFrame.public:getDisplaySizeH( )\
  4229.     return self.w\
  4230. end\
  4231. function UIFrame.public:getDisplaySizeV( )\
  4232.     return self.h\
  4233. end\
  4234. \
  4235. function 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\
  4241. end\
  4242. function 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\
  4248. end\
  4249. \
  4250. function UIFrame.public:getContentScrollH( )\
  4251.     return -self.cx\
  4252. end\
  4253. function UIFrame.public:getContentScrollV( )\
  4254.     return -self.cy\
  4255. end\
  4256. \
  4257. function UIFrame.public:setContentScrollH( scroll )\
  4258.     self.cx = -scroll\
  4259. end\
  4260. function UIFrame.public:setContentScrollV( scroll )\
  4261.     self.cy = -scroll\
  4262. end", meta={
  4263.   type = "class",
  4264. }};["UIInput"]={content="\
  4265. \
  4266. require \"UIElement\"\
  4267. \
  4268. UIInput:extends( UIElement )\
  4269. \
  4270. UIInput.handlesKeys = true\
  4271. UIInput.handlesScroll = true\
  4272. UIInput.tabIndex =  true\
  4273. \
  4274. UIInput.bc = colours.grey\
  4275. UIInput.fbc = colours.lightGrey\
  4276. UIInput.tc = colours.black\
  4277. UIInput.hbc = colours.blue\
  4278. UIInput.htc = colours.white\
  4279. \
  4280. UIInput.public \"bc\" \"number\"\
  4281. UIInput.public \"fbc\" \"number\"\
  4282. UIInput.public \"tc\" \"number\"\
  4283. UIInput.public \"hbc\" \"number\"\
  4284. UIInput.public \"htc\" \"number\"\
  4285. \
  4286. UIInput.onEnter = false\
  4287. UIInput.public \"onEnter\" \"function\"\
  4288. UIInput.public \"onCtrlKey\" \"function\"\
  4289. \
  4290. UIInput.public \"text\" \"string\"\
  4291. UIInput.public \"mask\" \"string\"\
  4292. \
  4293. UIInput.public \"scroll\" \"number\"\
  4294. \
  4295. UIInput.public \"mask\"\
  4296. function 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\
  4302. end\
  4303. \
  4304. function 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\
  4313. end\
  4314. \
  4315. function 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 )\
  4358. end\
  4359. \
  4360. function 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\
  4368. end\
  4369. \
  4370. function 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 )\
  4373. end\
  4374. \
  4375. function 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 )\
  4379. end\
  4380. \
  4381. function UIInput:getSelection( )\
  4382.     return self.text:sub( math.min( self.selection, self.cursor ), math.max( self.selection, self.cursor ) )\
  4383. end\
  4384. \
  4385. function UIInput.public:select( min, max )\
  4386.     self.selection = min\
  4387.     self.cursor = max\
  4388. end\
  4389. \
  4390. function UIInput.public:onMouseClick( rx, ry, button )\
  4391.     self.selection = false\
  4392.     self:setCursorPos( rx + self.scroll )\
  4393. end\
  4394. \
  4395. function UIInput.public:onMouseDrag( rx, ry )\
  4396.     if not self.selection then\
  4397.         self.selection = self.cursor\
  4398.     end\
  4399.     self:setCursorPos( rx + self.scroll )\
  4400. end\
  4401. \
  4402. function UIInput.public:onMouseScroll( rx, ry, dir )\
  4403.     self.scroll = math.max( math.min( self.scroll + dir, #self.text - self.w + 1 ), 0 )\
  4404. end\
  4405. \
  4406. function 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\
  4485. end\
  4486. \
  4487. function 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\
  4498. end\
  4499. \
  4500. function 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\
  4506. end\
  4507. \
  4508. function 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\
  4515. end\
  4516. \
  4517. function UIInput.public:focusOn( )\
  4518.     local handler = self.public:getHandler( )\
  4519.     if handler then\
  4520.         handler:setFocus( self.public )\
  4521.     end\
  4522. end", meta={
  4523.   type = "class",
  4524. }};["stringutils"]={content="\
  4525. \
  4526. function 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\
  4540. end\
  4541. \
  4542. function 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\
  4551. end\
  4552. \
  4553. local 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 )\
  4568. end\
  4569. \
  4570. function 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\
  4581. end", meta={
  4582.   type = "lib",
  4583. }};["UIElement"]={content="\
  4584. \
  4585. require \"UIHandler\"\
  4586. \
  4587. UIElement.public \"x\" \"number\"\
  4588. UIElement.public \"y\" \"number\"\
  4589. UIElement.public \"w\" \"number\"\
  4590. UIElement.public \"h\" \"number\"\
  4591. UIElement.public \"cx\" \"number\"\
  4592. UIElement.public \"cy\" \"number\"\
  4593. \
  4594. UIElement.public \"parent\"\
  4595. function 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\
  4601. end\
  4602. \
  4603. UIElement.handlesMouse = true\
  4604. UIElement.handlesScroll = false\
  4605. UIElement.handlesTab = false\
  4606. UIElement.tabIndex = false\
  4607. \
  4608. UIElement.public \"handlesMouse\"\
  4609. UIElement.public.handlesMouse.write = false\
  4610. UIElement.public \"handlesKeys\"\
  4611. UIElement.public.handlesKeys.write = false\
  4612. UIElement.public \"handlesScroll\"\
  4613. UIElement.public.handlesScroll.write = false\
  4614. UIElement.public \"handlesTab\"\
  4615. UIElement.public.handlesTab.write = false\
  4616. UIElement.public \"handlesEnter\"\
  4617. UIElement.public.handlesEnter.write = false\
  4618. UIElement.public \"tabIndex\" \"boolean\"\
  4619. \
  4620. UIElement.isScrollTarget = false\
  4621. UIElement.public \"isScrollTarget\"\
  4622. UIElement.public.isScrollTarget.write = false\
  4623. UIElement.scrollDirection = \"none\"\
  4624. UIElement.public \"scrollDirection\"\
  4625. UIElement.public.scrollDirection.write = false\
  4626. \
  4627. UIElement.public \"whenFocussed\" \"function\"\
  4628. UIElement.public \"whenUnFocussed\" \"function\"\
  4629. \
  4630. function 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\
  4639. end\
  4640. \
  4641. -- children\
  4642. \
  4643. function 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\
  4651. end\
  4652. \
  4653. function 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\
  4666. end\
  4667. \
  4668. function UIElement.public:getChildren( )\
  4669.     local t = { }\
  4670.     for i = 1, #self.children do\
  4671.         t[i] = self.children[i]\
  4672.     end\
  4673.     return t\
  4674. end\
  4675. \
  4676. function UIElement.public:clearChildren( )\
  4677.     for i = #self.children, 1, -1 do\
  4678.         self.children[i]:remove( )\
  4679.     end\
  4680. end\
  4681. \
  4682. local 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\
  4691. end\
  4692. \
  4693. function 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\
  4705. end\
  4706. \
  4707. -- parent dependant\
  4708. \
  4709. function 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\
  4718. end\
  4719. \
  4720. function UIElement.public:remove( )\
  4721.     if self.parent then\
  4722.         return self.parent:removeChild( self.public )\
  4723.     end\
  4724.     return false\
  4725. end\
  4726. \
  4727. function 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\
  4737. end\
  4738. \
  4739. function 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\
  4744. end\
  4745. \
  4746. function 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\
  4751. end\
  4752. \
  4753. function UIElement.public:centre( )\
  4754.     self.public:centreX( )\
  4755.     self.public:centreY( )\
  4756. end\
  4757. \
  4758. -- sibling dependant\
  4759. \
  4760. function 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\
  4779. end\
  4780. \
  4781. function 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\
  4800. end\
  4801. \
  4802. -- callbacks\
  4803. \
  4804. function 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\
  4812. end\
  4813. \
  4814. function 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 )\
  4824. end\
  4825. \
  4826. function UIElement.public:onMouseClick( rx, ry, button )\
  4827. \
  4828. end\
  4829. \
  4830. function UIElement.public:onMouseDrag( rx, ry, cx, cy, button )\
  4831. \
  4832. end\
  4833. \
  4834. function UIElement.public:onMouseScroll( rx, ry, dir )\
  4835. \
  4836. end\
  4837. \
  4838. function UIElement.public:onKeyPress( key, lastkey )\
  4839. \
  4840. end\
  4841. \
  4842. function UIElement.public:onTextInput( text, lastkey )\
  4843. \
  4844. end\
  4845. \
  4846. function UIElement.public:onFocus( )\
  4847.     if type( self.whenFocussed ) == \"function\" then\
  4848.         self.whenFocussed( self.public )\
  4849.     end\
  4850. end\
  4851. \
  4852. function UIElement.public:onUnFocus( )\
  4853.     if type( self.whenUnFocussed ) == \"function\" then\
  4854.         self.whenUnFocussed( self.public )\
  4855.     end\
  4856. end", meta={
  4857.   type = "class",
  4858. }};["filesystem"]={content="\
  4859. \
  4860. -- drives\
  4861. \
  4862. local drives = { }\
  4863. drives.C = Drive.redirect \"\"\
  4864. \
  4865. local function formatDriveName( name )\
  4866.     if type( name ) == \"string\" then\
  4867.         return name\
  4868.     end\
  4869.     return false\
  4870. end\
  4871. \
  4872. local 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\
  4881. end\
  4882. \
  4883. function 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\
  4896. end\
  4897. \
  4898. function unmount( name )\
  4899.     drives[formatDriveName( name )] = nil\
  4900. end\
  4901. \
  4902. function getmounted( name )\
  4903.     return drives[formatDriveName( name )]\
  4904. end\
  4905. \
  4906. function listmounted( )\
  4907.     local t = { }\
  4908.     for name in pairs( drives ) do\
  4909.         t[#t+1] = name\
  4910.     end\
  4911.     return t\
  4912. end\
  4913. \
  4914. -- types\
  4915. \
  4916. local extensions = {\
  4917.     txt = \"text\";\
  4918.     lua = \"lua\";\
  4919.     nim = \"nova_image\";\
  4920.     nac = \"archive\";\
  4921. }\
  4922. local 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. }\
  4935. local handlers = { }\
  4936. \
  4937. function addExtension( ext, type )\
  4938.     extensions[ext] = type\
  4939. end\
  4940. \
  4941. function addTypeDescription( type, description )\
  4942.     types[type] = description\
  4943. end\
  4944. \
  4945. function getTypeDescription( type )\
  4946.     return types[type]\
  4947. end\
  4948. \
  4949. function 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 )\
  4957. end\
  4958. \
  4959. function 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 )\
  4968. end\
  4969. \
  4970. function getHandlers( type )\
  4971.     return { unpack( handlers[type] or { } ) }\
  4972. end\
  4973. \
  4974. function 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\
  4986. end\
  4987. \
  4988. -- caching\
  4989. \
  4990. local cache = { }\
  4991. local function filecacheset( path, value )\
  4992.     path = path:lower( )\
  4993.     cache[path] = value\
  4994. end\
  4995. local function filecacheget( path )\
  4996.     path = path:lower( )\
  4997.     return cache[path]\
  4998. end\
  4999. \
  5000. function clearCache( )\
  5001.     cache = { }\
  5002. end\
  5003. \
  5004. -- read/write stuff (cache stuff!)\
  5005. \
  5006. local 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\
  5024. end\
  5025. local 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\
  5040. end\
  5041. \
  5042. -- name operations\
  5043. \
  5044. function getDirectory( path )\
  5045.     if not path:find \"/\" then return \"\" end\
  5046.     return path:gsub( \"/.-$\", \"\" ) -- remove filename\
  5047. end\
  5048. \
  5049. function getPath( path )\
  5050.     if not path:find \"/\" then return \"\" end\
  5051.     return path:gsub( \"/.-$\", \"\" ) -- remove filename\
  5052. end\
  5053. \
  5054. function 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\
  5060. end\
  5061. \
  5062. function 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 .\
  5068. end\
  5069. \
  5070. function merge( path, ... )\
  5071.     local paths = { ... }\
  5072.     for i = 1, #paths do\
  5073.         path = path .. \"/\" .. tostring( paths[i] )\
  5074.     end\
  5075.     return path:gsub( \"//+\", \"/\" )\
  5076. end\
  5077. \
  5078. -- file methods\
  5079. \
  5080. function readfile( path, password )\
  5081.     local content, err = read( path )\
  5082.     if content then\
  5083.         return FileData( content, password )\
  5084.     end\
  5085.     return false, err\
  5086. end\
  5087. \
  5088. function writefile( path, data, password )\
  5089.     if password then\
  5090.         data.password = password\
  5091.     end\
  5092.     return write( path, data:compile( ) )\
  5093. end\
  5094. \
  5095. function 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\
  5105. end\
  5106. \
  5107. function 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\
  5123. end\
  5124. \
  5125. function 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]\
  5134. end\
  5135. \
  5136. function 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\
  5147. end\
  5148. \
  5149. function 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\
  5158. end\
  5159. \
  5160. -- directory methods\
  5161. \
  5162. function 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\
  5179. end\
  5180. \
  5181. function 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\"\
  5197. end\
  5198. \
  5199. function emptyDirectory( path, filter )\
  5200. \
  5201. end\
  5202. \
  5203. -- existance checking\
  5204. \
  5205. function 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 ) )\
  5213. end\
  5214. \
  5215. function 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\
  5225. end\
  5226. \
  5227. function 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 ) )\
  5234. end\
  5235. \
  5236. -- space checking\
  5237. \
  5238. function 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 ) )\
  5244. end\
  5245. \
  5246. function 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 ) )\
  5252. end\
  5253. \
  5254. function 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 ) )\
  5260. end\
  5261. \
  5262. -- mixed methods\
  5263. \
  5264. function 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 ) )\
  5271. end\
  5272. \
  5273. function 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\
  5293. end\
  5294. \
  5295. function 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\
  5316. end\
  5317. \
  5318. function 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\
  5341. end\
  5342. \
  5343. function 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 ) )\
  5351. end\
  5352. \
  5353. function 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 )\
  5362. end", meta={
  5363.   type = "lib",
  5364. }};["Session"]={content="\
  5365. \
  5366. local imageraw = { }\
  5367. imageraw[0] = [[ F BF BF BF  F \
  5368. BF  F  F  F BF \
  5369. BF  F  F  F BF \
  5370. BF  F  F  F BF \
  5371. BF  F  F  F BF \
  5372. BF  F  F  F BF \
  5373. F BF BF BF  F ]]\
  5374. imageraw[1] = [[ F BF BF  F  F \
  5375. BF  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 \
  5380. BF BF BF BF BF ]]\
  5381. imageraw[2] = [[ F BF BF BF  F \
  5382. BF  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 \
  5387. BF BF BF BF BF ]]\
  5388. imageraw[3] = [[ F BF BF BF  F \
  5389. BF  F  F  F BF \
  5390. F  F  F  F BF \
  5391. F BF BF BF  F \
  5392. F  F  F  F BF \
  5393. BF  F  F  F BF \
  5394. F BF BF BF  F ]]\
  5395. imageraw[4] = [[ F  F  F BF  F \
  5396. F  F BF BF  F \
  5397. F BF  F BF  F \
  5398. F BF  F BF  F \
  5399. BF  F  F BF  F \
  5400. BF BF BF BF BF \
  5401. F  F  F BF  F ]]\
  5402. imageraw[5] = [[BF BF BF BF BF \
  5403. BF  F  F  F  F \
  5404. BF  F  F  F  F \
  5405. BF BF BF BF  F \
  5406. F  F  F  F BF \
  5407. BF  F  F  F BF \
  5408. F BF BF BF  F ]]\
  5409. imageraw[6] = [[ F BF BF BF  F \
  5410. BF  F  F  F BF \
  5411. BF  F  F  F  F \
  5412. BF BF BF BF  F \
  5413. BF  F  F  F BF \
  5414. BF  F  F  F BF \
  5415. F BF BF BF  F ]]\
  5416. imageraw[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 \
  5422. BF  F  F  F  F ]]\
  5423. imageraw[8] = [[ F BF BF BF  F \
  5424. BF  F  F  F BF \
  5425. BF  F  F  F BF \
  5426. F BF BF BF  F \
  5427. BF  F  F  F BF \
  5428. BF  F  F  F BF \
  5429. F BF BF BF  F ]]\
  5430. imageraw[9] = [[ F BF BF BF  F \
  5431. BF  F  F  F BF \
  5432. BF  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. \
  5438. local images = { }\
  5439. for i = 0, 9 do\
  5440.     images[i] = Image( 5, 7 )\
  5441.     images[i]:loadstr( imageraw[i] or \"\" )\
  5442. end\
  5443. \
  5444. images.home = Image( 8, 5 ):loadstr [[ F  F CF CF CF CF  F  F \
  5445. F CF CF CF CF CF CF  F \
  5446. CF 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 ]]\
  5449. images.shutdown = Image( 7, 5 ):loadstr [[ F  F  F EF  F  F  F \
  5450. F EF  F EF  F EF  F \
  5451. EF  F  F  F  F  F EF \
  5452. EF  F  F  F  F  F EF \
  5453. F EF EF EF EF EF  F ]]\
  5454. images.screenshot = Image( 8, 5 ):loadstr [[ F  F  F  F  F 7F 7F  F \
  5455. 7F 7F FF FF 7F 7F 7F 7F \
  5456. 7F FF 0F 3F FF 7F EF 7F \
  5457. 7F FF 3F 3F FF 7F DF 7F \
  5458. 7F 7F FF FF 7F 7F 7F 7F ]]\
  5459. \
  5460. images.files = Image( 9, 3 ):loadstr [[1F 1F 1F 1F 1F 1F 1F 1F 1F \
  5461. 4F 4F 47F47i47l47e47s4F 4F \
  5462. 4F 4F 4F 4F 4F 4F 4F 4F 4F ]]\
  5463. images.edit = Image( 9, 3 ):loadstr [[78178:77 0F 07E07d07i07t0F \
  5464. 78278:77 0F 08-08-08-08-0F \
  5465. 78378:77 0F 08-08-08-08-0F ]]\
  5466. images.paint = Image( 9, 3 ):loadstr [[BF 3F 3B-3B-3B-3B-3B-3F BF \
  5467. BF 3F 3EP3Ea3Ei3En3Et3F BF \
  5468. BF 3F 3B-3B-3B-3B-3B-3F BF ]]\
  5469. images.run = Image( 9, 3 ):loadstr [[F0RF0uF0nF4 F4 F4 F4 F4 F0 \
  5470. F4 F4CF4rF4aF4fF4tF4OF4SF0 \
  5471. F4>F4 F4_F0 F0 F0 F0 F0 F0 ]]\
  5472. images.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. \
  5477. images.folder = Image( 5, 3 ):loadstr [[10 10 10 0F 0F \
  5478. 40 40 40 4F 4F \
  5479. 40 40 40 40 40 ]]\
  5480. \
  5481. local 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\
  5592. end\
  5593. \
  5594. Session.public \"account\"\
  5595. Session.public.account.write = false\
  5596. Session.public \"UI\"\
  5597. Session.public.UI.write = false\
  5598. Session.public \"app_paths\"\
  5599. Session.public.app_paths.write = false\
  5600. Session.public \"apps\"\
  5601. Session.public.apps.write = false\
  5602. Session.public \"running\"\
  5603. Session.public.running.write = false\
  5604. Session.public \"background\"\
  5605. Session.public.background.write = false\
  5606. \
  5607. function 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\
  5667. end\
  5668. \
  5669. function 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\
  5682. end\
  5683. \
  5684. function 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\
  5691. end\
  5692. \
  5693. function Session.public:isRegistered( name )\
  5694.     return not not self.apps[name]\
  5695. end\
  5696. \
  5697. function 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\
  5732. end\
  5733. \
  5734. function 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\
  5746. end\
  5747. \
  5748. function 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\
  5755. end\
  5756. \
  5757. function 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\"\
  5772. end\
  5773. \
  5774. function 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\"\
  5796. end\
  5797. \
  5798. function Session.public:newThread( thread )\
  5799.     table.insert( self.threads, thread )\
  5800.     return thread\
  5801. end\
  5802. \
  5803. function 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\
  5952. end\
  5953. \
  5954. function 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\
  6117. end\
  6118. \
  6119. function 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       \
  6130. B0    B0       B0    B0             B0    B0             B0       B0       B0    \
  6131. B0       B0    B0    B0             B0       B0       B0       B0 B0 B0 B0 B0 B0 \
  6132. B0          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( ) )\
  6140. end", meta={
  6141.   type = "class",
  6142. }};["Sandbox"]={content="\
  6143. \
  6144. Sandbox.public \"environment\"\
  6145. Sandbox.public.environment.write = false\
  6146. Sandbox.public \"instance\"\
  6147. Sandbox.public.instance.write = false\
  6148. \
  6149. function 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. \
  6180.     if instance.app.runmode ~= \"compatibility\" then\
  6181.         -- classes\
  6182.         self.environment.class = class;\
  6183.         self.environment.Drive = Drive;\
  6184.         self.environment.Archive = Archive;\
  6185.         self.environment.FileData = FileData;\
  6186. \
  6187.         -- NovaUI\
  6188.         self.environment.NovaUI = { }\
  6189.         self.environment.NovaUI.Image = Image;\
  6190.         self.environment.NovaUI.UIButton = UIButton;\
  6191.         self.environment.NovaUI.UICanvas = UICanvas;\
  6192.         self.environment.NovaUI.UICode = UICode;\
  6193.         self.environment.NovaUI.UIElement = UIElement;\
  6194.         self.environment.NovaUI.UIFrame = UIFrame;\
  6195.         self.environment.NovaUI.UIImage = UIImage;\
  6196.         self.environment.NovaUI.UIInput = UIInput;\
  6197.         self.environment.NovaUI.UIKeyHandler = UIKeyHandler;\
  6198.         self.environment.NovaUI.UIMenu = UIMenu;\
  6199.         self.environment.NovaUI.UIScrollBar = UIScrollBar;\
  6200.         self.environment.NovaUI.UIText = UIText;\
  6201.         self.environment.NovaUI.UITextbox = UITextbox;\
  6202.         self.environment.NovaUI.buffer = buffer;\
  6203.         self.environment.NovaUI.clipboard = clipboard;\
  6204.         self.environment.NovaUI.display = display;\
  6205.         self.environment.NovaUI.markup = markup;\
  6206.         self.environment.NovaUI.stencil = stencil;\
  6207.         self.environment.NovaUI.Thread = function( f )\
  6208.             return instance:newThread( Thread( f ) );\
  6209.         end\
  6210. \
  6211.         self.environment.markup = setmetatable( { }, { __index = markup } )\
  6212.         self.environment.markup.MarkupPage = MarkupPage;\
  6213.     end\
  6214. \
  6215.     -- libs\
  6216.     self.environment.math = math;\
  6217.     self.environment.string = string;\
  6218.     self.environment.table = table;\
  6219.     self.environment.coroutine = coroutine;\
  6220.     self.environment.sha256 = sha256;\
  6221.     self.environment.encryption = encryption;\
  6222.     self.environment.stringutils = stringutils;\
  6223.     self.environment.keys = keys;\
  6224.     self.environment.colours = colours;\
  6225.     self.environment.colors = colors;\
  6226.     self.environment.vector = vector;\
  6227.     self.environment.bit = bit;\
  6228.     self.environment.http = http;\
  6229.     self.environment.textutils = textutils;\
  6230.     self.environment.rednet = rednet;\
  6231.     self.environment.os = os;\
  6232. \
  6233.     if instance.app.runmode ~= \"compatibility\" then\
  6234.         -- Nova API\
  6235.         self.environment.Nova = { }\
  6236.         self.environment.Nova.name = core.name\
  6237.         self.environment.Nova.version = core.version\
  6238.         self.environment.Nova.platform = core.platform\
  6239.         self.environment.Nova.app = { }\
  6240.         self.environment.Nova.app.UI = instance.content\
  6241.         self.environment.Nova.user = { }\
  6242.         self.environment.Nova.filesystem = filesystem\
  6243.         self.environment.Nova.clipboard = clipboard\
  6244.         self.environment.Nova.name = core.name\
  6245.         self.environment.Nova.path = core.path\
  6246.         self.environment.Nova.author = core.author\
  6247.         self.environment.Nova.network = {\
  6248.             open = network.open;\
  6249.             close = network.close;\
  6250.             send = network.send;\
  6251.             receive = network.receive;\
  6252.             listen = network.listen;\
  6253.             ping = com.ping;\
  6254.         }\
  6255. \
  6256.         function self.environment.Nova.getUpdateInfo( )\
  6257.             return core.getUpdateInfo( )\
  6258.         end\
  6259.         function self.environment.Nova.compareVersions( )\
  6260.             return core.compareVersions( )\
  6261.         end\
  6262.         function self.environment.Nova.update( )\
  6263.             return core.update( )\
  6264.         end\
  6265.         function self.environment.Nova.shutdown( )\
  6266.             instance.app.session:logout( )\
  6267.             core.finish( )\
  6268.             os.shutdown( )\
  6269.         end\
  6270.         function self.environment.Nova.restart( )\
  6271.             instance.app.session:logout( )\
  6272.             core.finish( )\
  6273.             os.reboot( )\
  6274.         end\
  6275.         function self.environment.Nova.log( ... )\
  6276.             return core.log( instance.app.name, ... )\
  6277.         end\
  6278.         function self.environment.Nova.time( )\
  6279.             return os.time( )\
  6280.         end\
  6281.         function self.environment.Nova.clock( )\
  6282.             return os.clock( )\
  6283.         end\
  6284.         function self.environment.Nova.irltime( )\
  6285.             return core.getirltime( )\
  6286.         end\
  6287.         function self.environment.Nova.irldate( )\
  6288.             return core.getirldate( )\
  6289.         end\
  6290.         function self.environment.Nova.day( )\
  6291.             return os.day( )\
  6292.         end\
  6293.         function self.environment.Nova.date( )\
  6294.             return error \"not yet implemented\"\
  6295.         end\
  6296.         function self.environment.Nova.sleep( n )\
  6297.             return sleep( n )\
  6298.         end\
  6299.         function self.environment.Nova.pullEvent( )\
  6300.             local ev = { coroutine.yield( ) }\
  6301.             if ev[1] == \"terminate\" then\
  6302.                 instance:close \"terminate\"\
  6303.             end\
  6304.             return unpack( ev )\
  6305.         end\
  6306.         function self.environment.Nova.pullEventRaw( )\
  6307.             return coroutine.yield( )\
  6308.         end\
  6309.         function self.environment.Nova.versionTable( )\
  6310.             return { major = core.version_major, minor = core.version_minor, patch = core.version_patch }\
  6311.         end\
  6312.         function self.environment.Nova.getLabel( )\
  6313.             return os.getComputerLabel( )\
  6314.         end\
  6315.         function self.environment.Nova.getID( )\
  6316.             return os.getComputerID( )\
  6317.         end\
  6318. \
  6319.         function self.environment.Nova.app.launch( name, ... )\
  6320.             instance.session:launch( name, { ... } )\
  6321.         end\
  6322.         function self.environment.Nova.app.register( path )\
  6323.             local a, err = instance.session:registerApp( path )\
  6324.             if not a then\
  6325.                 return false, err\
  6326.             end\
  6327.             return true\
  6328.         end\
  6329.         function self.environment.Nova.app.isRegistered( path )\
  6330.             return instance.session.app_paths[path] and instance.session.app_paths[path].name or false\
  6331.         end\
  6332.         function self.environment.Nova.app.listNames( )\
  6333.             local t = { }\
  6334.             for k, v in pairs( instance.session.apps ) do\
  6335.                 table.insert( t, k )\
  6336.             end\
  6337.             return t\
  6338.         end\
  6339.         function self.environment.Nova.app.getInstallPath( name )\
  6340.             if instance.session.apps[name] then\
  6341.                 return instance.session.apps[name].installpath\
  6342.             end\
  6343.             return false\
  6344.         end\
  6345.         function self.environment.Nova.app.getIcon( name )\
  6346.             if instance.session.apps[name] then\
  6347.                 return instance.session.apps[name].icon\
  6348.             end\
  6349.             return false\
  6350.         end\
  6351.         function self.environment.Nova.app.setData( index, value )\
  6352.             return instance.session.account:setData( instance.app.name, index, value )\
  6353.         end\
  6354.         function self.environment.Nova.app.getData( index )\
  6355.             return instance.session.account:getData( instance.app.name, index )\
  6356.         end\
  6357.         function self.environment.Nova.app.close( reason )\
  6358.             return instance:close( reason or \"internal\" )\
  6359.         end\
  6360.         function self.environment.Nova.app.newThread( func )\
  6361.             return instance:newThread( Thread( func ) )\
  6362.         end\
  6363.         function self.environment.Nova.app.setTitle( title )\
  6364.             instance.title = tostring( title )\
  6365.         end\
  6366.         function self.environment.Nova.app.require( file, ... )\
  6367.             file = file:gsub( \"%.\", \"/\" )\
  6368.             local content = instance.app:readFile( tostring( file ) .. \".lua\" )\
  6369.             if content then\
  6370.                 local f, err = loadstring( content, filesystem.getName( file ) )\
  6371.                 if f then\
  6372.                     local t = instance:newThread( Thread( f ) )\
  6373.                     return t\
  6374.                 else\
  6375.                     instance:showError( err:gsub( \"Sandbox.lua:103: \", \"\" ) )\
  6376.                 end\
  6377.             else\
  6378.                 instance:showError \"no such file\"\
  6379.             end\
  6380.         end\
  6381.         function self.environment.Nova.app.notifyUser( message, data )\
  6382.             local title = instance.session.UI:newChild( UIButton( instance.session.UI.w + 1, 2, 15, 1, instance.app.name ) )\
  6383.             local box = instance.session.UI:newChild( UIButton( instance.session.UI.w + 1, 3, 15, 3, message ) )\
  6384.             title.bc, box.bc = colours.blue, colours.lightBlue\
  6385.             title.tc, box.tc = colours.white, colours.blue\
  6386.             box.align = false\
  6387.             if data then\
  6388.                 function title.onClick( )\
  6389.                     instance.session:launch( instance.app.name, data )\
  6390.                 end\
  6391.                 box.onClick = title.onClick\
  6392.             end\
  6393.             instance.session:newThread( Thread( function( )\
  6394.                 for i = 1, 15 do\
  6395.                     title.x, box.x = title.x - 1, box.x - 1\
  6396.                     sleep( .1 )\
  6397.                 end\
  6398.                 sleep( 2 )\
  6399.                 for i = 1, 15 do\
  6400.                     title.x, box.x = title.x + 1, box.x + 1\
  6401.                     sleep( .1 )\
  6402.                 end\
  6403.                 title:remove( )\
  6404.                 box:remove( )\
  6405.             end ) )\
  6406.         end\
  6407.         self.environment.Nova.app.public = setmetatable( { }, { __newindex = function( _, k, v )\
  6408.             instance.public_variables[k] = v\
  6409.         end, __index = function( _, k )\
  6410.             return instance.public_variables[k]\
  6411.         end } )\
  6412.         self.environment.Nova.app.event = setmetatable( { }, { __newindex = function( _, k, v )\
  6413.             if type( v ) == \"function\" then\
  6414.                 instance.events[k] = v\
  6415.             else\
  6416.                 return error \"expected function\"\
  6417.             end\
  6418.         end, __index = instance.events } )\
  6419.         self.environment.Nova.app.running = setmetatable( { }, {\
  6420.             __index = function( _, name )\
  6421.                 local t = { }\
  6422.                 local apps = { }\
  6423.                 for i = 1, #instance.session.running do\
  6424.                     if instance.session.running[i].app.name == name then\
  6425.                         table.insert( apps, instance.session.running[i] )\
  6426.                     end\
  6427.                 end\
  6428.                 return setmetatable( t, {\
  6429.                     __index = function( _, k )\
  6430.                         if apps[k] then\
  6431.                             return setmetatable( { }, { __index = apps[k].public_variables } )\
  6432.                         end\
  6433.                     end;\
  6434.                 } )\
  6435.             end;\
  6436.             __newindex = { };\
  6437.         } )\
  6438.         self.environment.Nova.app.service = setmetatable( { }, {\
  6439.             __index = function( _, name )\
  6440.                 local t = { }\
  6441.                 local apps = { }\
  6442.                 for i = 1, #instance.session.background do\
  6443.                     if instance.session.background[i].app.name == name then\
  6444.                         table.insert( apps, instance.session.background[i] )\
  6445.                     end\
  6446.                 end\
  6447.                 return setmetatable( t, {\
  6448.                     __index = function( _, k )\
  6449.                         if apps[k] then\
  6450.                             return setmetatable( { }, { __index = apps[k].public_variables } )\
  6451.                         end\
  6452.                     end;\
  6453.                 } )\
  6454.             end;\
  6455.             __newindex = { };\
  6456.         } )\
  6457. \
  6458.         function self.environment.Nova.user.getName( )\
  6459.             return instance.app.session.account.username\
  6460.         end\
  6461.         function self.environment.Nova.user.rename( name )\
  6462.             return instance.app.session.account:rename( name )\
  6463.         end\
  6464.         function self.environment.Nova.user.changePassword( pass )\
  6465.             return instance.app.session.account:changePassword( pass )\
  6466.         end\
  6467.         function self.environment.Nova.user.logout( )\
  6468.             Thread( function( )\
  6469.                 instance.app.session:logout( )\
  6470.                 core.session = Session( )\
  6471.             end )\
  6472.         end\
  6473.         function self.environment.Nova.user.delete( )\
  6474.             Thread( function( )\
  6475.                 fs.delete( instance.app.session.account.userpath )\
  6476.                 instance.app.session:logout( )\
  6477.                 core.session = Session( )\
  6478.             end )\
  6479.         end\
  6480.         function self.environment.Nova.user.create( name, pass )\
  6481.             return Account.create( name, pass )\
  6482.         end\
  6483.         function self.environment.Nova.user.switch( name, pass )\
  6484.             Thread( function( )\
  6485.                 instance.app.session:logout( )\
  6486.                 core.session = Session( name, pass )\
  6487.             end )\
  6488.         end\
  6489.     end\
  6490. \
  6491.     return self.public\
  6492. end\
  6493. \
  6494. local function a( name )\
  6495.         -- user lib\
  6496.         self.environment.user.name = self.session.account.name\
  6497.         function self.environment.user.checkPassword( pass )\
  6498.             return self.session.account:getPassword( ) == sha256( pass )\
  6499.         end\
  6500. \
  6501.         -- running apps\
  6502.         function self.environment.app.focus( )\
  6503.             self.session:viewApp( self.app )\
  6504.         end\
  6505. end", meta={
  6506.   type = "class",
  6507. }};["encryption"]={content="\
  6508. \
  6509. local function sum( n, ... ) -- number n, number ...additions\
  6510.     local t = { ... }\
  6511.     for i = 1, #t do\
  6512.         n = n + t[i]\
  6513.     end\
  6514.     return n\
  6515. \
  6516.     -- number sum\
  6517. end\
  6518. \
  6519. local function loop( n, lim ) -- number n, number limit\
  6520.     while n > lim do\
  6521.         n = n - lim\
  6522.     end\
  6523.     while n < 1 do\
  6524.         n = n + lim\
  6525.     end\
  6526.     return n\
  6527.     -- number limited\
  6528. end\
  6529. \
  6530. local function shift( str, count )\
  6531.     local str = { str:byte( 1, #str ) }\
  6532.     for i = 1, #str do\
  6533.         str[i] = loop( str[i] + count, 255 )\
  6534.     end\
  6535.     for i = 1, #str do\
  6536.         str[i] = string.char( str[i] )\
  6537.     end\
  6538.     return table.concat( str, \"\" )\
  6539. end\
  6540. \
  6541. local function tohex( b ) -- string[4] bits\
  6542.     local n = 0\
  6543.     for i = 1, 4 do\
  6544.         n = n * 2\
  6545.         n = n + tonumber( b:sub( i, i ) )\
  6546.     end\
  6547.     if n >= 10 then\
  6548.         local hexes = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\" }\
  6549.         return hexes[n - 9]\
  6550.     end\
  6551.     return tostring( n )\
  6552. \
  6553.     -- string[1] hex\
  6554. end\
  6555. \
  6556. local function fromhex( h ) -- string[1] hex\
  6557.     local n = tonumber( h )\
  6558.     if not n then\
  6559.         local hexes = { [\"A\"] = 10, [\"B\"] = 11, [\"C\"] = 12, [\"D\"] = 13, [\"E\"] = 14, [\"F\"] = 15 }\
  6560.         n = hexes[h]\
  6561.     end\
  6562.     local str = \"\"\
  6563.     for i = 1, 4 do\
  6564.         str = str .. n % 2\
  6565.         n = math.floor( n / 2 )\
  6566.     end\
  6567.     return str:reverse( )\
  6568. \
  6569.     -- string[4] bits\
  6570. end\
  6571. \
  6572. local function xor( n1, n2 )\
  6573.     if n1 > 255 or n2 > 255 or n1 < 0 or n2 < 0 then\
  6574.         return error \"expected numbers between 0 and 255\"\
  6575.     end\
  6576.     local bit1, bit2 = { }, { }\
  6577.     for i = 1, 8 do\
  6578.         bit1[9-i] = n1 % 2 == 1\
  6579.         n1 = math.floor( n1 / 2 )\
  6580.     end\
  6581.     for i = 1, 8 do\
  6582.         bit2[9-i] = n2 % 2 == 1\
  6583.         n2 = math.floor( n2 / 2 )\
  6584.     end\
  6585.     local bits = { }\
  6586.     for i = 1, 8 do\
  6587.         bits[i] = ( bit1[i] and not bit2[i] ) or ( not bit1[i] and bit2[i] )\
  6588.     end\
  6589.     local n = 0\
  6590.     for i = 1, 8 do\
  6591.         n = n * 2\
  6592.         n = n + ( bits[i] and 1 or 0 )\
  6593.     end\
  6594.     return n\
  6595. end\
  6596. \
  6597. local function nand( n1, n2 )\
  6598.     if n1 > 255 or n2 > 255 or n1 < 0 or n2 < 0 then\
  6599.         return error \"expected numbers between 0 and 255\"\
  6600.     end\
  6601.     local bit1, bit2 = { }, { }\
  6602.     for i = 1, 8 do\
  6603.         bit1[9-i] = n1 % 2 == 1\
  6604.         n1 = math.floor( n1 / 2 )\
  6605.     end\
  6606.     for i = 1, 8 do\
  6607.         bit2[9-i] = n2 % 2 == 1\
  6608.         n2 = math.floor( n2 / 2 )\
  6609.     end\
  6610.     local bits = { }\
  6611.     for i = 1, 8 do\
  6612.         bits[i] = not bit1[i] == bit2[i]\
  6613.     end\
  6614.     local n = 0\
  6615.     for i = 1, 8 do\
  6616.         n = n * 2\
  6617.         n = n + ( bits[i] and 1 or 0 )\
  6618.     end\
  6619.     return n\
  6620. end\
  6621. \
  6622. local function tobits( n )\
  6623.     local str = \"\"\
  6624.     for i = 1, 8 do\
  6625.         str = str .. n % 2\
  6626.         n = math.floor( n / 2 )\
  6627.     end\
  6628.     return str:reverse( )\
  6629. end\
  6630. \
  6631. local function frombits( b )\
  6632.     local n = 0\
  6633.     for i = 1, 8 do\
  6634.         n = n * 2\
  6635.         n = n + tonumber( b:sub( i, i ) )\
  6636.     end\
  6637.     return n\
  6638. end\
  6639. \
  6640. local t = os.clock( )\
  6641. local function start( )\
  6642.     t = os.clock( )\
  6643. end\
  6644. local function yield( )\
  6645.     if os.clock( ) - t > .1 then\
  6646.         coroutine.yield( )\
  6647.         start( )\
  6648.     end\
  6649. end\
  6650. \
  6651. function encrypt( str, key ) -- string text, string key\
  6652.     local enc = \"\"\
  6653.     start( )\
  6654.     for i = 1, #str do\
  6655.         math.randomseed( sum( key:byte( 1, #key ) ) )\
  6656.         key = shift( key, math.random( 1, 100 ) )\
  6657.         local ki = loop( i, #key )\
  6658.         local a = str:sub( i, i ):byte( )\
  6659.         local b = key:sub( ki, ki ):byte( )\
  6660.         enc = enc .. tobits( xor( a, b ) )\
  6661.         yield( )\
  6662.     end\
  6663.     local enc2 = \"\"\
  6664.     for i = 1, #enc / 4 do\
  6665.         enc2 = enc2 .. tohex( enc:sub( i * 4 - 3, i * 4 ) )\
  6666.         yield( )\
  6667.     end\
  6668.     return enc2\
  6669. \
  6670.     -- string cipher\
  6671. end\
  6672. \
  6673. function decrypt( str, key ) -- string cipher, string key\
  6674.     start( )\
  6675.     local dec2 = \"\"\
  6676.     for i = 1, #str do\
  6677.         dec2 = dec2 .. fromhex( str:sub( i, i ) )\
  6678.         yield( )\
  6679.     end\
  6680.     str = dec2\
  6681.     local dec = \"\"\
  6682.     local keys = { }\
  6683.     for i = 1, #str / 8 do\
  6684.         math.randomseed( sum( key:byte( 1, #key ) ) )\
  6685.         keys[i] = shift( key, math.random( 1, 100 ) )\
  6686.         key = keys[i]\
  6687.         yield( )\
  6688.     end\
  6689.     for i = 1, #str / 8 do\
  6690.         local ki = loop( i, #key )\
  6691.         local a = frombits( str:sub( ( i - 1 ) * 8 + 1, i * 8 ) )\
  6692.         local b = string.byte( keys[i]:sub( ki, ki ) )\
  6693.         dec = dec .. string.char( nand( a, b ) )\
  6694.         yield( )\
  6695.     end\
  6696.     return dec\
  6697. \
  6698.     -- string text\
  6699. end", meta={
  6700.   type = "lib",
  6701. }};["MarkupLoadedObject"]={content="\
  6702. \
  6703. MarkupLoadedObject.public \"element\" (UIElement)\
  6704. \
  6705. MarkupLoadedObject.public \"mode\" \"string\"\
  6706. \
  6707. MarkupLoadedObject.public \"x\" \"number\"\
  6708. MarkupLoadedObject.public \"y\" \"number\"\
  6709. MarkupLoadedObject.public \"content\"\
  6710. MarkupLoadedObject.public.content.write = false\
  6711. \
  6712. function MarkupLoadedObject:MarkupLoadedObject( )\
  6713.     self.mode = \"block\"\
  6714. \
  6715.     self.x = 0\
  6716.     self.y = 0\
  6717.     self.content = {\
  6718.         width = 1;\
  6719.         height = 1;\
  6720.     }\
  6721.     self.element = nil\
  6722. \
  6723.     return self.public\
  6724. end\
  6725. \
  6726. function MarkupLoadedObject.public:initialise( class, ... )\
  6727.     self.element = class(\
  6728.         self.x,\
  6729.         self.y,\
  6730.         self.content.width,\
  6731.         self.content.height,\
  6732.         ...\
  6733.     )\
  6734. end", meta={
  6735.   type = "class",
  6736. }};["Image"]={content="\
  6737. \
  6738. local colourLookup = {\
  6739.     [\"0\"] = colours.white;\
  6740.     [\"1\"] = colours.orange;\
  6741.     [\"2\"] = colours.magenta;\
  6742.     [\"3\"] = colours.lightBlue;\
  6743.     [\"4\"] = colours.yellow;\
  6744.     [\"5\"] = colours.lime;\
  6745.     [\"6\"] = colours.pink;\
  6746.     [\"7\"] = colours.grey;\
  6747.     [\"8\"] = colours.lightGrey;\
  6748.     [\"9\"] = colours.cyan;\
  6749.     [\"A\"] = colours.purple;\
  6750.     [\"B\"] = colours.blue;\
  6751.     [\"C\"] = colours.brown;\
  6752.     [\"D\"] = colours.green;\
  6753.     [\"E\"] = colours.red;\
  6754.     [\"F\"] = colours.black;\
  6755.     [\" \"] = 0;\
  6756. }\
  6757. \
  6758. local colourSave = { }\
  6759. for k, v in pairs( colourLookup ) do\
  6760.     colourSave[v] = k\
  6761. end\
  6762. \
  6763. function Image:Image( w, h )\
  6764.     self.pixels = { }\
  6765. \
  6766.     for y = 1, h or 1 do\
  6767.         self.pixels[y] = { }\
  6768.         for x = 1, w or 1 do\
  6769.             self.pixels[y][x] = { bc = 0, tc = 0, char = \"\" }\
  6770.         end\
  6771.     end\
  6772. \
  6773.     return self.public\
  6774. end\
  6775. \
  6776. function Image.public:getSize( )\
  6777.     return #( self.pixels[1] or { } ), #self.pixels\
  6778. end\
  6779. \
  6780. function Image.public:foreach( f )\
  6781.     local w, h = #( self.pixels[1] or { } ), #self.pixels\
  6782.     local pixels = { }\
  6783.     for y = 1, h do\
  6784.         pixels[y] = { }\
  6785.         for x = 1, w do\
  6786.             pixels[y][x] = { }\
  6787.             local bc, tc, char = f( x, y, self.public:getPixel( x, y ) )\
  6788.             pixels[y][x].bc = bc or self.pixels[y][x].bc\
  6789.             pixels[y][x].tc = tc or self.pixels[y][x].tc\
  6790.             pixels[y][x].char = char or self.pixels[y][x].char\
  6791.         end\
  6792.     end\
  6793.     for y = 1, h do\
  6794.         for x = 1, w do\
  6795.             self.pixels[y][x].bc = pixels[y][x].bc\
  6796.             self.pixels[y][x].tc = pixels[y][x].tc\
  6797.             self.pixels[y][x].char = pixels[y][x].char\
  6798.         end\
  6799.     end\
  6800. end\
  6801. \
  6802. function Image.public:pixel( x, y, bc, tc, char )\
  6803.     if self.pixels[y] and self.pixels[y][x] then\
  6804.         --if bc == 0 then bc = pixels[y][x].bc end\
  6805.         --if tc == 0 then char = pixels[y][x].char tc = pixels[y][x].tc end\
  6806.         self.pixels[y][x] = { bc = bc, tc = tc, char = char }\
  6807.         return true\
  6808.     end\
  6809.     return false\
  6810. end\
  6811. \
  6812. function Image.public:getPixel( x, y )\
  6813.     if self.pixels[y] and self.pixels[y][x] then\
  6814.         return self.pixels[y][x].bc, self.pixels[y][x].tc, self.pixels[y][x].char\
  6815.     end\
  6816.     return false\
  6817. end\
  6818. \
  6819. function Image.public:savestr( str )\
  6820.     local w, h = #( self.pixels[1] or { } ), #self.pixels\
  6821.     local str = \"\"\
  6822.     for y = 1, h do\
  6823.         for x = 1, w do\
  6824.             local bc = colourSave[self.pixels[y][x].bc]\
  6825.             local tc = colourSave[self.pixels[y][x].tc]\
  6826.             local char = self.pixels[y][x].char\
  6827.             if #char == 0 then\
  6828.                 char = \" \"\
  6829.                 bc = colourSave[0]\
  6830.             end\
  6831.             str = str .. bc .. tc .. char\
  6832.         end\
  6833.         str = str .. \"\\n\"\
  6834.     end\
  6835.     return str:sub( 1, -2 )\
  6836. end\
  6837. \
  6838. function Image.public:loadstr( str )\
  6839.     local lines = { }\
  6840.     local last = 1\
  6841.     for i = 1, #str do\
  6842.         if str:sub( i, i ) == \"\\n\" then\
  6843.             table.insert( lines, str:sub( last, i - 1 ) )\
  6844.             last = i + 1\
  6845.         end\
  6846.     end\
  6847.     table.insert( lines, str:sub( last ) )\
  6848.     local width = #lines[1] / 3\
  6849.     local height = #lines\
  6850.     self.public:resize( width, height )\
  6851.     for i = 1, #lines do\
  6852.         local x = 1\
  6853.         for pixel in lines[i]:gmatch( \"[0123456789ABCDEF ][0123456789ABCDEF ].\" ) do\
  6854.             local bc = pixel:sub( 1, 1 )\
  6855.             local tc = pixel:sub( 2, 2 )\
  6856.             local ch = pixel:sub( 3, 3 )\
  6857.             self.public:pixel( x, i, colourLookup[bc], colourLookup[tc], ch )\
  6858.             x = x + 1\
  6859.         end\
  6860.     end\
  6861.     return self.public\
  6862. end\
  6863. \
  6864. function Image.public:resize( w, h, bc, tc, char )\
  6865.     while #self.pixels > h do\
  6866.         table.remove( self.pixels, #self.pixels )\
  6867.     end\
  6868.     while #self.pixels < h do\
  6869.         local t = { }\
  6870.         for i = 1, #( self.pixels[1] or { } ) do\
  6871.             t[i] = { bc = bc or 0, tc = tc or 0, char = char or \"\" }\
  6872.         end\
  6873.         table.insert( self.pixels, t )\
  6874.     end\
  6875.     if self.pixels[1] then\
  6876.         local cw = #self.pixels[1]\
  6877.         while cw > w do\
  6878.             for y = 1, h do\
  6879.                 table.remove( self.pixels[y], #self.pixels[y] )\
  6880.             end\
  6881.             cw = cw - 1\
  6882.         end\
  6883.         while cw < w do\
  6884.             for y = 1, h do\
  6885.                 table.insert( self.pixels[y], { bc = bc or 0, tc = tc or 0, char = char or \"\" } )\
  6886.             end\
  6887.             cw = cw + 1\
  6888.         end\
  6889.     end\
  6890. end", meta={
  6891.   type = "class",
  6892. }};["MarkupSpace"]={content="\
  6893. \
  6894. require \"MarkupGenericObject\"\
  6895. \
  6896. MarkupSpace:extends( MarkupGenericObject )\
  6897. \
  6898. MarkupSpace.public.etype = \"space\"\
  6899. \
  6900. function MarkupSpace:MarkupSpace( ... )\
  6901.     self:MarkupGenericObject( ... )\
  6902. \
  6903.     return self.public\
  6904. end\
  6905. \
  6906. function MarkupSpace.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
  6907.     if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
  6908.         if colours[value] or value == \"transparent\" then\
  6909.             self.bc = colours[value] or 0\
  6910.         end\
  6911.     end\
  6912.     self.page.needsReload = true\
  6913. end\
  6914. \
  6915. function MarkupSpace.public:loadObject( positionHandler )\
  6916.     self.page:newStyle {\
  6917.         bc = self.bc;\
  6918.     }\
  6919.     local ob = MarkupLoadedObject( )\
  6920.     ob.mode = \"inline\"\
  6921.     ob.x, ob.y = positionHandler.x, positionHandler.y\
  6922.     ob:initialise( UIText, \"\" )\
  6923.     ob.element.bc = self.page:getStyle \"bc\"\
  6924.     self.page:removeStyle( )\
  6925.     return { ob }\
  6926.     -- generate a list of MarkupLoadedObjects\
  6927. end", meta={
  6928.   type = "class",
  6929. }};["MarkupInput"]={content="\
  6930. \
  6931. require \"MarkupGenericObject\"\
  6932. \
  6933. MarkupInput:extends( MarkupGenericObject )\
  6934. \
  6935. MarkupInput.public.etype = \"input\"\
  6936. \
  6937. function MarkupInput:MarkupInput( ... )\
  6938.     self:MarkupGenericObject( ... )\
  6939. \
  6940.     self.mask = false\
  6941. \
  6942.     return self.public\
  6943. end\
  6944. \
  6945. function MarkupInput.public:setAttribute( attribute, value )\
  6946.     if MarkupGenericObject.attributes[attribute] then\
  6947.         MarkupGenericObject.attributes[attribute]( self, value )\
  6948.     end\
  6949.     if attribute == \"tc\" or attribute == \"textColour\" then\
  6950.         if colours[value] or value == \"transparent\" then\
  6951.             self.tc = colours[value] or 0\
  6952.         end\
  6953.     end\
  6954.     if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
  6955.         if colours[value] or value == \"transparent\" then\
  6956.             self.bc = colours[value] or 0\
  6957.         end\
  6958.     end\
  6959.     if attribute == \"fbc\" or attribute == \"focussedColour\" then\
  6960.         if colours[value] or value == \"transparent\" then\
  6961.             self.fbc = colours[value] or 0\
  6962.         end\
  6963.     end\
  6964.     if attribute == \"mask\" then\
  6965.         if value == false or value == \"false\" then\
  6966.             self.mask = false\
  6967.         elseif type( value ) == \"string\" then\
  6968.             self.mask = value:sub( 1, 1 )\
  6969.         end\
  6970.     end\
  6971.     self.page.needsReposition = true\
  6972. end\
  6973. \
  6974. \
  6975. function MarkupInput.public:loadObject( positionHandler )\
  6976. \
  6977.     local w, h = 0, 1\
  6978.     if self.width.type == \"text\" and self.width.value == \"auto\" then\
  6979.         w = positionHandler.w - positionHandler.x + 1\
  6980.     elseif self.width.type == \"percent\" then\
  6981.         w = self.width.value / 100 * positionHandler.w\
  6982.     elseif self.width.type == \"pixel\" then\
  6983.         w = self.width.value\
  6984.     end\
  6985.     local loadedObject = MarkupLoadedObject( )\
  6986.     loadedObject.x = positionHandler.x\
  6987.     loadedObject.y = positionHandler.y\
  6988.     loadedObject.mode = \"inline\"\
  6989.     loadedObject.content.width = w\
  6990.     loadedObject.content.height = h\
  6991.     local input\
  6992.     if not self.element then\
  6993.         loadedObject:initialise( UIInput, self.mask )\
  6994.         input = loadedObject.element\
  6995.         self.element = loadedObject.element\
  6996.     else\
  6997.         self.element.x = loadedObject.x\
  6998.         self.element.y = loadedObject.y\
  6999.         self.element.w = loadedObject.content.width\
  7000.         self.element.h = loadedObject.content.height\
  7001.         input = self.element\
  7002.     end\
  7003.     input.bc = self.bc or self.page:getStyle \"bc\"\
  7004.     input.tc = self.tc or self.page:getStyle \"tc\"\
  7005.     input.fbc = self.fbc or self.page:getStyle \"bc\"\
  7006.     loadedObject.element = input\
  7007. \
  7008.     function input:whenFocussed( )\
  7009.         core.log \"Focussed on the input\"\
  7010.     end\
  7011.     function input:onEnter()\
  7012.         core.log( self.text )\
  7013.     end\
  7014. \
  7015.     return { loadedObject }\
  7016.     -- generate a list of MarkupLoadedObjects\
  7017. end\
  7018. \
  7019. function MarkupInput.public:scriptObject( )\
  7020.     return setmetatable( {}, {\
  7021.         __newindex = function( _, k, v )\
  7022.             if type( v ) ~= \"string\" then\
  7023.                 v = tostring( v )\
  7024.             end\
  7025.             return self.public:setAttribute( k, v )\
  7026.         end;\
  7027.         __index = function( _, k )\
  7028.             if k == \"width\" then\
  7029.                 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 )\
  7030.             elseif k == \"height\" then\
  7031.                 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 )\
  7032.             elseif k == \"bc\" or k == \"backgroundColour\" or k == \"colour\" then\
  7033.                 return self.bc\
  7034.             elseif k == \"tc\" or k == \"textColour\" then\
  7035.                 return self.tc\
  7036.             elseif k == \"mask\" then\
  7037.                 return self.mask\
  7038.             elseif k == \"text\" then\
  7039.                 return self.element and self.element.text or \"\"\
  7040.             else\
  7041.                 return error \"cannot access this attribute\"\
  7042.             end\
  7043.         end;\
  7044.     } )\
  7045. end", meta={
  7046.   type = "class",
  7047. }};["handshake"]={content="\
  7048. -- Handshake\
  7049. -- Used with permission from 1lann\
  7050. -- Compressed using Lua Minifier (https://mothereff.in/lua-minifier)\
  7051. local 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={
  7052.   type = "lib",
  7053. }};["MarkupNewline"]={content="\
  7054. \
  7055. require \"MarkupGenericObject\"\
  7056. \
  7057. MarkupNewline:extends( MarkupGenericObject )\
  7058. \
  7059. MarkupNewline.public.etype = \"newline\"\
  7060. \
  7061. function MarkupNewline:MarkupNewline( ... )\
  7062.     self:MarkupGenericObject( ... )\
  7063. \
  7064.     return self.public\
  7065. end\
  7066. \
  7067. function MarkupNewline.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
  7068.     if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
  7069.         if colours[value] or value == \"transparent\" then\
  7070.             self.bc = colours[value] or 0\
  7071.         end\
  7072.     end\
  7073.     self.page.needsReload = true\
  7074. end\
  7075. \
  7076. function MarkupNewline.public:loadObject( positionHandler )\
  7077.     self.page:newStyle {\
  7078.         bc = self.bc;\
  7079.     }\
  7080.     local ob = MarkupLoadedObject( )\
  7081.     ob.x, ob.y = positionHandler.x, positionHandler.y\
  7082.     ob:initialise( UIText, \"\" )\
  7083.     ob.element.bc = self.page:getStyle \"bc\"\
  7084.     self.page:removeStyle( )\
  7085.     return { ob }\
  7086.     -- generate a list of MarkupLoadedObjects\
  7087. end", meta={
  7088.   type = "class",
  7089. }};["UICanvas"]={content="\
  7090. \
  7091. local colour = term.isColour( )\
  7092. \
  7093. require \"UIElement\"\
  7094. require \"Image\"\
  7095. UICanvas:extends( UIElement )\
  7096. \
  7097. UICanvas.public \"cx\" \"number\"\
  7098. UICanvas.public \"cy\" \"number\"\
  7099. UICanvas.public \"cb\" \"boolean\"\
  7100. UICanvas.public \"bc\" \"number\"\
  7101. UICanvas.public \"tc\" \"number\"\
  7102. UICanvas.public \"image\"\
  7103. UICanvas.public.image.write = false\
  7104. \
  7105. UICanvas.handlesKeys = true\
  7106. UICanvas.handlesScroll = true\
  7107. UICanvas.handlesTab = true\
  7108. UICanvas.handlesEnter = true\
  7109. \
  7110. local function termObject( canvas )\
  7111.     local t = { }\
  7112.     function t.write( str )\
  7113.         if type( str ) ~= \"string\" and type( str ) ~= \"number\" then return error \"expected string\" end\
  7114.         str = tostring( str )\
  7115.         for x = 1, #str do\
  7116.             canvas.image:pixel( canvas.cx, canvas.cy, canvas.bc, canvas.tc, str:sub( x, x ) )\
  7117.             canvas.cx = canvas.cx + 1\
  7118.         end\
  7119.     end\
  7120.     function t.clearLine( )\
  7121.         canvas.image:foreach( function( x, y, bc, tc, char )\
  7122.             if y == canvas.cy then\
  7123.                 return canvas.bc, canvas.tc, \" \"\
  7124.             end\
  7125.         end )\
  7126.     end\
  7127.     function t.clear( )\
  7128.         canvas.image:foreach( function( x, y, bc, tc, char )\
  7129.             return canvas.bc, canvas.tc, \" \"\
  7130.         end )\
  7131.     end\
  7132.     function t.setCursorPos( x, y )\
  7133.         if type( x ) ~= \"number\" or type( y ) ~= \"number\" then\
  7134.             return error \"expected number, number\"\
  7135.         end\
  7136.         canvas.cx = x\
  7137.         canvas.cy = y\
  7138.     end\
  7139.     function t.getCursorPos( )\
  7140.         return canvas.cx, canvas.cy\
  7141.     end\
  7142.     function t.setBackgroundColour( col )\
  7143.         if type( col ) ~= \"number\" then\
  7144.             return error \"expected number\"\
  7145.         end\
  7146.         canvas.bc = col\
  7147.     end\
  7148.     function t.setTextColour( col )\
  7149.         if type( col ) ~= \"number\" then\
  7150.             return error \"expected number\"\
  7151.         end\
  7152.         canvas.tc = col\
  7153.     end\
  7154.     function t.setBackgroundColor( col )\
  7155.         if type( col ) ~= \"number\" then\
  7156.             return error \"expected number\"\
  7157.         end\
  7158.         canvas.bc = col\
  7159.     end\
  7160.     function t.setTextColor( col )\
  7161.         if type( col ) ~= \"number\" then\
  7162.             return error \"expected number\"\
  7163.         end\
  7164.         canvas.tc = col\
  7165.     end\
  7166.     function t.scroll( dir )\
  7167.         canvas.image:foreach( function( x, y, bc, tc, char )\
  7168.             if canvas.image:getPixel( x, y + dir ) then\
  7169.                 return canvas.image:getPixel( x, y + dir )\
  7170.             else\
  7171.                 return canvas.bc, canvas.tc, \" \"\
  7172.             end\
  7173.         end )\
  7174.     end\
  7175.     function t.setCursorBlink( state )\
  7176.         canvas.cb = not not state\
  7177.     end\
  7178.     function t.isColour( )\
  7179.         return colour\
  7180.     end\
  7181.     function t.isColor( )\
  7182.         return colour\
  7183.     end\
  7184.     function t.getSize( )\
  7185.         return canvas.w, canvas.h\
  7186.     end\
  7187. \
  7188.     return t\
  7189. end\
  7190. \
  7191. local function luaEnvironment( canvas )\
  7192.     local env = { }\
  7193.     env.fs = fs\
  7194.     env.term = term\
  7195.     env._VERSION = _VERSION\
  7196.     env.pairs = pairs\
  7197.     env.ipairs = ipairs\
  7198.     env.select = select\
  7199.     env.unpack = unpack\
  7200.     env.setfenv = setfenv\
  7201.     env.getfenv = getfenv\
  7202.     env.setmetatable = setmetatable\
  7203.     env.getmetatable = getmetatable\
  7204.     env.next = next\
  7205.     env.rawset = rawset\
  7206.     env.rawget = rawget\
  7207.     env.rawequal = rawequal\
  7208.     env.type = type\
  7209.     env.tostring = tostring\
  7210.     env.tonumber = tonumber\
  7211.     env.pcall = pcall\
  7212.     env.xpcall = xpcall\
  7213.     env.loadstring = loadstring\
  7214.     env.assert = assert\
  7215.     env.error = error\
  7216.     env.sleep = sleep\
  7217.     env.__inext = __inext\
  7218.     env.math = math\
  7219.     env.string = string\
  7220.     env.table = table\
  7221.     env.coroutine = coroutine\
  7222.     env.keys = keys\
  7223.     env.colours = colours\
  7224.     env.colors = colors\
  7225.     env.vector = vector\
  7226.     env.bit = bit\
  7227.     env.http = http\
  7228.     env.write = write\
  7229.     env.print = print\
  7230.     env.printError = printError\
  7231.     env.read = read\
  7232.     env.rednet = rednet\
  7233.     local tAPIsLoading = { }\
  7234.     env.os = setmetatable( {\
  7235.         pullEventRaw = function( sFilter )\
  7236.             while true do\
  7237.                 local event = { coroutine.yield( ) }\
  7238.                 if not sFilter or sFilter == event[1] then\
  7239.                     return unpack( event )\
  7240.                 end\
  7241.             end\
  7242.         end;\
  7243.         pullEvent = function( sFilter )\
  7244.             while true do\
  7245.                 local event = { coroutine.yield( ) }\
  7246.                 if event[1] == \"terminate\" then\
  7247.                     error( \"Terminated\", 0 )\
  7248.                 end\
  7249.                 if not sFilter or sFilter == event[1] then\
  7250.                     return unpack( event )\
  7251.                 end\
  7252.             end\
  7253.         end;\
  7254.         run = function( _tEnv, _sPath, ... )\
  7255.             local tArgs = { ... }\
  7256.             local fnFile, err = env.loadfile( _sPath )\
  7257.             if fnFile then\
  7258.                 local tEnv = _tEnv\
  7259.                 --setmetatable( tEnv, { __index = function(t,k) return _G[k] end } )\
  7260.                 setmetatable( tEnv, { __index = env } )\
  7261.                 setfenv( fnFile, tEnv )\
  7262.                 local ok, err = pcall( function()\
  7263.                     fnFile( unpack( tArgs ) )\
  7264.                 end )\
  7265.                 if not ok then\
  7266.                     if err and err ~= \"\" then\
  7267.                         printError( err )\
  7268.                     end\
  7269.                     return false\
  7270.                 end\
  7271.                 return true\
  7272.             end\
  7273.             if err and err ~= \"\" then\
  7274.                 printError( err )\
  7275.             end\
  7276.             return false\
  7277.         end;\
  7278.         loadAPI = function( _sPath )\
  7279.             local sName = fs.getName( _sPath )\
  7280.             if tAPIsLoading[sName] == true then\
  7281.                 printError( \"API \"..sName..\" is already being loaded\" )\
  7282.                 return false\
  7283.             end\
  7284.             tAPIsLoading[sName] = true\
  7285.                 \
  7286.             local tEnv = {}\
  7287.             setmetatable( tEnv, { __index = env } )\
  7288.             local fnAPI, err = loadfile( _sPath )\
  7289.             if fnAPI then\
  7290.                 setfenv( fnAPI, tEnv )\
  7291.                 fnAPI()\
  7292.             else\
  7293.                 printError( err )\
  7294.                 tAPIsLoading[sName] = nil\
  7295.                 return false\
  7296.             end\
  7297.             \
  7298.             local tAPI = {}\
  7299.             for k,v in pairs( tEnv ) do\
  7300.                 tAPI[k] =  v\
  7301.             end\
  7302.             \
  7303.             env[sName] = tAPI    \
  7304.             tAPIsLoading[sName] = nil\
  7305.             return true\
  7306.         end;\
  7307.         unloadAPI = function( _sName )\
  7308.             if _sName ~= \"_G\" and type(env[_sName]) == \"table\" then\
  7309.                 env[_sName] = nil\
  7310.             end\
  7311.         end;\
  7312.     }, { __index = os } );\
  7313.     env.help = help\
  7314.     env.io = io\
  7315.     env.parallel = parallel\
  7316. \
  7317.     env.loadfile = function( _sFile )\
  7318.         local file = env.fs.open( _sFile, \"r\" )\
  7319.         if file then\
  7320.             local func, err = loadstring( file.readAll(), env.fs.getName( _sFile ) )\
  7321.             file.close()\
  7322.             return func, err\
  7323.         end\
  7324.         return nil, \"File not found\"\
  7325.     end\
  7326. \
  7327.     env.dofile = function( _sFile )\
  7328.         local fnFile, e = env.loadfile( _sFile )\
  7329.         if fnFile then\
  7330.             setfenv( fnFile, env )\
  7331.             return fnFile()\
  7332.         else\
  7333.             error( e, 2 )\
  7334.         end\
  7335.     end\
  7336. \
  7337.     env.shell = shell\
  7338.     env.multishell = multishell\
  7339.     env.redstone = redstone -- change in future, use device system\
  7340.     env.rs = rs -- change in future, use device system\
  7341.     env.gps = gps -- change in future, use Nova gps system\
  7342.     env.peripheral = peripheral -- change in future, use device system\
  7343.     env.disk = disk -- change in future, use filesystem\
  7344.     env.window = window -- oh crap...\
  7345.     env.textutils = textutils\
  7346.     env.paintutils = paintutils\
  7347.     env.term = term\
  7348. \
  7349.     env._G = env\
  7350. \
  7351.     return env\
  7352. end\
  7353. \
  7354. function UICanvas:UICanvas( x, y, w, h )\
  7355.     self:UIElement( x, y, w, h )\
  7356.     self.image = Image( w, h )\
  7357.     self.image:foreach( function( x, y, bc, tc, char )\
  7358.         return colours.black, colours.white, \" \"\
  7359.     end )\
  7360.     self.term = termObject( self.public )\
  7361.     self.running = false\
  7362.     self.co = false\
  7363.     self.cx = 1\
  7364.     self.cy = 1\
  7365.     self.bc = colours.black\
  7366.     self.tc = colours.white\
  7367.     self.cb = false\
  7368.     self.environment = luaEnvironment( )\
  7369.     self.thread = Thread( function( )\
  7370.         while true do\
  7371.             local event = { coroutine.yield( ) }\
  7372.             if event[1] ~= \"mouse_click\"\
  7373.             and event[1] ~= \"mouse_drag\"\
  7374.             and event[1] ~= \"mouse_scroll\"\
  7375.             and event[1] ~= \"key\"\
  7376.             and event[1] ~= \"char\"\
  7377.             and event[1] ~= \"update\" then\
  7378.                 self.public:passEvent( unpack( event ) )\
  7379.             end\
  7380.         end\
  7381.     end, true )\
  7382.     return self.public\
  7383. end\
  7384. \
  7385. function UICanvas.public:setTask( func )\
  7386.     setfenv( func, self.environment )\
  7387.     self.co = coroutine.create( func )\
  7388.     self.running = true\
  7389. end\
  7390. \
  7391. function UICanvas.public:passEvent( ... )\
  7392.     if not self.running then return end\
  7393.     local prev = term.redirect( self.term )\
  7394.     local ok, err = coroutine.resume( self.co, ... )\
  7395.     term.redirect( prev )\
  7396.     if not ok then\
  7397.         self.running = false\
  7398.         local prev = term.redirect( self.term )\
  7399.         self.environment.printError( err )\
  7400.         term.redirect( prev )\
  7401.     end\
  7402.     if coroutine.status( self.co ) == \"dead\" then\
  7403.         self.running = false\
  7404.     end\
  7405. end\
  7406. \
  7407. function UICanvas.public:draw( x, y )\
  7408.     local layer = stencil.addLayer( x, y, self.w, self.h )\
  7409.     self.image:resize( self.w, self.h )\
  7410.     self.image:foreach( function( px, py, bc, tc, char )\
  7411.         stencil.pixel( x + px - 1, y + py - 1, bc, tc, char )\
  7412.         return bc, tc, char\
  7413.     end )\
  7414.     if self.cb then\
  7415.         stencil.setCursorBlink( x + self.cx - 1, y + self.cy - 1, self.tc )\
  7416.     end\
  7417.     local c = { }\
  7418.     for i, child in ipairs( self.children ) do\
  7419.         c[i] = child\
  7420.     end\
  7421.     for i, child in ipairs( c ) do\
  7422.         child:draw( x + child.x - 1 + self.cx, y + child.y - 1 + self.cy )\
  7423.     end\
  7424.     stencil.closeLayer( layer )\
  7425. end\
  7426. \
  7427. function UICanvas.public:onMouseClick( x, y, button )\
  7428.     self.public:passEvent( \"mouse_click\", button, x, y )\
  7429. end\
  7430. \
  7431. function UICanvas.public:onMouseDrag( x, y, cx, cy, button )\
  7432.     self.public:passEvent( \"mouse_drag\", button, x, y )\
  7433. end\
  7434. \
  7435. function UICanvas.public:onMouseScroll( x, y, dir )\
  7436.     self.public:passEvent( \"mouse_scroll\", dir, x, y )\
  7437. end\
  7438. \
  7439. function UICanvas.public:onKeyPress( key, lastkey )\
  7440.     self.public:passEvent( \"key\", key )\
  7441. end\
  7442. \
  7443. function UICanvas.public:onTextInput( text, lastkey )\
  7444.     self.public:passEvent( \"char\", text )\
  7445. end", meta={
  7446.   type = "class",
  7447. }};["clipboard"]={content="\
  7448. local 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={
  7449.   type = "lib",
  7450. }};["ttf"]={content="\
  7451. local pack = {\
  7452. Run={\
  7453. [\"icon.nim\"]=\"F0RF0uF0nF4 F4 F4 F4 F4 F0 \\nF4 F4CF4rF4aF4fF4tF4OF4SF0 \\nF4>F4 F4_F0 F0 F0 F0 F0 F0 \",\
  7454. [\"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\",\
  7455. [\"appconfig.txt\"]=\"handles = {\\n    \\\"unknown\\\";\\n \\\"lua\\\";\\n};\",\
  7456. }\
  7457. ,\
  7458. NetServer={\
  7459. [\"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\",\
  7460. [\"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\",\
  7461. [\"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]]\",\
  7462. [\"icon.nim\"]=\"--[[\\n  type = \\\"nova_image\\\",\\n]]07N07e07t07S07e07r07v07e07r\\n08<08p08>08H08i08<08/08p08>\\n08<08p08>08.08.08<08/08p08>\",\
  7463. [\"appconfig.txt\"]=\"handles = {\\n    \\\"NetServerPlugin\\\";\\n};\\nextensions = {\\n   [\\\"nsp\\\"] = \\\"NetServerPlugin\\\";\\n};\\nfiletypes = {\\n    [\\\"NetServerPlugin\\\"] = \\\"NetServer plugin file\\\";\\n};\\nservices = {\\n   \\\"responder\\\";\\n};\",\
  7464. }\
  7465. ,\
  7466. Files={\
  7467. [\"icon.nim\"]=\"1F 1F 1F 1F 1F 1F 1F 1F 1F \\n4F 4F 47F47i47l47e47s4F 4F \\n4F 4F 4F 4F 4F 4F 4F 4F 4F \",\
  7468. [\"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\",\
  7469. }\
  7470. ,\
  7471. Browser={\
  7472. [\"icon.nim\"]=\"--[[\\n  type = \\\"nova_image\\\",\\n]]B0 40 40 40                \\nB0       D0     7N 7e 7t   \\nE0 E0 E0 D0                \",\
  7473. [\"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\",\
  7474. [\"appconfig.txt\"]=\"extensions = {\\n nml = \\\"NovaMarkupLanguage\\\";\\n};\\nfiletypes = {\\n   NovaMarkupLanguage = \\\"Nova Markup Language file\\\";\\n};\\nhandles = {\\n   \\\"NovaMarkupLanguage\\\";\\n};\",\
  7475. }\
  7476. ,\
  7477. Settings={\
  7478. [\"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 \",\
  7479. [\"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\\\" };\\\\\"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\\\" };\\\\\"space\\\", \\\"rule\\\";\\n { type = \\\"label\\\", name = \\\"Network\\\" };\\n    \\\"space\\\";\\n   { type = \\\"display\\\", name = \\\"not yet implemented\\\" };\\n} )\",\
  7480. }\
  7481. ,\
  7482. Paint={\
  7483. [\"icon.nim\"]=\"BF 3F 3B-3B-3B-3B-3B-3F BF \\nBF 3F 3EP3Ea3Ei3En3Et3F BF \\nBF 3F 3B-3B-3B-3B-3B-3F BF \",\
  7484. [\"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\",\
  7485. [\"appconfig.txt\"]=\"handles = {\\n    \\\"nova_image\\\";\\n};\",\
  7486. }\
  7487. ,\
  7488. Edit={\
  7489. [\"icon.nim\"]=\"78178:77 0F 07E07d07i07t0F \\n78278:77 0F 08-08-08-08-0F \\n78378:77 0F 08-08-08-08-0F \",\
  7490. [\"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\",\
  7491. [\"appconfig.txt\"]=\"handles = {\\n    \\\"text\\\";\\n    \\\"lua\\\";\\n \\\"unknown\\\";\\n \\\"lib\\\";\\n \\\"class\\\";\\n};\",\
  7492. }\
  7493. ,\
  7494. Test={\
  7495. [\"main.lua\"]=\"\\nlocal random = Nova.app.service.Desktop[1].misc\\nNovaUI.display.alert( Nova.app.UI, random, true )\\nNova.app.close( )\",\
  7496. }\
  7497. ,\
  7498. BBTetris={\
  7499. [\"appconfig.txt\"]=\"runmode = \\\"compatibility\\\";\\nfullscreen = true;\",\
  7500. [\"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\",\
  7501. [\"credit.txt\"]=\"All credit for this program goes to Bomb Bloke\",\
  7502. }\
  7503. ,\
  7504. Desktop={\
  7505. [\"appconfig.txt\"]=\"services = {\\n   \\\"launcher\\\";\\n}\",\
  7506. [\"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\",\
  7507. [\"launcher.lua\"]=\"\\nlocal s = Nova.app.getData \\\"startup\\\"\\nif s ~= false then\\n  Nova.app.launch \\\"Desktop\\\"\\nend\",\
  7508. }\
  7509. ,\
  7510. }\
  7511. \
  7512. function ttf( table, dir )\
  7513.     if not fs.isDir( dir ) then\
  7514.         fs.makeDir( dir )\
  7515.     end\
  7516.     for k, v in pairs( table ) do\
  7517.         if type( v ) == \"table\" then\
  7518.             ttf( v, dir..\"/\"..k )\
  7519.         elseif type( v ) == \"string\" then\
  7520.             local f = fs.open( dir..\"/\"..k, \"w\" )\
  7521.             f.write( v )\
  7522.             f.close( )\
  7523.         end\
  7524.     end\
  7525. end\
  7526. return function( path )\
  7527.     ttf( pack, path )\
  7528. end", meta={
  7529.   type = "lib",
  7530. }};["UIMenu"]={content="\
  7531. \
  7532. require \"UIElement\"\
  7533. \
  7534. UIMenu:extends( UIElement )\
  7535. \
  7536. UIMenu.handlesMouse = false\
  7537. UIMenu.handlesScroll = true\
  7538. UIMenu.isScrollTarget = true\
  7539. UIMenu.scrollDirection = \"vertical\"\
  7540. UIMenu.public \"padding\" \"number\"\
  7541. \
  7542. function UIMenu:UIMenu( x, y, w, h, direction )\
  7543.     self:UIElement( x, y, w, h )\
  7544.     self.scrollDirection = direction or \"vertical\"\
  7545.     self.padding = 0\
  7546.     return self.public\
  7547. end\
  7548. \
  7549. function UIMenu.public:update( dt )\
  7550.     local max = self.scrollDirection == \"vertical\" and self.h or self.w\
  7551.     if self.scrollDirection == \"vertical\" then\
  7552.         for i = 1, #self.children do\
  7553.             max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
  7554.         end\
  7555.     else\
  7556.         for i = 1, #self.children do\
  7557.             max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
  7558.         end\
  7559.     end\
  7560.     if self.scrollDirection == \"vertical\" then\
  7561.         if max - self.h < -self.cy then\
  7562.             self.cy = -max + self.h\
  7563.         end\
  7564.     else\
  7565.         if max - self.w < -self.cx then\
  7566.             self.cx = -max + self.w\
  7567.         end\
  7568.     end\
  7569.     if max == ( self.scrollDirection == \"vertical\" and self.h or self.w ) then\
  7570.         self.handlesScroll = false\
  7571.     else\
  7572.         self.handlesScroll = true\
  7573.     end\
  7574.     local c = { }\
  7575.     for i, child in ipairs( self.children ) do\
  7576.         c[i] = child\
  7577.     end\
  7578.     for i, child in ipairs( c ) do\
  7579.         child:update( dt )\
  7580.     end\
  7581.     local x, y = 1, 1\
  7582.     for i, child in ipairs( self.children ) do\
  7583.         child.x = x\
  7584.         child.y = y\
  7585.         if self.scrollDirection == \"vertical\" then\
  7586.             child.w = self.w\
  7587.             y = y + child.h + self.padding\
  7588.         else\
  7589.             child.h = self.h\
  7590.             x = x + child.w + self.padding\
  7591.         end\
  7592.     end\
  7593. end\
  7594. \
  7595. function UIMenu.public:onMouseScroll( rx, ry, dir )\
  7596.     if self.scrollDirection == \"vertical\" then\
  7597.         self.cy = self.cy - dir\
  7598.         local max = 0\
  7599.         for i = 1, #self.children do\
  7600.             max = math.max( max, self.children[i].y + self.children[i].h )\
  7601.         end\
  7602.         max = max - self.h - 1\
  7603.         if self.cy < -max then\
  7604.             self.cy = -max\
  7605.         end\
  7606.         if self.cy > 0 then self.cy = 0 end\
  7607.     else\
  7608.         self.cx = self.cx - dir\
  7609.         local max = 0\
  7610.         for i = 1, #self.children do\
  7611.             max = math.max( max, self.children[i].x + self.children[i].w )\
  7612.         end\
  7613.         max = max - self.w - 1\
  7614.         if self.cx < -max then\
  7615.             self.cx = -max\
  7616.         end\
  7617.         if self.cx > 0 then self.cx = 0 end\
  7618.     end\
  7619. end\
  7620. \
  7621. function UIMenu.public:getDisplaySizeH( )\
  7622.     return self.w\
  7623. end\
  7624. function UIMenu.public:getDisplaySizeV( )\
  7625.     return self.h\
  7626. end\
  7627. \
  7628. function UIMenu.public:getContentSizeH( )\
  7629.     local max = self.w\
  7630.     for i = 1, #self.children do\
  7631.         max = math.max( max, self.children[i].x + self.children[i].w - 1 )\
  7632.     end\
  7633.     return max\
  7634. end\
  7635. function UIMenu.public:getContentSizeV( )\
  7636.     local max = self.h\
  7637.     for i = 1, #self.children do\
  7638.         max = math.max( max, self.children[i].y + self.children[i].h - 1 )\
  7639.     end\
  7640.     return max\
  7641. end\
  7642. \
  7643. function UIMenu.public:getContentScrollH( )\
  7644.     return -self.cx\
  7645. end\
  7646. function UIMenu.public:getContentScrollV( )\
  7647.     return -self.cy\
  7648. end\
  7649. \
  7650. function UIMenu.public:setContentScrollH( scroll )\
  7651.     self.cx = -scroll\
  7652. end\
  7653. function UIMenu.public:setContentScrollV( scroll )\
  7654.     self.cy = -scroll\
  7655. end", meta={
  7656.   type = "class",
  7657. }};["MarkupText"]={content="\
  7658. \
  7659. require \"MarkupGenericObject\"\
  7660. \
  7661. MarkupText:extends( MarkupGenericObject )\
  7662. \
  7663. MarkupText.public.etype = \"text\"\
  7664. \
  7665. function MarkupText:MarkupText( ... )\
  7666.     self:MarkupGenericObject( ... )\
  7667. \
  7668.     self.innerNML = { }\
  7669. \
  7670.     return self.public\
  7671. end\
  7672. \
  7673. function MarkupText.public:setAttribute( attribute, value ) -- protected objects should do innerNML stuff\
  7674.     if MarkupGenericObject.attributes[attribute] then\
  7675.         MarkupGenericObject.attributes[attribute]( self, value )\
  7676.     end\
  7677.     if attribute == \"tc\" or attribute == \"textColour\" then\
  7678.         if colours[value] or value == \"transparent\" then\
  7679.             self.tc = colours[value] or 0\
  7680.         end\
  7681.     end\
  7682.     if attribute == \"bc\" or attribute == \"backgroundColour\" or attribute == \"colour\" then\
  7683.         if colours[value] or value == \"transparent\" then\
  7684.             self.bc = colours[value] or 0\
  7685.         end\
  7686.     end\
  7687.     if attribute == \"align\" then\
  7688.         if value == \"left\" or value == \"right\" or value == \"centre\" then\
  7689.             self.align = value\
  7690.         end\
  7691.     end\
  7692.     if attribute == \"innerNML\" then\
  7693.         self.innerNML = self.page:loadAST( value )\
  7694.     end\
  7695.     self.page.needsReposition = true\
  7696. end\
  7697. \
  7698. function MarkupText.public:loadObject( positionHandler )\
  7699. \
  7700.     self.page:newStyle {\
  7701.         bc = self.bc;\
  7702.         tc = self.tc;\
  7703.         align = self.align;\
  7704.     }\
  7705.     for i = #self.innerNML, 1, -1 do\
  7706.         if self.innerNML[i].remove then\
  7707.             table.remove( self.objects, i )\
  7708.         end\
  7709.     end\
  7710.     local objects = { }\
  7711.     if self.width.type == \"text\" and self.width.value == \"auto\" and self.height.type == \"text\" and self.height.value == \"auto\" then\
  7712.         for i = 1, #self.innerNML do\
  7713.             local o = positionHandler:newObject( self.innerNML[i], self.page )\
  7714.             for i = 1, #o do\
  7715.                 table.insert( objects, o[i] )\
  7716.             end\
  7717.         end\
  7718.     else\
  7719.         local w, h = 0, 0\
  7720.         if self.width.type == \"text\" and self.width.value == \"auto\" then\
  7721.             w = positionHandler.w - positionHandler.x + 1\
  7722.         elseif self.width.type == \"percent\" then\
  7723.             w = self.width.value / 100 * positionHandler.w\
  7724.         elseif self.width.type == \"pixel\" then\
  7725.             w = self.width.value\
  7726.         end\
  7727.         if self.height.type == \"text\" and self.height.value == \"auto\" then\
  7728.             h = positionHandler.h - positionHandler.y + 1\
  7729.         elseif self.height.type == \"percent\" then\
  7730.             h = self.height.value / 100 * positionHandler.h\
  7731.         elseif self.height.type == \"pixel\" then\
  7732.             h = self.height.value\
  7733.         end\
  7734.         local pos = MarkupPositionHandler( w, h )\
  7735.         for i = 1, #self.innerNML do\
  7736.             local o = pos:newObject( self.innerNML[i], self.page )\
  7737.             for i = 1, #o do\
  7738.                 table.insert( objects, o[i] )\
  7739.             end\
  7740.         end\
  7741.         if self.height.type == \"text\" and self.height.value == \"auto\" then\
  7742.             h = 0\
  7743.             for i = 1, #objects do\
  7744.                 if objects[i].y + objects[i].content.height - 1 > h then\
  7745.                     h = objects[i].y + objects[i].content.height - 1\
  7746.                 end\
  7747.             end\
  7748.         end\
  7749.         local rw, rh = w, h\
  7750.         local scrollbarv, scrollbarh = false, false\
  7751.         for i = 1, #objects do\
  7752.             if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
  7753.                 w = w - 1\
  7754.             end\
  7755.             if objects[i].x + objects[i].content.width - 1 > w and not scrollbarh then\
  7756.                 h = h - 1\
  7757.                 scrollbarh = true\
  7758.             end\
  7759.             if objects[i].y + objects[i].content.height - 1 > h and not scrollbarv then\
  7760.                 scrollbarv = true\
  7761.             end\
  7762.             if scrollbarv and scrollbarh then\
  7763.                 break\
  7764.             end\
  7765.         end\
  7766.         local loadedObject = MarkupLoadedObject( )\
  7767.         loadedObject.x = positionHandler.x\
  7768.         loadedObject.y = positionHandler.y\
  7769.         loadedObject.mode = \"inline\"\
  7770.         loadedObject.content.width = rw\
  7771.         loadedObject.content.height = rh\
  7772.         loadedObject:initialise( UIFrame )\
  7773.         local f = loadedObject.element\
  7774.         f:newChild( UIText( 1, 1, rw, rh, \"\" ) ).bc = self.page:getStyle \"bc\"\
  7775.         local cf\
  7776.         if not scrollbarv and not scrollbarh then\
  7777.             cf = f\
  7778.         else\
  7779.             cf = f:newChild( UIFrame( 1, 1, w, h ) )\
  7780.         end\
  7781.         for i = 1, #objects do\
  7782.             cf:newChild( objects[i].element )\
  7783.         end\
  7784.         if scrollbarv then\
  7785.             f:newChild( UIScrollBar( rw, 1, 1, h, cf ) )\
  7786.         end\
  7787.         if scrollbarh then\
  7788.             f:newChild( UIScrollBar( rh, 1, rw, 1, cf, \"horizontal\" ) )\
  7789.         end\
  7790.         objects = { loadedObject }\
  7791.     end\
  7792. \
  7793.     self.page:removeStyle( )\
  7794.     return objects\
  7795.     -- generate a list of MarkupLoadedObjects\
  7796. end\
  7797. \
  7798. function MarkupText.public:scriptObject( )\
  7799.     return setmetatable( {}, {\
  7800.         __newindex = function( _, k, v )\
  7801.             if type( v ) ~= \"string\" then\
  7802.                 v = tostring( v )\
  7803.             end\
  7804.             if k == \"innerNML\" then\
  7805.                 v = v:gsub( \"<!%-%-*.-%-%->\", \"\" )\
  7806.                 v = markup_parse.parse( v, self.page )\
  7807.             end\
  7808.             return self.public:setAttribute( k, v )\
  7809.         end;\
  7810.         __index = function( _, k )\
  7811.             if k == \"width\" then\
  7812.                 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 )\
  7813.             elseif k == \"height\" then\
  7814.                 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 )\
  7815.             elseif k == \"bc\" or k == \"backgroundColour\" or k == \"colour\" then\
  7816.                 return self.bc\
  7817.             elseif k == \"tc\" or k == \"textColour\" then\
  7818.                 return self.tc\
  7819.             elseif k == \"align\" then\
  7820.                 return self.align\
  7821.             else\
  7822.                 return error \"cannot access this attribute\"\
  7823.             end\
  7824.         end;\
  7825.     } )\
  7826. end", meta={
  7827.   type = "class",
  7828. }};["main"]={content="\
  7829. require \"clipboard\"\
  7830. require \"com\"\
  7831. require \"core\"\
  7832. require \"encryption\"\
  7833. require \"filesystem\"\
  7834. require \"network\"\
  7835. require \"sha256\"\
  7836. require \"stringutils\"\
  7837. require \"Thread\"\
  7838. require \"UI\"\
  7839. \
  7840. local w, h = term.getSize( )\
  7841. \
  7842. if _G.NovaRunning then\
  7843.     return\
  7844. end\
  7845. _G.NovaRunning = true\
  7846. \
  7847. if not term.isColour( ) then\
  7848.     error( \"Nova requires an advanced computer\", 0 )\
  7849. end\
  7850. \
  7851. term.setBackgroundColour( colours.black )\
  7852. term.clear( )\
  7853. sleep( 0.1 )\
  7854. term.setBackgroundColour( colours.grey )\
  7855. term.clear( )\
  7856. sleep( 0.1 )\
  7857. term.setBackgroundColour( colours.lightGrey )\
  7858. term.clear( )\
  7859. sleep( 0.1 )\
  7860. \
  7861. Thread( function( )\
  7862.     require \"kernel\"\
  7863. end ).onException = function( _, err )\
  7864.     core.error( err )\
  7865. end\
  7866. \
  7867. com.updateModems( )\
  7868. \
  7869. local function update( event, dt )\
  7870.     if event[1] == \"update\" then\
  7871.         Thread.update( { \"update\", dt } )\
  7872.         buffer.drawChanges( )\
  7873.         buffer.clear( )\
  7874.     else\
  7875.         com.listen( event )\
  7876.         com.clear( )\
  7877.         Thread.update( { unpack( event ) }, true )\
  7878.         network.clearBuffers( )\
  7879.     end\
  7880.     filesystem.clearCache( )\
  7881. end\
  7882. \
  7883. ok, err = pcall( function( )\
  7884.     local time = os.clock( )\
  7885.     local timer = os.startTimer( 0 )\
  7886.     os.queueEvent \"start\"\
  7887.     while core.running and not core.error_message do\
  7888.         local ev = { coroutine.yield( ) }\
  7889.         local dt = os.clock( ) - time\
  7890.         time = os.clock( )\
  7891.         if ev[1] == \"timer\" and ev[2] == timer then\
  7892.             update( { \"update\" }, dt )\
  7893.             timer = os.startTimer( 0.05 )\
  7894.         else\
  7895.             update( ev, dt )\
  7896.         end\
  7897.     end\
  7898. end )\
  7899. \
  7900. core.finish( )\
  7901. \
  7902. if not ok or core.error_message then\
  7903.     core.log( \"fatal error\", core.error_message or err )\
  7904.     local w, h = term.getSize( )\
  7905.     term.setBackgroundColour( colours.blue )\
  7906.     term.clear( )\
  7907.     term.setTextColour( colours.white )\
  7908.     term.setCursorPos( 1, 1 )\
  7909.     term.write \"Nova has encountered a fatal error.\"\
  7910.     term.setCursorPos( 1, 2 )\
  7911.     print \"Please report this message to the creator\\n\"\
  7912.     term.setTextColour( colours.white )\
  7913.     print( core.error_message or err )\
  7914.     parallel.waitForAny( function( )\
  7915.         os.pullEvent \"key\"\
  7916.     end, function( )\
  7917.         os.pullEvent \"mouse_click\"\
  7918.     end )\
  7919. end", meta={}};["MarkupFrame"]={content="\
  7920. \
  7921. require \"MarkupGenericObject\"\
  7922. \
  7923. MarkupFrame:extends( MarkupGenericObject )", meta={
  7924.   type = "class",
  7925. }};["core"]={content="\
  7926. \
  7927. require \"ttf\"\
  7928. \
  7929. name = \"Nova Horizon\"\
  7930. version_major = 1\
  7931. version_minor = 2\
  7932. version_patch = 6\
  7933. version = (\"%d.%d.%02d\"):format( version_major, version_minor, version_patch )\
  7934. author = \"Benedict Allen\"\
  7935. \
  7936. platform = \"computer\"\
  7937. local w, h = term.getSize( )\
  7938. if turtle then\
  7939.     platform = \"turtle\"\
  7940. elseif pocket or w < 39 then\
  7941.     platform = \"pocket\"\
  7942. end\
  7943. \
  7944. running = true\
  7945. path = \"Nova\"\
  7946. \
  7947. session = false\
  7948. UI = false\
  7949. \
  7950. if not fs.isDir( path ) then\
  7951.     fs.makeDir( path )\
  7952. end\
  7953. if not fs.isDir( path .. \"/apps\" ) then\
  7954.     fs.makeDir( path .. \"/apps\" )\
  7955.     ttf( path .. \"/apps\" )\
  7956. end\
  7957. if not fs.isDir( path .. \"/user\" ) then\
  7958.     fs.makeDir( path .. \"/user\" )\
  7959. end\
  7960. \
  7961. start_time = os.clock( )\
  7962. \
  7963. error_message = false\
  7964. \
  7965. function error( message )\
  7966.     core.error_message = message\
  7967. end\
  7968. \
  7969. local file = path .. \"/log.txt\"\
  7970. local h = fs.open( file, \"w\" )\
  7971. if h then\
  7972.     h.close( )\
  7973. end\
  7974. \
  7975. function log( pre, ... )\
  7976.     local data\
  7977.     if ... then\
  7978.         local t = { ... }\
  7979.         data = tostring( t[1] )\
  7980.         for i = 2, #t do\
  7981.             data = data .. \", \" .. tostring( t[i] )\
  7982.         end\
  7983.     end\
  7984.     local h = fs.open( file, \"a\" )\
  7985.     if h then\
  7986.         local time = \"[\" .. os.clock( ) - start_time .. \"] \"\
  7987.         if data then\
  7988.             h.write( time .. tostring( pre ) .. \": \" .. textutils.serialize( data ) .. \"\\n\" )\
  7989.         else\
  7990.             h.write( time .. tostring( pre ) .. \"\\n\" )\
  7991.         end\
  7992.         h.close( )\
  7993.         return true\
  7994.     end\
  7995.     return false\
  7996. end\
  7997. \
  7998. function getRunTime( )\
  7999.     return os.clock( ) - start_time\
  8000. end\
  8001. \
  8002. function finish( )\
  8003.     if core.session then\
  8004.         core.session:logout( )\
  8005.     end\
  8006.     core.log \"stopping\"\
  8007.     _G.NovaRunning = false\
  8008. end\
  8009. \
  8010. function getUpdateInfo( )\
  8011.     local t = {\
  8012.         name = \"Nova Horizon\";\
  8013.         version_major = version_major;\
  8014.         version_minor = version_minor;\
  8015.         version_patch = version_patch;\
  8016.     }\
  8017.     pcall( function( )\
  8018.         local h = http.get \"http://pastebin.com/raw.php?i=K8MKteNu\"\
  8019.         if h then\
  8020.             t = textutils.unserialize( h.readAll( ) ) or t\
  8021.             h.close( )\
  8022.         end\
  8023.     end )\
  8024.     return t\
  8025. end\
  8026. \
  8027. function compareVersions( t )\
  8028.     t = t or getUpdateInfo( )\
  8029.     if t.version_major > version_major then\
  8030.         return true\
  8031.     elseif t.version_major == version_major then\
  8032.         if t.version_minor > version_minor then\
  8033.             return true\
  8034.         elseif t.version_minor == version_minor then\
  8035.             return t.version_patch > version_patch\
  8036.         end\
  8037.     end\
  8038.     return false\
  8039. end\
  8040. \
  8041. function update( updateInfo )\
  8042.     updateInfo = updateInfo or getUpdateInfo( )\
  8043.     local h = http.get( \"http://pastebin.com/raw.php?i=\" .. updateInfo.link )\
  8044.     if h then\
  8045.         if type( updateInfo.pre_update ) == \"function\" then\
  8046.             setfenv( updateInfo.pre_update, getfenv( ) )\
  8047.             updateInfo.pre_update( )\
  8048.         end\
  8049.         local content = h.readAll( )\
  8050.         h.close( )\
  8051.         filesystem.delete( \"C:/\" .. path .. \"/apps\" )\
  8052.         filesystem.delete \"C:/startup\"\
  8053.         filesystem.writefile( \"startup\", FileData( content ) )\
  8054.         if type( updateInfo.post_update ) == \"function\" then\
  8055.             setfenv( updateInfo.post_update, getfenv( ) )\
  8056.             updateInfo.post_update( )\
  8057.         end\
  8058.         return true\
  8059.     end\
  8060.     return false\
  8061. end\
  8062. \
  8063. -- Credits to lbphacker for code below\
  8064. local 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\
  8065. --time since last update\
  8066. local firstepoch, firstclock, tslu\
  8067. function getirltime(gmtoffset)\
  8068.     if not firstepoch or os.clock( ) - tslu > 300 then\
  8069.         pcall( function( )\
  8070.             local httpResponseHandle = http.get \"http://lbphacker.hu/cctime.php\"\
  8071.             if not httpResponseHandle then\
  8072.                 return false\
  8073.             end\
  8074.             firstepoch = tonumber(httpResponseHandle.readAll())\
  8075.             if not firstepoch then\
  8076.                 return false\
  8077.             end\
  8078.             firstclock = os.clock()\
  8079.             httpResponseHandle.close()\
  8080.             tslu = os.clock( )\
  8081.         end )\
  8082.     end\
  8083.     if firstepoch then\
  8084.         local y, j, m, d, w, h, n, s = gmtime(firstepoch + math.floor(os.clock() - firstclock) + (gmtoffset or 0) * 3600)\
  8085.         return {h = h, m = n, s = s}\
  8086.     end\
  8087.     return { h = math.floor( os.time( ) / 60 ), m = os.time( ) % 60, s = 0 }\
  8088. end\
  8089. \
  8090. local months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }\
  8091. \
  8092. function getirldate(gmtoffset)\
  8093.     if not firstepoch or os.clock( ) - tslu > 300 then\
  8094.         pcall( function( )\
  8095.             local httpResponseHandle = http.get \"http://lbphacker.hu/cctime.php\"\
  8096.             if not httpResponseHandle then\
  8097.                 return false\
  8098.             end\
  8099.             firstepoch = tonumber(httpResponseHandle.readAll())\
  8100.             if not firstepoch then\
  8101.                 return false\
  8102.             end\
  8103.             firstclock = os.clock()\
  8104.             httpResponseHandle.close()\
  8105.             tslu = os.clock( )\
  8106.         end )\
  8107.     end\
  8108.     if firstepoch then\
  8109.         local y, j, m, d, w, h, n, s = gmtime(firstepoch + math.floor(os.clock() - firstclock) + (gmtoffset or 0) * 3600)\
  8110.         return {y = y, j = j, m = m, d = d, w = w}\
  8111.     end\
  8112.     local day = os.day( )\
  8113.     local month = 12\
  8114.     local year = math.floor( day / 365 )\
  8115.     day = day - year * 365\
  8116.     for i = 1, #months do\
  8117.         if day < months[i] then\
  8118.             month = i\
  8119.             break\
  8120.         else\
  8121.             day = day - months[i]\
  8122.         end\
  8123.     end\
  8124.     return { y = year, j = 0, m = month, d = day, w = 0 }\
  8125. end", meta={
  8126.   type = "lib",
  8127. }};["stencil"]={content="\
  8128. \
  8129. local function linewrap( str, w )\
  8130.     for i = 1, w + 1 do\
  8131.         if str:sub( i, i ) == \"\\n\" then\
  8132.             return str:sub( 1, i - 1 ), str:sub( i + 1 )\
  8133.         end\
  8134.     end\
  8135.     if #str <= w then\
  8136.         return str, \"\"\
  8137.     end\
  8138.     if str:sub( w + 1, w + 1 ):find \"%s\" then\
  8139.         return str:sub( 1, w ), str:sub( w + 1 ):gsub( \"^%s+\", \"\" )\
  8140.     end\
  8141.     for i = w, 1, -1 do\
  8142.         if str:sub( i, i ):find \"%s\" then\
  8143.             return str:sub( 1, i ), str:sub( i + 1 )\
  8144.         end\
  8145.     end\
  8146.     return str:sub( 1, w ), str:sub( w + 1 )\
  8147. end\
  8148. \
  8149. local function wordwrap( str, w, h )\
  8150.     if w < 1 then return { } end\
  8151.     local lines = { }\
  8152.     local line\
  8153.     while #str > 0 do\
  8154.         line, str = linewrap( str, w )\
  8155.         table.insert( lines, line )\
  8156.     end\
  8157.     while #lines > h do\
  8158.         lines[#lines] = nil\
  8159.     end\
  8160.     return lines\
  8161. end\
  8162. \
  8163. local function bbox( x1, y1, w1, h1, x2, y2, w2, h2 )\
  8164.     local x, y, w, h\
  8165.     if x1 + w1 <= x2 or y1 + h1 <= y2 or x2 + w2 <= x1 or y2 + h2 <= y1 then\
  8166.         return false\
  8167.     end\
  8168.     x = math.max( x1, x2 )\
  8169.     w = math.min( x1 + w1, x2 + w2 ) - x\
  8170.     y = math.max( y1, y2 )\
  8171.     h = math.min( y1 + h1, y2 + h2 ) - y\
  8172.     return x, y, w, h\
  8173. end\
  8174. \
  8175. local layers = { }\
  8176. local w, h = term.getSize( )\
  8177. local limit = { x = 1, y = 1, w = w, h = h }\
  8178. \
  8179. function pixel( x, y, bc, tc, char )\
  8180.     if limit and x >= limit.x and x < limit.x + limit.w and y >= limit.y and y < limit.y + limit.h then\
  8181.         buffer.setPixel( x, y, bc, tc, char )\
  8182.         return true\
  8183.     end\
  8184.     return false\
  8185. end\
  8186. \
  8187. function textLine( x, y, bc, tc, text )\
  8188.     for i = 1, #text do\
  8189.         pixel( x + i - 1, y, bc, tc, text:sub( i, i ) )\
  8190.     end\
  8191. end\
  8192. \
  8193. function rectangle( x, y, w, h, bc, tc, char )\
  8194.     for i = 0, h - 1 do\
  8195.         textLine( x, y + i, bc, tc, ( char or \" \" ):rep( w ) )\
  8196.     end\
  8197. end\
  8198. \
  8199. function text( x, y, w, h, bc, tc, text, pad )\
  8200.     local lines = wordwrap( text, w, h )\
  8201.     if pad then\
  8202.         pad( lines )\
  8203.     end\
  8204.     if bc ~= 0 then\
  8205.         while #lines < h do\
  8206.             table.insert( lines, string.rep( \" \", math.max( w, 0 ) ) )\
  8207.         end\
  8208.     end\
  8209.     for i = 1, #lines do\
  8210.         local line = lines[i]:sub( 1, w )\
  8211.         if bc ~= 0 then\
  8212.             line = line .. string.rep( \" \", math.max( w - #line, 0 ) )\
  8213.         end\
  8214.         textLine( x, y + i - 1, bc, tc, line )\
  8215.     end\
  8216. end\
  8217. \
  8218. function setCursorBlink( x, y, col )\
  8219.     if limit and x >= limit.x and x < limit.x + limit.w and y >= limit.y and y < limit.y + limit.h then\
  8220.         buffer.setCursorBlink( x, y, col )\
  8221.         return true\
  8222.     end\
  8223.     return false\
  8224. end\
  8225. \
  8226. function addLayer( x, y, w, h )\
  8227.     table.insert( layers, { x = x, y = y, w = w, h = h } )\
  8228.     if limit then\
  8229.         x, y, w, h = bbox( x, y, w, h, limit.x, limit.y, limit.w, limit.h )\
  8230.         if x then\
  8231.             limit = { x = x, y = y, w = w, h = h }\
  8232.         else\
  8233.             limit = false\
  8234.         end\
  8235.     end\
  8236.     return #layers\
  8237. end\
  8238. \
  8239. function closeLayer( layer )\
  8240.     while layers[layer] do\
  8241.         table.remove( layers, layer )\
  8242.     end\
  8243.     limit = { x = 1, y = 1, w = w, h = h }\
  8244.     for i, l in ipairs( layers ) do\
  8245.         local x, y, w, h = bbox( limit.x, limit.y, limit.w, limit.h, l.x, l.y, l.w, l.h )\
  8246.         if x then\
  8247.             limit = { x = x, y = y, w = w, h = h }\
  8248.         else\
  8249.             limit = false\
  8250.             break\
  8251.         end\
  8252.     end\
  8253. end\
  8254. \
  8255. function layerCount( )\
  8256.     return #layers\
  8257. end\
  8258. \
  8259. function getDrawArea( )\
  8260.     if limit then\
  8261.         return limit.x, limit.y, limit.w, limit.h\
  8262.     end\
  8263.     return 0, 0, 0, 0\
  8264. end", meta={
  8265.   type = "lib",
  8266. }};["Account"]={content="\
  8267. \
  8268. Account.public \"username\"\
  8269. Account.public.username.write = false\
  8270. Account.public \"password\"\
  8271. Account.public.password.write = false\
  8272. Account.public \"shaPassword\"\
  8273. Account.public.shaPassword.write = false\
  8274. Account.public \"userpath\"\
  8275. Account.public.userpath.write = false\
  8276. \
  8277. function Account:Account( name, pass )\
  8278.     if not fs.isDir( core.path .. \"/user/\" .. name ) then\
  8279.         return false, \"no such user\"\
  8280.     end\
  8281. \
  8282.     -- try to login\
  8283.     local userpath = core.path .. \"/user/\" .. name .. \"/\"\
  8284.     local h = fs.open( userpath .. \"passcheck.txt\", \"r\" )\
  8285.     if h then\
  8286.         local passcheck = h.readAll( )\
  8287.         h.close( )\
  8288.         if encryption.decrypt( passcheck, sha256( pass ) ) ~= name then\
  8289.             return false, \"incorrect password\"\
  8290.         end\
  8291.     else\
  8292.         return false, \"corrupt user\"\
  8293.     end\
  8294. \
  8295.     self.username = name\
  8296.     self.password = pass\
  8297.     self.shaPassword = sha256( pass )\
  8298.     self.userpath = userpath\
  8299. \
  8300.     return self.public\
  8301. end\
  8302. \
  8303. function Account.public:rename( name )\
  8304.     if fs.exists( core.path .. \"/user/\" .. name ) then\
  8305.         return false, \"user exists\"\
  8306.     end\
  8307.     local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
  8308.     if h then\
  8309.         h.write( encryption.encrypt( name, self.shaPassword ) )\
  8310.         h.close( )\
  8311.         if not pcall( function( )\
  8312.             fs.move( self.userpath, core.path .. \"/user/\" .. name )\
  8313.         end ) then\
  8314.             local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
  8315.             if h then\
  8316.                 h.write( encryption.encrypt( self.username, self.shaPassword ) )\
  8317.                 h.close( )\
  8318.             end\
  8319.             return false\
  8320.         end\
  8321.         self.username = name\
  8322.         self.userpath = core.path .. \"/user/\" .. name .. \"/\"\
  8323.         return true\
  8324.     end\
  8325.     return false\
  8326. end\
  8327. \
  8328. function Account.public:changePassword( pass )\
  8329.     local h = fs.open( self.userpath .. \"passcheck.txt\", \"w\" )\
  8330.     if h then\
  8331.         self.password = pass\
  8332.         self.shaPassword = sha256( pass )\
  8333.         h.write( encryption.encrypt( self.username, self.shaPassword ) )\
  8334.         h.close( )\
  8335.     end\
  8336.     return false\
  8337. end\
  8338. \
  8339. function Account.public:setData( index, entry, value )\
  8340.     local data\
  8341.     local h = fs.open( self.userpath .. index, \"r\" )\
  8342.     if h then\
  8343.         data = textutils.unserialize( h.readAll( ) ) or { }\
  8344.         h.close( )\
  8345.     else\
  8346.         data = { }\
  8347.     end\
  8348.     data[entry] = value\
  8349.     local h = fs.open( self.userpath .. index, \"w\" )\
  8350.     if h then\
  8351.         h.write( textutils.serialize( data ) )\
  8352.         h.close( )\
  8353.         return true\
  8354.     end\
  8355.     return false\
  8356. end\
  8357. \
  8358. function Account.public:getData( index, entry )\
  8359.     local data\
  8360.     local h = fs.open( self.userpath .. index, \"r\" )\
  8361.     if h then\
  8362.         data = textutils.unserialize( h.readAll( ) ) or { }\
  8363.         h.close( )\
  8364.         return data[entry]\
  8365.     end\
  8366. end\
  8367. \
  8368. function Account.public:hasAccess( request ) -- things like root filesystem access, settings, etc\
  8369.     return true\
  8370. end\
  8371. \
  8372. function Account.static.create( name, pass )\
  8373.     if fs.exists( core.path .. \"/user/\" .. name ) then\
  8374.         return false, \"account already exists\"\
  8375.     end\
  8376.     fs.makeDir( core.path .. \"/user/\" .. name )\
  8377.     local h = fs.open( core.path .. \"/user/\" .. name .. \"/passcheck.txt\", \"w\" )\
  8378.     if h then\
  8379.         h.write( encryption.encrypt( name, sha256( pass ) ) )\
  8380.         h.close( )\
  8381.     else\
  8382.         fs.delete( core.path .. \"/user/\" .. name )\
  8383.     end\
  8384.     fs.makeDir( core.path .. \"/user/\" .. name .. \"/data\" )\
  8385.     fs.makeDir( core.path .. \"/user/\" .. name .. \"/data/screenshots\" )\
  8386. end", meta={
  8387.   type = "class",
  8388. }};["MarkupPage"]={content="\
  8389. \
  8390. MarkupPage.public \"errors\"\
  8391. MarkupPage.public.errors.write = false\
  8392. MarkupPage.public \"ids\"\
  8393. MarkupPage.public.ids.write = false\
  8394. MarkupPage.public \"environment\"\
  8395. MarkupPage.public.environment.write = false\
  8396. MarkupPage.public \"threads\"\
  8397. MarkupPage.public.threads.write = false\
  8398. \
  8399. MarkupPage.public \"needsReposition\" \"boolean\"\
  8400. \
  8401. function MarkupPage:MarkupPage( w, h, content )\
  8402. \
  8403.     self.w = w\
  8404.     self.h = h\
  8405.     self.errors = { }\
  8406.     self.ids = { }\
  8407.     self.objects = { }\
  8408.     self.threads = { }\
  8409.     self.scriptelements = { }\
  8410. \
  8411.     self.styles = {\
  8412.         { bc = 1, tc = colours.grey, align = \"left\" };\
  8413.     }\
  8414. \
  8415.     self.needsReposition = false\
  8416. \
  8417.     self.environment = DocumentEnvironment( self.public, self )\
  8418. \
  8419.     if content then\
  8420.         self.public:loadMarkup( tostring( content ) )\
  8421.     end\
  8422. \
  8423.     self.threads[1] = Thread( function( )\
  8424.         while true do\
  8425.             if self.needsReposition then\
  8426.                 self.public:repositionElements( )\
  8427.             end\
  8428.             for i = #self.threads, 1, -1 do\
  8429.                 if not self.threads[i]:isRunning( ) and not self.threads[i]:isPaused( ) then\
  8430. \
  8431.                 end\
  8432.             end\
  8433.             coroutine.yield( )\
  8434.         end\
  8435.     end )\
  8436.     self.threads[1].onException = function( _, err )\
  8437.         table.insert( self.errors, { source = \"internal\", level = \"fatal\", message = err } )\
  8438.         self.threads[1]:restart( )\
  8439.     end\
  8440. \
  8441.     return self.public\
  8442. end\
  8443. \
  8444. function MarkupPage.public:loadMarkup( content )\
  8445.     content = content:gsub( \"<!%-%-*.-%-%->\", \"\" )\
  8446.     local ast = markup_parse.parse( content, self.public )\
  8447.     self.objects = self.public:loadAST( ast )\
  8448.     self.needsReposition = true\
  8449. end\
  8450. \
  8451. function MarkupPage.public:loadAST( ast )\
  8452.     local t = { }\
  8453.     for i = 1, #ast do\
  8454.         if type( ast[i] ) == \"table\" then\
  8455.             if ast[i].type == \"script\" then\
  8456.                 if not ast[i].loaded then\
  8457.                     local s = ast[i].attributes.innerNML[1]\
  8458.                     local source = ast[i].attributes.name and ( \"script \\\"\" .. ast[i].attributes.name .. \"\\\"\" ) or ( \"script on line \" .. ast[i].source )\
  8459.                     local f, err = loadstring( s, \"\" )\
  8460.                     if f then\
  8461.                         local t = Thread( f )\
  8462.                         function t.onException( _, err )\
  8463.                             table.insert( self.errors, { source = source, level = \"error\", message = err } )\
  8464.                         end\
  8465.                         t.environment = self.environment\
  8466.                         table.insert( self.threads, t )\
  8467.                     else\
  8468.                         table.insert( self.errors, { source = source, level = \"error\", message = err } )\
  8469.                     end\
  8470.                     ast[i].loaded = true\
  8471.                 end\
  8472.             else\
  8473.                 local _type = ast[i].type\
  8474.                 if nova_markup.objects[_type] then\
  8475.                     local object = nova_markup.objects[_type]( ast[i].source ) -- MarkupGenericObject\
  8476.                     object.page = self.public\
  8477.                     for k, v in pairs( ast[i].attributes ) do\
  8478.                         object:setAttribute( k, v, ast[i].source, true )\
  8479.                     end\
  8480.                     table.insert( t, object )\
  8481.                 else\
  8482.                     table.insert( self.errors, { source = ast[i].source, level = \"warning\", message = \"no such element \\\"\" .. _type .. \"\\\"\" } )\
  8483.                 end\
  8484.             end\
  8485.         else\
  8486.             ast[i] = ast[i]:gsub( \"^%s+\", \"\" ):gsub( \"%s+$\", \"\" )\
  8487.             if #ast[i] > 0 then\
  8488.                 table.insert( t, ast[i] )\
  8489.             end\
  8490.         end\
  8491.     end\
  8492.     return t\
  8493. end\
  8494. \
  8495. function MarkupPage.public:repositionElements( )\
  8496.     local elementPositionHandler = MarkupPositionHandler( self.w, self.h )\
  8497.     local frame = UIFrame( 1, 1, self.w, self.h )\
  8498.     for i = #self.objects, 1, -1 do\
  8499.         if self.objects[i].remove then\
  8500.             table.remove( self.objects, i )\
  8501.         end\
  8502.     end\
  8503.     for i = 1, #self.objects do\
  8504.         local loadedObjects = elementPositionHandler:newObject( self.objects[i], self.public )\
  8505.         -- MarkupLoadedObject list\
  8506.         for i = 1, #loadedObjects do\
  8507.             frame:newChild( loadedObjects[i].element )\
  8508.         end\
  8509.     end\
  8510.     if self.frame then\
  8511.         self.frame:clearChildren( )\
  8512.         local c = frame:getChildren( )\
  8513.         for i = 1, #c do\
  8514.             self.frame:newChild( c[i] )\
  8515.         end\
  8516.     else\
  8517.         self.frame = frame\
  8518.     end\
  8519.     self.needsReposition = false\
  8520.     return self.frame\
  8521. end\
  8522. \
  8523. function MarkupPage.public:resize( w, h )\
  8524.     self.w = w\
  8525.     self.h = h\
  8526.     self.public:repositionElements( )\
  8527. end\
  8528. \
  8529. MarkupPage.static.objects = ob\
  8530. \
  8531. function MarkupPage.public:newStyle( t )\
  8532.     table.insert( self.styles, t )\
  8533. end\
  8534. \
  8535. function MarkupPage.public:removeStyle( )\
  8536.     table.remove( self.styles, #self.styles )\
  8537. end\
  8538. \
  8539. function MarkupPage.public:getStyle( index, default )\
  8540.     for i = #self.styles, 1, -1 do\
  8541.         if self.styles[i][index] ~= nil then\
  8542.             return self.styles[i][index]\
  8543.         end\
  8544.     end\
  8545.     return default\
  8546. end", meta={
  8547.   type = "class",
  8548. }};["UIHandler"]={content="\
  8549. \
  8550. require \"stencil\"\
  8551. \
  8552. UIHandler.public.w, UIHandler.public.h = term.getSize( )\
  8553. \
  8554. UIHandler.public \"focus\"\
  8555. UIHandler.public.focus.write = false\
  8556. \
  8557. function UIHandler:UIHandler( )\
  8558.     self.children = { }\
  8559.     self.focus = false\
  8560.     self.lastx = 0\
  8561.     self.lasty = 0\
  8562.     self.lastt = false\
  8563.     self.lastkey = nil\
  8564.     self.lastkeytime = false\
  8565.     return self.public\
  8566. end\
  8567. \
  8568. function UIHandler.public:draw( )\
  8569.     local c = { }\
  8570.     for i, child in ipairs( self.children ) do\
  8571.         c[i] = child\
  8572.     end\
  8573.     for i, child in ipairs( c ) do\
  8574.         child:draw( child.x, child.y )\
  8575.     end\
  8576. end\
  8577. \
  8578. function UIHandler.public:update( dt )\
  8579.     local c = { }\
  8580.     for i, child in ipairs( self.children ) do\
  8581.         c[i] = child\
  8582.     end\
  8583.     for i, child in ipairs( c ) do\
  8584.         child:update( dt )\
  8585.     end\
  8586.     if self.lastkeytime then\
  8587.         if os.clock( ) - self.lastkeytime > 0.3 then\
  8588.             self.lastkey = nil\
  8589.             self.lastkeytime = false\
  8590.         end\
  8591.     end\
  8592. end\
  8593. \
  8594. local function findMouseTarget( children, parent, handler, x, y )\
  8595.     x = x - parent.cx\
  8596.     y = y - parent.cy\
  8597.     for i = #children, 1, -1 do\
  8598.         local child = children[i]\
  8599.         if x >= child.x and y >= child.y and x < child.x + child.w and y < child.y + child.h then\
  8600.             local ok, c, data = findMouseTarget( child:getChildren( ), child, handler, x - child.x + 1, y - child.y + 1 )\
  8601.             if ok then\
  8602.                 return true, c, data\
  8603.             end\
  8604.             local ok, data = handler( child, parent, x, y )\
  8605.             if ok then\
  8606.                 return true, child, data\
  8607.             end\
  8608.         end\
  8609.     end\
  8610.     return false\
  8611. end\
  8612. \
  8613. local function screenCoords( child )\
  8614.     local x, y = child.x, child.y\
  8615.     while child.parent and child.parent:type( ) ~= \"UIHandler\" do -- everything but a UIHandler will have a parent\
  8616.         child = child.parent\
  8617.         x = x + child.x - 1 + child.cx\
  8618.         y = y + child.y - 1 + child.cy\
  8619.     end\
  8620.     return x, y\
  8621. end\
  8622. \
  8623. local function merge( t1, t2 )\
  8624.     local t = { }\
  8625.     for i, v in ipairs( t1 ) do\
  8626.         table.insert( t, v )\
  8627.     end\
  8628.     for i, v in ipairs( t2 ) do\
  8629.         table.insert( t, v )\
  8630.     end\
  8631.     return t\
  8632. end\
  8633. \
  8634. function UIHandler.public:event( event )\
  8635.     if event[1] == \"mouse_click\" then\
  8636.         self.lastx = event[3]\
  8637.         self.lasty = event[4]\
  8638.         local found, child, data = findMouseTarget( self.children, {cx=0, cy=0}, function( child, parent, x, y )\
  8639.             if child.handlesMouse then\
  8640.                 return true, { rx = x - child.x + 1, ry = y - child.y + 1 }\
  8641.             end\
  8642.         end, event[3], event[4] )\
  8643.         if found then\
  8644.             if child ~= self.focus and self.focus then\
  8645.                 self.focus:onUnFocus( )\
  8646.             end\
  8647.             if child ~= self.focus then\
  8648.                 child:onFocus( )\
  8649.             end\
  8650.             self.focus = child\
  8651.             child:onMouseClick( data.rx, data.ry, event[2] )\
  8652.             local sx, sy = screenCoords( child )\
  8653.             self.lastt = { child = child, sx = sx, sy = sy, x = child.x, y = child.y }\
  8654.         else\
  8655.             self.lastt = false\
  8656.             if self.focus then\
  8657.                 self.focus:onUnFocus( )\
  8658.             end\
  8659.             self.focus = false\
  8660.         end\
  8661.     elseif event[1] == \"mouse_scroll\" then\
  8662.         local found, child, data = findMouseTarget( self.children, {cx=0, cy=0}, function( child, parent, x, y )\
  8663.             if child.handlesScroll then\
  8664.                 return true, { rx = x - child.x + 1, ry = y - child.y + 1 }\
  8665.             end\
  8666.         end, event[3], event[4] )\
  8667.         if found then\
  8668.             child:onMouseScroll( data.rx, data.ry, event[2] )\
  8669.         end\
  8670.     elseif event[1] == \"mouse_drag\" then\
  8671.         if self.lastt then\
  8672.             local cx, cy = event[3] - self.lastx, event[4] - self.lasty\
  8673.             local sx, sy = self.lastt.sx + ( self.lastt.child.x - self.lastt.x ), self.lastt.sy + ( self.lastt.child.y - self.lastt.y )\
  8674.             self.lastt.child:onMouseDrag( event[3] - sx + 1, event[4] - sy + 1, cx, cy, event[2] )\
  8675.             self.lastx = event[3]\
  8676.             self.lasty = event[4]\
  8677.         end\
  8678.     elseif event[1] == \"key\" then\
  8679.         if event[2] == keys.tab then\
  8680. \
  8681.             if self.focus and self.focus.handlesTab then\
  8682.                 self.focus:onKeyPress( keys.tab, self.lastkey )\
  8683.             else\
  8684.                 local tabs = { }\
  8685.                 for i = 1, #self.children do\
  8686.                     local t2 = self.children[i]:getTabIndexes( )\
  8687.                     if #t2 > 0 then\
  8688.                         tabs = merge( tabs, t2 )\
  8689.                     end\
  8690.                 end\
  8691.                 local of = self.focus\
  8692.                 local found = false\
  8693.                 for i = 1, #tabs do\
  8694.                     if tabs[i] == self.focus then\
  8695.                         if self.lastkey == keys.leftShift or self.lastkey == keys.rightShift then\
  8696.                             if tabs[i-1] then\
  8697.                                 self.focus = tabs[i-1]\
  8698.                             else\
  8699.                                 self.focus = tabs[#tabs]\
  8700.                             end\
  8701.                         else\
  8702.                             if tabs[i+1] then\
  8703.                                 self.focus = tabs[i+1]\
  8704.                             else\
  8705.                                 self.focus = tabs[1]\
  8706.                             end\
  8707.                         end\
  8708.                         found = true\
  8709.                         break\
  8710.                     end\
  8711.                 end\
  8712.                 if not found then\
  8713.                     self.focus = tabs[1]\
  8714.                 end\
  8715.                 if of ~= self.focus then\
  8716.                     if of then\
  8717.                         of:onUnFocus( )\
  8718.                     end\
  8719.                     if self.focus then\
  8720.                         self.focus:onFocus( )\
  8721.                     end\
  8722.                 end\
  8723.             end\
  8724.         else\
  8725.             if self.focus and ( self.focus.handlesKeys or ( self.focus.handlesEnter and event[2] == keys.enter ) ) then\
  8726.                 self.focus:onKeyPress( event[2], self.lastkey )\
  8727.             else\
  8728.                 local function find( c )\
  8729.                     for i = #c, 1, -1 do\
  8730.                         local ok, obj = find( c[i]:getChildren( ) )\
  8731.                         if ok then\
  8732.                             return true, obj\
  8733.                         end\
  8734.                         if c[i].handlesKeys then\
  8735.                             c[i]:onKeyPress( event[2], self.lastkey )\
  8736.                             return true, c[i]\
  8737.                         end\
  8738.                     end\
  8739.                 end\
  8740.                 find( self.children )\
  8741.             end\
  8742.         end\
  8743.     elseif event[1] == \"char\" then\
  8744.         if self.focus and self.focus.handlesKeys then\
  8745.             self.focus:onTextInput( event[2], self.lastkey )\
  8746.         else\
  8747.             local function find( c )\
  8748.                 for i = #c, 1, -1 do\
  8749.                     local ok, obj = find( c[i]:getChildren( ) )\
  8750.                     if ok then\
  8751.                         return true, obj\
  8752.                     end\
  8753.                     if c[i].handlesKeys then\
  8754.                         c[i]:onTextInput( event[2], self.lastkey )\
  8755.                         return true, c[i]\
  8756.                     end\
  8757.                 end\
  8758.             end\
  8759.             find( self.children )\
  8760.         end\
  8761.     end\
  8762.     if event[1] == \"key\" or ( event[1] == \"char\" and self.lastkey ) then\
  8763.         if event[1] == \"key\" then\
  8764.             self.lastkey = event[2]\
  8765.         end\
  8766.         self.lastkeytime = os.clock( )\
  8767.     else\
  8768.         self.lastkey = nil\
  8769.         self.lastkeytime = false\
  8770.     end\
  8771. end\
  8772. \
  8773. function UIHandler.public:newChild( child )\
  8774.     if class.typeOf( child, UIElement ) then\
  8775.         table.insert( self.children, child )\
  8776.         child.parent = self.public\
  8777.         return child\
  8778.     else\
  8779.         error( \"expected UIElement child\", 2 )\
  8780.     end\
  8781. end\
  8782. \
  8783. function UIHandler.public:removeChild( child )\
  8784.     local removed = false\
  8785.     for i = #self.children, 1, -1 do\
  8786.         if self.children[i] == child then\
  8787.             table.remove( self.children, i )\
  8788.             removed = true\
  8789.         end\
  8790.     end\
  8791.     return removed\
  8792. end\
  8793. \
  8794. function UIHandler.public:unFocus( )\
  8795.     if self.focus then\
  8796.         self.focus:onUnFocus( )\
  8797.         self.focus = false\
  8798.     end\
  8799. end\
  8800. \
  8801. function UIHandler.public:setFocus( child )\
  8802.     if class.typeOf( child, UIElement ) then\
  8803.         if self.focus ~= child then\
  8804.             if self.focus then\
  8805.                 self.focus:onUnFocus( )\
  8806.             end\
  8807.             child:onFocus( )\
  8808.             self.focus = child\
  8809.         end\
  8810.     else\
  8811.         error( \"expected UIElement child\", 3 )\
  8812.     end\
  8813. end", meta={
  8814.   type = "class",
  8815. }};}
  8816. --/end of files
  8817. local 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"}
  8818. --[[type="lib"]]
  8819.  
  8820. -- Swift Class Library
  8821. -- Made by awsumben13
  8822. -- Feel free to use this in any of your projects but keep this at the top and give credit where necessary
  8823.  
  8824. --[[
  8825.     This class library offers:
  8826.         Inheritance "Class:extends( other class )"
  8827.         Initialiser methods "function ClassName:ClassName( )"
  8828.         Public / private variables with customisable read/write access "Class.public.x.write = false"
  8829.         Static variables ( accessed from the class object ) "Class.static.x = 5"
  8830.         Custom metamethods including __index and __newindex "Class.meta.index = { }"
  8831. ]]
  8832.  
  8833. local class = { }
  8834.  
  8835. function class.new( name )
  8836.  
  8837.     local object = { }
  8838.     local public = { }
  8839.  
  8840.     object.public = { }
  8841.     object.private = { }
  8842.     object.static = { }
  8843.     object.name = name
  8844.     object.extends = false
  8845.     object.class = public
  8846.  
  8847.     public.name = name
  8848.  
  8849.     local customindex = false
  8850.     local customnewindex = false
  8851.     local objectmeta = { }
  8852.  
  8853.     function public:new( ... )
  8854.  
  8855.         local ob = { }
  8856.         local pb = { }
  8857.  
  8858.         ob.class = public
  8859.         ob.public = pb
  8860.  
  8861.         setmetatable( ob, {
  8862.             __index = object.private;
  8863.         } )
  8864.  
  8865.         function pb:type( full )
  8866.             return ob.class:type( full )
  8867.         end
  8868.  
  8869.         function pb:typeOf( class )
  8870.             return ob.class:typeOf( class )
  8871.         end
  8872.  
  8873.         local obmeta = { }
  8874.         obmeta.__index = function( _, k )
  8875.             if object.public[k] then
  8876.                 if object.public[k].read then
  8877.                     local val
  8878.                     if customindex then
  8879.                         if type( customindex ) == "function" then
  8880.                             return customindex( ob, k )
  8881.                         else
  8882.                             return customindex[k]
  8883.                         end
  8884.                     elseif type( object.public[k].read ) == "function" then
  8885.                         val = object.public[k].read( ob )
  8886.                     elseif object.public[k].value ~= nil then
  8887.                         val = object.public[k].value
  8888.                     else
  8889.                         val = ob[k]
  8890.                     end
  8891.                     if type( val ) == "function" then
  8892.                         return function( self, ... )
  8893.                             if self == pb then
  8894.                                 return val( ob, ... )
  8895.                             end
  8896.                             return val( self, ... )
  8897.                         end
  8898.                     end
  8899.                     return val
  8900.                 else
  8901.                     error( "variable has no read access", 2 )
  8902.                 end
  8903.             elseif customindex then
  8904.                 if type( customindex ) == "function" then
  8905.                     return customindex( ob, k )
  8906.                 else
  8907.                     return customindex[k]
  8908.                 end
  8909.             else
  8910.                 error( "no such variable \"" .. tostring( k ) .. "\"", 2 )
  8911.             end
  8912.         end;
  8913.         obmeta.__newindex = function( _, k, v )
  8914.             if object.public[k] then
  8915.                 if object.public[k].write then
  8916.                     if customnewindex then
  8917.                         if type( customnewindex ) == "function" then
  8918.                             return customnewindex( ob, k, v )
  8919.                         else
  8920.                             customnewindex[k] = v
  8921.                         end
  8922.                     elseif type( object.public[k].write ) == "function" then
  8923.                         object.public[k].write( ob, v )
  8924.                     else
  8925.                         ob[k] = v
  8926.                     end
  8927.                 else
  8928.                     error( "variable has no write access", 2 )
  8929.                 end
  8930.             else
  8931.                 error( "no such variable \"" .. tostring( k ) .. "\"", 2 )
  8932.             end
  8933.         end;
  8934.         obmeta.__tostring = function( )
  8935.             return object.name
  8936.         end;
  8937.         obmeta.__metatable = "SwiftClassObject";
  8938.  
  8939.         for k, v in pairs( objectmeta ) do
  8940.             obmeta["__" .. tostring( k )] = v
  8941.         end
  8942.  
  8943.         setmetatable( pb, obmeta )
  8944.  
  8945.         local c = object
  8946.         while true do
  8947.             if type( c.private[c.name] ) == "function" then
  8948.                 return c.private[c.name]( ob, ... )
  8949.             end
  8950.             if c.extends then
  8951.                 c = c.extends
  8952.             else
  8953.                 break
  8954.             end
  8955.         end
  8956.  
  8957.         return pb
  8958.     end
  8959.  
  8960.     function public:type( full )
  8961.         local str = ""
  8962.         if full then
  8963.             local c = object.extends
  8964.             while c do
  8965.                 str = c.name .. "." .. str
  8966.                 c = c.extends
  8967.             end
  8968.         end
  8969.         return str .. object.name
  8970.     end
  8971.  
  8972.     function public:typeOf( other )
  8973.         if type( other ) == "table" then
  8974.             if getmetatable( other ) == "SwiftClass" then
  8975.                 local ob = object
  8976.                 while ob do
  8977.                     if ob.class == other then
  8978.                         return true
  8979.                     end
  8980.                     ob = ob.extends
  8981.                 end
  8982.             end
  8983.         end
  8984.         return false
  8985.     end
  8986.  
  8987.     function public:extends( ob )
  8988.         ob:extend( object )
  8989.     end
  8990.  
  8991.     function public:extend( ob )
  8992.         setmetatable( ob.static, { __index = object.static } )
  8993.         setmetatable( ob.public, { __index = object.public } )
  8994.         setmetatable( ob.private, { __index = object.private } )
  8995.         ob.extends = object
  8996.     end
  8997.  
  8998.     local meta = { }
  8999.     meta.__index = function( _, k )
  9000.         if k == "static" then
  9001.             return setmetatable( { }, {
  9002.                 __newindex = function( _, k, v )
  9003.                     object.static[k] = v
  9004.                 end;
  9005.                 __metatable = { };
  9006.             } )
  9007.         elseif k == "public" then
  9008.             return setmetatable( { }, {
  9009.                 __newindex = function( _, k, v )
  9010.                     object.public[k] = {
  9011.                         read = true;
  9012.                         write = false;
  9013.                         value = v;
  9014.                     }
  9015.                 end;
  9016.                 __call = function( _, k )
  9017.                     object.public[k] = {
  9018.                         read = true;
  9019.                         write = true;
  9020.                         value = nil;
  9021.                     }
  9022.                     return function( _type )
  9023.                         object.public[k].write = function( ob, value )
  9024.                             if type( value ) == _type or class.typeOf( value, _type ) then
  9025.                                 ob[k] = value
  9026.                             else
  9027.                                 if class.type( _type ) == "Class" then
  9028.                                     error( "expected " .. _type:type( ) .. " " .. k, 3 )
  9029.                                 else
  9030.                                     error( "expected " .. _type .. " " .. k, 3 )
  9031.                                 end
  9032.                             end
  9033.                         end
  9034.                     end
  9035.                 end;
  9036.                 __index = function( _, k )
  9037.                     if object.public[k] then
  9038.                         return setmetatable( { }, {
  9039.                             __newindex = function( _, name, v )
  9040.                                 if name == "read" then
  9041.                                     if type( v ) == "boolean" or type( v ) == "function" then
  9042.                                         object.public[k].read = v
  9043.                                     else
  9044.                                         error( "invalid modifier value", 2 )
  9045.                                     end
  9046.                                 elseif name == "write" then
  9047.                                     if type( v ) == "boolean" or type( v ) == "function" then
  9048.                                         object.public[k].write = v
  9049.                                     else
  9050.                                         error( "invalid modifier value", 2 )
  9051.                                     end
  9052.                                 else
  9053.                                     error( "invalid modifier name", 2 )
  9054.                                 end
  9055.                             end;
  9056.                             __metatable = { };
  9057.                         } )
  9058.                     else
  9059.                         error( "public index " .. tostring( k ) .. " not found", 2 )
  9060.                     end
  9061.                 end;
  9062.                 __metatable = { };
  9063.             } )
  9064.         elseif k == "meta" then
  9065.             return setmetatable( { }, {
  9066.                 __index = function( _, k )
  9067.                     if k == "index" then
  9068.                         return customindex
  9069.                     elseif k == "newindex" then
  9070.                         return customnewindex
  9071.                     else
  9072.                         return objectmeta[k]
  9073.                     end
  9074.                 end;
  9075.                 __newindex = function( _, k, v )
  9076.                     if k == "metatable" then
  9077.                         error( "cannot change this metamethod", 2 )
  9078.                     elseif k == "index" then
  9079.                         if type( v ) == "function" or type( v ) == "table" or v == nil then
  9080.                             customindex = v
  9081.                         else
  9082.                             error( "cannot use type " .. type( v ) .. " for index metamethod", 2 )
  9083.                         end
  9084.                     elseif k == "newindex" then
  9085.                         if type( v ) == "function" or type( v ) == "table" or v == nil then
  9086.                             customnewindex = v
  9087.                         else
  9088.                             error( "cannot use type " .. type( v ) .. " for newindex metamethod", 2 )
  9089.                         end
  9090.                     else
  9091.                         objectmeta[k] = v
  9092.                     end
  9093.                 end;
  9094.                 __metatable = { };
  9095.             } )
  9096.         else
  9097.             return object.static[k]
  9098.         end
  9099.     end
  9100.     meta.__newindex = function( _, k, v )
  9101.         object.private[k] = v
  9102.     end;
  9103.     meta.__call = function( self, ... )
  9104.         return self:new( ... )
  9105.     end;
  9106.     meta.__totring = function( )
  9107.         return "Class"
  9108.     end;
  9109.     meta.__metatable = "SwiftClass"
  9110.  
  9111.     setmetatable( public, meta )
  9112.  
  9113.     return public
  9114. end
  9115.  
  9116. function class.public( name )
  9117.     local object = class.new( name )
  9118.     getfenv( )[name] = object
  9119. end
  9120.  
  9121. function class.type( object )
  9122.     if type( object ) == "table" then
  9123.         if getmetatable( object ) == "SwiftClass" then
  9124.             return "Class"
  9125.         end
  9126.         if getmetatable( object ) == "SwiftClassObject" then
  9127.             return object:type( )
  9128.         end
  9129.     end
  9130.     return type( object )
  9131. end
  9132.  
  9133. function class.typeOf( object, ... )
  9134.     local types = { ... }
  9135.     for i = 1, #types do
  9136.         if type( object ) == "table" and getmetatable( object ) == "SwiftClassObject" then
  9137.             if object:typeOf( types[i] ) then
  9138.                 return types[i]
  9139.             end
  9140.         elseif type( object ) == "table" and getmetatable( object ) == "SwiftClass" then
  9141.             if types[i] == "Class" then
  9142.                 return "Class"
  9143.             end
  9144.         else
  9145.             if type( object ) == types[i] then
  9146.                 return types[i]
  9147.             end
  9148.         end
  9149.     end
  9150.     return false
  9151. end
  9152.  
  9153. setmetatable( class, { __call = function( _, ... ) return class.new( ... ) end } )
  9154. local args = { ... }
  9155. local global = getfenv( )
  9156. local custom = setmetatable( { class = class, ARGS = args }, { __index = global } )
  9157. function custom.export( variable, value )
  9158.     if custom[variable] ~= nil or value then
  9159.         global[variable] = custom[variable] == nil and value or custom[variable]
  9160.     end
  9161. end
  9162. local required = { }
  9163. local function require( file, ... )
  9164.     if not required[file] and files[file] then
  9165.         required[file] = true
  9166.         if files[file].meta.type == "class" then
  9167.             custom[file] = class( file )
  9168.         end
  9169.         local fenv = setmetatable( { }, { __index = custom } )
  9170.         local f, err = loadstring( files[file].content, file .. ".lua" )
  9171.         if f then
  9172.             setfenv( f, fenv )
  9173.             local ok, data = pcall( f )
  9174.             if ok then
  9175.                 if files[file].meta.type == "lib" then
  9176.                     if data ~= nil then
  9177.                         custom[file] = data
  9178.                         return data
  9179.                     else
  9180.                         local t = { }
  9181.                         for k, v in pairs( fenv ) do
  9182.                             t[k] = v
  9183.                         end
  9184.                         custom[file] = t
  9185.                         return t
  9186.                     end
  9187.                 end
  9188.                 return data
  9189.             else
  9190.                 error( data, 0 )
  9191.             end
  9192.         else
  9193.             error( err, 0 )
  9194.         end
  9195.     end
  9196. end
  9197. custom.require = require
  9198.  
  9199. for i = 1, #autorun do
  9200.     require( autorun[i] )
  9201. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement