awsumben13

Flare installer

Aug 18th, 2015
1,602
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 236.46 KB | None | 0 0
  1. local files = {
  2.     ["/LICENSE"] = "The MIT License (MIT)\
  3. \
  4. Copyright (c) 2015 Ben\
  5. \
  6. Permission is hereby granted, free of charge, to any person obtaining a copy\
  7. of this software and associated documentation files (the \"Software\"), to deal\
  8. in the Software without restriction, including without limitation the rights\
  9. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
  10. copies of the Software, and to permit persons to whom the Software is\
  11. furnished to do so, subject to the following conditions:\
  12. \
  13. The above copyright notice and this permission notice shall be included in all\
  14. copies or substantial portions of the Software.\
  15. \
  16. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
  17. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
  18. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
  19. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
  20. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
  21. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
  22. SOFTWARE.\
  23. ";
  24.     ["/bin/build"] = "\
  25. local args = { ... }\
  26. \
  27. local paths = {}\
  28. local main = \"init\"\
  29. local output = \"./run\"\
  30. local includeFlare = false\
  31. local minify = false\
  32. \
  33. local help = [[build [options] [dirs]\
  34. \
  35. options:\
  36.    -o output - the output file (default=./run) without .lua, '.' is replaced with the project path\
  37.    -m main - the main file (default=init) without .lua\
  38.    -c - minify source code\
  39.    -f - include Flare source files in the build\
  40. \
  41. dirs:\
  42.    Each directory given is added as a path that Flare looks in when requiring files\
  43. \
  44. examples:\
  45.    build MyProject - builds MyProject with main file 'MyProject/init.lua' and outputs to 'MyProject/run.lua'\
  46.    build Test -m main -o ./out - builds Test with main file 'Test/main.lua' and outputs to 'Test/out.lua']]\
  47. \
  48. if args[1] == \"-h\" or args[1] == \"help\" then\
  49. \9return print( help )\
  50. end\
  51. \
  52. local flag\
  53. for i = 1, #args do\
  54. \9if args[i] == \"-m\" then\
  55. \9\9flag = \"main\"\
  56. \9elseif args[i] == \"-o\" then\
  57. \9\9flag = \"output\"\
  58. \9elseif args[i] == \"-c\" then\
  59. \9\9minify = true\
  60. \9elseif args[i] == \"-f\" then\
  61. \9\9includeFlare = true\
  62. \9elseif flag == \"main\" then\
  63. \9\9flag = nil\
  64. \9\9main = args[i]\
  65. \9elseif flag == \"output\" then\
  66. \9\9flag = nil\
  67. \9\9output = args[i]\
  68. \9else\
  69. \9\9paths[#paths + 1] = args[i]\
  70. \9end\
  71. end\
  72. \
  73. local minAPI\
  74. if minify then\
  75. \9minAPI = setmetatable( {}, { __index = _ENV or getfenv() } )\
  76. \9local h = fs.open( \"Flare/minify.lua\", \"r\" )\
  77. \9if h then\
  78. \9\9local f, err = load( h.readAll(), \"minify\", nil, minAPI )\
  79. \9\9h.close()\
  80. \9\9if not f then\
  81. \9\9\9error( err, 0 )\
  82. \9\9end\
  83. \9\9f()\
  84. \9else\
  85. \9\9error( \"failed to open Flare/minify.lua\", 0 )\
  86. \9end\
  87. end\
  88. \
  89. local userpathcount = #paths\
  90. if userpathcount == 0 then\
  91. \9error( \"expected one or more paths\", 0 )\
  92. end\
  93. \
  94. for i = 1, userpathcount do\
  95. \9if not fs.isDir( paths[i] ) then\
  96. \9\9error( \"no such directory \" .. (\"%q\"):format( paths[i] ), 0 )\
  97. \9end\
  98. end\
  99. \
  100. local function formatFileContent( str )\
  101. \9if minify then\
  102. \9\9return (\"%q\"):format( minAPI.Rebuild.MinifyString( str ) ):gsub( \"\\\\\\n\", \"\\\\n\" )\
  103. \9end\
  104. \9return (\"%q\"):format( str )\
  105. end\
  106. \
  107. local FLAREPATH = \"Flare/lib;Flare/lib/elements\"\
  108. for seg in FLAREPATH:gmatch \"[^;]+\" do\
  109. \9paths[#paths + 1] = seg\
  110. end\
  111. \
  112. local required = {}\
  113. local lookup = {}\
  114. \
  115. local files = \"{\\n\"\
  116. \
  117. local function requirefile( file )\
  118. \9if required[file] then return end\
  119. \9required[file] = true\
  120. \
  121. \9local path = file:gsub( \"%.\", \"/\" )\
  122. \9local fpath, isFlareFile\
  123. \9\
  124. \9for i = 1, #paths do\
  125. \9\9if fs.exists( paths[i] .. \"/\" .. path .. \".lua\" ) and not fs.isDir( paths[i] .. \"/\" .. path .. \".lua\" ) then\
  126. \9\9\9fpath = paths[i] .. \"/\" .. path .. \".lua\"\
  127. \9\9elseif fs.exists( paths[i] .. \"/\" .. path .. \"/init.lua\" ) and not fs.isDir( paths[i] .. \"/\" .. path .. \"/init.lua\" ) then\
  128. \9\9\9fpath = paths[i] .. \"/\" .. path .. \"/init.lua\"\
  129. \9\9end\
  130. \9\9if fpath then\
  131. \9\9\9isFlareFile = i > userpathcount\
  132. \9\9\9break\
  133. \9\9end\
  134. \9end\
  135. \
  136. \9if not fpath then\
  137. \9\9error( \"failed to find file \" .. (\"%q\"):format( file ) .. \" in paths given\", 0 )\
  138. \9end\
  139. \
  140. \9lookup[file] = fpath\
  141. \
  142. \9local h = fs.open( fpath, \"r\" )\
  143. \9local content = h.readAll()\
  144. \9h.close()\
  145. \
  146. \9local function _err()\
  147. \9\9error( \"@\", 0 )\
  148. \9end\
  149. \9local env = setmetatable( { require = requirefile }, {} )\
  150. \
  151. \9local f, err = load( content, file, nil, env )\
  152. \9if not f then\
  153. \9\9error( err, 0 )\
  154. \9end\
  155. \
  156. \9getmetatable( env ).__index = _err\
  157. \9getmetatable( env ).__newindex = _err\
  158. \
  159. \9local ok, err = pcall( f, file )\
  160. \9if not ok and err ~= \"@\" then\
  161. \9\9error( err, 0 )\
  162. \9end\
  163. \
  164. \9if includeFlare or not isFlareFile then\
  165. \9\9print( \"Including file '\" .. file .. \"' (\" .. fpath .. \")\" )\
  166. \9\9files = files .. \"\\t[\" .. (\"%q\"):format( file ) .. \"] = \" .. formatFileContent( content ) .. \";\\n\"\
  167. \9end\
  168. end\
  169. \
  170. requirefile( main )\
  171. \
  172. if includeFlare then\
  173. \9requirefile \"class\"\
  174. \9requirefile \"UIView\"\
  175. \9requirefile \"Timer\"\
  176. \9requirefile \"graphics.ScreenCanvas\"\
  177. \9requirefile \"Event.Event\"\
  178. \9requirefile \"Event.MouseEvent\"\
  179. \9requirefile \"Event.KeyboardEvent\"\
  180. \9requirefile \"Event.TextEvent\"\
  181. \9requirefile \"Timer\"\
  182. end\
  183. \
  184. files = files .. \"}\"\
  185. \
  186. local FlareInstaller = [[\
  187. if not fs.exists \"Flare\" then\
  188. \9print \"Downloading Flare\"\
  189. \9local h = http.get \"http://pastebin.com/raw.php?i=SD25GhYf\"\
  190. \9if h then\
  191. \9\9local f, err = load( h.readAll(), \"installer\", nil, _ENV or getfenv() )\
  192. \9\9h.close()\
  193. \9\9f()\
  194. \9else\
  195. \9\9return error( \"Cannot install Flare\", 0 )\
  196. \9end\
  197. end\
  198. ]]\
  199. \
  200. local str = \"local files = \" .. files .. \"\\n\" .. ( includeFlare and \"\" or  FlareInstaller ) .. \"local loader\"\
  201. \
  202. if includeFlare then\
  203. \9local run = fs.open( \"Flare/run.lua\", \"r\" )\
  204. \9if run then\
  205. \9\9str = str .. \" = \" .. formatFileContent( run.readAll() )\
  206. \9\9run.close()\
  207. \9else\
  208. \9\9error( \"failed to read Flare\", 0 )\
  209. \9end\
  210. else\
  211. \9str = str .. \"\\nlocal h = fs.open( \\\"Flare/run.lua\\\", \\\"r\\\" )\\nif h then\\n\\tloader = h.readAll()\\n\\th.close()\\nelse\\n\\terror( \\\"failed to read Flare\\\", 0 )\\nend\"\
  212. end\
  213. \
  214. str = str .. \"\\nlocal f, err = load( loader, \\\"Flare\\\", nil, _ENV or getfenv() )\\\
  215. if not f then\\\
  216. \9error( \\\"there was a problem with Flare!: \\\" .. err, 0 )\\\
  217. end\\\
  218. f( files, \" .. (\"%q\"):format( main ) .. \", ... )\"\
  219. \
  220. local w = {}\
  221. local path = output:gsub( \"%.\", paths[1] ) .. \".lua\"\
  222. if not w[path] then\
  223. \9w[path] = true\
  224. \9local h = fs.open( path, \"w\" )\
  225. \9if h then\
  226. \9\9h.write( str )\
  227. \9\9h.close()\
  228. \9else\
  229. \9\9print( \"Failed to open output file \" .. (\"%q\"):format( path ) )\
  230. \9end\
  231. end";
  232.     ["/bin/debug"] = "\
  233. local args = { ... }\
  234. \
  235. local paths = {}\
  236. local main = \"init\"\
  237. local output = \"./run\"\
  238. \
  239. local help = [[debug [options] [dirs]\
  240. \
  241. options:\
  242.    -o output - the output file (default=./run) without .lua, '.' is replaced with the project path\
  243.    -m main - the main file (default=init) without .lua\
  244.    -c - minify source code\
  245.    -f - include Flare source files in the build\
  246. \
  247. dirs:\
  248.    Each directory given is added as a path that Flare looks in when requiring files\
  249. \
  250. examples:\
  251.    debug MyProject - builds MyProject with main file 'MyProject/init.lua' and outputs to 'MyProject/run.lua'\
  252.    debug Test -m main -o ./out - builds Test with main file 'Test/main.lua' and outputs to 'Test/out.lua']]\
  253. \
  254. if args[1] == \"-h\" or args[1] == \"help\" then\
  255. \9return print( help )\
  256. end\
  257. \
  258. local flag\
  259. for i = 1, #args do\
  260. \9if args[i] == \"-m\" then\
  261. \9\9flag = \"main\"\
  262. \9elseif args[i] == \"-o\" then\
  263. \9\9flag = \"output\"\
  264. \9elseif args[i] == \"-c\" then\
  265. \9\9\
  266. \9elseif args[i] == \"-f\" then\
  267. \9\9\
  268. \9elseif flag == \"main\" then\
  269. \9\9flag = nil\
  270. \9elseif flag == \"output\" then\
  271. \9\9flag = nil\
  272. \9\9output = args[i]\
  273. \9else\
  274. \9\9paths[#paths + 1] = args[i]\
  275. \9end\
  276. end\
  277. \
  278. local userpathcount = #paths\
  279. if userpathcount == 0 then\
  280. \9error( \"expected one or more paths\", 0 )\
  281. end\
  282. \
  283. loadfile \"Flare/bin/build\" ( unpack( args ) )\
  284. shell.run( output:gsub( \"%.\", paths[1] ) .. \".lua\" )";
  285.     ["/bin/extract"] = "\
  286. local path, output = ...\
  287. \
  288. local function assertype( a, b, c )\
  289. \9return type( a ) == b and a or error( c, 0 )\
  290. end\
  291. \
  292. assertype( path, \"string\", \"expected input path as arg#1\" )\
  293. assertype( output, \"string\", \"expected output path as arg#2\" )\
  294. \
  295. if fs.isDir( path ) then\
  296. \9error( \"input path is a directory\", 0 )\
  297. end\
  298. if fs.exists( output ) and not fs.isDir( output ) then\
  299. \9error( \"output path is not a directory\", 0 )\
  300. end\
  301. \
  302. local h = fs.open( path, \"r\" )\
  303. local content = h.readAll()\
  304. h.close()\
  305. \
  306. if content:find \"}\\nif not fs.exists \\\"Flare\\\" then\\n\" then\
  307. \9local pos = #content - content:reverse():find( (\"}\\nif not fs.exists \\\"Flare\\\" then\\n\"):reverse() ) - 30\
  308. \9local f = content:sub( #(\"local files = \") + 1, pos )\
  309. \9local t = textutils.unserialize( f )\
  310. \9if type( t ) == \"table\" then\
  311. \9\9for k, v in pairs( t ) do\
  312. \9\9\9local h = fs.open( output .. \"/\" .. k, \"w\" )\
  313. \9\9\9h.write( v )\
  314. \9\9\9h.close()\
  315. \9\9end\
  316. \9else\
  317. \9\9error( \"couldn't unserialize application\", 0 )\
  318. \9end\
  319. else\
  320. \9error( \"path is not a Flare application\", 0 )\
  321. end";
  322.     ["/bin/pack"] = "\
  323. local args = { ... }\
  324. \
  325. local function assertype( a, b, c )\
  326. \9return type( a ) == b and a or error( c, 0 )\
  327. end\
  328. \
  329. local input = assertype( args[1], \"string\", \"expected input path as arg#1\" )\
  330. local output = assertype( args[2], \"string\", \"expected output path as arg#2\" )\
  331. \
  332. if not fs.isDir( input ) then\
  333. \9error( \"input path is not a directory\", 0 )\
  334. end\
  335. if fs.isDir( output ) then\
  336. \9error( \"output path is a directory\", 0 )\
  337. end\
  338. \
  339. local str = \"{\\n\"\
  340. \
  341. local function scandir( path, name )\
  342. \9for _, file in ipairs( fs.list( path ) ) do\
  343. \9\9local filename = name .. \"/\" .. file\
  344. \9\9if fs.isDir( path .. \"/\" .. file ) then\
  345. \9\9\9scandir( path .. \"/\" .. file, filename )\
  346. \9\9else\
  347. \9\9\9local h = fs.open( path .. \"/\" .. file, \"r\" )\
  348. \9\9\9local content = h.readAll()\
  349. \9\9\9h.close()\
  350. \9\9\9str = str .. \"\\t[\" .. (\"%q\"):format( filename ) .. \"] = \" .. (\"%q\"):format( content ):gsub( \"\\\\\\n\", \"\\\\n\" ) .. \";\\n\"\
  351. \9\9end\
  352. \9end\
  353. end\
  354. \
  355. scandir( input, \"\" )\
  356. \
  357. local h = fs.open( output, \"w\" )\
  358. if h then\
  359. \9h.write( str .. \"}\" )\
  360. \9h.close()\
  361. else\
  362. \9error( \"failed to open output file\", 0 )\
  363. end";
  364.     ["/bin/unpack"] = "\
  365. local args = { ... }\
  366. \
  367. local function assertype( a, b, c )\
  368. \9return type( a ) == b and a or error( c, 0 )\
  369. end\
  370. \
  371. local input = assertype( args[1], \"string\", \"expected input path as arg#1\" )\
  372. local output = assertype( args[2], \"string\", \"expected output path as arg#2\" )\
  373. \
  374. if not fs.exists( input ) then\
  375. \9error( \"input path does not exist\", 0 )\
  376. elseif fs.isDir( input ) then\
  377. \9error( \"input path is a directory\", 0 )\
  378. end\
  379. if fs.exists( output ) and not fs.isDir( output ) then\
  380. \9error( \"output path is not a directory\", 0 )\
  381. end\
  382. \
  383. local h = fs.open( input, \"r\" )\
  384. local content = h.readAll()\
  385. h.close()\
  386. \
  387. if not fs.exists( output ) then\
  388. \9fs.makeDir( output )\
  389. end\
  390. \
  391. local files = textutils.unserialize( content )\
  392. if type( files ) ~= \"table\" then\
  393. \9error( \"input path is not an unpackable file\", 0 )\
  394. end\
  395. \
  396. for k, v in pairs( files ) do\
  397. \9local h = fs.open( output .. k, \"w\" )\
  398. \9if h then\
  399. \9\9h.write( v )\
  400. \9\9h.close()\
  401. \9else\
  402. \9\9error( \"failed to open file \" .. k, 0 )\
  403. \9end\
  404. end";
  405.     ["/lib/Event/Event.lua"] = "\
  406. class \"Event\" {\
  407. \9handled = false;\
  408. \9name = \"\";\
  409. \9parameters = {};\
  410. \
  411. \9MOUSEDOWN = 0;\
  412. \9MOUSEUP = 1;\
  413. \9MOUSEDRAG = 2;\
  414. \9MOUSESCROLL = 3;\
  415. \9MOUSEPING = 4;\
  416. \9KEYDOWN = 5;\
  417. \9KEYUP = 6;\
  418. \9TEXT = 7;\
  419. \9PASTE = 8;\
  420. }\
  421. \
  422. function Event:init( name, parameters )\
  423. \9self.name = name\
  424. \9self.parameters = parameters or {}\
  425. end";
  426.     ["/lib/Event/KeyboardEvent.lua"] = "\
  427. require \"Event.Event\"\
  428. \
  429. class \"KeyboardEvent\" extends \"Event\" {\
  430. \9key = 0;\
  431. \9modifiers = {};\
  432. }\
  433. \
  434. function KeyboardEvent:init( name, key, modifiers, parameters )\
  435. \9self.key = keys.getName( key )\
  436. \9self.modifiers = modifiers\
  437. \9self.super:init( name, parameters )\
  438. end\
  439. \
  440. function KeyboardEvent:matchesHotkey( hotkey )\
  441. \9for segment in ( hotkey:match \"^(.+)%-\" or \"\" ):gmatch \"[^%-]+\" do\
  442. \9\9if segment == \"ctrl\" and not self.modifiers.leftCtrl and not self.modifiers.rightCtrl then\
  443. \9\9\9return false\
  444. \9\9elseif segment == \"shift\" and not self.modifiers.leftShift and not self.modifiers.rightShift then\
  445. \9\9\9return false\
  446. \9\9elseif segment ~= \"ctrl\" and segment ~= \"shift\" and not self.modifiers[segment] then\
  447. \9\9\9return false\
  448. \9\9end\
  449. \9end\
  450. \9return self.key == hotkey:gsub( \".+%-\", \"\" )\
  451. end";
  452.     ["/lib/Event/MouseEvent.lua"] = "\
  453. require \"Event.Event\"\
  454. \
  455. class \"MouseEvent\" extends \"Event\" { useSetters = true;\
  456. \9x = 0;\
  457. \9y = 0;\
  458. \9button = 1;\
  459. \
  460. \9parent = nil;\
  461. \
  462. \9within = true;\
  463. \
  464. \9BUTTONLEFT = 1;\
  465. \9BUTTONRIGHT = 2;\
  466. \9BUTTONMIDDLE = 3;\
  467. }\
  468. \
  469. function MouseEvent:init( name, x, y, button, within, parameters )\
  470. \9self.x = x\
  471. \9self.y = y\
  472. \9self.button = button\
  473. \9self.within = within\
  474. \9self.super:init( name, parameters )\
  475. \9self.mt.__tostring = self.tostring\
  476. end\
  477. \
  478. function MouseEvent:clone( x, y, within )\
  479. \9local new = MouseEvent( self.name, self.x - x, self.y - y, self.button, self.within and within, self.parameters )\
  480. \9new.parent = self\
  481. \9new.handled = self.handled\
  482. \9return new\
  483. end\
  484. \
  485. function MouseEvent:isInArea( x, y, width, height )\
  486. \9local sx, sy = self.x, self.y\
  487. \9return self.within and sx >= x and sy >= y and sx < x + width and sy < y + height\
  488. end\
  489. \
  490. function MouseEvent:setHandled( handled )\
  491. \9self.handled = handled\
  492. \9if handled and self.parent then\
  493. \9\9self.parent.handled = true\
  494. \9end\
  495. end\
  496. \
  497. function MouseEvent:tostring()\
  498. \9return \"[Instance] MouseEvent[\" .. self.button .. \"] @\" .. self.x .. \",\" .. self.y\
  499. end";
  500.     ["/lib/Event/TextEvent.lua"] = "\
  501. require \"Event.Event\"\
  502. \
  503. class \"TextEvent\" extends \"Event\" {\
  504. \9text = \"\";\
  505. }\
  506. \
  507. function TextEvent:init( name, text, parameters )\
  508. \9self.text = text\
  509. \9self.super:init( name, parameters )\
  510. end";
  511.     ["/lib/Timer.lua"] = "\
  512. class \"Timer\"\
  513. \
  514. local time, timers, fpstimer = 0, {}, nil\
  515. \
  516. local function getTimer( n )\
  517. \9local timeout = os.clock() + n\
  518. \9for i = 1, #timers do\
  519. \9\9if timers[i].timeout == timeout then\
  520. \9\9\9return timers[i]\
  521. \9\9end\
  522. \9end\
  523. \9local timer = {\
  524. \9\9timeout = timeout;\
  525. \9\9timer = os.startTimer( n );\
  526. \9}\
  527. \9timers[#timers + 1] = timer;\
  528. \9return timer\
  529. end\
  530. \
  531. local function updateTimers( timer )\
  532. \9for i = #timers, 1, -1 do\
  533. \9\9if timers[i].timer == timer then\
  534. \9\9\9for a = 1, #timers[i] do\
  535. \9\9\9\9timers[i][a]()\
  536. \9\9\9end\
  537. \9\9\9table.remove( timers, i )\
  538. \9\9\9return true\
  539. \9\9end\
  540. \9end\
  541. \9return false\
  542. end\
  543. \
  544. function Timer.step()\
  545. \9time = os.clock()\
  546. end\
  547. \
  548. function Timer.getDelta()\
  549. \9return os.clock() - time\
  550. end\
  551. \
  552. function Timer.getFPS()\
  553. \9return fps\
  554. end\
  555. \
  556. function Timer.setFPS( f )\
  557. \9fps = f\
  558. \9fpstimer = fpstimer or ( f and os.startTimer( 1 / f ) )\
  559. end\
  560. \
  561. function Timer.queue( n, action )\
  562. \9local t = getTimer( n )\
  563. \9t[#t + 1] = action\
  564. \9return t.timer\
  565. end\
  566. \
  567. function Timer.sleep( n )\
  568. \9local t = getTimer( n ).timer\
  569. \9repeat\
  570. \9\9local _, timer = coroutine.yield \"timer\"\
  571. \9until timer == t\
  572. end\
  573. \
  574. function Timer.update( event, timer )\
  575. \9if event == \"timer\" then\
  576. \9\9if timer == fpstimer then\
  577. \9\9\9fpstimer = fps and os.startTimer( 1 / fps ) or nil\
  578. \9\9\9os.queueEvent \"update\"\
  579. \9\9\9return true\
  580. \9\9else\
  581. \9\9\9return updateTimers( timer )\
  582. \9\9end\
  583. \9end\
  584. end";
  585.     ["/lib/Tween.lua"] = "local tween = {\
  586. \9_VERSION     = 'tween 2.0.0',\
  587. \9_DESCRIPTION = 'tweening for lua',\
  588. \9_URL         = 'https://github.com/kikito/tween.lua',\
  589. \9_LICENSE     = [[\
  590. \9\9MIT LICENSE\
  591. \9\9Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga\
  592. \9\9Permission is hereby granted, free of charge, to any person obtaining a\
  593. \9\9copy of this software and associated documentation files (the\
  594. \9\9\"Software\"), to deal in the Software without restriction, including\
  595. \9\9without limitation the rights to use, copy, modify, merge, publish,\
  596. \9\9distribute, sublicense, and/or sell copies of the Software, and to\
  597. \9\9permit persons to whom the Software is furnished to do so, subject to\
  598. \9\9the following conditions:\
  599. \9\9The above copyright notice and this permission notice shall be included\
  600. \9\9in all copies or substantial portions of the Software.\
  601. \9\9THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\
  602. \9\9OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\
  603. \9\9MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\
  604. \9\9IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\
  605. \9\9CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\
  606. \9\9TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\
  607. \9\9SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\
  608. \9]]\
  609. }\
  610. \
  611. -- easing\
  612. \
  613. -- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.\
  614. -- For all easing functions:\
  615. -- t = time == how much time has to pass for the tweening to complete\
  616. -- b = begin == starting property value\
  617. -- c = change == ending - beginning\
  618. -- d = duration == running time. How much time has passed *right now*\
  619. \
  620. local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin\
  621. \
  622. -- linear\
  623. local function linear(t, b, c, d) return c * t / d + b end\
  624. \
  625. -- quad\
  626. local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end\
  627. local function outQuad(t, b, c, d)\
  628. \9t = t / d\
  629. \9return -c * t * (t - 2) + b\
  630. end\
  631. local function inOutQuad(t, b, c, d)\
  632. \9t = t / d * 2\
  633. \9if t < 1 then return c / 2 * pow(t, 2) + b end\
  634. \9return -c / 2 * ((t - 1) * (t - 3) - 1) + b\
  635. end\
  636. local function outInQuad(t, b, c, d)\
  637. \9if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end\
  638. \9return inQuad((t * 2) - d, b + c / 2, c / 2, d)\
  639. end\
  640. \
  641. -- cubic\
  642. local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end\
  643. local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end\
  644. local function inOutCubic(t, b, c, d)\
  645. \9t = t / d * 2\
  646. \9if t < 1 then return c / 2 * t * t * t + b end\
  647. \9t = t - 2\
  648. \9return c / 2 * (t * t * t + 2) + b\
  649. end\
  650. local function outInCubic(t, b, c, d)\
  651. \9if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end\
  652. \9return inCubic((t * 2) - d, b + c / 2, c / 2, d)\
  653. end\
  654. \
  655. -- quart\
  656. local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end\
  657. local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end\
  658. local function inOutQuart(t, b, c, d)\
  659. \9t = t / d * 2\
  660. \9if t < 1 then return c / 2 * pow(t, 4) + b end\
  661. \9return -c / 2 * (pow(t - 2, 4) - 2) + b\
  662. end\
  663. local function outInQuart(t, b, c, d)\
  664. \9if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end\
  665. \9return inQuart((t * 2) - d, b + c / 2, c / 2, d)\
  666. end\
  667. \
  668. -- quint\
  669. local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end\
  670. local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end\
  671. local function inOutQuint(t, b, c, d)\
  672. \9t = t / d * 2\
  673. \9if t < 1 then return c / 2 * pow(t, 5) + b end\
  674. \9return c / 2 * (pow(t - 2, 5) + 2) + b\
  675. end\
  676. local function outInQuint(t, b, c, d)\
  677. \9if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end\
  678. \9return inQuint((t * 2) - d, b + c / 2, c / 2, d)\
  679. end\
  680. \
  681. -- sine\
  682. local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end\
  683. local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end\
  684. local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end\
  685. local function outInSine(t, b, c, d)\
  686. \9if t < d / 2 then return outSine(t * 2, b, c / 2, d) end\
  687. \9return inSine((t * 2) -d, b + c / 2, c / 2, d)\
  688. end\
  689. \
  690. -- expo\
  691. local function inExpo(t, b, c, d)\
  692. \9if t == 0 then return b end\
  693. \9return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001\
  694. end\
  695. local function outExpo(t, b, c, d)\
  696. \9if t == d then return b + c end\
  697. \9return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b\
  698. end\
  699. local function inOutExpo(t, b, c, d)\
  700. \9if t == 0 then return b end\
  701. \9if t == d then return b + c end\
  702. \9t = t / d * 2\
  703. \9if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end\
  704. \9return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b\
  705. end\
  706. local function outInExpo(t, b, c, d)\
  707. \9if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end\
  708. \9return inExpo((t * 2) - d, b + c / 2, c / 2, d)\
  709. end\
  710. \
  711. -- circ\
  712. local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end\
  713. local function outCirc(t, b, c, d)  return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end\
  714. local function inOutCirc(t, b, c, d)\
  715. \9t = t / d * 2\
  716. \9if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end\
  717. \9t = t - 2\
  718. \9return c / 2 * (sqrt(1 - t * t) + 1) + b\
  719. end\
  720. local function outInCirc(t, b, c, d)\
  721. \9if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end\
  722. \9return inCirc((t * 2) - d, b + c / 2, c / 2, d)\
  723. end\
  724. \
  725. -- elastic\
  726. local function calculatePAS(p,a,c,d)\
  727. \9p, a = p or d * 0.3, a or 0\
  728. \9if a < abs(c) then return p, c, p / 4 end -- p, a, s\
  729. \9return p, a, p / (2 * pi) * asin(c/a) -- p,a,s\
  730. end\
  731. local function inElastic(t, b, c, d, a, p)\
  732. \9local s\
  733. \9if t == 0 then return b end\
  734. \9t = t / d\
  735. \9if t == 1  then return b + c end\
  736. \9p,a,s = calculatePAS(p,a,c,d)\
  737. \9t = t - 1\
  738. \9return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b\
  739. end\
  740. local function outElastic(t, b, c, d, a, p)\
  741. \9local s\
  742. \9if t == 0 then return b end\
  743. \9t = t / d\
  744. \9if t == 1 then return b + c end\
  745. \9p,a,s = calculatePAS(p,a,c,d)\
  746. \9return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b\
  747. end\
  748. local function inOutElastic(t, b, c, d, a, p)\
  749. \9local s\
  750. \9if t == 0 then return b end\
  751. \9t = t / d * 2\
  752. \9if t == 2 then return b + c end\
  753. \9p,a,s = calculatePAS(p,a,c,d)\
  754. \9t = t - 1\
  755. \9if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end\
  756. \9return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b\
  757. end\
  758. local function outInElastic(t, b, c, d, a, p)\
  759. \9if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end\
  760. \9return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)\
  761. end\
  762. \
  763. -- back\
  764. local function inBack(t, b, c, d, s)\
  765. \9s = s or 1.70158\
  766. \9t = t / d\
  767. \9return c * t * t * ((s + 1) * t - s) + b\
  768. end\
  769. local function outBack(t, b, c, d, s)\
  770. \9s = s or 1.70158\
  771. \9t = t / d - 1\
  772. \9return c * (t * t * ((s + 1) * t + s) + 1) + b\
  773. end\
  774. local function inOutBack(t, b, c, d, s)\
  775. \9s = (s or 1.70158) * 1.525\
  776. \9t = t / d * 2\
  777. \9if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end\
  778. \9t = t - 2\
  779. \9return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b\
  780. end\
  781. local function outInBack(t, b, c, d, s)\
  782. \9if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end\
  783. \9return inBack((t * 2) - d, b + c / 2, c / 2, d, s)\
  784. end\
  785. \
  786. -- bounce\
  787. local function outBounce(t, b, c, d)\
  788. \9t = t / d\
  789. \9if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end\
  790. \9if t < 2 / 2.75 then\
  791. \9\9t = t - (1.5 / 2.75)\
  792. \9\9return c * (7.5625 * t * t + 0.75) + b\
  793. \9elseif t < 2.5 / 2.75 then\
  794. \9\9t = t - (2.25 / 2.75)\
  795. \9\9return c * (7.5625 * t * t + 0.9375) + b\
  796. \9end\
  797. \9t = t - (2.625 / 2.75)\
  798. \9return c * (7.5625 * t * t + 0.984375) + b\
  799. end\
  800. local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end\
  801. local function inOutBounce(t, b, c, d)\
  802. \9if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end\
  803. \9return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b\
  804. end\
  805. local function outInBounce(t, b, c, d)\
  806. \9if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end\
  807. \9return inBounce((t * 2) - d, b + c / 2, c / 2, d)\
  808. end\
  809. \
  810. tween.easing = {\
  811. \9linear    = linear,\
  812. \9inQuad    = inQuad,    outQuad    = outQuad,    inOutQuad    = inOutQuad,    outInQuad    = outInQuad,\
  813. \9inCubic   = inCubic,   outCubic   = outCubic,   inOutCubic   = inOutCubic,   outInCubic   = outInCubic,\
  814. \9inQuart   = inQuart,   outQuart   = outQuart,   inOutQuart   = inOutQuart,   outInQuart   = outInQuart,\
  815. \9inQuint   = inQuint,   outQuint   = outQuint,   inOutQuint   = inOutQuint,   outInQuint   = outInQuint,\
  816. \9inSine    = inSine,    outSine    = outSine,    inOutSine    = inOutSine,    outInSine    = outInSine,\
  817. \9inExpo    = inExpo,    outExpo    = outExpo,    inOutExpo    = inOutExpo,    outInExpo    = outInExpo,\
  818. \9inCirc    = inCirc,    outCirc    = outCirc,    inOutCirc    = inOutCirc,    outInCirc    = outInCirc,\
  819. \9inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,\
  820. \9inBack    = inBack,    outBack    = outBack,    inOutBack    = inOutBack,    outInBack    = outInBack,\
  821. \9inBounce  = inBounce,  outBounce  = outBounce,  inOutBounce  = inOutBounce,  outInBounce  = outInBounce\
  822. }\
  823. \
  824. \
  825. \
  826. -- private stuff\
  827. \
  828. local function copyTables(destination, keysTable, valuesTable)\
  829. \9valuesTable = valuesTable or keysTable\
  830. \9local mt = getmetatable(keysTable)\
  831. \9if mt and getmetatable(destination) == nil then\
  832. \9\9setmetatable(destination, mt)\
  833. \9end\
  834. \9for k,v in pairs(keysTable) do\
  835. \9\9if type(v) == 'table' then\
  836. \9\9\9destination[k] = copyTables({}, v, valuesTable[k])\
  837. \9\9else\
  838. \9\9\9destination[k] = valuesTable[k]\
  839. \9\9end\
  840. \9end\
  841. \9return destination\
  842. end\
  843. \
  844. local function checkSubjectAndTargetRecursively(subject, target, path)\
  845. \9path = path or {}\
  846. \9local targetType, newPath\
  847. \9for k,targetValue in pairs(target) do\
  848. \9\9targetType, newPath = type(targetValue), copyTables({}, path)\
  849. \9\9table.insert(newPath, tostring(k))\
  850. \9\9if targetType == 'number' then\
  851. \9\9\9assert(type(subject[k]) == 'number', \"Parameter '\" .. table.concat(newPath,'/') .. \"' is missing from subject or isn't a number\")\
  852. \9\9elseif targetType == 'table' then\
  853. \9\9\9checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)\
  854. \9\9else\
  855. \9\9\9assert(targetType == 'number', \"Parameter '\" .. table.concat(newPath,'/') .. \"' must be a number or table of numbers\")\
  856. \9\9end\
  857. \9end\
  858. end\
  859. \
  860. local function checkNewParams(duration, subject, target, easing)\
  861. \9assert(type(duration) == 'number' and duration > 0, \"duration must be a positive number. Was \" .. tostring(duration))\
  862. \9local tsubject = type(subject)\
  863. \9assert(tsubject == 'table' or tsubject == 'userdata', \"subject must be a table or userdata. Was \" .. tostring(subject))\
  864. \9assert(type(target)== 'table', \"target must be a table. Was \" .. tostring(target))\
  865. \9assert(type(easing)=='function', \"easing must be a function. Was \" .. tostring(easing))\
  866. \9checkSubjectAndTargetRecursively(subject, target)\
  867. end\
  868. \
  869. local function getEasingFunction(easing)\
  870. \9easing = easing or \"linear\"\
  871. \9if type(easing) == 'string' then\
  872. \9\9local name = easing\
  873. \9\9easing = tween.easing[name]\
  874. \9\9if type(easing) ~= 'function' then\
  875. \9\9\9error(\"The easing function name '\" .. name .. \"' is invalid\")\
  876. \9\9end\
  877. \9end\
  878. \9return easing\
  879. end\
  880. \
  881. local function performEasingOnSubject(subject, target, initial, clock, duration, easing, round )\
  882. \9local t,b,c,d\
  883. \9for k,v in pairs(target) do\
  884. \9\9if type(v) == 'table' then\
  885. \9\9\9performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing, round)\
  886. \9\9else\
  887. \9\9\9t,b,c,d = clock, initial[k], v - initial[k], duration\
  888. \9\9\9subject[k] = round and math.floor( easing(t,b,c,d) + .5 ) or easing( t, b, c, d )\
  889. \9\9end\
  890. \9end\
  891. end\
  892. \
  893. -- Tween methods\
  894. \
  895. class \"Tween\" {\
  896. \9duration = 0;\
  897. \9subject = 0;\
  898. \9target = 0;\
  899. \9easing = 0;\
  900. \9initial = 0;\
  901. \9clock = 0;\
  902. \9round = false;\
  903. \
  904. \9DEFAULT_EASING = \"inOutSine\";\
  905. \9DEFAULT_DURATION = .3;\
  906. }\
  907. \
  908. function Tween:init( subject, target, duration, easing )\
  909. \9easing = easing or self.DEFAULT_EASING\
  910. \9duration = duration or self.DEFAULT_DURATION\
  911. \9easing = getEasingFunction( easing )\
  912. \9checkNewParams( duration, subject, target, easing )\
  913. \9self.duration = duration\
  914. \9self.subject = subject\
  915. \9self.target = target\
  916. \9self.easing = easing\
  917. \9self.initial = copyTables( {}, target, subject )\
  918. end\
  919. \
  920. function Tween:set( clock )\
  921. \9self.clock = clock\
  922. \9if self.clock <= 0 then\
  923. \9\9self.clock = 0\
  924. \9\9copyTables(self.subject, self.initial)\
  925. \9elseif self.clock >= self.duration then -- the tween has expired\
  926. \9\9self.clock = self.duration\
  927. \9\9copyTables(self.subject, self.target)\
  928. \9else\
  929. \9\9performEasingOnSubject( self.subject, self.target, self.initial, self.clock, self.duration, self.easing, self.round )\
  930. \9end\
  931. \9return self.clock >= self.duration\
  932. end\
  933. \
  934. function Tween:reset()\
  935. \9return self:set( 0 )\
  936. end\
  937. \
  938. function Tween:update( dt )\
  939. \9return self:set( self.clock + dt )\
  940. end";
  941.     ["/lib/UIAnimationHandler.lua"] = "\
  942. require \"Tween\"\
  943. require \"Timer\"\
  944. \
  945. local function kickTimer( self )\
  946. \9local time = os.clock()\
  947. \9self.updateTimer = self.updateTimer or Timer.queue( .05, function()\
  948. \9\9self:update( os.clock() - time )\
  949. \9end )\
  950. end\
  951. \
  952. class \"UIAnimationHandler\" {\
  953. \9tweens = {};\
  954. }\
  955. \
  956. function UIAnimationHandler:init()\
  957. \9self.tweens = {}\
  958. end\
  959. \
  960. function UIAnimationHandler:killTween( label )\
  961. \9local tweens = self.tweens\
  962. \9for i = 1, #tweens do\
  963. \9\9if tweens[i].label == label then\
  964. \9\9\9table.remove( tweens, i )\
  965. \9\9\9break\
  966. \9\9end\
  967. \9end\
  968. end\
  969. \
  970. function UIAnimationHandler:createTween( label, object, target, duration, easing )\
  971. \9self:killTween( label )\
  972. \9kickTimer( self )\
  973. \
  974. \9local tween = Tween( object, target, duration, easing )\
  975. \9tween.label = label\
  976. \9self.tweens[#self.tweens + 1] = tween\
  977. \9return tween\
  978. end\
  979. \
  980. function UIAnimationHandler:createRoundedTween( label, object, target, duration, easing )\
  981. \9self:killTween( label )\
  982. \9kickTimer( self )\
  983. \
  984. \9local tween = Tween( object, target, duration, easing )\
  985. \9tween.round = true\
  986. \9tween.label = label\
  987. \9self.tweens[#self.tweens + 1] = tween\
  988. \
  989. \9return tween\
  990. end\
  991. \
  992. function UIAnimationHandler:update( dt )\
  993. \9self.updateTimer = nil\
  994. \9local tweens = self.tweens\
  995. \9for i = #tweens, 1, -1 do\
  996. \9\9if tweens[i]:update( dt ) then\
  997. \9\9\9if tweens[i].onFinish then\
  998. \9\9\9\9tweens[i]:onFinish()\
  999. \9\9\9end\
  1000. \9\9\9table.remove( tweens, i )\
  1001. \9\9end\
  1002. \9end\
  1003. \9if #tweens > 0 then\
  1004. \9\9kickTimer( self )\
  1005. \9end\
  1006. end";
  1007.     ["/lib/UIElement.lua"] = "\
  1008. require \"UIAnimationHandler\"\
  1009. require \"Event.MouseEvent\"\
  1010. require \"Event.KeyboardEvent\"\
  1011. require \"Event.TextEvent\"\
  1012. require \"graphics.DrawingCanvas\"\
  1013. \
  1014. local function copytable( t )\
  1015. \9return { unpack( t ) }\
  1016. end\
  1017. \
  1018. class \"UIElement\" { useSetters = true,\
  1019. \9id = \"NOID\";\
  1020. \9tags = {};\
  1021. \
  1022. \9x = 0;\
  1023. \9y = 0;\
  1024. \9width = 0;\
  1025. \9height = 0;\
  1026. \
  1027. \9ox = 0;\
  1028. \9oy = 0;\
  1029. \
  1030. \9children = {};\
  1031. \9parent = nil;\
  1032. \
  1033. \9handlesMouse = true;\
  1034. \9handlesKeyboard = false;\
  1035. \9handlesText = false;\
  1036. \
  1037. \9canvas = nil;\
  1038. \9animationHandler = nil;\
  1039. \9changed = true;\
  1040. \
  1041. \9transitionTime = .3;\
  1042. }\
  1043. \
  1044. function UIElement:init( x, y, width, height )\
  1045. \9self.tags = {}\
  1046. \9self.children = {}\
  1047. \9self.raw.x = x\
  1048. \9self.raw.y = y\
  1049. \9self.raw.width = width\
  1050. \9self.raw.height = height\
  1051. \9self.animationHandler = UIAnimationHandler()\
  1052. \9self.canvas = DrawingCanvas( 0, 0, width, height )\
  1053. \
  1054. \9self.mt.__tostring = self.tostring\
  1055. end\
  1056. \
  1057. function UIElement:getChildById( id, recursive )\
  1058. \
  1059. \9for i = #self.children, 1, -1 do\
  1060. \9\9local child = self.children[i]\
  1061. \9\9if child.id == id then\
  1062. \9\9\9return child\
  1063. \9\9elseif recursive then\
  1064. \9\9\9child = child:getChildById( id, true )\
  1065. \9\9\9if child then\
  1066. \9\9\9\9return child\
  1067. \9\9\9end\
  1068. \9\9end\
  1069. \9end\
  1070. \
  1071. end\
  1072. \
  1073. UIElement.getElementById = UIElement.getChildById\
  1074. \
  1075. function UIElement:getChildrenByTag( tag, recursive )\
  1076. \
  1077. \9local children = {}\
  1078. \9for i = 1, #self.children do\
  1079. \9\9local child = self.children[i]\
  1080. \9\9for t = 1, #child.tags do\
  1081. \9\9\9if child.tags[t] == tag then\
  1082. \9\9\9\9children[#children + 1] = child\
  1083. \9\9\9\9break\
  1084. \9\9\9end\
  1085. \9\9end\
  1086. \9\9if recursive then\
  1087. \9\9\9local e = child:getChildrenByTag( tag, true )\
  1088. \9\9\9for c = 1, #e do\
  1089. \9\9\9\9children[#children + 1] = e[c]\
  1090. \9\9\9end\
  1091. \9\9end\
  1092. \9end\
  1093. \
  1094. \9return children\
  1095. \
  1096. end\
  1097. \
  1098. function UIElement:childrenWithTag( tag, recursive )\
  1099. \
  1100. \9local children = self:getChildrenByTag( tag, recursive )\
  1101. \9local i = 0\
  1102. \9return function()\
  1103. \9\9i = i + 1\
  1104. \9\9return children[i]\
  1105. \9end\
  1106. \
  1107. end\
  1108. \
  1109. function UIElement:getChildrenAt( x, y )\
  1110. \
  1111. \9local ping = MouseEvent( Event.MOUSEPING, x + self.ox, y + self.oy )\
  1112. \9ping.elements = {}\
  1113. \
  1114. \9for i = #self.children, 1, -1 do\
  1115. \9\9self.children[i]:handle( ping )\
  1116. \9end\
  1117. \9return unpack( ping.elements )\
  1118. \
  1119. end\
  1120. \
  1121. function UIElement:childrenAt( x, y )\
  1122. \
  1123. \9local children = self:getChildrenAt( tag, recursive )\
  1124. \9local i = 0\
  1125. \9return function()\
  1126. \9\9i = i + 1\
  1127. \9\9return children[i]\
  1128. \9end\
  1129. \
  1130. end\
  1131. \
  1132. function UIElement:addTag( tag )\
  1133. \9self.tags[#self.tags + 1] = tag\
  1134. end\
  1135. \
  1136. function UIElement:removeTag( tag )\
  1137. \
  1138. \9for i = #self.tags, 1, -1 do\
  1139. \9\9if self.tags[i] == tag then\
  1140. \9\9\9table.remove( self.tags, i )\
  1141. \9\9end\
  1142. \9end\
  1143. \
  1144. end\
  1145. \
  1146. function UIElement:hasTag( tag )\
  1147. \
  1148. \9for i = #self.tags, 1, -1 do\
  1149. \9\9if self.tags[i] == tag then\
  1150. \9\9\9return true\
  1151. \9\9end\
  1152. \9end\
  1153. \9return false\
  1154. \
  1155. end\
  1156. \
  1157. function UIElement:addChild( child )\
  1158. \
  1159. \9if child.parent then\
  1160. \9\9child.parent:removeChild( child )\
  1161. \9end\
  1162. \
  1163. \9self.children[#self.children + 1] = child\
  1164. \9child.raw.parent = self\
  1165. \9child:onParentChanged()\
  1166. \
  1167. \9self.changed = true\
  1168. \
  1169. \9return child\
  1170. \
  1171. end\
  1172. \
  1173. function UIElement:removeChild( child )\
  1174. \
  1175. \9for i = #self.children, 1, -1 do\
  1176. \9\9if self.children[i] == child then\
  1177. \9\9\9table.remove( self.children, i )\
  1178. \9\9\9child.raw.parent = nil\
  1179. \9\9\9child:onParentChanged()\
  1180. \9\9\9self.changed = true\
  1181. \9\9\9break\
  1182. \9\9end\
  1183. \9end\
  1184. \9return child\
  1185. \
  1186. end\
  1187. \
  1188. function UIElement:setParent( parent )\
  1189. \
  1190. \9if parent then\
  1191. \9\9parent:addChild( self )\
  1192. \9elseif self.parent then\
  1193. \9\9self.parent:removeChild( self )\
  1194. \9end\
  1195. \
  1196. end\
  1197. \
  1198. function UIElement:remove()\
  1199. \9if self.parent then\
  1200. \9\9self.parent:removeChild( self )\
  1201. \9end\
  1202. end\
  1203. \
  1204. function UIElement:transitionInLeft( easing )\
  1205. \9self.x = -self.width\
  1206. \9return self.animationHandler:createRoundedTween( \"x\", self, { x = 0 }, self.transitionTime, easing )\
  1207. end\
  1208. \
  1209. function UIElement:transitionInLeftFrom( x, easing )\
  1210. \9self.x = x\
  1211. \9return self.animationHandler:createRoundedTween( \"x\", self, { x = 0 }, self.transitionTime, easing )\
  1212. end\
  1213. \
  1214. function UIElement:transitionInRight( easing )\
  1215. \9self.x = self.parent.width\
  1216. \9return self.animationHandler:createRoundedTween( \"x\", self, { x = self.parent.width - self.width }, self.transitionTime, easing )\
  1217. end\
  1218. \
  1219. function UIElement:transitionInRightFrom( x, easing )\
  1220. \9self.x = x\
  1221. \9return self.animationHandler:createRoundedTween( \"x\", self, { x = self.parent.width - self.width }, self.transitionTime, easing )\
  1222. end\
  1223. \
  1224. function UIElement:transitionInTop( easing )\
  1225. \9self.y = -self.height\
  1226. \9return self.animationHandler:createRoundedTween( \"y\", self, { y = 0 }, self.transitionTime, easing )\
  1227. end\
  1228. \
  1229. function UIElement:transitionInTopFrom( y, easing )\
  1230. \9self.y = y\
  1231. \9return self.animationHandler:createRoundedTween( \"y\", self, { y = 0 }, self.transitionTime, easing )\
  1232. end\
  1233. \
  1234. function UIElement:transitionInBottom( easing )\
  1235. \9self.y = self.parent.height\
  1236. \9return self.animationHandler:createRoundedTween( \"y\", self, { y = self.parent.height - self.height }, self.transitionTime, easing )\
  1237. end\
  1238. \
  1239. function UIElement:transitionInBottomFrom( y, easing )\
  1240. \9self.y = y\
  1241. \9return self.animationHandler:createRoundedTween( \"y\", self, { y = self.parent.height - self.height }, self.transitionTime, easing )\
  1242. end\
  1243. \
  1244. function UIElement:transitionOutLeft( easing )\
  1245. \9local tween = self.animationHandler:createRoundedTween( \"x\", self, { x = -self.width }, self.transitionTime, easing )\
  1246. \9function tween.onFinish() self:remove() end\
  1247. end\
  1248. \
  1249. function UIElement:transitionOutLeftTo( x, easing )\
  1250. \9local tween = self.animationHandler:createRoundedTween( \"x\", self, { x = x }, self.transitionTime, easing )\
  1251. \9function tween.onFinish() self:remove() end\
  1252. end\
  1253. \
  1254. function UIElement:transitionOutRight( easing )\
  1255. \9local tween = self.animationHandler:createRoundedTween( \"x\", self, { x = self.parent.width }, self.transitionTime, easing )\
  1256. \9function tween.onFinish() self:remove() end\
  1257. end\
  1258. \
  1259. function UIElement:transitionOutRightTo( x, easing )\
  1260. \9local tween = self.animationHandler:createRoundedTween( \"x\", self, { x = x }, self.transitionTime, easing )\
  1261. \9function tween.onFinish() self:remove() end\
  1262. end\
  1263. \
  1264. function UIElement:transitionOutTop( easing )\
  1265. \9local tween = self.animationHandler:createRoundedTween( \"y\", self, { y = -self.height }, self.transitionTime, easing )\
  1266. \9function tween.onFinish() self:remove() end\
  1267. end\
  1268. \
  1269. function UIElement:transitionOutTopTo( y, easing )\
  1270. \9local tween = self.animationHandler:createRoundedTween( \"y\", self, { y = y }, self.transitionTime, easing )\
  1271. \9function tween.onFinish() self:remove() end\
  1272. end\
  1273. \
  1274. function UIElement:transitionOutBottom( easing )\
  1275. \9local tween = self.animationHandler:createRoundedTween( \"y\", self, { y = self.parent.height }, self.transitionTime, easing )\
  1276. \9function tween.onFinish() self:remove() end\
  1277. end\
  1278. \
  1279. function UIElement:transitionOutBottomTo( y, easing )\
  1280. \9local tween = self.animationHandler:createRoundedTween( \"y\", self, { y = y }, self.transitionTime, easing )\
  1281. \9function tween.onFinish() self:remove() end\
  1282. end\
  1283. \
  1284. function UIElement:bringToFront()\
  1285. \9if self.parent and self.parent.children[#self.parent.children] ~= self then\
  1286. \9\9for i = #self.parent.children, 1, -1 do\
  1287. \9\9\9if self.parent.children[i] == self then\
  1288. \9\9\9\9table.remove( self.parent.children, i )\
  1289. \9\9\9end\
  1290. \9\9end\
  1291. \9\9self.parent.children[#self.parent.children + 1] = self\
  1292. \9\9self.parent.changed = true\
  1293. \9end\
  1294. end\
  1295. \
  1296. function UIElement:setChanged( changed )\
  1297. \9self.changed = changed\
  1298. \9if changed and self.parent then self.parent.changed = true end\
  1299. end\
  1300. \
  1301. function UIElement:setX( x )\
  1302. \9self.raw.x = x\
  1303. \9if self.parent then self.parent.changed = true end\
  1304. end\
  1305. \
  1306. function UIElement:setAnimatedX( x )\
  1307. \9return self.animationHandler:createRoundedTween( \"x\", self, { x = x }, self.transitionTime )\
  1308. end\
  1309. \
  1310. function UIElement:setY( y )\
  1311. \9self.raw.y = y\
  1312. \9if self.parent then self.parent.changed = true end\
  1313. end\
  1314. \
  1315. function UIElement:setAnimatedY( y )\
  1316. \9return self.animationHandler:createRoundedTween( \"y\", self, { y = y }, self.transitionTime )\
  1317. end\
  1318. \
  1319. function UIElement:setOx( ox )\
  1320. \9self.raw.ox = ox\
  1321. \9self.changed = true\
  1322. end\
  1323. \
  1324. function UIElement:setAnimatedOX( ox )\
  1325. \9return self.animationHandler:createRoundedTween( \"ox\", self, { ox = ox }, self.transitionTime )\
  1326. end\
  1327. \
  1328. function UIElement:setOy( oy )\
  1329. \9self.raw.oy = oy\
  1330. \9self.changed = true\
  1331. end\
  1332. \
  1333. function UIElement:setAnimatedOY( oy )\
  1334. \9return self.animationHandler:createRoundedTween( \"oy\", self, { oy = oy }, self.transitionTime )\
  1335. end\
  1336. \
  1337. function UIElement:setWidth( width )\
  1338. \9self.raw.width = width\
  1339. \9for i = 1, #self.children do\
  1340. \9\9self.children[i]:onParentResized()\
  1341. \9end\
  1342. \9self.canvas.width = width\
  1343. \9self.changed = true\
  1344. end\
  1345. \
  1346. function UIElement:setAnimatedWidth( width )\
  1347. \9return self.animationHandler:createRoundedTween( \"width\", self, { width = width }, self.transitionTime )\
  1348. end\
  1349. \
  1350. function UIElement:setHeight( height )\
  1351. \9self.raw.height = height\
  1352. \9for i = 1, #self.children do\
  1353. \9\9self.children[i]:onParentResized()\
  1354. \9end\
  1355. \9self.canvas.height = height\
  1356. \9self.changed = true\
  1357. end\
  1358. \
  1359. function UIElement:setAnimatedHeight( height )\
  1360. \9return self.animationHandler:createRoundedTween( \"height\", self, { height = height }, self.transitionTime )\
  1361. end\
  1362. \
  1363. function UIElement:update( dt )\
  1364. \9self:onUpdate( dt )\
  1365. \9for i = 1, #self.children do\
  1366. \9\9self.children[i]:update( dt )\
  1367. \9end\
  1368. end\
  1369. \
  1370. function UIElement:draw()\
  1371. \
  1372. \9if not self.changed then return end\
  1373. \9self.changed = false\
  1374. \
  1375. \9local canvas = self.canvas\
  1376. \9canvas.cursor = nil\
  1377. \9self:onDraw()\
  1378. \
  1379. \9for i = 1, #self.children do\
  1380. \9\9local child = self.children[i]\
  1381. \9\9child:draw()\
  1382. \9\9child.canvas:drawTo( canvas, child.x + self.ox, child.y + self.oy )\
  1383. \9\9if child.canvas.cursor then\
  1384. \9\9\9canvas.cursor = {\
  1385. \9\9\9\9x = child.canvas.cursor.x + child.x + self.ox;\
  1386. \9\9\9\9y = child.canvas.cursor.y + child.y + self.oy;\
  1387. \9\9\9\9colour = child.canvas.cursor.colour;\
  1388. \9\9\9}\
  1389. \9\9end\
  1390. \9end\
  1391. \
  1392. end\
  1393. \
  1394. function UIElement:handle( event )\
  1395. \
  1396. \9local children = {}\
  1397. \9for i = 1, #self.children do\
  1398. \9\9children[i] = self.children[i]\
  1399. \9end\
  1400. \9if event:typeOf( MouseEvent ) then\
  1401. \9\9local within = event:isInArea( 0, 0, self.width, self.height )\
  1402. \9\9for i = #children, 1, -1 do\
  1403. \9\9\9local child = children[i]\
  1404. \9\9\9child:handle( event:clone( child.x + self.ox, child.y + self.oy, within ) )\
  1405. \9\9end\
  1406. \9else\
  1407. \9\9for i = #children, 1, -1 do\
  1408. \9\9\9children[i]:handle( event )\
  1409. \9\9end\
  1410. \9end\
  1411. \
  1412. \9if event:typeOf( MouseEvent ) and self.handlesMouse then\
  1413. \9\9if event.name == Event.MOUSEPING and event:isInArea( 0, 0, self.width, self.height ) then\
  1414. \9\9\9local elements = event.elements\
  1415. \9\9\9elements[#elements + 1] = self\
  1416. \9\9end\
  1417. \9\9self:onMouseEvent( event )\
  1418. \9elseif event:typeOf( KeyboardEvent ) and self.handlesKeyboard then\
  1419. \9\9self:onKeyboardEvent( event )\
  1420. \9elseif event:typeOf( TextEvent ) and self.handlesText then\
  1421. \9\9self:onTextEvent( event )\
  1422. \9end\
  1423. \
  1424. end\
  1425. \
  1426. function UIElement:onUpdate( dt ) end\
  1427. function UIElement:onDraw() end\
  1428. function UIElement:onMouseEvent( event ) end\
  1429. function UIElement:onKeyboardEvent( event ) end\
  1430. function UIElement:onTextEvent( event ) end\
  1431. function UIElement:onParentChanged() end\
  1432. function UIElement:onParentResized() end\
  1433. \
  1434. function UIElement:tostring()\
  1435. \9return \"[Instance] \" .. self.class.name .. \" \" .. tostring( self.id ) .. \" (\" .. self.x .. \",\" .. self.y .. \" \" .. self.width .. \"x\" .. self.height .. \")\"\
  1436. end";
  1437.     ["/lib/class.lua"] = "\
  1438. local type, sub, upper, unpack = type, string.sub, string.upper, unpack\
  1439. \
  1440. -- cache the names of the property getter/setter functions\
  1441. local setters = setmetatable( {}, { __index = function( self, k ) if type( k ) ~= \"string\" then return end local v = \"set\" .. upper( sub( k, 1, 1 ) ) .. sub( k, 2 ) self[k] = v return v end; } )\
  1442. local getters = setmetatable( {}, { __index = function( self, k ) if type( k ) ~= \"string\" then return end local v = \"get\" .. upper( sub( k, 1, 1 ) ) .. sub( k, 2 ) self[k] = v return v end; } )\
  1443. \
  1444. local last_created\
  1445. local class = {}\
  1446. \
  1447. local function __tostring( self )\
  1448. \9return \"[Class] \" .. self.name\
  1449. end\
  1450. \
  1451. local function newSuper( object, super )\
  1452. \9local t = {}\
  1453. \9if super.super then\
  1454. \9\9t.super = newSuper( object, super.super )\
  1455. \9end\
  1456. \9setmetatable( t, { __index = function( t, k )\
  1457. \9\9if type( super[k] ) == \"function\" then\
  1458. \9\9\9return function( self, ... )\
  1459. \9\9\9\9if self == t then\
  1460. \9\9\9\9\9self = object\
  1461. \9\9\9\9end\
  1462. \9\9\9\9object.super = t.super\
  1463. \9\9\9\9local v = { super[k]( self, ... ) }\
  1464. \9\9\9\9object.super = t\
  1465. \9\9\9\9return unpack( v )\
  1466. \9\9\9end\
  1467. \9\9else\
  1468. \9\9\9return super[k]\
  1469. \9\9end\
  1470. \9end, __newindex = super, __tostring = function( self )\
  1471. \9\9return \"[Super] \" .. tostring( super ) .. \" of \" .. tostring( object )\
  1472. \9end } )\
  1473. \9return t\
  1474. end\
  1475. \
  1476. function class:new( name ) -- creates a new class\
  1477. \
  1478. \9local mt = {}\
  1479. \9mt.__index = self\
  1480. \9mt.__type = name\
  1481. \9mt.__isClass = true\
  1482. \9mt.__tostring = __tostring\
  1483. \
  1484. \9local classobj = {} -- the class object\
  1485. \9classobj.name = name\
  1486. \9classobj.mt = mt\
  1487. \
  1488. \9function mt:__call( ... )\
  1489. \9\9return self:new( ... )\
  1490. \9end\
  1491. \
  1492. \9function classobj:new( ... ) -- creates an instance\
  1493. \9\9local ID = sub( tostring {}, 8 )\
  1494. \9\9local instance, classobj = {}, self\
  1495. \
  1496. \9\9local useProxy = self.useGetters or self.useSetters\
  1497. \
  1498. \9\9local raw = useProxy and setmetatable( {}, { __index = classobj } ) or instance\
  1499. \9\9instance.raw = raw\
  1500. \
  1501. \9\9instance.class = self\
  1502. \9\9if self.super then\
  1503. \9\9\9instance.super = newSuper( instance, self.super )\
  1504. \9\9end\
  1505. \
  1506. \9\9instance.mt = {}\
  1507. \9\9instance.mt.__isInstance = true\
  1508. \
  1509. \9\9function instance.mt:__tostring()\
  1510. \9\9\9return \"[Instance] \" .. self.class.name .. \" \" .. ID\
  1511. \9\9end\
  1512. \
  1513. \9\9for k, v in pairs( self.mt ) do\
  1514. \9\9\9if k ~= \"__isClass\" and ( k ~= \"__tostring\" or v ~= __tostring ) then\
  1515. \9\9\9\9instance.mt[k] = v\
  1516. \9\9\9end\
  1517. \9\9end\
  1518. \
  1519. \9\9if self.useGetters then\
  1520. \9\9\9local getting = {}\
  1521. \
  1522. \9\9\9if type( classobj.get ) == \"function\" then\
  1523. \9\9\9\9local genericgetter = classobj.get\
  1524. \9\9\9\9function instance.mt:__index( k )\
  1525. \9\9\9\9\9if not getting[k] then\
  1526. \9\9\9\9\9\9local getter = getters[k]\
  1527. \9\9\9\9\9\9if getter and type( classobj[getter] ) == \"function\" then\
  1528. \9\9\9\9\9\9\9getting[k] = true\
  1529. \9\9\9\9\9\9\9local value = classobj[getter]( self )\
  1530. \9\9\9\9\9\9\9getting[k] = nil\
  1531. \9\9\9\9\9\9\9return value\
  1532. \9\9\9\9\9\9end\
  1533. \9\9\9\9\9\9getting[k] = true\
  1534. \9\9\9\9\9\9local use, value = genericgetter( self, k )\
  1535. \9\9\9\9\9\9getting[k] = nil\
  1536. \9\9\9\9\9\9if use then return value end\
  1537. \9\9\9\9\9end\
  1538. \9\9\9\9\9return raw[k]\
  1539. \9\9\9\9end\
  1540. \9\9\9else\
  1541. \9\9\9\9function instance.mt:__index( k )\
  1542. \9\9\9\9\9if not getting[k] then\
  1543. \9\9\9\9\9\9local getter = getters[k]\
  1544. \9\9\9\9\9\9if getter and type( classobj[getter] ) == \"function\" then\
  1545. \9\9\9\9\9\9\9getting[k] = true\
  1546. \9\9\9\9\9\9\9local value = classobj[getter]( self )\
  1547. \9\9\9\9\9\9\9getting[k] = nil\
  1548. \9\9\9\9\9\9\9return value\
  1549. \9\9\9\9\9\9end\
  1550. \9\9\9\9\9end\
  1551. \9\9\9\9\9return raw[k]\
  1552. \9\9\9\9end\
  1553. \9\9\9end\
  1554. \9\9else\
  1555. \9\9\9instance.mt.__index = useProxy and raw or self\
  1556. \9\9end\
  1557. \9\9if self.useSetters then\
  1558. \9\9\9local setting = {}\
  1559. \
  1560. \9\9\9if type( classobj.set ) == \"function\" then\
  1561. \9\9\9\9local genericsetter = classobj.set\
  1562. \9\9\9\9function instance.mt:__newindex( k, v )\
  1563. \9\9\9\9\9if not setting[k] then\
  1564. \9\9\9\9\9\9local setter = setters[k]\
  1565. \9\9\9\9\9\9if setter and type( classobj[setter] ) == \"function\" then\
  1566. \9\9\9\9\9\9\9setting[k] = true\
  1567. \9\9\9\9\9\9\9classobj[setter]( self, v )\
  1568. \9\9\9\9\9\9\9setting[k] = nil\
  1569. \9\9\9\9\9\9\9return\
  1570. \9\9\9\9\9\9end\
  1571. \9\9\9\9\9\9setting[k] = true\
  1572. \9\9\9\9\9\9local use = genericsetter( self, k, v )\
  1573. \9\9\9\9\9\9setting[k] = nil\
  1574. \9\9\9\9\9\9if use then return end\
  1575. \9\9\9\9\9end\
  1576. \9\9\9\9\9raw[k] = v\
  1577. \9\9\9\9end\
  1578. \9\9\9else\
  1579. \9\9\9\9function instance.mt:__newindex( k, v )\
  1580. \9\9\9\9\9if not setting[k] then\
  1581. \9\9\9\9\9\9local setter = setters[k]\
  1582. \9\9\9\9\9\9if setter and type( classobj[setter] ) == \"function\" then\
  1583. \9\9\9\9\9\9\9setting[k] = true\
  1584. \9\9\9\9\9\9\9classobj[setter]( self, v )\
  1585. \9\9\9\9\9\9\9setting[k] = nil\
  1586. \9\9\9\9\9\9\9return\
  1587. \9\9\9\9\9\9end\
  1588. \9\9\9\9\9end\
  1589. \9\9\9\9\9raw[k] = v\
  1590. \9\9\9\9end\
  1591. \9\9\9end\
  1592. \9\9end\
  1593. \
  1594. \9\9for k, v in pairs( self.mt ) do\
  1595. \9\9\9if k ~= \"__isClass\" and k ~= \"__call\" and k ~= \"__index\" and ( k ~= \"__tostring\" or v ~= __tostring ) then\
  1596. \9\9\9\9instance.mt[k] = v\
  1597. \9\9\9end\
  1598. \9\9end\
  1599. \
  1600. \9\9setmetatable( instance, instance.mt )\
  1601. \
  1602. \9\9if type( instance.init ) == \"function\" then -- initialise the instance\
  1603. \9\9\9instance:init( ... )\
  1604. \9\9end\
  1605. \
  1606. \9\9return instance\
  1607. \
  1608. \9end\
  1609. \
  1610. \9setmetatable( classobj, mt )\
  1611. \
  1612. \9getfenv( 2 )[name] = classobj\
  1613. \9last_created = classobj\
  1614. \
  1615. \9return function( data )\
  1616. \9\9for k, v in pairs( data ) do\
  1617. \9\9\9classobj[k] = v\
  1618. \9\9end\
  1619. \9end\
  1620. end\
  1621. \
  1622. function class:typeOf( _class )\
  1623. \9if type( self ) ~= \"table\" then return false end\
  1624. \9if self.class then return self.class:typeOf( _class ) end\
  1625. \9if self == _class then return true end\
  1626. \9if self.super then return self.super:typeOf( _class ) end\
  1627. \9return false\
  1628. end\
  1629. \
  1630. function class:type()\
  1631. \9local _type = type( self )\
  1632. \9pcall( function()\
  1633. \9\9_type = getmetatable( self ).__type or _type\
  1634. \9end )\
  1635. \9return _type\
  1636. end\
  1637. \
  1638. function class:can( method )\
  1639. \9return type( self[method] ) == \"function\"\
  1640. end\
  1641. \
  1642. function class:has( t, weak )\
  1643. \9for k, v in pairs( t ) do\
  1644. \9\9if ( not weak and self[k] ~= v ) or type( self[k] ) ~= type( v ) then\
  1645. \9\9\9return false\
  1646. \9\9end\
  1647. \9end\
  1648. \9return true\
  1649. end\
  1650. \
  1651. function class:mixin( t )\
  1652. \9for k, v in pairs( t ) do\
  1653. \9\9self[k] = v\
  1654. \9end\
  1655. end\
  1656. \
  1657. function class:extends( class )\
  1658. \9self.super = class\
  1659. \9for k, v in pairs( class.mt ) do -- things like __add\
  1660. \9\9if k ~= \"__index\" and k ~= \"__type\" and k ~= \"__tostring\" and k ~= \"__call\" and k ~= \"__isClass\" then\
  1661. \9\9\9self.mt[k] = v\
  1662. \9\9end\
  1663. \9end\
  1664. \9self.mt.__index = class\
  1665. end\
  1666. \
  1667. function class:isClass()\
  1668. \9return pcall( function() if not getmetatable( self ).__isClass then error \"\" end end ), nil\
  1669. end\
  1670. \
  1671. function class.last()\
  1672. \9return last_created\
  1673. end\
  1674. \
  1675. setmetatable( class, { __call = function( self, ... ) return self:new( ... ) end } )\
  1676. \
  1677. return class";
  1678.     ["/lib/clipboard.lua"] = "\
  1679. local c = { { \"empty\" } }\
  1680. \
  1681. local clipboard = {}\
  1682. \
  1683. function clipboard.put( t )\
  1684. \9c = t\
  1685. end\
  1686. \
  1687. function clipboard.clear()\
  1688. \9c = { { \"empty\" } }\
  1689. end\
  1690. \
  1691. function clipboard.get( type )\
  1692. \9for i = 1, #c do\
  1693. \9\9if c[i][1] == type then\
  1694. \9\9\9return c[i][2]\
  1695. \9\9end\
  1696. \9end\
  1697. end\
  1698. \
  1699. return clipboard";
  1700.     ["/lib/elements/UIButton.lua"] = "\
  1701. require \"UIElement\"\
  1702. local shader = require \"graphics.shader\"\
  1703. local UIEventHelpers = require \"util.UIEventHelpers\"\
  1704. \
  1705. class \"UIButton\" extends \"UIElement\" {\
  1706. \9colour = 1;\
  1707. \9textColour = colours.grey;\
  1708. \9text = \"\";\
  1709. \9holding = false;\
  1710. \9noAlign = false;\
  1711. }\
  1712. \
  1713. function UIButton:init( x, y, w, h, text )\
  1714. \9self.super:init( x, y, w, h )\
  1715. \9self.text = text\
  1716. end\
  1717. \
  1718. function UIButton:onMouseEvent( event )\
  1719. \9local mode = UIEventHelpers.clicking.handleMouseEvent( self, event )\
  1720. \9if mode == \"down\" or mode == \"up\" then\
  1721. \9\9self.holding = mode == \"down\"\
  1722. \9elseif mode == \"click\" then\
  1723. \9\9self.holding = false\
  1724. \9\9if self.onClick then\
  1725. \9\9\9self:onClick()\
  1726. \9\9end\
  1727. \9end\
  1728. end\
  1729. \
  1730. function UIButton:onDraw()\
  1731. \9if self.holding then\
  1732. \9\9local colour = shader.lighten[self.colour]\
  1733. \9\9self.canvas:clear( colour == self.colour and shader.darken[colour] or colour )\
  1734. \9else\
  1735. \9\9self.canvas:clear( self.colour )\
  1736. \9end\
  1737. \9self.canvas:drawWrappedText( 0, 0, self.width, self.height, {\
  1738. \9\9text = self.text;\
  1739. \9\9alignment = self.noAlign and \"left\" or \"centre\";\
  1740. \9\9verticalAlignment = self.noAlign and \"top\" or \"centre\";\
  1741. \9\9textColour = self.textColour;\
  1742. \9} )\
  1743. end\
  1744. \
  1745. function UIButton:setHolding( state )\
  1746. \9self.raw.holding = state\
  1747. \9self.changed = true\
  1748. end\
  1749. \
  1750. function UIButton:setText( text )\
  1751. \9self.raw.text = text == nil and \"\" or tostring( text )\
  1752. \9self.changed = true\
  1753. end\
  1754. \
  1755. function UIButton:setColour( colour )\
  1756. \9self.raw.colour = colour\
  1757. \9self.changed = true\
  1758. end\
  1759. \
  1760. function UIButton:setTextColour( textColour )\
  1761. \9self.raw.textColour = textColour\
  1762. \9self.changed = true\
  1763. end";
  1764.     ["/lib/elements/UICheckbox.lua"] = "\
  1765. require \"UIElement\"\
  1766. local UIEventHelpers = require \"util.UIEventHelpers\"\
  1767. \
  1768. class \"UICheckbox\" extends \"UIElement\" {\
  1769. \9colour = colours.lightGrey;\
  1770. \9checkColour = colours.black;\
  1771. \9check = \"x\";\
  1772. \9toggled = false;\
  1773. }\
  1774. \
  1775. function UICheckbox:init( x, y )\
  1776. \9self.super:init( x, y, 1, 1 )\
  1777. end\
  1778. \
  1779. function UICheckbox:onLabelPressed()\
  1780. \9self.toggled = not self.toggled\
  1781. \9if self.onToggle then\
  1782. \9\9self:onToggle()\
  1783. \9end\
  1784. end\
  1785. \
  1786. function UICheckbox:onMouseEvent( event )\
  1787. \
  1788. \9if UIEventHelpers.clicking.handleMouseEvent( self, event ) == \"click\" then\
  1789. \9\9self.toggled = not self.toggled\
  1790. \9\9if self.onToggle then\
  1791. \9\9\9self:onToggle()\
  1792. \9\9end\
  1793. \9end\
  1794. \
  1795. end\
  1796. \
  1797. function UICheckbox:onDraw()\
  1798. \
  1799. \9self.canvas:clear( self.colour )\
  1800. \9self.canvas:drawPoint( 0, 0, {\
  1801. \9\9colour = self.colour\
  1802. \9} )\
  1803. \9if self.toggled then\
  1804. \9\9self.canvas:drawText( 0, 0, {\
  1805. \9\9\9textColour = self.checkColour;\
  1806. \9\9\9text = self.check;\
  1807. \9\9} )\
  1808. \9end\
  1809. \
  1810. end\
  1811. \
  1812. function UICheckbox:setWidth() end\
  1813. function UICheckbox:setHeight() end\
  1814. \
  1815. function UICheckbox:setHolding( state )\
  1816. \9self.raw.holding = state\
  1817. \9self.changed = true\
  1818. end\
  1819. \
  1820. function UICheckbox:setColour( colour )\
  1821. \9self.raw.colour = colour\
  1822. \9self.changed = true\
  1823. end\
  1824. \
  1825. function UICheckbox:setCheckColour( colour )\
  1826. \9self.raw.checkColour = colour\
  1827. \9self.changed = trur\
  1828. end\
  1829. \
  1830. function UICheckbox:setCheck( check )\
  1831. \9self.raw.check = tostring( check )\
  1832. \9self.changed = true\
  1833. end\
  1834. \
  1835. function UICheckbox:setToggled( bool )\
  1836. \9self.raw.toggled = bool\
  1837. \9self.changed = true\
  1838. end";
  1839.     ["/lib/elements/UIColourSelector.lua"] = "\
  1840. require \"UIElement\"\
  1841. local UIEventHelpers = require \"util.UIEventHelpers\"\
  1842. \
  1843. local log4 = math.log( 4 )\
  1844. \
  1845. class \"UIColourSelector\" extends \"UIElement\" {\
  1846. \9ratio = 4/4; -- 1/16, 2/8, 8/2, 16/1\
  1847. }\
  1848. \
  1849. function UIColourSelector:init( x, y, w, h, ratio )\
  1850. \9self.super:init( x, y, w, h )\
  1851. \
  1852. \9self.raw.ratio = ratio\
  1853. \9self.width = w or 8\
  1854. \9self.height = h or 4\
  1855. end\
  1856. \
  1857. function UIColourSelector:onMouseEvent( event )\
  1858. \9if UIEventHelpers.clicking.handleMouseEvent( self, event ) == \"click\" then\
  1859. \9\9local columns = 2 ^ ( 2 + math.log( self.ratio ) / log4 )\
  1860. \9\9local rows = 2 ^ ( 2 - math.log( self.ratio ) / log4 )\
  1861. \9\9local pixelwidth = self.width / columns\
  1862. \9\9local pixelheight = self.height / rows\
  1863. \9\9local x = math.floor( event.x / pixelwidth )\
  1864. \9\9local y = math.floor( event.y / pixelheight )\
  1865. \9\9local index = x + y * columns\
  1866. \9\9if self.onSelect then\
  1867. \9\9\9self:onSelect( 2 ^ index, index )\
  1868. \9\9end\
  1869. \9end\
  1870. end\
  1871. \
  1872. function UIColourSelector:onDraw()\
  1873. \9local columns = 2 ^ ( 2 + math.log( self.ratio ) / log4 )\
  1874. \9local rows = 2 ^ ( 2 - math.log( self.ratio ) / log4 )\
  1875. \9local pixelwidth = self.width / columns\
  1876. \9local pixelheight = self.height / rows\
  1877. \9local x, y = 0, 0\
  1878. \9for i = 0, 15 do\
  1879. \9\9self.canvas:drawRectangle( x, y, pixelwidth, pixelheight, {\
  1880. \9\9\9colour = 2 ^ i;\
  1881. \9\9\9filled = true;\
  1882. \9\9} )\
  1883. \9\9x = x + pixelwidth\
  1884. \9\9if x == self.width then\
  1885. \9\9\9x = 0\
  1886. \9\9\9y = y + pixelheight\
  1887. \9\9end\
  1888. \9end\
  1889. end\
  1890. \
  1891. function UIColourSelector:setWidth( width )\
  1892. \9local columns = 2 ^ ( 2 + math.log( self.ratio ) / log4 )\
  1893. \9self.super:setWidth( math.max( columns, math.floor( width / columns ) * columns ) )\
  1894. end\
  1895. function UIColourSelector:setHeight( height )\
  1896. \9local rows = 2 ^ ( 2 - math.log( self.ratio ) / log4 )\
  1897. \9self.super:setHeight( math.max( rows, math.floor( height / rows ) * rows ) )\
  1898. end\
  1899. \
  1900. function UIColourSelector:setRatio( ratio )\
  1901. \9if ratio == 1/16 or ratio == 2/8 or ratio == 4/4 or ratio == 8/2 or ratio == 16/1 then\
  1902. \9\9self.raw.ratio = ratio\
  1903. \9\9self.width = self.width\
  1904. \9\9self.height = self.height\
  1905. \9else\
  1906. \9\9error( \"unsupported ratio\", 3 )\
  1907. \9end\
  1908. end";
  1909.     ["/lib/elements/UIContainer.lua"] = "\
  1910. require \"UIElement\"\
  1911. require \"Event.MouseEvent\"\
  1912. \
  1913. local UIDrawingHelpers = require \"util.UIDrawingHelpers\"\
  1914. local UIEventHelpers = require \"util.UIEventHelpers\"\
  1915. \
  1916. class \"UIContainer\" extends \"UIElement\" {\
  1917. \9colour = 1;\
  1918. \
  1919. \9scrollbars = true;\
  1920. }\
  1921. \
  1922. UIContainer:mixin( UIEventHelpers.scrollbar.mixin )\
  1923. \
  1924. function UIContainer:draw()\
  1925. \
  1926. \9if not self.changed then return end\
  1927. \
  1928. \9local canvas = self.canvas\
  1929. \9canvas:clear( self.colour )\
  1930. \
  1931. \9self.super:draw()\
  1932. \
  1933. \9if self.scrollbars then\
  1934. \9\9UIDrawingHelpers.scrollbar.drawScrollbars( self )\
  1935. \9end\
  1936. \
  1937. end\
  1938. \
  1939. function UIContainer:onMouseEvent( event )\
  1940. \9if event:isInArea( 0, 0, self.width, self.height ) then\
  1941. \9\9UIEventHelpers.scrollbar.handleMouseScroll( self, event )\
  1942. \9end\
  1943. end\
  1944. \
  1945. function UIContainer:handle( event )\
  1946. \
  1947. \9if event:typeOf( MouseEvent ) and self.scrollbars then\
  1948. \9\9UIEventHelpers.scrollbar.handleMouseEvent( self, event )\
  1949. \9end\
  1950. \9return UIElement.handle( self, event )\
  1951. \
  1952. end\
  1953. \
  1954. function UIContainer:getContentWidth()\
  1955. \9local max = 0\
  1956. \9for i = 1, #self.children do\
  1957. \9\9max = math.max( max, self.children[i].x + self.children[i].width )\
  1958. \9end\
  1959. \9return max\
  1960. end\
  1961. \
  1962. function UIContainer:getContentHeight()\
  1963. \9local max = 0\
  1964. \9for i = 1, #self.children do\
  1965. \9\9max = math.max( max, self.children[i].y + self.children[i].height )\
  1966. \9end\
  1967. \9return max\
  1968. end\
  1969. \
  1970. function UIContainer:setWidth( width )\
  1971. \9self.super:setWidth( width )\
  1972. \9if self:getContentWidth() + self.ox < self.width then\
  1973. \9\9self.ox = math.min( 0, self.width - self:getContentWidth() )\
  1974. \9end\
  1975. end\
  1976. \
  1977. function UIContainer:setHeight( height )\
  1978. \9self.super:setHeight( height )\
  1979. \9if self:getContentHeight() + self.oy < self.height then\
  1980. \9\9self.oy = math.min( 0, self.height - self:getContentHeight() )\
  1981. \9end\
  1982. end\
  1983. \
  1984. function UIContainer:setColour( colour )\
  1985. \9self.raw.colour = colour\
  1986. \9self.changed = true\
  1987. end\
  1988. \
  1989. function UIContainer:setScrollbars( scrollbars )\
  1990. \9self.raw.scrollbars = scrollbars\
  1991. \9self.changed = true\
  1992. end";
  1993.     ["/lib/elements/UIFileDialogue.lua"] = "\
  1994. -- line 169\
  1995. -- add in settings button (needs UIMenu first, which idk how to implement)\
  1996. \
  1997. require \"graphics.DrawingCanvas\"\
  1998. \
  1999. require \"UIElement\"\
  2000. require \"UIButton\"\
  2001. require \"UITextInput\"\
  2002. require \"UIPanel\"\
  2003. require \"UIContainer\"\
  2004. require \"UIImage\"\
  2005. require \"UIWindow\"\
  2006. require \"UILabel\"\
  2007. \
  2008. local function getSize( path )\
  2009. \9if fs.isDir( path ) then\
  2010. \9\9local n = 0\
  2011. \9\9for i, v in ipairs( fs.list( path ) ) do\
  2012. \9\9\9n = n + getSize( path .. \"/\" .. v )\
  2013. \9\9end\
  2014. \9\9return n\
  2015. \9end\
  2016. \9return tonumber( select( 2, pcall( fs.getSize, path ) ) ) or 0\
  2017. end\
  2018. local function fmtSize( size )\
  2019. \9local ending = \" bytes\"\
  2020. \9if size >= 1024 then\
  2021. \9\9size = math.floor( size / 1024 )\
  2022. \9\9ending = \"KB\"\
  2023. \9end\
  2024. \9if size >= 1024 then\
  2025. \9\9size = math.floor( size / 1024 )\
  2026. \9\9ending = \"MB\"\
  2027. \9end\
  2028. \9return size .. ending\
  2029. end\
  2030. \
  2031. class \"UIFileDialogue\" extends \"UIElement\" {\
  2032. \9icons = {}; -- static\
  2033. \
  2034. \9sortBy = \"name\"; -- , size\
  2035. \9sortOrder = \"ascending\"; -- , descending\
  2036. \9custom_sorter = nil;\
  2037. \
  2038. \9mode = \"open\"; -- save\
  2039. \
  2040. \9showBackButton = true;\
  2041. \9showForwardButton = true;\
  2042. \9showUpButton = true;\
  2043. \9showSettingsButton = true;\
  2044. \9showButtons = true;\
  2045. \9showFileName = true;\
  2046. \
  2047. \9backButton = nil;\
  2048. \9forwardButton = nil;\
  2049. \9upButton = nil;\
  2050. \9settingsButton = nil;\
  2051. \9addressBar = nil;\
  2052. \9button1 = nil;\
  2053. \9button2 = nil;\
  2054. \9fileNameBox = nil;\
  2055. \
  2056. \9content = nil;\
  2057. \
  2058. \9header = nil;\
  2059. \9background = nil;\
  2060. \
  2061. \9headerColour = colours.grey;\
  2062. \9backgroundColour = colours.white;\
  2063. \9headerButtonColour = colours.lightGrey;\
  2064. \9buttonColour = colours.grey;\
  2065. \
  2066. \9path = nil;\
  2067. \9history = {};\
  2068. \9hindex = 0;\
  2069. \
  2070. \9fileobjects = {};\
  2071. \9window = nil;\
  2072. }\
  2073. \
  2074. UIFileDialogue.icons.default = DrawingCanvas( 4, 3 )\
  2075. UIFileDialogue.icons.default:mapPixels {\
  2076. \9{  1, { 1, 256, \"-\" } };\
  2077. \9{  2, { 1, 256, \"-\" } };\
  2078. \9{  3, { 1, 256, \"-\" } };\
  2079. \9{  4, { 1, 256, \"-\" } };\
  2080. \9{  5, { 1, 256, \"f\" } };\
  2081. \9{  6, { 1, 256, \"i\" } };\
  2082. \9{  7, { 1, 256, \"l\" } };\
  2083. \9{  8, { 1, 256, \"e\" } };\
  2084. \9{  9, { 1, 256, \"-\" } };\
  2085. \9{ 10, { 1, 256, \"-\" } };\
  2086. \9{ 11, { 1, 256, \"-\" } };\
  2087. \9{ 12, { 1, 256, \"-\" } };\
  2088. }\
  2089. \
  2090. UIFileDialogue.icons.folder = DrawingCanvas( 4, 3 )\
  2091. UIFileDialogue.icons.folder:mapPixels {\
  2092. \9{  1, { 16, 128, \"f\" } };\
  2093. \9{  2, { 16, 128, \"l\" } };\
  2094. \9{  3, { 16, 128, \"d\" } };\
  2095. \9{  4, { 16, 128, \"r\" } };\
  2096. \9{  5, { 16, 128, \" \" } };\
  2097. \9{  6, { 16, 128, \" \" } };\
  2098. \9{  7, { 16, 128, \" \" } };\
  2099. \9{  8, { 16, 128, \" \" } };\
  2100. \9{  9, { 16, 128, \" \" } };\
  2101. \9{ 10, { 16, 128, \" \" } };\
  2102. \9{ 11, { 16, 128, \" \" } };\
  2103. \9{ 12, { 16, 128, \">\" } };\
  2104. }\
  2105. \
  2106. function UIFileDialogue:init( x, y, w, h, path )\
  2107. \
  2108. \9self.super:init( x, y, w, h )\
  2109. \
  2110. \9self.backButton = UIButton( 0, 0, 3, 1, \" < \" )\
  2111. \9self.forwardButton = UIButton( 0, 0, 3, 1, \" > \" )\
  2112. \9self.upButton = UIButton( 0, 0, 3, 1, \" ^ \" )\
  2113. \9self.settingsButton = UIButton( 0, 0, 3, 1, \" = \" )\
  2114. \9self.addressBar = UITextInput( 0, 1, 0 )\
  2115. \9self.button1 = UIButton( 0, 0, 8, 1, \"Open\" )\
  2116. \9self.button2 = UIButton( 0, 0, 8, 1, \"Cancel\" )\
  2117. \9self.fileNameBox = UITextInput( 0, 0, 0 )\
  2118. \
  2119. \9self.content = UIContainer( 1, 4, w - 2, h - 5 )\
  2120. \9self.header = UIPanel( 0, 0, w, 3, self.headerColour )\
  2121. \9self.background = UIPanel( 0, 3, w, h - 3, self.backgroundColour )\
  2122. \
  2123. \9self.backButton.colour = self.headerButtonColour\
  2124. \9self.forwardButton.colour = self.headerButtonColour\
  2125. \9self.upButton.colour = self.headerButtonColour\
  2126. \9self.settingsButton.colour = self.headerButtonColour\
  2127. \
  2128. \9self.button1.colour = self.buttonColour\
  2129. \9self.button1.textColour = colours.white\
  2130. \9self.button2.colour = self.buttonColour\
  2131. \9self.button2.textColour = colours.white\
  2132. \
  2133. \9self.fileNameBox.focussedColour = colours.lightGrey\
  2134. \
  2135. \9function self.addressBar:onTab()\
  2136. \9\9self.parent.fileNameBox:focusOn()\
  2137. \9end\
  2138. \9function self.fileNameBox:onTab()\
  2139. \9\9self.parent.addressBar:focusOn()\
  2140. \9end\
  2141. \9function self.addressBar:onEnter()\
  2142. \9\9self.parent:goto( self.text )\
  2143. \9end\
  2144. \9function self.fileNameBox:onEnter()\
  2145. \9\9self.parent.button1:onClick()\
  2146. \9end\
  2147. \
  2148. \9function self.backButton:onClick()\
  2149. \9\9self.parent:back()\
  2150. \9end\
  2151. \9function self.forwardButton:onClick()\
  2152. \9\9self.parent:forward()\
  2153. \9end\
  2154. \9function self.upButton:onClick()\
  2155. \9\9self.parent:goto( self.parent.path:match \"(.+)/\" or \"\" )\
  2156. \9end\
  2157. \
  2158. \9function self.button1:onClick()\
  2159. \9\9if self.parent.fileNameBox.text == \"\" then\
  2160. \9\9\9-- error\
  2161. \9\9elseif self.mode == \"save\" then\
  2162. \9\9\9if self.onSave then\
  2163. \9\9\9\9self:onSave( self.parent.path .. \"/\" .. self.parent.fileNameBox.text )\
  2164. \9\9\9end\
  2165. \9\9else\
  2166. \9\9\9self.parent:goto( self.parent.path .. \"/\" .. self.parent.fileNameBox.text )\
  2167. \9\9end\
  2168. \9\9self.parent.fileNameBox.text = \"\"\
  2169. \9end\
  2170. \9function self.button2:onClick()\
  2171. \9\9if self.parent.onCancel then\
  2172. \9\9\9self.parent:onCancel()\
  2173. \9\9end\
  2174. \9end\
  2175. \
  2176. \9self.raw.history = {}\
  2177. \9self.raw.fileobjects = {}\
  2178. \
  2179. \9self:goto( path or \"\" )\
  2180. \
  2181. \9self.width = w\
  2182. \9self.height = h\
  2183. end\
  2184. \
  2185. function UIFileDialogue:back()\
  2186. \9self.hindex = math.max( 1, self.hindex - 1 )\
  2187. \9self:show( self.history[self.hindex] )\
  2188. end\
  2189. \
  2190. function UIFileDialogue:forward()\
  2191. \9self.hindex = math.min( #self.history, self.hindex + 1 )\
  2192. \9self:show( self.history[self.hindex] )\
  2193. end\
  2194. \
  2195. function UIFileDialogue:show( path )\
  2196. \9if fs.isDir( path ) then\
  2197. \9\9self.raw.path = path\
  2198. \9\9self.addressBar.text = path\
  2199. \9\9self:calculateFiles()\
  2200. \9\9return true\
  2201. \9else\
  2202. \9\9if self.window then\
  2203. \9\9\9self.window:remove()\
  2204. \9\9end\
  2205. \
  2206. \9\9local win = self:addChild( UIWindow( math.floor( self.width / 2 - 10.5 ), math.max( 4, math.floor( self.height / 2 - 4 ) ), 21, 8 ) )\
  2207. \9\9win.title = \"Path not a folder\"\
  2208. \
  2209. \9\9win.resizeable = false\
  2210. \9\9win.moveable = false\
  2211. \
  2212. \9\9win.content:addChild( UILabel( 1, 1, \"The path you input\" ) )\
  2213. \9\9win.content:addChild( UILabel( 1, 2, \"does not exist!\" ) )\
  2214. \
  2215. \9\9local button = win.content:addChild( UIButton( math.floor( win.width / 2 - 2 ), 4, 4, 1, \"ok\" ) )\
  2216. \9\9button.colour = colours.lightGrey\
  2217. \9\9button.textColour = colours.black\
  2218. \
  2219. \9\9function button:onClick()\
  2220. \9\9\9self.parent.parent:onClose()\
  2221. \9\9\9self.parent.parent:remove()\
  2222. \9\9end\
  2223. \
  2224. \9\9function win:onClose()\
  2225. \9\9\9self.parent.window = nil\
  2226. \9\9end\
  2227. \
  2228. \9\9self.window = win\
  2229. \9end\
  2230. end\
  2231. \
  2232. function UIFileDialogue:goto( path )\
  2233. \9while self.history[self.hindex + 1] do\
  2234. \9\9self.history[#self.history] = nil\
  2235. \9end\
  2236. \9if fs.exists( path ) and not fs.isDir( path ) then\
  2237. \9\9if self.mode == \"open\" and self.onOpen then\
  2238. \9\9\9self:onOpen( path )\
  2239. \9\9elseif self.mode == \"save\" and self.onSave then\
  2240. \9\9\9self:onSave( path )\
  2241. \9\9end\
  2242. \9else\
  2243. \9\9if path ~= self.path then\
  2244. \9\9\9if self:show( path ) then\
  2245. \9\9\9\9self.hindex = self.hindex + 1\
  2246. \9\9\9\9self.history[self.hindex] = path\
  2247. \9\9\9end\
  2248. \9\9end\
  2249. \9end\
  2250. end\
  2251. \
  2252. function UIFileDialogue:reposition()\
  2253. \
  2254. \9local function position( element, condition, x, y )\
  2255. \9\9if condition then\
  2256. \9\9\9self:addChild( element )\
  2257. \9\9\9element.x = x\
  2258. \9\9\9element.y = y\
  2259. \9\9else\
  2260. \9\9\9self:removeChild( element )\
  2261. \9\9end\
  2262. \9\9return condition\
  2263. \9end\
  2264. \
  2265. \9local x = 1\
  2266. \9self:addChild( self.header )\
  2267. \9self:addChild( self.background )\
  2268. \9self:addChild( self.content )\
  2269. \9self:addChild( self.addressBar )\
  2270. \
  2271. \9if self.window then\
  2272. \9\9self:addChild( self.window )\
  2273. \9\9self.window.x = math.floor( self.width / 2 - self.window.width / 2 )\
  2274. \9\9self.window.y = math.max( 4, math.floor( self.height / 2 - self.window.height / 2 ) )\
  2275. \9end\
  2276. \
  2277. \9self.header.width = self.width\
  2278. \9self.background.width = self.width\
  2279. \9self.background.height = self.height - 3\
  2280. \9self.content.width = self.width - 2\
  2281. \
  2282. \9if position( self.backButton, self.showBackButton, x, 1 ) then\
  2283. \9\9x = x + 4\
  2284. \9end\
  2285. \9if position( self.forwardButton, self.showForwardButton, x, 1 ) then\
  2286. \9\9x = x + 4\
  2287. \9end\
  2288. \9if position( self.upButton, self.showUpButton, x, 1 ) then\
  2289. \9\9x = x + 4\
  2290. \9end\
  2291. \9self.addressBar.x = x\
  2292. \9self.addressBar.y = 1\
  2293. \9self.addressBar.width = self.width - x - 1\
  2294. \9x = self.width\
  2295. \9if position( self.settingsButton, self.showSettingsButton, x - 4, 1 ) then\
  2296. \9\9self.addressBar.width = self.addressBar.width - 4\
  2297. \9end\
  2298. \
  2299. \9self.content.height = self.height - 5 - ( ( self.showButtons or self.showFileName ) and 2 or 0 )\
  2300. \
  2301. \9local x = self.width\
  2302. \9if position( self.button2, self.showButtons, x - 9, self.height - 2 ) then\
  2303. \9\9x = x - 18\
  2304. \9end\
  2305. \9position( self.button1, self.showButtons, x, self.height - 2 )\
  2306. \9if position( self.fileNameBox, self.showFileName, 1, self.height - 2 ) then\
  2307. \9\9if self.width <= 30 then\
  2308. \9\9\9self.fileNameBox.y = self.height - 4\
  2309. \9\9\9self.content.height = self.content.height - 2\
  2310. \9\9\9self.button1.x = 1\
  2311. \9\9\9self.fileNameBox.width = self.width - 2\
  2312. \9\9else\
  2313. \9\9\9self.fileNameBox.width = x - 2\
  2314. \9\9end\
  2315. \9end\
  2316. \
  2317. \9local width = self.content.width\
  2318. \9if #self.fileobjects * 4 - 1 > self.content.height then\
  2319. \9\9width = width - 1\
  2320. \9end\
  2321. \9for i = 1, #self.fileobjects do\
  2322. \9\9local ob = self.fileobjects[i]\
  2323. \9\9ob.width = width\
  2324. \9\9ob:getChildById \"clicker\" .width = width\
  2325. \9\9ob:getChildById \"name\" .width = width - 4\
  2326. \9\9ob:getChildById \"extn\" .width = width - 4\
  2327. \9\9ob:getChildById \"size\" .width = width - 4\
  2328. \9end\
  2329. \
  2330. end\
  2331. \
  2332. function UIFileDialogue:calculateFiles()\
  2333. \
  2334. \9self.fileobjects = {}\
  2335. \9for i = #self.content.children, 1, -1 do\
  2336. \9\9self.content.children[i]:remove()\
  2337. \9end\
  2338. \
  2339. \9local path = self.path\
  2340. \
  2341. \9local files = fs.list( path )\
  2342. \9local width = self.content.width\
  2343. \9if #files * 4 - 1 > self.content.height then\
  2344. \9\9width = width - 1\
  2345. \9end\
  2346. \
  2347. \9local objects = {}\
  2348. \9for i = 1, #files do\
  2349. \9\9objects[i] = {\
  2350. \9\9\9type = fs.isDir( path .. \"/\" .. files[i] ) and \"directory\" or \"file\";\
  2351. \9\9\9name = files[i];\
  2352. \9\9\9path = path .. \"/\" .. files[i];\
  2353. \9\9\9extension = files[i]:match( \"%.(.-)$\" ) or \"\";\
  2354. \9\9\9size = getSize( path .. \"/\" .. files[i] );\
  2355. \9\9}\
  2356. \9end\
  2357. \
  2358. \9if self.customSorter then\
  2359. \9\9table.sort( objects, self.customSorter )\
  2360. \9elseif self.sortBy == \"name\" then\
  2361. \9\9table.sort( objects, function( a, b )\
  2362. \9\9\9if a.type == b.type then\
  2363. \9\9\9\9return a.name < b.name\
  2364. \9\9\9end\
  2365. \9\9\9return a.type == \"directory\"\
  2366. \9\9end )\
  2367. \9elseif self.sortBy == \"size\" then\
  2368. \9\9table.sort( objects, function( a, b )\
  2369. \9\9\9if a.type == b.type then\
  2370. \9\9\9\9return a.size < b.size\
  2371. \9\9\9end\
  2372. \9\9\9return a.type == \"directory\"\
  2373. \9\9end )\
  2374. \9end\
  2375. \
  2376. \9local start, fin, step = 1, #objects, 1\
  2377. \9if self.sortOrder == \"descending\" then\
  2378. \9\9start, fin, step = #objects, 1, -1\
  2379. \9end\
  2380. \9local y = 0\
  2381. \9for i = start, fin, step do\
  2382. \9\9local container = self.content:addChild( UIElement( 0, y, width, 3 ) )\
  2383. \9\9local icon = container:addChild( UIImage( 0, 0, objects[i].type == \"directory\" and self.icons.folder or self.icons[objects[i].extension] or self.icons.default ) )\
  2384. \9\9local name = container:addChild( UILabel( 5, 0, objects[i].name ) )\
  2385. \9\9local extn = container:addChild( UILabel( 5, 1, objects[i].extension ) )\
  2386. \9\9local size = container:addChild( UILabel( 5, 2, fmtSize( objects[i].size ) ) )\
  2387. \9\9local clicker = container:addChild( UIButton( 0, 0, container.width, container.height, \"\" ) )\
  2388. \
  2389. \9\9name.textColour = colours.grey\
  2390. \
  2391. \9\9icon.id = \"icon\"\
  2392. \9\9name.id = \"name\"\
  2393. \9\9extn.id = \"extn\"\
  2394. \9\9size.id = \"size\"\
  2395. \9\9clicker.id = \"clicker\"\
  2396. \
  2397. \9\9name.width = math.min( name.width, container.width - 4 )\
  2398. \9\9extn.width = math.min( extn.width, container.width - 4 )\
  2399. \9\9size.width = math.min( size.width, container.width - 4 )\
  2400. \
  2401. \9\9function clicker.canvas:drawTo() end\
  2402. \
  2403. \9\9function clicker:onClick()\
  2404. \9\9\9self.parent.parent.parent:goto( path .. \"/\" .. objects[i].name )\
  2405. \9\9end\
  2406. \
  2407. \9\9self.fileobjects[i] = container\
  2408. \
  2409. \9\9y = y + 4\
  2410. \9end\
  2411. end\
  2412. \
  2413. function UIFileDialogue:setWidth( width )\
  2414. \9local min1 = 9 + ( self.showBackButton and 4 or 0 ) + ( self.showForwardButton and 4 or 0 ) + ( self.showUpButton and 4 or 0 ) + ( self.showSettingsButton and 4 or 0 )\
  2415. \9local min2 = 13\
  2416. \9local min3 = 1 + ( self.showButtons and 18 or 0 ) + ( ( self.width > 30 and self.showFileName and 10 ) or 0 )\
  2417. \9self.super:setWidth( math.max( width, min1, min2, min3 ) )\
  2418. \9self:reposition()\
  2419. end\
  2420. \
  2421. function UIFileDialogue:setHeight( height )\
  2422. \9local min = 13 + ( ( self.showButtons or self.showFileName ) and 2 ) + ( self.width <= 30 and 2 or 0 )\
  2423. \9self.super:setHeight( math.max( height, min ) )\
  2424. \9self:reposition()\
  2425. end\
  2426. \
  2427. function UIFileDialogue:setPath( path )\
  2428. \9self:goto( tostring( path ) )\
  2429. end\
  2430. \
  2431. function UIFileDialogue:setShowBackButton( bool )\
  2432. \9self.raw.showBackButton = bool\
  2433. \9self:reposition()\
  2434. end\
  2435. \
  2436. function UIFileDialogue:setShowForwardButton( bool )\
  2437. \9self.raw.showForwardButton = bool\
  2438. \9self:reposition()\
  2439. end\
  2440. \
  2441. function UIFileDialogue:setShowUpButton( bool )\
  2442. \9self.raw.showUpButton = bool\
  2443. \9self:reposition()\
  2444. end\
  2445. \
  2446. function UIFileDialogue:setShowSettingsButton( bool )\
  2447. \9self.raw.showSettingsButton = bool\
  2448. \9self:reposition()\
  2449. end\
  2450. \
  2451. function UIFileDialogue:setShowFileName( bool )\
  2452. \9self.raw.showFileName = bool\
  2453. \9self:reposition()\
  2454. end\
  2455. \
  2456. function UIFileDialogue:setShowButtons( bool )\
  2457. \9self.raw.showButtons = bool\
  2458. \9self:reposition()\
  2459. end\
  2460. \
  2461. function UIFileDialogue:setIcons( icons )\
  2462. \9self.raw.icons = icons\
  2463. \9self:calculateFiles()\
  2464. end\
  2465. \
  2466. function UIFileDialogue:setSortBy( sortBy )\
  2467. \9self.raw.sortBy = sortBy\
  2468. \9self:calculateFiles()\
  2469. end\
  2470. \
  2471. function UIFileDialogue:setSortOrder( sortOrder )\
  2472. \9self.raw.sortOrder = sortOrder\
  2473. \9self:calculateFiles()\
  2474. end\
  2475. \
  2476. function UIFileDialogue:setCustomSorter( sorter )\
  2477. \9self.customSorter = sorter\
  2478. \9self:calculateFiles()\
  2479. end\
  2480. \
  2481. function UIFileDialogue:setMode( mode )\
  2482. \9if mode == \"open\" then\
  2483. \9\9self.raw.mode = \"open\"\
  2484. \9\9self.button1.text = \"Open\"\
  2485. \9elseif mode == \"save\" then\
  2486. \9\9self.raw.mode = \"save\"\
  2487. \9\9self.button1.text = \"Save\"\
  2488. \9else\
  2489. \9\9error( \"no such mode '\" .. tostring( mode ) .. \"'\", 3 )\
  2490. \9end\
  2491. end\
  2492. \
  2493. function UIFileDialogue:setHeaderColour( colour )\
  2494. \9self.raw.headerColour = colour\
  2495. \9self.header.colour = colour\
  2496. end\
  2497. \
  2498. function UIFileDialogue:setBackgroundColour( colour )\
  2499. \9self.raw.backgroundColour = colour\
  2500. \9self.background.colour = colour\
  2501. end\
  2502. \
  2503. function UIFileDialogue:setHeaderButtonColour( colour )\
  2504. \9self.raw.headerButtonColour = colour\
  2505. \9self.backButton.colour = colour\
  2506. \9self.forwardButton.colour = colour\
  2507. \9self.upButton.colour = colour\
  2508. \9self.settingsButton.colour = colour\
  2509. end\
  2510. \
  2511. function UIFileDialogue:setButtonColour( colour )\
  2512. \9self.raw.buttonColour = colour\
  2513. \9self.button1.colour = colour\
  2514. \9self.button2.colour = colour\
  2515. end";
  2516.     ["/lib/elements/UIImage.lua"] = "\
  2517. require \"UIElement\"\
  2518. require \"graphics.Image\"\
  2519. local shader = require \"graphics.shader\"\
  2520. local UIEventHelpers = require \"util.UIEventHelpers\"\
  2521. local UIDrawingHelpers = require \"util.UIDrawingHelpers\"\
  2522. \
  2523. class \"UIImage\" extends \"UIElement\" {\
  2524. \9image = nil;\
  2525. }\
  2526. \
  2527. UIImage:mixin( UIEventHelpers.scrollbar.mixin )\
  2528. \
  2529. function UIImage:init( x, y, image )\
  2530. \9self.image = image\
  2531. \9self.super:init( x, y, self.image.width, self.image.height )\
  2532. end\
  2533. \
  2534. function UIImage:onMouseEvent( event )\
  2535. \9UIEventHelpers.scrollbar.handleMouseEvent( self, event )\
  2536. \9UIEventHelpers.scrollbar.handleMouseScroll( self, event )\
  2537. \9if UIEventHelpers.clicking.handleMouseEvent( self, event ) == \"click\" then\
  2538. \9\9if self.onClick then\
  2539. \9\9\9self:onClick()\
  2540. \9\9end\
  2541. \9end\
  2542. end\
  2543. \
  2544. function UIImage:onDraw()\
  2545. \9self.canvas:clear()\
  2546. \9self.image:drawTo( self.canvas, self.ox, self.oy )\
  2547. \9UIDrawingHelpers.scrollbar.drawScrollbars( self )\
  2548. end\
  2549. \
  2550. function UIImage:setImage( image )\
  2551. \9if type( image ) == \"string\" then\
  2552. \9\9self.raw.image = Image( image )\
  2553. \9else\
  2554. \9\9self.raw.image = image\
  2555. \9end\
  2556. \9self.changed = true\
  2557. end\
  2558. \
  2559. function UIImage:setWidth( width )\
  2560. \9self.super:setWidth( width )\
  2561. \9if self:getContentWidth() + self.ox < self.width then\
  2562. \9\9self.ox = math.min( 0, self.width - self:getContentWidth() )\
  2563. \9end\
  2564. end\
  2565. \
  2566. function UIImage:setHeight( height )\
  2567. \9self.super:setHeight( height )\
  2568. \9if self:getContentHeight() + self.oy < self.height then\
  2569. \9\9self.oy = math.min( 0, self.height - self:getContentHeight() )\
  2570. \9end\
  2571. end\
  2572. \
  2573. function UIImage:getContentWidth()\
  2574. \9return self.image.width\
  2575. end\
  2576. \
  2577. function UIImage:getContentHeight()\
  2578. \9return self.image.height\
  2579. end";
  2580.     ["/lib/elements/UILabel.lua"] = "\
  2581. require \"UIElement\"\
  2582. local UIEventHelpers = require \"util.UIEventHelpers\"\
  2583. \
  2584. class \"UILabel\" extends \"UIElement\" {\
  2585. \9link = nil;\
  2586. \9textColour = colours.lightGrey;\
  2587. \9text = \"\";\
  2588. }\
  2589. \
  2590. function UILabel:init( x, y, text, link )\
  2591. \9self.super:init( x, y, #text, 1 )\
  2592. \9self.raw.text = text\
  2593. \9self.raw.link = link\
  2594. end\
  2595. \
  2596. function UILabel:onMouseEvent( event )\
  2597. \9local mode = UIEventHelpers.clicking.handleMouseEvent( self, event )\
  2598. \9if mode == \"click\" then\
  2599. \9\9if self.link and self.link.onLabelPressed then\
  2600. \9\9\9self.link:onLabelPressed()\
  2601. \9\9end\
  2602. \9end\
  2603. end\
  2604. \
  2605. function UILabel:onDraw()\
  2606. \9self.canvas:drawText( 0, 0, {\
  2607. \9\9text = self.text:sub( 1, self.width );\
  2608. \9\9textColour = self.textColour;\
  2609. \9} )\
  2610. end\
  2611. \
  2612. function UILabel:setText( text )\
  2613. \9self.width = #tostring( text )\
  2614. \9self.raw.text = tostring( text )\
  2615. \9self.changed = true\
  2616. end\
  2617. \
  2618. function UILabel:setTextColour( textColour )\
  2619. \9self.raw.textColour = textColour\
  2620. \9self.changed = true\
  2621. end";
  2622.     ["/lib/elements/UIMenu.lua"] = "\
  2623. require \"UIElement\"\
  2624. require \"UIContainer\"\
  2625. require \"UIPanel\"\
  2626. require \"UILabel\"\
  2627. require \"UIButton\"\
  2628. require \"Event.Event\"\
  2629. local shader = require \"graphics.shader\"\
  2630. local UIEventHelpers = require \"util.UIEventHelpers\"\
  2631. \
  2632. local function checkContent( options )\
  2633. \9for i = 1, #options do\
  2634. \9\9if options[i].type == \"button\" or options[i].type == \"menu\" or options[i].type == \"label\" then\
  2635. \9\9\9if type( options[i].text ) ~= \"string\" then\
  2636. \9\9\9\9error( \"expected string text for option \" .. i .. \", got \" .. type( options[i].text ), 4 )\
  2637. \9\9\9end\
  2638. \9\9end\
  2639. \9end\
  2640. end\
  2641. \
  2642. local function constructContent( menu, options, parent )\
  2643. \9local width, height = 2, #options\
  2644. \9for i = 1, #options do\
  2645. \9\9if options[i].type == \"button\" or options[i].type == \"menu\" then\
  2646. \9\9\9width = math.max( width, #options[i].text + 2 )\
  2647. \9\9elseif options[i].type == \"label\" then\
  2648. \9\9\9width = math.max( width, #options[i].text + 3 )\
  2649. \9\9elseif options[i].type == \"custom\" then\
  2650. \9\9\9width = math.max( width, 2 + options[i].element.width )\
  2651. \9\9\9height = height + options[i].element.height - 1\
  2652. \9\9end\
  2653. \9end\
  2654. \
  2655. \9local container = UIContainer( 0, 0, width + 1, math.min( 10, height ) + 1 )\
  2656. \9local shadow = container:addChild( UIPanel( 1, 1, width, math.min( 10, height ), colours.grey ) )\
  2657. \9local content = container:addChild( UIContainer( 0, 0, width, math.min( 10, height ) ) )\
  2658. \9content.id = \"content\"\
  2659. \
  2660. \9local children = {}\
  2661. \
  2662. \9function content:clickRegistered( sx, sy )\
  2663. \9\9if not ( sx >= 0 and sy >= 0 and sx < self.width and sy < self.height ) then -- not within self\
  2664. \9\9\9for i = 1, #children do\
  2665. \9\9\9\9if children[i].parent == self.parent.parent and children[i]:getElementById \"content\" :clickRegistered( self.parent.x + sx - children[i].x, self.parent.y + sy - children[i].y ) then\
  2666. \9\9\9\9\9return true\
  2667. \9\9\9\9end\
  2668. \9\9\9end\
  2669. \9\9\9return false\
  2670. \9\9end\
  2671. \9\9return true\
  2672. \9end\
  2673. \
  2674. \9function content:onMouseEvent( event )\
  2675. \
  2676. \9\9UIContainer.onMouseEvent( self, event )\
  2677. \9\
  2678. \9\9if event.name == Event.MOUSEUP and not self:clickRegistered( event.x, event.y ) then\
  2679. \9\9\9if parent then\
  2680. \9\9\9\9menu:closeSubframe( self.parent )\
  2681. \9\9\9else\
  2682. \9\9\9\9menu:close()\
  2683. \9\9\9end\
  2684. \9\9end\
  2685. \
  2686. \9end\
  2687. \
  2688. \9local y = 0\
  2689. \
  2690. \9for i = 1, #options do\
  2691. \9\9local element\
  2692. \9\9if options[i].type == \"button\" then\
  2693. \9\9\9element = content:addChild( UIButton( 1, y, width - 2, 1, options[i].text ) )\
  2694. \9\9\9element.noAlign = true\
  2695. \9\9\9element.onClick = options[i].onClick\
  2696. \9\9\9y = y + 1\
  2697. \9\9elseif options[i].type == \"menu\" then\
  2698. \9\9\9local _y = y\
  2699. \9\9\9element = content:addChild( UIButton( 1, y, width - 2, 1, options[i].text ) )\
  2700. \9\9\9element.noAlign = true\
  2701. \9\9\9local content = constructContent( menu, options[i].content, true )\
  2702. \
  2703. \9\9\9function element:onClick()\
  2704. \9\9\9\9menu:openSubframe( content )\
  2705. \9\9\9\9content.x = self.parent.x + self.parent.width\
  2706. \9\9\9\9content.y = self.parent.y + _y\
  2707. \9\9\9end\
  2708. \
  2709. \9\9\9children[#children + 1] = content\
  2710. \
  2711. \9\9\9y = y + 1\
  2712. \9\9elseif options[i].type == \"label\" then\
  2713. \9\9\9element = content:addChild( UILabel( 2, y, options[i].text ) )\
  2714. \9\9\9y = y + 1\
  2715. \9\9elseif options[i].type == \"rule\" then\
  2716. \9\9\9element = content:addChild( UILabel( 1, y, (\"-\"):rep( width - 2 ) ) )\
  2717. \9\9\9y = y + 1\
  2718. \9\9elseif options[i].type == \"space\" then\
  2719. \9\9\9y = y + 1\
  2720. \9\9elseif options[i].type == \"custom\" then\
  2721. \9\9\9element = content:addChild( options[i].element )\
  2722. \9\9\9element.x = 1\
  2723. \9\9\9element.y = y\
  2724. \9\9\9element.width = width - 2\
  2725. \9\9\9y = y + element.height\
  2726. \9\9end\
  2727. \
  2728. \
  2729. \9\9if element then\
  2730. \9\9\9container:addChild( element )\
  2731. \9\9end\
  2732. \9end\
  2733. \
  2734. \9return container\
  2735. end\
  2736. \
  2737. class \"UIMenu\" extends \"UIElement\" {\
  2738. \9colour = 1;\
  2739. \9textColour = colours.grey;\
  2740. \9text = \"\";\
  2741. \9holding = false;\
  2742. \9content = nil;\
  2743. \9contentFrame = nil;\
  2744. \9isOpen = false;\
  2745. \9subframes = {};\
  2746. }\
  2747. \
  2748. function UIMenu:init( x, y, w, h, text, options, contentFrame )\
  2749. \9self.super:init( x, y, w, h )\
  2750. \9self.text = text\
  2751. \9checkContent( options )\
  2752. \9self.raw.content = constructContent( self, options )\
  2753. \9self.raw.contentFrame = contentFrame\
  2754. \9self.raw.subframes = {}\
  2755. end\
  2756. \
  2757. function UIMenu:open()\
  2758. \9if self.closed ~= os.clock() then\
  2759. \9\9if not self.isOpen then\
  2760. \9\9\9( self.contentFrame or self.parent ):addChild( self.content )\
  2761. \9\9\9if self.onOpened then\
  2762. \9\9\9\9self:onOpened()\
  2763. \9\9\9end\
  2764. \9\9\9self.isOpen = true\
  2765. \9\9end\
  2766. \9end\
  2767. end\
  2768. \
  2769. function UIMenu:openSubframe( frame )\
  2770. \9if frame.closed ~= os.clock() then\
  2771. \9\9frame.parent = self.contentFrame or self.parent\
  2772. \9\9self.subframes[#self.subframes + 1] = frame\
  2773. \9end\
  2774. end\
  2775. \
  2776. function UIMenu:closeSubframe( frame )\
  2777. \9frame:remove()\
  2778. \9frame.closed = os.clock()\
  2779. \9for i = #self.subframes, 1, -1 do\
  2780. \9\9if self.subframes[i] == frame then\
  2781. \9\9\9table.remove( self.subframes, i )\
  2782. \9\9end\
  2783. \9end\
  2784. end\
  2785. \
  2786. function UIMenu:close()\
  2787. \9self.closed = os.clock()\
  2788. \9if self.isOpen then\
  2789. \9\9self.content:remove()\
  2790. \9\9for i = #self.subframes, 1, -1 do\
  2791. \9\9\9self.subframes[i]:remove()\
  2792. \9\9\9self.subframes[i] = nil\
  2793. \9\9end\
  2794. \9\9if self.onClosed then\
  2795. \9\9\9self:onClosed()\
  2796. \9\9end\
  2797. \9\9self.isOpen = false\
  2798. \9end\
  2799. end\
  2800. \
  2801. function UIMenu:onMouseEvent( event )\
  2802. \9local mode = UIEventHelpers.clicking.handleMouseEvent( self, event )\
  2803. \9if mode == \"down\" or mode == \"up\" then\
  2804. \9\9self.holding = mode == \"down\"\
  2805. \9elseif mode == \"click\" then\
  2806. \9\9self.holding = false\
  2807. \9\9if self.isOpen then\
  2808. \9\9\9self:close()\
  2809. \9\9else\
  2810. \9\9\9self:open()\
  2811. \9\9end\
  2812. \9end\
  2813. end\
  2814. \
  2815. function UIMenu:onDraw()\
  2816. \9if self.holding then\
  2817. \9\9local colour = shader.lighten[self.colour]\
  2818. \9\9self.canvas:clear( colour == self.colour and shader.darken[colour] or colour )\
  2819. \9else\
  2820. \9\9self.canvas:clear( self.colour )\
  2821. \9end\
  2822. \9self.canvas:drawWrappedText( 0, 0, self.width, self.height, {\
  2823. \9\9text = self.text;\
  2824. \9\9alignment = \"centre\";\
  2825. \9\9verticalAlignment = \"centre\";\
  2826. \9\9textColour = self.textColour;\
  2827. \9} )\
  2828. end\
  2829. \
  2830. function UIMenu:setHolding( state )\
  2831. \9self.raw.holding = state\
  2832. \9self.changed = true\
  2833. end\
  2834. \
  2835. function UIMenu:setText( text )\
  2836. \9self.raw.text = text == nil and \"\" or tostring( text )\
  2837. \9self.changed = true\
  2838. end\
  2839. \
  2840. function UIMenu:setColour( colour )\
  2841. \9self.raw.colour = colour\
  2842. \9self.changed = true\
  2843. end\
  2844. \
  2845. function UIMenu:setTextColour( textColour )\
  2846. \9self.raw.textColour = textColour\
  2847. \9self.changed = true\
  2848. end\
  2849. \
  2850. function UIMenu:setContentFrame( frame )\
  2851. \9self.contentFrame = frame\
  2852. \9if self.isOpen and frame then\
  2853. \9\9self.content.parent = frame\
  2854. \9\9for i = #self.subframes, 1, -1 do\
  2855. \9\9\9self.subframes[i].parent = frame\
  2856. \9\9end\
  2857. \9end\
  2858. end";
  2859.     ["/lib/elements/UIMultilineTextInput.lua"] = "\
  2860. require \"UIElement\"\
  2861. \
  2862. local UIDrawingHelpers = require \"util.UIDrawingHelpers\"\
  2863. local UIEventHelpers = require \"util.UIEventHelpers\"\
  2864. \
  2865. class \"UIMultilineTextInput\" extends \"UIElement\" {\
  2866. \9lines = {};\
  2867. \9fmtLines = {};\
  2868. \
  2869. \9cx = 1;\
  2870. \9cy = 1;\
  2871. \
  2872. \9selected = false;\
  2873. \9scx = 1;\
  2874. \9scy = 1;\
  2875. \
  2876. \9focussed = true;\
  2877. \
  2878. \9handlesKeyboard = true;\
  2879. \9handlesText = true;\
  2880. }\
  2881. \
  2882. function UIMultilineTextInput:init( x, y, width, height )\
  2883. \9self.super:init( x, y, width, height )\
  2884. \9self.lines = {}\
  2885. end\
  2886. \
  2887. function UIMultilineTextInput:recolourLine( i )\
  2888. \9if self.lines[i] then\
  2889. \9\9local f = self.colourer or function( _, line )\
  2890. \9\9\9local t = {}\
  2891. \9\9\9for i = 1, #line do\
  2892. \9\9\9\9t[i] = { colours.white, colours.grey, line:sub( i, i ) }\
  2893. \9\9\9end\
  2894. \9\9\9return t\
  2895. \9\9end\
  2896. \9\9self.fmtLines[i] = f( self, self.lines[i] )\
  2897. \9end\
  2898. end\
  2899. \
  2900. function UIMultilineTextInput:setCursor( x, y )\
  2901. \9\
  2902. end\
  2903. \
  2904. function UIMultilineTextInput:write( text )\
  2905. \
  2906. \9if self.selected then\
  2907. \9\9local y1, y2, x1, x2 = self.cy, self.scy, self.cx, self.scx\
  2908. \9\9if y1 > y1 or ( y1 == y2 and x1 > x2 ) then\
  2909. \9\9\9y2, y1, x2, x1 = y1, y2, x1, x2\
  2910. \9\9end\
  2911. \
  2912. \9\9self.lines[y1] = self.lines[y1]:sub( 1, x1 - 1 ) .. self.lines[y2]:sub( x2 )\
  2913. \
  2914. \9\9for i = 1, y2 - y1 do\
  2915. \9\9\9table.remove( self.lines, y1 + i )\
  2916. \9\9\9table.remove( self.fmtLines, y1 + i )\
  2917. \9\9end\
  2918. \
  2919. \9\9self.selected = false\
  2920. \9\9self.cy = y1\
  2921. \9\9self.cx = x1\
  2922. \9end\
  2923. \
  2924. \9local newlines = select( 2, text:gsub( \"\\n\", \"\" ) )\
  2925. \
  2926. \9for i = 1, newlines do\
  2927. \9\9table.insert( self.lines, self.cy + 1, \"\" )\
  2928. \9\9table.insert( self.fmtLines, self.cy + 1, {} )\
  2929. \9end\
  2930. \
  2931. \9local ending = self.lines[self.cy]:sub( self.cx )\
  2932. \9self.lines[self.cy] = self.lines[self.cy]:sub( 1, self.cx - 1 )\
  2933. \9local i = self.cy\
  2934. \
  2935. \9for line in text:gmatch \"[^\\n]+\" do\
  2936. \9\9self.lines[i] = self.lines[i] .. line\
  2937. \9\9i = i + 1\
  2938. \9end\
  2939. \9self.lines[self.cy + newlines] = self.lines[self.cy + newlines] .. ending\
  2940. \
  2941. \9for i = 0, newlines do\
  2942. \9\9self:recolourLine( self.cy + i )\
  2943. \9end\
  2944. \
  2945. \9self.cy = self.cy + newlines\
  2946. \9if newlines > 0 then self.cx = 1 end\
  2947. \9self.cx = self.cx + #( text:match \"^.*\\n(.-)$\" or text )\
  2948. \
  2949. end\
  2950. \
  2951. function UIMultilineTextInput:onKeyboardEvent( event )\
  2952. \9do return end\
  2953. \9if not event.handled and self.focussed and event.name == Event.KEYDOWN then\
  2954. \9\9if event:matchesHotkey \"shift-left\" then\
  2955. \9\9\9self.selection = self.selection or self.cursor\
  2956. \9\9\9self.cursor = self.cursor - 1\
  2957. \9\9\9self.changed = true\
  2958. \9\9\9event.handled = true\
  2959. \9\9elseif event:matchesHotkey \"shift-right\" then\
  2960. \9\9\9self.selection = self.selection or self.cursor\
  2961. \9\9\9self.cursor = self.cursor + 1\
  2962. \9\9\9self.changed = true\
  2963. \9\9\9event.handled = true\
  2964. \9\9elseif event:matchesHotkey \"ctrl-a\" then\
  2965. \9\9\9self.cursor = #self.text\
  2966. \9\9\9self.selection = 0\
  2967. \9\9\9self.changed = true\
  2968. \9\9\9event.handled = true\
  2969. \9\9elseif event:matchesHotkey \"ctrl-c\" then\
  2970. \9\9\9if self.selection then\
  2971. \9\9\9\9clipboard.put {\
  2972. \9\9\9\9\9{ \"plaintext\", self.text:sub( math.min( self.cursor, self.selection ) + 1, math.max( self.cursor, self.selection ) + 1 ) }\
  2973. \9\9\9\9}\
  2974. \9\9\9else\
  2975. \9\9\9\9clipboard.put {\
  2976. \9\9\9\9\9{ \"plaintext\", self.text }\
  2977. \9\9\9\9}\
  2978. \9\9\9end\
  2979. \9\9\9event.handled = true\
  2980. \9\9elseif event:matchesHotkey \"ctrl-x\" then\
  2981. \9\9\9if self.selection then\
  2982. \9\9\9\9clipboard.put {\
  2983. \9\9\9\9\9{ \"plaintext\", self.text:sub( math.min( self.cursor, self.selection ) + 1, math.max( self.cursor, self.selection ) + 1 ) }\
  2984. \9\9\9\9}\
  2985. \9\9\9\9self:write \"\"\
  2986. \9\9\9else\
  2987. \9\9\9\9clipboard.put {\
  2988. \9\9\9\9\9{ \"plaintext\", self.text }\
  2989. \9\9\9\9}\
  2990. \9\9\9\9self.text = \"\"\
  2991. \9\9\9end\
  2992. \9\9\9self.changed = true\
  2993. \9\9\9event.handled = true\
  2994. \9\9elseif event:matchesHotkey \"ctrl-b\" then\
  2995. \9\9\9local text = clipboard.get \"plaintext\"\
  2996. \9\9\9if text then\
  2997. \9\9\9\9self:write( text )\
  2998. \9\9\9\9self.changed = true\
  2999. \9\9\9end\
  3000. \9\9\9event.handled = true\
  3001. \9\9elseif event:matchesHotkey \"left\" then\
  3002. \9\9\9if self.selection then\
  3003. \9\9\9\9self.cursor = math.min( self.cursor, self.selection )\
  3004. \9\9\9\9self.selection = nil\
  3005. \9\9\9else\
  3006. \9\9\9\9self.cursor = self.cursor - 1\
  3007. \9\9\9end\
  3008. \9\9\9self.changed = true\
  3009. \9\9\9event.handled = true\
  3010. \9\9elseif event:matchesHotkey \"right\" then\
  3011. \9\9\9if self.selection then\
  3012. \9\9\9\9self.cursor = math.max( self.cursor, self.selection )\
  3013. \9\9\9\9self.selection = nil\
  3014. \9\9\9else\
  3015. \9\9\9\9self.cursor = self.cursor + 1\
  3016. \9\9\9end\
  3017. \9\9\9self.changed = true\
  3018. \9\9\9event.handled = true\
  3019. \9\9elseif event:matchesHotkey \"backspace\" then\
  3020. \9\9\9if self.selection then\
  3021. \9\9\9\9self:write \"\"\
  3022. \9\9\9\9self.changed = true\
  3023. \9\9\9elseif self.cursor > 0 then\
  3024. \9\9\9\9self.cursor = self.cursor - 1\
  3025. \9\9\9\9self.text = self.text:sub( 1, self.cursor ) .. self.text:sub( self.cursor + 2 )\
  3026. \9\9\9end\
  3027. \9\9\9event.handled = true\
  3028. \9\9elseif event:matchesHotkey \"shift-home\" then\
  3029. \9\9\9self.selection = self.cursor\
  3030. \9\9\9self.cursor = 0\
  3031. \9\9\9self.changed = true\
  3032. \9\9\9event.handled = true\
  3033. \9\9elseif event:matchesHotkey \"shift-end\" then\
  3034. \9\9\9self.selection = self.cursor\
  3035. \9\9\9self.cursor = #self.text\
  3036. \9\9\9self.changed = true\
  3037. \9\9\9event.handled = true\
  3038. \9\9elseif event:matchesHotkey \"home\" then\
  3039. \9\9\9self.cursor = 0\
  3040. \9\9\9self.selection = nil\
  3041. \9\9\9self.changed = true\
  3042. \9\9\9event.handled = true\
  3043. \9\9elseif event:matchesHotkey \"end\" then\
  3044. \9\9\9self.cursor = #self.text\
  3045. \9\9\9self.selection = nil\
  3046. \9\9\9self.changed = true\
  3047. \9\9\9event.handled = true\
  3048. \9\9elseif event:matchesHotkey \"delete\" then\
  3049. \9\9\9self.text = self.text:sub( 1, self.cursor) .. self.text:sub( self.cursor + 2 )\
  3050. \9\9\9event.handled = true\
  3051. \9\9elseif event:matchesHotkey \"enter\" then\
  3052. \9\9\9self.focussed = false\
  3053. \9\9\9self.changed = true\
  3054. \9\9\9if self.onEnter then\
  3055. \9\9\9\9self:onEnter()\
  3056. \9\9\9end\
  3057. \9\9\9event.handled = true\
  3058. \9\9elseif event:matchesHotkey \"tab\" then\
  3059. \9\9\9self.focussed = false\
  3060. \9\9\9self.changed = true\
  3061. \9\9\9if self.onTab then\
  3062. \9\9\9\9self:onTab()\
  3063. \9\9\9end\
  3064. \9\9\9event.handled = true\
  3065. \9\9end\
  3066. \9end\
  3067. end\
  3068. \
  3069. function UIMultilineTextInput:onTextEvent( event )\
  3070. \9if not event.handled and self.focussed then\
  3071. \9\9self:write( event.text )\
  3072. \9\9self.changed = true\
  3073. \9\9event.handled = true\
  3074. \9end\
  3075. end\
  3076. \
  3077. function UIMultilineTextInput:onDraw()\
  3078. \
  3079. \9self.canvas:clear( colours.lightGrey )\
  3080. \9self.canvas:drawPreformattedText( self.ox, self.oy, self.width, self.height, {\
  3081. \9\9text = self.fmtLines;\
  3082. \9\9verticalAlignment = \"top\";\
  3083. \9\9selectedColour = self.selectedColour;\
  3084. \9\9selectedTextColour = self.selectedTextColour;\
  3085. \9} )\
  3086. \
  3087. \9-- UIDrawingHelpers.scrollbar.drawScrollbars( self )\
  3088. \
  3089. end\
  3090. \
  3091. function UIMultilineTextInput:setText( text )\
  3092. \9self.lines = {}\
  3093. \9self.fmtLines = {}\
  3094. \9for line in text:gmatch \"[^\\n]+\" do\
  3095. \9\9self.lines[#self.lines + 1] = line\
  3096. \9\9self.fmtLines[#self.fmtLines + 1] = {}\
  3097. \9end\
  3098. \
  3099. \9for i = 1, #self.lines do\
  3100. \9\9self:recolourLine( i )\
  3101. \9end\
  3102. end\
  3103. \
  3104. function UIMultilineTextInput:setTextColourer( colourer )\
  3105. \
  3106. end";
  3107.     ["/lib/elements/UIPanel.lua"] = "\
  3108. require \"UIElement\"\
  3109. \
  3110. class \"UIPanel\" extends \"UIElement\" {\
  3111. \9colour = 1;\
  3112. }\
  3113. \
  3114. function UIPanel:init( x, y, w, h, col )\
  3115. \9self.super:init( x, y, w, h )\
  3116. \9self.colour = col\
  3117. end\
  3118. \
  3119. function UIPanel:onDraw()\
  3120. \9self.canvas:clear( self.colour )\
  3121. end\
  3122. \
  3123. function UIPanel:onMouseEvent( event )\
  3124. \9if not event.handled and event:isInArea( 0, 0, self.width, self.height ) and event.name == Event.MOUSEDOWN then\
  3125. \9\9event.handled = true\
  3126. \9end\
  3127. end\
  3128. \
  3129. function UIPanel:setColour( colour )\
  3130. \9self.raw.colour = colour\
  3131. \9self.changed = true\
  3132. end";
  3133.     ["/lib/elements/UIRadioButton.lua"] = "\
  3134. require \"UIElement\"\
  3135. local UIEventHelpers = require \"util.UIEventHelpers\"\
  3136. \
  3137. local groups = { [0] = {} }\
  3138. \
  3139. class \"UIRadioButton\" extends \"UIElement\" {\
  3140. \9colour = colours.lightGrey;\
  3141. \9checkColour = colours.black;\
  3142. \9check = \"@\"; --set to \" \" for a whole pixel\
  3143. \9group = 0;\
  3144. \9toggled = false;\
  3145. }\
  3146. \
  3147. function UIRadioButton:init( x, y )\
  3148. \9self.super:init( x, y, 1, 1 )\
  3149. \9groups[0][#groups[0] + 1] = self\
  3150. end\
  3151. \
  3152. function UIRadioButton:onLabelPressed()\
  3153. \9self.toggled = not self.toggled\
  3154. end\
  3155. \
  3156. function UIRadioButton:onMouseEvent( event )\
  3157. \
  3158. \9if UIEventHelpers.clicking.handleMouseEvent( self, event ) == \"click\" then\
  3159. \9\9self.holding = false\
  3160. \9\9self.toggled = not self.toggled\
  3161. \9end\
  3162. \
  3163. end\
  3164. \
  3165. function UIRadioButton:onDraw()\
  3166. \
  3167. \9self.canvas:clear( self.colour )\
  3168. \9self.canvas:drawPoint( 0, 0, {\
  3169. \9\9colour = self.colour\
  3170. \9} )\
  3171. \9if self.toggled then\
  3172. \9\9self.canvas:drawText( 0, 0, {\
  3173. \9\9\9textColour = self.checkColour;\
  3174. \9\9\9text = self.check;\
  3175. \9\9} )\
  3176. \9end\
  3177. \
  3178. end\
  3179. \
  3180. function UIRadioButton:setWidth() end\
  3181. function UIRadioButton:setHeight() end\
  3182. \
  3183. function UIRadioButton:setHolding( state )\
  3184. \9self.raw.holding = state\
  3185. \9self.changed = true\
  3186. end\
  3187. \
  3188. function UIRadioButton:setColour( colour )\
  3189. \9self.raw.colour = colour\
  3190. \9self.changed = true\
  3191. end\
  3192. \
  3193. function UIRadioButton:setCheckColour( colour )\
  3194. \9self.raw.checkColour = colour\
  3195. \9self.changed = trur\
  3196. end\
  3197. \
  3198. function UIRadioButton:setCheck( check )\
  3199. \9self.raw.check = check\
  3200. \9self.changed = true\
  3201. end\
  3202. \
  3203. function UIRadioButton:setToggled( toggled )\
  3204. \9if toggled then\
  3205. \9\9for i = 1, #groups[self.group] do\
  3206. \9\9\9groups[self.group][i].toggled = false\
  3207. \9\9end\
  3208. \9end\
  3209. \9local t = self.toggled\
  3210. \9self.raw.toggled = toggled\
  3211. \9if toggled ~= t and self.onToggle then\
  3212. \9\9self:onToggle()\
  3213. \9end\
  3214. \9self.changed = true\
  3215. end\
  3216. \
  3217. function UIRadioButton:setGroup( group )\
  3218. \9for i = #groups[self.group], 1, -1 do\
  3219. \9\9if groups[self.group][i] == self then\
  3220. \9\9\9table.remove( groups[self.group], i )\
  3221. \9\9end\
  3222. \9end\
  3223. \9if #groups[self.group] == 0 then\
  3224. \9\9groups[self.group] = nil\
  3225. \9end\
  3226. \9groups[group] = groups[group] or {}\
  3227. \9groups[group][#groups[group] + 1] = self\
  3228. \9self.raw.group = group\
  3229. end";
  3230.     ["/lib/elements/UITabs.lua"] = "\
  3231. require \"Timer\"\
  3232. require \"UIElement\"\
  3233. \
  3234. local function optionWidth( option )\
  3235. \9return #option + 2\
  3236. end\
  3237. local function formatOptions( options )\
  3238. \9local t, x = {}, 0\
  3239. \9for i = 1, #options do\
  3240. \9\9local width = optionWidth( options[i] )\
  3241. \9\9t[i] = { x = x, width = width, text = options[i] }\
  3242. \9\9x = x + width + 1\
  3243. \9end\
  3244. \9return t\
  3245. end\
  3246. local function getOptionTID( t, x )\
  3247. \9for i = 1, #t do\
  3248. \9\9if x >= t[i].x and x < t[i].x + t[i].width then\
  3249. \9\9\9return t[i], i, x - t[i].x\
  3250. \9\9end\
  3251. \9end\
  3252. end\
  3253. \
  3254. class \"UITabs\" extends \"UIElement\" {\
  3255. \9options = {};\
  3256. \9showButtons = false;\
  3257. \
  3258. \9selected = nil;\
  3259. \9selectedOffset = 0;\
  3260. \9selectedWidth = 0;\
  3261. \
  3262. \9colour = 1;\
  3263. \9textColour = colours.grey;\
  3264. \9selectedColour = colours.cyan;\
  3265. \9selectedTextColour = colours.white;\
  3266. \9buttonColour = colours.lightGrey;\
  3267. \9buttonTextColour = colours.white;\
  3268. \9separator = \"|\";\
  3269. \9seperatorTextColour = colours.lightGrey;\
  3270. \
  3271. \9scrolling = nil;\
  3272. \9scrollingTimer = nil;\
  3273. }\
  3274. \
  3275. function UITabs:init( x, y, width, options )\
  3276. \9self.super:init( x, y, width, 1 )\
  3277. \9self.options = options\
  3278. \9self.width = width\
  3279. end\
  3280. \
  3281. function UITabs:startScrollingTimer()\
  3282. \9if self.scrolling then\
  3283. \9\9self.ox = math.min( math.max( self.width - self:getContentWidth() - ( self.showButtons and 2 or 0 ), self.ox + self.scrolling ), 0 )\
  3284. \9\9self.scrollingTimer = self.scrollingTimer or Timer.queue( .05, function()\
  3285. \9\9\9self.scrollingTimer = nil\
  3286. \9\9\9self:startScrollingTimer()\
  3287. \9\9end )\
  3288. \9else\
  3289. \9\9self.scrollingTimer = nil\
  3290. \9end\
  3291. end\
  3292. \
  3293. function UITabs:changeSelection( x, width )\
  3294. \9self.animationHandler:createRoundedTween( \"selectedOffset\", self, { selectedOffset = x }, self.transitionTime )\
  3295. \9self.animationHandler:createRoundedTween( \"selectedWidth\", self, { selectedWidth = width }, self.transitionTime )\
  3296. \9if x < -self.ox then\
  3297. \9\9self.animatedOX = -x\
  3298. \9elseif x + width > self.width - self.ox - ( self.showButtons and 2 or 0 ) then\
  3299. \9\9self.animatedOX = -( x + width + ( self.showButtons and 2 or 0 ) - self.width )\
  3300. \9end\
  3301. end\
  3302. \
  3303. function UITabs:select( index )\
  3304. \9local t = formatOptions( self.options )[index]\
  3305. \9if t then\
  3306. \9\9self:changeSelection( t.x, t.width )\
  3307. \9\9if self.selected ~= index and self.onSelect then\
  3308. \9\9\9self.raw.selected = index\
  3309. \9\9\9self:onSelect( index )\
  3310. \9\9else\
  3311. \9\9\9self.raw.selected = index\
  3312. \9\9end\
  3313. \9end\
  3314. end\
  3315. \
  3316. function UITabs:onDraw()\
  3317. \9self.canvas:clear( self.colour )\
  3318. \9local x = 0\
  3319. \9if self.showButtons then\
  3320. \9\9self.canvas:drawPoint( 0, 0, {\
  3321. \9\9\9colour = self.buttonColour;\
  3322. \9\9\9textColour = self.buttonTextColour;\
  3323. \9\9\9character = \"<\";\
  3324. \9\9} )\
  3325. \9\9self.canvas:drawPoint( self.width - 1, 0, {\
  3326. \9\9\9colour = self.buttonColour;\
  3327. \9\9\9textColour = self.buttonTextColour;\
  3328. \9\9\9character = \">\";\
  3329. \9\9} )\
  3330. \9\9x = 1\
  3331. \9end\
  3332. \9local options = formatOptions( self.options )\
  3333. \9for i = 0, self.width - ( self.showButtons and 3 or 1 ) do\
  3334. \9\9local t, _, d = getOptionTID( options, i - self.ox )\
  3335. \9\9if t then\
  3336. \9\9\9local selected = i - self.ox >= self.selectedOffset and i - self.ox < self.selectedOffset + self.selectedWidth\
  3337. \9\9\9self.canvas:drawPoint( x + i, 0, {\
  3338. \9\9\9\9colour = selected and self.selectedColour or self.colour;\
  3339. \9\9\9\9textColour = selected and self.selectedTextColour or self.textColour;\
  3340. \9\9\9\9character = ( d == 0 or d == t.width - 1 ) and \" \" or t.text:sub( d, d );\
  3341. \9\9\9} )\
  3342. \9\9elseif i - self.ox < self:getContentWidth() then\
  3343. \9\9\9local selected = i - self.ox >= self.selectedOffset and i - self.ox < self.selectedOffset + self.selectedWidth\
  3344. \9\9\9self.canvas:drawPoint( x + i, 0, {\
  3345. \9\9\9\9colour = selected and self.selectedColour or self.colour;\
  3346. \9\9\9\9textColour = selected and self.selectedTextColour or self.seperatorTextColour;\
  3347. \9\9\9\9character = selected and \" \" or self.separator;\
  3348. \9\9\9} )\
  3349. \9\9end\
  3350. \9end\
  3351. end\
  3352. \
  3353. function UITabs:onMouseEvent( event )\
  3354. \9if event.handled then return end\
  3355. \
  3356. \9if event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, 1 ) then\
  3357. \9\9if event.x == 0 then\
  3358. \9\9\9self.scrolling = 1\
  3359. \9\9\9self:startScrollingTimer()\
  3360. \9\9\9event.handled = true\
  3361. \9\9elseif event.x == self.width - 1 then\
  3362. \9\9\9self.scrolling = -1\
  3363. \9\9\9self:startScrollingTimer()\
  3364. \9\9\9event.handled = true\
  3365. \9\9else\
  3366. \9\9\9self.holding = {\
  3367. \9\9\9\9button = event.button;\
  3368. \9\9\9\9moved = false;\
  3369. \9\9\9\9x = event.x - self.ox;\
  3370. \9\9\9}\
  3371. \9\9\9event.handled = true\
  3372. \9\9end\
  3373. \9elseif event.name == Event.MOUSEDRAG and self.holding then\
  3374. \9\9self.holding.moved = true\
  3375. \9\9self.ox = math.min( math.max( self.width - self:getContentWidth() - ( self.showButtons and 2 or 0 ), event.x - self.holding.x ), 0 )\
  3376. \9\9event.handled = true\
  3377. \9elseif event.name == Event.MOUSEUP and self.scrolling then\
  3378. \9\9self.scrolling = nil\
  3379. \9elseif event.name == Event.MOUSEUP and self.holding then\
  3380. \9\9if not self.holding.moved then\
  3381. \9\9\9local _, i = getOptionTID( formatOptions( self.options ), event.x - self.ox - ( self.showButtons and 1 or 0 ) )\
  3382. \9\9\9if i then\
  3383. \9\9\9\9self:select( i )\
  3384. \9\9\9end\
  3385. \9\9end\
  3386. \9\9self.holding = nil\
  3387. \9\9event.handled = true\
  3388. \9end\
  3389. end\
  3390. \
  3391. function UITabs:getContentWidth()\
  3392. \9local w = 0\
  3393. \9for i = 1, #self.options do\
  3394. \9\9w = w + 2 + #self.options[i]\
  3395. \9end\
  3396. \9return w + #self.options - 1\
  3397. end\
  3398. \
  3399. function UITabs:setHeight() end\
  3400. \
  3401. function UITabs:setWidth( width )\
  3402. \9self.super:setWidth( width )\
  3403. \9local w = self:getContentWidth()\
  3404. \9if w > width then\
  3405. \9\9self.showButtons = true\
  3406. \9else\
  3407. \9\9self.showButtons = false\
  3408. \9end\
  3409. \9if w + self.ox < self.width then\
  3410. \9\9self.ox = math.min( 0, self.width - w )\
  3411. \9end\
  3412. end\
  3413. \
  3414. \
  3415. function UITabs:setSelectedOffset( offset )\
  3416. \9self.raw.selectedOffset = offset\
  3417. \9self.changed = true\
  3418. end\
  3419. \
  3420. function UITabs:setSelectedWidth( width )\
  3421. \9self.raw.selectedWidth = width\
  3422. \9self.changed = true\
  3423. end\
  3424. \
  3425. function UITabs:setShowButtons( show )\
  3426. \9self.raw.showButtons = show\
  3427. \9self.changed = true\
  3428. end\
  3429. \
  3430. function UITabs:setSelected( option )\
  3431. \9return self:select( option )\
  3432. end\
  3433. \
  3434. function UITabs:setColour( colour )\
  3435. \9self.raw.colour = colour\
  3436. \9self.changed = true\
  3437. end\
  3438. \
  3439. function UITabs:setTextColour( colour )\
  3440. \9self.raw.textColour = colour\
  3441. \9self.changed = true\
  3442. end\
  3443. \
  3444. function UITabs:setSelectedColour( colour )\
  3445. \9self.raw.selectedColour = colour\
  3446. \9self.changed = true\
  3447. end\
  3448. \
  3449. function UITabs:setSelectedTextColour( colour )\
  3450. \9self.raw.selectedTextColour = colour\
  3451. \9self.changed = true\
  3452. end\
  3453. \
  3454. function UITabs:setButtonColour( colour )\
  3455. \9self.raw.buttonColour = colour\
  3456. \9self.changed = true\
  3457. end\
  3458. \
  3459. function UITabs:setButtonTextColour( colour )\
  3460. \9self.raw.buttonTextColour = colour\
  3461. \9self.changed = true\
  3462. end\
  3463. \
  3464. function UITabs:setSeperator( separator )\
  3465. \9self.raw.separator = separator\
  3466. \9self.changed = true\
  3467. end\
  3468. \
  3469. function UITabs:setSeperatorTextColour( colour )\
  3470. \9self.raw.seperatorTextColour = colour\
  3471. \9self.changed = true\
  3472. end";
  3473.     ["/lib/elements/UITerminal.lua"] = "\
  3474. require \"graphics.TermCanvas\"\
  3475. \
  3476. require \"Event.Event\"\
  3477. \
  3478. require \"UIElement\"\
  3479. \
  3480. class \"UITerminal\" extends \"UIElement\" {\
  3481. \9handlesKeyboard = true;\
  3482. \9handlesText = true;\
  3483. \9\
  3484. \9holding = false;\
  3485. }\
  3486. \
  3487. function UITerminal:init( x, y, w, h )\
  3488. \9self.super:init( x, y, w, h )\
  3489. \
  3490. \9self.canvas = TermCanvas( w, h )\
  3491. \9self.term = self.canvas:getTermRedirect()\
  3492. end\
  3493. \
  3494. function UITerminal:wrap()\
  3495. \9return term.redirect( self.term )\
  3496. end\
  3497. \
  3498. function UITerminal:onMouseEvent( event )\
  3499. \9if event.handled or not self.onEvent then return end\
  3500. \
  3501. \9if event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, self.height ) then\
  3502. \9\9self.holding = true\
  3503. \9\9self:onEvent( \"mouse_click\", event.button, event.x + 1, event.y + 1 )\
  3504. \9\9event.handled = true\
  3505. \9\9self.changed = true\
  3506. \9elseif event.name == Event.MOUSESCROLL and event:isInArea( 0, 0, self.width, self.height ) then\
  3507. \9\9self:onEvent( \"mouse_scroll\", event.button, event.x + 1, event.y + 1 )\
  3508. \9\9event.handled = true\
  3509. \9\9self.changed = true\
  3510. \9elseif event.name == Event.MOUSEUP and self.holding then\
  3511. \9\9self.holding = false\
  3512. \9\9self:onEvent( \"mouse_up\", event.button, event.x + 1, event.y + 1 )\
  3513. \9\9event.handled = true\
  3514. \9\9self.changed = true\
  3515. \9elseif event.name == Event.MOUSEDRAG and self.holding then\
  3516. \9\9self:onEvent( \"mouse_drag\", event.button, event.x + 1, event.y + 1 )\
  3517. \9\9event.handled = true\
  3518. \9\9self.changed = true\
  3519. \9end\
  3520. end\
  3521. \
  3522. function UITerminal:onKeyboardEvent( event )\
  3523. \9if event.handled or not self.onEvent then return end\
  3524. \
  3525. \9if event.name == Event.KEYUP then\
  3526. \9\9self:onEvent( \"key_up\", keys[event.key] )\
  3527. \9\9event.handled = true\
  3528. \9\9self.changed = true\
  3529. \9elseif event.name == Event.KEYDOWN then\
  3530. \9\9self:onEvent( \"key\", keys[event.key], event.parameters.isRepeat )\
  3531. \9\9event.handled = true\
  3532. \9\9self.changed = true\
  3533. \9end\
  3534. end\
  3535. \
  3536. function UITerminal:onTextEvent( event )\
  3537. \9if event.handled or not self.onEvent then return end\
  3538. \
  3539. \9if event.name == Event.TEXT then\
  3540. \9\9self:onEvent( \"char\", event.text )\
  3541. \9\9event.handled = true\
  3542. \9\9self.changed = true\
  3543. \9elseif event.name == Event.PASTE then\
  3544. \9\9self:onEvent( \"paste\", event.text )\
  3545. \9\9event.handled = true\
  3546. \9\9self.changed = true\
  3547. \9end\
  3548. end\
  3549. \
  3550. function UITerminal:setWidth( width )\
  3551. \9self.super:setWidth( width )\
  3552. \9if self.onEvent then\
  3553. \9\9self:onEvent \"term_resize\"\
  3554. \9end\
  3555. end\
  3556. \
  3557. function UITerminal:setHeight( height )\
  3558. \9self.super:setHeight( height )\
  3559. \9if self.onEvent then\
  3560. \9\9self:onEvent \"term_resize\"\
  3561. \9end\
  3562. end\
  3563. \
  3564. function UITerminal:handle( event )\
  3565. \
  3566. \9if event.class == Event and self.onEvent then\
  3567. \9\9self:onEvent( event.name, unpack( event.parameters ) )\
  3568. \9\9self.changed = true\
  3569. \9end\
  3570. \9UIElement.handle( self, event )\
  3571. \
  3572. end\
  3573. \
  3574. function UITerminal:onDraw()\
  3575. \9if self.canvas.term_cb then\
  3576. \9\9self.canvas.cursor = {\
  3577. \9\9\9x = self.canvas.term_x - 1;\
  3578. \9\9\9y = self.canvas.term_y - 1;\
  3579. \9\9\9colour = self.canvas.term_tc;\
  3580. \9\9}\
  3581. \9end\
  3582. end";
  3583.     ["/lib/elements/UIText.lua"] = "\
  3584. require \"UIElement\"\
  3585. \
  3586. local markup = require \"util.markup\"\
  3587. local UIDrawingHelpers = require \"util.UIDrawingHelpers\"\
  3588. local UIEventHelpers = require \"util.UIEventHelpers\"\
  3589. \
  3590. class \"UIText\" extends \"UIElement\" {\
  3591. \9colour = 1;\
  3592. \9textColour = colours.grey;\
  3593. \9text = \"\";\
  3594. \9selectedColour = colours.blue;\
  3595. \9selectedTextColour = colours.white;\
  3596. \
  3597. \9alignment = \"top\";\
  3598. \9wrap = true;\
  3599. \9selectable = true;\
  3600. \
  3601. \9internalWidth = nil;\
  3602. \9internalHeight = nil;\
  3603. \
  3604. \9formattedText = nil;\
  3605. \9formattedTextInfo = nil;\
  3606. \9handlesKeyboard = true;\
  3607. }\
  3608. \
  3609. UIText:mixin( UIEventHelpers.scrollbar.mixin )\
  3610. \
  3611. function UIText:init( x, y, w, h, text )\
  3612. \9self.super:init( x, y, w, h )\
  3613. \9self.text = text\
  3614. end\
  3615. \
  3616. function UIText:onMouseEvent( event )\
  3617. \9\
  3618. \9UIEventHelpers.scrollbar.handleMouseEvent( self, event )\
  3619. \9UIEventHelpers.scrollbar.handleMouseScroll( self, event )\
  3620. \
  3621. \9if self.selectable then\
  3622. \9\9UIEventHelpers.textSelection.handleMouseEvent( self, event, self.formattedTextInfo, self.alignment, math.max( self:getContentWidth(), self.internalWidth or self.width ), math.max( self:getContentHeight(), self.internalHeight or self.height ) )\
  3623. \9end\
  3624. end\
  3625. \
  3626. function UIText:onKeyboardEvent( event )\
  3627. \9if self.selectable then\
  3628. \9\9UIEventHelpers.textSelection.handleKeyboardEvent( self, event, self.stream )\
  3629. \9end\
  3630. end\
  3631. \
  3632. function UIText:onDraw()\
  3633. \9self.canvas:clear( self.colour )\
  3634. \9self.canvas:drawPreformattedText( self.ox, self.oy, math.max( self:getContentWidth(), self.internalWidth or self.width ), math.max( self:getContentHeight(), self.internalHeight or self.height ), {\
  3635. \9\9text = self.formattedText;\
  3636. \9\9verticalAlignment = self.alignment;\
  3637. \9\9selectedColour = self.selectedColour;\
  3638. \9\9selectedTextColour = self.selectedTextColour;\
  3639. \9} )\
  3640. \
  3641. \9UIDrawingHelpers.scrollbar.drawScrollbars( self )\
  3642. end\
  3643. \
  3644. function UIText:updateText()\
  3645. \9local a, b, c = markup.parse( self.text, self.colour, self.textColour, self.wrap and ( self.internalWidth or self.width ), self.wrap and self.internalHeight, true )\
  3646. \9self.formattedTextInfo, self.formattedText, self.stream = a, b, c\
  3647. \9self.changed = true\
  3648. end\
  3649. \
  3650. function UIText:getContentWidth()\
  3651. \9local max = 0\
  3652. \9for i = 1, #self.formattedText do\
  3653. \9\9max = math.max( #self.formattedText[i] - ( self.formattedText[i][#self.formattedText[i]][3] == \"\\n\" and 1 or 0 ), max )\
  3654. \9end\
  3655. \9return max\
  3656. end\
  3657. \
  3658. function UIText:getContentHeight()\
  3659. \9return #self.formattedText\
  3660. end\
  3661. \
  3662. function UIText:setText( text )\
  3663. \9self.raw.text = tostring( text )\
  3664. \9self:updateText()\
  3665. end\
  3666. \
  3667. function UIText:setColour( colour )\
  3668. \9self.raw.colour = colour\
  3669. \9self:updateText()\
  3670. end\
  3671. \
  3672. function UIText:setTextColour( textColour )\
  3673. \9self.raw.textColour = textColour\
  3674. \9self:updateText()\
  3675. end\
  3676. \
  3677. function UIText:setWrap( wrap )\
  3678. \9self.raw.wrap = wrap\
  3679. \9self:updateText()\
  3680. end\
  3681. \
  3682. function UIText:setInternalWidth( width )\
  3683. \9self.raw.internalWidth = width\
  3684. \9self:updateText()\
  3685. end\
  3686. \
  3687. function UIText:setInternalHeight( height )\
  3688. \9self.raw.internalHeight = height\
  3689. \9self:updateText()\
  3690. end\
  3691. \
  3692. function UIText:setWidth( width )\
  3693. \9self.super:setWidth( width )\
  3694. \9self:updateText()\
  3695. \9if self:getContentWidth() + self.ox < self.width then\
  3696. \9\9self.ox = math.min( 0, self.width - self:getContentWidth() )\
  3697. \9end\
  3698. end\
  3699. \
  3700. function UIText:setHeight( height )\
  3701. \9self.super:setHeight( height )\
  3702. \9self:updateText()\
  3703. \9if self:getContentHeight() + self.oy < self.height then\
  3704. \9\9self.oy = math.min( 0, self.height - self:getContentHeight() )\
  3705. \9end\
  3706. end\
  3707. \
  3708. function UIText:setAlignment( alignment )\
  3709. \9self.raw.alignment = alignment\
  3710. \9self.changed = true\
  3711. end\
  3712. \
  3713. function UIText:setSelectedColour( colour )\
  3714. \9self.raw.selectedColour = colour\
  3715. \9self.changed = true\
  3716. end\
  3717. \
  3718. function UIText:setSelectedTextColour( colour )\
  3719. \9self.raw.selectedTextColour = colour\
  3720. \9self.changed = true\
  3721. end";
  3722.     ["/lib/elements/UITextInput.lua"] = "\
  3723. require \"UIElement\"\
  3724. \
  3725. local clipboard = require \"clipboard\"\
  3726. \
  3727. class \"UITextInput\" extends \"UIElement\" {\
  3728. \9text = \"\";\
  3729. \9mask = nil;\
  3730. \9colour = colours.lightGrey;\
  3731. \9textColour = colours.grey;\
  3732. \9focussedColour = colours.white;\
  3733. \9focussedTextColour = colours.grey;\
  3734. \9selectedColour = colours.blue;\
  3735. \9selectedTextColour = colours.white;\
  3736. \
  3737. \9cursor = 0;\
  3738. \9selection = nil;\
  3739. \9focussed = false;\
  3740. \
  3741. \9handlesKeyboard = true;\
  3742. \9handlesText = true;\
  3743. }\
  3744. \
  3745. function UITextInput:init( x, y, w )\
  3746. \9self.super:init( x, y, w, 1 )\
  3747. end\
  3748. \
  3749. function UITextInput:write( text )\
  3750. \
  3751. \9if self.selection then\
  3752. \9\9self.raw.text = self.text:sub( 1, math.min( self.selection, self.cursor ) )\
  3753. \9\9.. text\
  3754. \9\9.. self.text:sub( math.max( self.selection, self.cursor ) + 2 )\
  3755. \9\9self.cursor = math.min( self.selection, self.cursor ) + #text\
  3756. \9\9self.selection = nil\
  3757. \
  3758. \9else\
  3759. \9\9self.raw.text = self.text:sub( 1, self.cursor ) .. text .. self.text:sub( self.cursor + 1 )\
  3760. \9\9self.cursor = self.cursor + #text\
  3761. \9end\
  3762. \
  3763. end\
  3764. \
  3765. function UITextInput:focusOn()\
  3766. \9if #self.text > 0 then\
  3767. \9\9self.selection = 0\
  3768. \9end\
  3769. \9self.cursor = #self.text\
  3770. \9self.focussed = true\
  3771. \9self.changed = true\
  3772. end\
  3773. \
  3774. function UITextInput:onLabelPressed()\
  3775. \9self:focusOn()\
  3776. end\
  3777. \
  3778. function UITextInput:setCursor( cursor )\
  3779. \
  3780. \9self.raw.cursor = math.max( math.min( cursor, #self.text ), 0 )\
  3781. \9if self.cursor + self.ox < 1 then\
  3782. \9\9self.ox = math.min( -self.cursor, 0 )\
  3783. \9elseif self.cursor + self.ox >= self.width - 1 then\
  3784. \9\9self.ox = self.width - 1 - self.cursor\
  3785. \9end\
  3786. \
  3787. end\
  3788. \
  3789. function UITextInput:onMouseEvent( event )\
  3790. \
  3791. \9if event.name == Event.MOUSEDOWN and event.handled then\
  3792. \9\9self.focussed = false\
  3793. \9\9self.changed = true\
  3794. \9end\
  3795. \9if event.handled then return end\
  3796. \
  3797. \9if event.name == Event.MOUSEDOWN then\
  3798. \9\9if event:isInArea( 0, 0, self.width, self.height ) then\
  3799. \9\9\9self.focussed = true\
  3800. \9\9\9self.cursor = event.x - self.ox\
  3801. \9\9\9event.handled = true\
  3802. \9\9else\
  3803. \9\9\9self.focussed = false\
  3804. \9\9end\
  3805. \9\9self.selection = nil\
  3806. \9\9self.changed = true\
  3807. \9elseif event.name == Event.MOUSEDRAG and self.focussed then\
  3808. \9\9if not self.selection then\
  3809. \9\9\9self.selection = self.cursor\
  3810. \9\9end\
  3811. \9\9event.handled = true\
  3812. \9\9self.cursor = math.max( 0, math.min( event.x - self.ox, #self.text ) )\
  3813. \9\9self.changed = true\
  3814. \9end\
  3815. \
  3816. end\
  3817. \
  3818. function UITextInput:onKeyboardEvent( event )\
  3819. \9if not event.handled and self.focussed and event.name == Event.KEYDOWN then\
  3820. \9\9if event:matchesHotkey \"shift-left\" then\
  3821. \9\9\9self.selection = self.selection or self.cursor\
  3822. \9\9\9self.cursor = self.cursor - 1\
  3823. \9\9\9self.changed = true\
  3824. \9\9\9event.handled = true\
  3825. \9\9elseif event:matchesHotkey \"shift-right\" then\
  3826. \9\9\9self.selection = self.selection or self.cursor\
  3827. \9\9\9self.cursor = self.cursor + 1\
  3828. \9\9\9self.changed = true\
  3829. \9\9\9event.handled = true\
  3830. \9\9elseif event:matchesHotkey \"ctrl-a\" then\
  3831. \9\9\9self.cursor = #self.text\
  3832. \9\9\9self.selection = 0\
  3833. \9\9\9self.changed = true\
  3834. \9\9\9event.handled = true\
  3835. \9\9elseif event:matchesHotkey \"ctrl-c\" then\
  3836. \9\9\9if self.selection then\
  3837. \9\9\9\9clipboard.put {\
  3838. \9\9\9\9\9{ \"plaintext\", self.text:sub( math.min( self.cursor, self.selection ) + 1, math.max( self.cursor, self.selection ) + 1 ) }\
  3839. \9\9\9\9}\
  3840. \9\9\9else\
  3841. \9\9\9\9clipboard.put {\
  3842. \9\9\9\9\9{ \"plaintext\", self.text }\
  3843. \9\9\9\9}\
  3844. \9\9\9end\
  3845. \9\9\9event.handled = true\
  3846. \9\9elseif event:matchesHotkey \"ctrl-x\" then\
  3847. \9\9\9if self.selection then\
  3848. \9\9\9\9clipboard.put {\
  3849. \9\9\9\9\9{ \"plaintext\", self.text:sub( math.min( self.cursor, self.selection ) + 1, math.max( self.cursor, self.selection ) + 1 ) }\
  3850. \9\9\9\9}\
  3851. \9\9\9\9self:write \"\"\
  3852. \9\9\9else\
  3853. \9\9\9\9clipboard.put {\
  3854. \9\9\9\9\9{ \"plaintext\", self.text }\
  3855. \9\9\9\9}\
  3856. \9\9\9\9self.text = \"\"\
  3857. \9\9\9end\
  3858. \9\9\9self.changed = true\
  3859. \9\9\9event.handled = true\
  3860. \9\9elseif event:matchesHotkey \"ctrl-b\" then\
  3861. \9\9\9local text = clipboard.get \"plaintext\"\
  3862. \9\9\9if text then\
  3863. \9\9\9\9self:write( text )\
  3864. \9\9\9\9self.changed = true\
  3865. \9\9\9end\
  3866. \9\9\9event.handled = true\
  3867. \9\9elseif event:matchesHotkey \"left\" then\
  3868. \9\9\9if self.selection then\
  3869. \9\9\9\9self.cursor = math.min( self.cursor, self.selection )\
  3870. \9\9\9\9self.selection = nil\
  3871. \9\9\9else\
  3872. \9\9\9\9self.cursor = self.cursor - 1\
  3873. \9\9\9end\
  3874. \9\9\9self.changed = true\
  3875. \9\9\9event.handled = true\
  3876. \9\9elseif event:matchesHotkey \"right\" then\
  3877. \9\9\9if self.selection then\
  3878. \9\9\9\9self.cursor = math.max( self.cursor, self.selection )\
  3879. \9\9\9\9self.selection = nil\
  3880. \9\9\9else\
  3881. \9\9\9\9self.cursor = self.cursor + 1\
  3882. \9\9\9end\
  3883. \9\9\9self.changed = true\
  3884. \9\9\9event.handled = true\
  3885. \9\9elseif event:matchesHotkey \"backspace\" then\
  3886. \9\9\9if self.selection then\
  3887. \9\9\9\9self:write \"\"\
  3888. \9\9\9\9self.changed = true\
  3889. \9\9\9elseif self.cursor > 0 then\
  3890. \9\9\9\9self.cursor = self.cursor - 1\
  3891. \9\9\9\9self.text = self.text:sub( 1, self.cursor ) .. self.text:sub( self.cursor + 2 )\
  3892. \9\9\9end\
  3893. \9\9\9event.handled = true\
  3894. \9\9elseif event:matchesHotkey \"shift-home\" then\
  3895. \9\9\9self.selection = self.cursor\
  3896. \9\9\9self.cursor = 0\
  3897. \9\9\9self.changed = true\
  3898. \9\9\9event.handled = true\
  3899. \9\9elseif event:matchesHotkey \"shift-end\" then\
  3900. \9\9\9self.selection = self.cursor\
  3901. \9\9\9self.cursor = #self.text\
  3902. \9\9\9self.changed = true\
  3903. \9\9\9event.handled = true\
  3904. \9\9elseif event:matchesHotkey \"home\" then\
  3905. \9\9\9self.cursor = 0\
  3906. \9\9\9self.selection = nil\
  3907. \9\9\9self.changed = true\
  3908. \9\9\9event.handled = true\
  3909. \9\9elseif event:matchesHotkey \"end\" then\
  3910. \9\9\9self.cursor = #self.text\
  3911. \9\9\9self.selection = nil\
  3912. \9\9\9self.changed = true\
  3913. \9\9\9event.handled = true\
  3914. \9\9elseif event:matchesHotkey \"delete\" then\
  3915. \9\9\9self.text = self.text:sub( 1, self.cursor) .. self.text:sub( self.cursor + 2 )\
  3916. \9\9\9event.handled = true\
  3917. \9\9elseif event:matchesHotkey \"enter\" then\
  3918. \9\9\9self.focussed = false\
  3919. \9\9\9self.changed = true\
  3920. \9\9\9if self.onEnter then\
  3921. \9\9\9\9self:onEnter()\
  3922. \9\9\9end\
  3923. \9\9\9event.handled = true\
  3924. \9\9elseif event:matchesHotkey \"tab\" then\
  3925. \9\9\9self.focussed = false\
  3926. \9\9\9self.changed = true\
  3927. \9\9\9if self.onTab then\
  3928. \9\9\9\9self:onTab()\
  3929. \9\9\9end\
  3930. \9\9\9event.handled = true\
  3931. \9\9end\
  3932. \9end\
  3933. end\
  3934. \
  3935. function UITextInput:onTextEvent( event )\
  3936. \9if not event.handled and self.focussed then\
  3937. \9\9self:write( event.text )\
  3938. \9\9self.changed = true\
  3939. \9\9event.handled = true\
  3940. \9end\
  3941. end\
  3942. \
  3943. function UITextInput:onDraw()\
  3944. \9self.canvas:clear( self.focussed and self.focussedColour or self.colour )\
  3945. \9if self.selection then\
  3946. \9\9local min, max = math.min( self.selection, self.cursor ), math.max( self.selection, self.cursor )\
  3947. \
  3948. \9\9self.canvas:drawHorizontalLine( self.ox + min, 0, math.abs( self.selection - self.cursor ) + 1, {\
  3949. \9\9\9colour = self.selectedColour;\
  3950. \9\9} )\
  3951. \9\9self.canvas:drawText( self.ox, 0, {\
  3952. \9\9\9text = ( self.mask and self.mask:rep( #self.text ) or self.text ):sub( 1, min );\
  3953. \9\9\9textColour = self.focussed and self.focussedTextColour or self.textColour\
  3954. \9\9} )\
  3955. \9\9self.canvas:drawText( self.ox + min, 0, {\
  3956. \9\9\9text = ( self.mask and self.mask:rep( #self.text ) or self.text ):sub( min + 1, max + 1 );\
  3957. \9\9\9textColour = self.selectedTextColour;\
  3958. \9\9} )\
  3959. \9\9self.canvas:drawText( self.ox + max + 1, 0, {\
  3960. \9\9\9text = ( self.mask and self.mask:rep( #self.text ) or self.text ):sub( max + 2 );\
  3961. \9\9\9textColour = self.focussed and self.focussedTextColour or self.textColour\
  3962. \9\9} )\
  3963. \9else\
  3964. \9\9self.canvas:drawText( self.ox, 0, {\
  3965. \9\9\9text = self.mask and self.mask:rep( #self.text ) or self.text;\
  3966. \9\9\9textColour = self.focussed and self.focussedTextColour or self.textColour\
  3967. \9\9} )\
  3968. \9end\
  3969. \9if self.focussed then\
  3970. \9\9self.canvas.cursor = {\
  3971. \9\9\9x = self.cursor + self.ox;\
  3972. \9\9\9y = 0;\
  3973. \9\9\9colour = self.focussedTextColour;\
  3974. \9\9}\
  3975. \9end\
  3976. end\
  3977. \
  3978. function UITextInput:setText( text )\
  3979. \9self.raw.text = tostring( text )\
  3980. \9if #text < self.cursor then\
  3981. \9\9self.cursor = #tostring( text )\
  3982. \9end\
  3983. \9self.changed = true\
  3984. end\
  3985. \
  3986. function UITextInput:setMask( mask )\
  3987. \9self.raw.mask = mask\
  3988. \9self.changed = true\
  3989. end\
  3990. \
  3991. function UITextInput:setFocussedColour( colour )\
  3992. \9self.raw.focussedColour = colour\
  3993. \9self.changed = true\
  3994. end\
  3995. \
  3996. function UITextInput:setFocussedTextColour( textColour )\
  3997. \9self.raw.focussedTextColour = textColour\
  3998. \9self.changed = true\
  3999. end\
  4000. \
  4001. function UITextInput:setColour( colour )\
  4002. \9self.raw.colour = colour\
  4003. \9self.changed = true\
  4004. end\
  4005. \
  4006. function UITextInput:setTextColour( textColour )\
  4007. \9self.raw.textColour = textColour\
  4008. \9self.changed = true\
  4009. end\
  4010. \
  4011. function UITextInput:setSelectedColour( colour )\
  4012. \9self.raw.selectedColour = colour\
  4013. \9self.changed = true\
  4014. end\
  4015. \
  4016. function UITextInput:setSelectedTextColour( colour )\
  4017. \9self.raw.selectedTextColour = colour\
  4018. \9self.changed = true\
  4019. end\
  4020. \
  4021. function UITextInput:setFocussed( focussed )\
  4022. \9self.raw.focussed = focussed\
  4023. \9self.changed = true\
  4024. end\
  4025. \
  4026. function UITextInput:setHeight() end";
  4027.     ["/lib/elements/UIToggle.lua"] = "\
  4028. require \"UIElement\"\
  4029. local shader = require \"graphics.shader\"\
  4030. local UIEventHelpers = require \"util.UIEventHelpers\"\
  4031. \
  4032. class \"UIToggle\" extends \"UIElement\" {\
  4033. \9colour = colours.grey;\
  4034. \9activeColour = colours.green;\
  4035. \9inactiveColour = colours.red;\
  4036. \9holding = false;\
  4037. \9toggled = false;\
  4038. }\
  4039. \
  4040. function UIToggle:init( x, y, w, h )\
  4041. \9self.super:init( x, y, w, h )\
  4042. end\
  4043. \
  4044. function UIToggle:onLabelPressed()\
  4045. \9self.toggled = not self.toggled\
  4046. \9if self.onToggle then\
  4047. \9\9self:onToggle()\
  4048. \9end\
  4049. end\
  4050. \
  4051. function UIToggle:onMouseEvent( event )\
  4052. \
  4053. \9local mode = UIEventHelpers.clicking.handleMouseEvent( self, event )\
  4054. \9if mode == \"down\" or mode == \"up\" then\
  4055. \9\9self.holding = mode == \"down\"\
  4056. \
  4057. \9elseif mode == \"click\" then\
  4058. \9\9self.holding = false\
  4059. \9\9self.toggled = not self.toggled\
  4060. \9\9if self.onToggle then\
  4061. \9\9\9self:onToggle()\
  4062. \9\9end\
  4063. \9end\
  4064. \
  4065. end\
  4066. \
  4067. function UIToggle:onDraw()\
  4068. \
  4069. \9self.canvas:clear( self.colour )\
  4070. \9local size = math.floor( self.width * 1 / 3 )\
  4071. \9local x = ( self.holding and math.floor( self.width / 2 - size / 2 ) ) or ( not self.toggled and 0 ) or self.width - size\
  4072. \9local colour = self.toggled and self.activeColour or self.inactiveColour\
  4073. \
  4074. \9self.canvas:drawRectangle( x, 0, size, self.height, {\
  4075. \9\9colour = colour;\
  4076. \9\9filled = true;\
  4077. \9} )\
  4078. \
  4079. end\
  4080. \
  4081. function UIToggle:setWidth( width )\
  4082. \9self.width = math.min( width, 4 )\
  4083. end\
  4084. \
  4085. function UIToggle:setHolding( state )\
  4086. \9self.raw.holding = state\
  4087. \9self.changed = true\
  4088. end\
  4089. \
  4090. function UIToggle:setColour( colour )\
  4091. \9self.raw.colour = colour\
  4092. \9self.changed = true\
  4093. end\
  4094. \
  4095. function UIToggle:setActiveColour( colour )\
  4096. \9self.raw.activeColour = colour\
  4097. \9self.changed = true\
  4098. end\
  4099. \
  4100. function UIToggle:setInactiveColour( colour )\
  4101. \9self.raw.inactiveColour = colour\
  4102. \9self.changed = true\
  4103. end\
  4104. \
  4105. function UIToggle:setToggled( bool )\
  4106. \9self.raw.toggled = bool\
  4107. \9self.changed = true\
  4108. end";
  4109.     ["/lib/elements/UIView.lua"] = "\
  4110. require \"UIElement\"\
  4111. \
  4112. class \"UIView\" extends \"UIElement\" {\
  4113. \9shortcuts = {};\
  4114. }\
  4115. \
  4116. function UIView:init( ... )\
  4117. \9self.shortcuts = {}\
  4118. \9return self.super:init( ... )\
  4119. end\
  4120. \
  4121. function UIView:createShortcut( identifier, shortcut, action )\
  4122. \9if self:shortcutExists( identifier ) then\
  4123. \9\9self:setShortcut( identifier, shortcut )\
  4124. \9\9self:setShortcutAction( identifier, action )\
  4125. \9else\
  4126. \9\9self.shortcuts[#self.shortcuts + 1] = { identifier, shortcut, action }\
  4127. \9end\
  4128. end\
  4129. \
  4130. function UIView:shortcutExists( identifier )\
  4131. \9for i = 1, #self.shortcuts do\
  4132. \9\9if self.shortcuts[i][1] == identifier then\
  4133. \9\9\9return true\
  4134. \9\9end\
  4135. \9end\
  4136. \9return false\
  4137. end\
  4138. \
  4139. function UIView:deleteShortcut( identifier )\
  4140. \9for i = 1, #self.shortcuts do\
  4141. \9\9if self.shortcuts[i][1] == identifier then\
  4142. \9\9\9table.remove( self.shortcuts, i )\
  4143. \9\9\9return true\
  4144. \9\9end\
  4145. \9end\
  4146. end\
  4147. \
  4148. function UIView:getShortcuts()\
  4149. \9local t = {}\
  4150. \9for i = 1, #self.shortcuts do\
  4151. \9\9t[i] = self.shortcuts[i][1]\
  4152. \9end\
  4153. \9return t\
  4154. end\
  4155. \
  4156. function UIView:setShortcut( identifier, shortut )\
  4157. \9for i = 1, #self.shortcuts do\
  4158. \9\9if self.shortcuts[i][1] == identifier then\
  4159. \9\9\9self.shortcuts[i][2] = shortcut\
  4160. \9\9\9return true\
  4161. \9\9end\
  4162. \9end\
  4163. \9return false\
  4164. end\
  4165. \
  4166. function UIView:getShortcut( identifier )\
  4167. \9for i = 1, #self.shortcuts do\
  4168. \9\9if self.shortcuts[i][1] == identifier then\
  4169. \9\9\9return self.shortcuts[i][2]\
  4170. \9\9end\
  4171. \9end\
  4172. end\
  4173. \
  4174. function UIView:setShortcutAction( identifier, action )\
  4175. \9for i = 1, #self.shortcuts do\
  4176. \9\9if self.shortcuts[i][1] == identifier then\
  4177. \9\9\9self.shortcuts[i][3] = action\
  4178. \9\9\9return true\
  4179. \9\9end\
  4180. \9end\
  4181. \9return false\
  4182. end\
  4183. \
  4184. function UIView:getShortcutAction( identifier )\
  4185. \9for i = 1, #self.shortcuts do\
  4186. \9\9if self.shortcuts[i][1] == identifier then\
  4187. \9\9\9return self.shortcuts[i][3]\
  4188. \9\9end\
  4189. \9end\
  4190. end\
  4191. \
  4192. function UIView:handle( event )\
  4193. \
  4194. \9if event.name == Event.KEYDOWN then\
  4195. \9\9local a, l = nil, -1\
  4196. \9\9for i = 1, #self.shortcuts do\
  4197. \9\9\9if #self.shortcuts[i][2] > l and event:matchesHotkey( self.shortcuts[i][2] ) then\
  4198. \9\9\9\9a, l = self.shortcuts[i][3], #self.shortcuts[i][2]\
  4199. \9\9\9end\
  4200. \9\9end\
  4201. \9\9if a then\
  4202. \9\9\9event.handled = true\
  4203. \9\9\9a()\
  4204. \9\9end\
  4205. \9end\
  4206. \
  4207. \9return UIElement.handle( self, event )\
  4208. \
  4209. end";
  4210.     ["/lib/elements/UIWindow.lua"] = "\
  4211. require \"UIElement\"\
  4212. require \"UIContainer\"\
  4213. require \"Event.MouseEvent\"\
  4214. \
  4215. local UIDrawingHelpers = require \"util.UIDrawingHelpers\"\
  4216. local UIEventHelpers = require \"util.UIEventHelpers\"\
  4217. \
  4218. class \"UIWindow\" extends \"UIElement\" {\
  4219. \9minWidth = 15;\
  4220. \9minHeight = 6;\
  4221. \9maxWidth = term.getSize();\
  4222. \9maxHeight = select( 2, term.getSize() );\
  4223. \
  4224. \9title = \"Window\";\
  4225. \9titleColour = colours.cyan;\
  4226. \9titleTextColour = colours.white;\
  4227. \
  4228. \9shadowColour = colours.grey;\
  4229. \
  4230. \9closeable = true;\
  4231. \9resizeable = true;\
  4232. \9moveable = true;\
  4233. \
  4234. \9content = nil;\
  4235. }\
  4236. \
  4237. UIWindow:mixin( UIEventHelpers.scrollbar.mixin )\
  4238. \
  4239. function UIWindow:init( x, y, w, h )\
  4240. \9self.super:init( x, y, w, h )\
  4241. \9self.content = self:addChild( UIContainer( 0, 1, w - 1, h - 2 ) )\
  4242. end\
  4243. \
  4244. function UIWindow:onMouseEvent( event )\
  4245. \9if event.handled then return end\
  4246. \
  4247. \9if event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, self.height ) then\
  4248. \
  4249. \9\9if event.y == 0 and event.x == self.width - 2 and self.closeable then\
  4250. \9\9\9if self.onClose then\
  4251. \9\9\9\9self:onClose()\
  4252. \9\9\9end\
  4253. \9\9\9self:remove()\
  4254. \9\9elseif event.x == self.width - 1 and event.y == self.height - 1 and self.resizeable then\
  4255. \9\9\9self.dragging = {\
  4256. \9\9\9\9button = event.button;\
  4257. \9\9\9\9mode = \"resize\";\
  4258. \9\9\9}\
  4259. \
  4260. \9\9elseif event.y == 0 and event.x < self.width - 1 and self.moveable then\
  4261. \9\9\9self.dragging = {\
  4262. \9\9\9\9x = event.x;\
  4263. \9\9\9\9y = event.y;\
  4264. \9\9\9\9button = event.button;\
  4265. \9\9\9\9mode = \"move\";\
  4266. \9\9\9}\
  4267. \
  4268. \9\9end\
  4269. \9\9event.handled = true\
  4270. \
  4271. \9elseif event.name == Event.MOUSEDRAG and self.dragging and self.dragging.mode == \"move\" then\
  4272. \9\9self.x = self.x + event.x - self.dragging.x\
  4273. \9\9self.y = self.y + event.y - self.dragging.y\
  4274. \9\9if self.onMove then\
  4275. \9\9\9self:onMove()\
  4276. \9\9end\
  4277. \
  4278. \9elseif event.name == Event.MOUSEDRAG and self.dragging and self.dragging.mode == \"resize\" then\
  4279. \9\9self.width = event.x + 1\
  4280. \9\9self.height = event.y + 1\
  4281. \9\9if self.onResize then\
  4282. \9\9\9self:onResize()\
  4283. \9\9end\
  4284. \
  4285. \9elseif event.name == Event.MOUSEUP and self.dragging then\
  4286. \9\9if event.button == self.dragging.button then\
  4287. \9\9\9event.handled = true\
  4288. \9\9\9self.dragging = nil\
  4289. \9\9end\
  4290. \
  4291. \9end\
  4292. end\
  4293. \
  4294. function UIWindow:onDraw()\
  4295. \
  4296. \9self.canvas:clear()\
  4297. \9self.canvas:drawVerticalLine( self.width - 1, 1, self.height - 1, {\
  4298. \9\9colour = self.shadowColour;\
  4299. \9} )\
  4300. \9self.canvas:drawHorizontalLine( 1, self.height - 1, self.width - 1, {\
  4301. \9\9colour = self.shadowColour;\
  4302. \9} )\
  4303. \9self.canvas:drawHorizontalLine( 0, 0, self.width - 1, {\
  4304. \9\9colour = self.titleColour\
  4305. \9} )\
  4306. \9self.canvas:drawText( 0, 0, {\
  4307. \9\9text = self.title;\
  4308. \9\9textColour = self.titleTextColour;\
  4309. \9} )\
  4310. \9if self.closeable then\
  4311. \9\9self.canvas:drawPoint( self.width - 2, 0, {\
  4312. \9\9\9character = \"x\";\
  4313. \9\9\9colour = colours.red;\
  4314. \9\9\9textColour = colours.white;\
  4315. \9\9} )\
  4316. \9end\
  4317. \
  4318. end\
  4319. \
  4320. function UIWindow:handle( event )\
  4321. \
  4322. \9if not event.handled and event:typeOf( MouseEvent ) and event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, self.height ) then\
  4323. \9\9local parent = self.parent\
  4324. \9\9if parent and parent.children[#parent.children] ~= self then\
  4325. \9\9\9parent:addChild( self )\
  4326. \9\9end\
  4327. \9end\
  4328. \9return UIElement.handle( self, event )\
  4329. \
  4330. end\
  4331. \
  4332. function UIWindow:setWidth( width )\
  4333. \9self.super:setWidth( math.max( math.min( width, self.maxWidth ), self.minWidth ) )\
  4334. \9self.content.width = self.width - 1\
  4335. end\
  4336. \
  4337. function UIWindow:setHeight( height )\
  4338. \9self.super:setHeight( math.max( math.min( height, self.maxHeight ), self.minHeight ) )\
  4339. \9self.content.height = self.height - 2\
  4340. end\
  4341. \
  4342. function UIWindow:setTitle( title )\
  4343. \9self.raw.title = tostring( title )\
  4344. \9self.changed = true\
  4345. end\
  4346. \
  4347. function UIWindow:setTitleColour( colour )\
  4348. \9self.raw.titleColour = colour\
  4349. \9self.changed = true\
  4350. end\
  4351. \
  4352. function UIWindow:setTitleTextColour( colour )\
  4353. \9self.raw.titleTextColour = colour\
  4354. \9self.changed = true\
  4355. end\
  4356. \
  4357. function UIWindow:setCloseable( closeable )\
  4358. \9self.raw.closeable = closeable\
  4359. \9self.changed = true\
  4360. end\
  4361. \
  4362. function UIWindow:setShadowColour( colour )\
  4363. \9self.raw.shadowColour = colour\
  4364. \9self.changed = true\
  4365. end\
  4366. \
  4367. function UIWindow:setMinWidth( width )\
  4368. \9if width > self.width then\
  4369. \9\9self.width = width\
  4370. \9end\
  4371. \9self.raw.minWidth = width\
  4372. end\
  4373. \
  4374. function UIWindow:setMaxWidth( width )\
  4375. \9if width < self.width then\
  4376. \9\9self.width = width\
  4377. \9end\
  4378. \9self.raw.maxWidth = width\
  4379. end\
  4380. \
  4381. function UIWindow:setMinHeight( height )\
  4382. \9if height > self.height then\
  4383. \9\9self.height = height\
  4384. \9end\
  4385. \9self.raw.minHeight = height\
  4386. end\
  4387. \
  4388. function UIWindow:setMaxHeight( height )\
  4389. \9if height < self.height then\
  4390. \9\9self.height = height\
  4391. \9end\
  4392. \9self.raw.maxHeight = height\
  4393. end";
  4394.     ["/lib/graphics/Canvas.lua"] = "\
  4395. local insert = table.insert\
  4396. local remove = table.remove\
  4397. local min, max = math.min, math.max\
  4398. local unpack = unpack\
  4399. \
  4400. class \"Canvas\" { useSetters = true,\
  4401. \9x = 0;\
  4402. \9y = 0;\
  4403. \9width = 0;\
  4404. \9height = 0;\
  4405. \
  4406. \9colour = 0;\
  4407. \
  4408. \9buffer = nil;\
  4409. \
  4410. \9cursor = nil;\
  4411. }\
  4412. \
  4413. function Canvas:init( x, y, width, height )\
  4414. \9self.raw.x = width and x or 0\
  4415. \9self.raw.y = height and y or 0\
  4416. \9self.raw.width = width or x or select( 1, term.getSize() )\
  4417. \9self.raw.height = height or y or select( 2, term.getSize() )\
  4418. \
  4419. \9local buffer = {}\
  4420. \9for i = 1, self.raw.width * self.raw.height do\
  4421. \9\9buffer[i] = { self.colour, 1, \" \" }\
  4422. \9end\
  4423. \9self.raw.buffer = buffer\
  4424. \
  4425. \9self.mt.__tostring = self.tostring\
  4426. end\
  4427. \
  4428. function Canvas:mapPixels( pixels )\
  4429. \9local buffer = self.buffer\
  4430. \9for i = 1, #pixels do\
  4431. \9\9buffer[pixels[i][1]] = pixels[i][2]\
  4432. \9end\
  4433. end\
  4434. \
  4435. function Canvas:mapColour( pixels, colour )\
  4436. \9local buffer = self.buffer\
  4437. \9local px = { colour or 0, 1, \" \" }\
  4438. \9for i = 1, #pixels do\
  4439. \9\9buffer[pixels[i]] = px\
  4440. \9end\
  4441. end\
  4442. \
  4443. function Canvas:mapStaticPixel( pixels, px )\
  4444. \9local buffer = self.buffer\
  4445. \9for i = 1, #pixels do\
  4446. \9\9buffer[pixels[i]] = px\
  4447. \9end\
  4448. end\
  4449. \
  4450. function Canvas:drawPixels( pixels )\
  4451. \9local buffer = self.buffer\
  4452. \9for i = 1, #pixels do\
  4453. \9\9local pos = pixels[i][1]\
  4454. \9\9if buffer[pos] then\
  4455. \9\9\9local bc, tc, char = unpack( pixels[i][2] )\
  4456. \9\9\9if bc == 0 then\
  4457. \9\9\9\9bc = buffer[pos][1]\
  4458. \9\9\9end\
  4459. \9\9\9if tc == 0 or char == \"\" then\
  4460. \9\9\9\9tc = buffer[pos][2]\
  4461. \9\9\9\9char = buffer[pos][3]\
  4462. \9\9\9end\
  4463. \9\9\9buffer[pos] = { bc, tc, char }\
  4464. \9\9end\
  4465. \9end\
  4466. end\
  4467. \
  4468. function Canvas:drawColour( pixels, colour )\
  4469. \9if colour == 0 then return end\
  4470. \9return self:mapColour( pixels, colour )\
  4471. end\
  4472. \
  4473. function Canvas:drawStaticPixel( pixels, px )\
  4474. \9local buffer = self.buffer\
  4475. \9local tbc, tchar = px.bc == 0, px.tc == 0 or px.char == \"\"\
  4476. \9if tbc or tchar and not ( tbc and tchar ) then\
  4477. \9\9local bc, tc, char = unpack( px )\
  4478. \9\9for i = 1, #pixels do\
  4479. \9\9\9local pos = pixels[i]\
  4480. \9\9\9buffer[pos] = { tbc and buffer[pos][1] or bc, tchar and buffer[pos][2] or tc, tchar and buffer[pos][3] or char }\
  4481. \9\9end\
  4482. \9elseif not tbc and not tchar then\
  4483. \9\9for i = 1, #pixels do\
  4484. \9\9\9buffer[pixels[i]] = px\
  4485. \9\9end\
  4486. \9end\
  4487. end\
  4488. \
  4489. function Canvas:clear( colour )\
  4490. \9local buffer = self.buffer\
  4491. \9local px = { colour or self.colour, 1, \" \" }\
  4492. \9for i = 1, self.width * self.height do\
  4493. \9\9buffer[i] = px\
  4494. \9end\
  4495. end\
  4496. \
  4497. function Canvas:clone()\
  4498. \9local new = self.class( self.width, self.height )\
  4499. \9local b1, b2 = new.buffer, self.buffer\
  4500. \9for i = 1, #b2 do\
  4501. \9\9b1[i] = b2[i]\
  4502. \9end\
  4503. \9return new\
  4504. end\
  4505. \
  4506. function Canvas:cloneAs( class )\
  4507. \9local new = class( self.width, self.height )\
  4508. \9local b1, b2 = new.buffer, self.buffer\
  4509. \9for i = 1, #b2 do\
  4510. \9\9b1[i] = b2[i]\
  4511. \9end\
  4512. \9return new\
  4513. end\
  4514. \
  4515. function Canvas:drawTo( canvas, x, y )\
  4516. \9x = ( x or 0 ) + self.x\
  4517. \9y = ( y or 0 ) + self.y\
  4518. \9local pixels = {}\
  4519. \9local buffer = self.buffer\
  4520. \9local width = self.width\
  4521. \9local swidth = canvas.width\
  4522. \
  4523. \9local i = 1\
  4524. \9local spos = 1\
  4525. \
  4526. \9for py = 0, self.height - 1 do\
  4527. \9\9local pos = ( py + y ) * swidth + x + 1\
  4528. \9\9for px = 1, width do\
  4529. \9\9\9if px + x > 0 and px + x <= swidth then\
  4530. \9\9\9\9pixels[i] = { pos, buffer[spos] }\
  4531. \9\9\9\9i = i + 1\
  4532. \9\9\9end\
  4533. \9\9\9pos = pos + 1\
  4534. \9\9\9spos = spos + 1\
  4535. \9\9end\
  4536. \9end\
  4537. \
  4538. \9canvas:drawPixels( pixels )\
  4539. end\
  4540. \
  4541. function Canvas:setWidth( width )\
  4542. \9local height, buffer, raw = self.height, self.buffer, self.raw\
  4543. \9local px = { self.colour, 1, \" \" }\
  4544. \
  4545. \9while raw.width < width do\
  4546. \9\9for i = 1, height do\
  4547. \9\9\9insert( buffer, ( raw.width + 1 ) * i, px )\
  4548. \9\9end\
  4549. \9\9raw.width = raw.width + 1\
  4550. \9end\
  4551. \
  4552. \9while raw.width > width do\
  4553. \9\9for i = height, 1, -1 do\
  4554. \9\9\9remove( buffer, raw.width * i )\
  4555. \9\9end\
  4556. \9\9raw.width = raw.width - 1\
  4557. \9end\
  4558. end\
  4559. \
  4560. function Canvas:setHeight( height )\
  4561. \9local width, buffer, raw = self.width, self.buffer, self.raw\
  4562. \9local px = { self.colour, 1, \" \" }\
  4563. \9\
  4564. \9while raw.height < height do\
  4565. \9\9for i = 1, width do\
  4566. \9\9\9buffer[#buffer + 1] = px\
  4567. \9\9end\
  4568. \9\9raw.height = raw.height + 1\
  4569. \9end\
  4570. \
  4571. \9while raw.height > height do\
  4572. \9\9for i = 1, width do\
  4573. \9\9\9buffer[#buffer] = nil\
  4574. \9\9end\
  4575. \9\9raw.height = raw.height - 1\
  4576. \9end\
  4577. end\
  4578. \
  4579. function Canvas:tostring()\
  4580. \9return \"[Instance] \" .. self.class.name .. \" (\" .. self.x .. \",\" .. self.y .. \" \" .. self.width .. \"x\" .. self.height .. \")\"\
  4581. end";
  4582.     ["/lib/graphics/DrawingCanvas.lua"] = "\
  4583. --[[\
  4584. \
  4585. function DrawingCanvas:drawLine( x1, y1, x2, y2, options )\
  4586. \9local dx, dy = x2 - x1, y2 - y1\
  4587. \9if dx == 0 then\
  4588. \9\9return self:drawVerticalLine( x1, min( y1, y2 ), abs( y2 - y1 ) + 1, options )\
  4589. \9elseif dy == 0 then\
  4590. \9\9return self:drawHorizontalLine( min( x1, x2 ), y1, abs( x2 - x1 ) + 1, options )\
  4591. \9end\
  4592. \
  4593. \9local m = dy / dx\
  4594. \9local c = y1 - m * x1 + .5\
  4595. \9local step = min( 1, 1 / abs( m ) )\
  4596. \
  4597. \9local drawPoint = self.drawPoint\
  4598. \
  4599. \9drawPoint( self, x1, y1, options )\
  4600. \9drawPoint( self, x2, y2, options )\
  4601. \
  4602. \9for x = min( x1, x2 ), max( x1, x2 ), step do\
  4603. \9\9drawPoint( self, floor( x + .5 ), floor( m * x + c ), options )\
  4604. \9end\
  4605. end\
  4606. \
  4607. function DrawingCanvas:drawWeightlessLine( x1, y1, x2, y2, options )\
  4608. \9local width = self.width\
  4609. \9local px = { options.colour or 1, options.textColour or 1, options.text or \" \" }\
  4610. \9local pixels = { { floor( y1 + .5 ) * width + x1 + 1, px }, { floor( y2 + .5 ) * width + x2 + 1, px } }\
  4611. \9local dx, dy = x2 - x1, y2 - y1\
  4612. \9if abs( dx ) < .0001 then\
  4613. \9\9local pos = floor( ( min( y1, y2 ) ) * width + x1 + 1.5 )\
  4614. \9\9for i = 0, abs( y2 - y1 ) do\
  4615. \9\9\9pixels[#pixels + 1] = { pos, px }\
  4616. \9\9\9pos = pos + width\
  4617. \9\9end\
  4618. \9elseif abs( dy ) < .0001 then\
  4619. \9\9local pos = floor( y1 + .5 ) * width + floor( min( x1, x2 ) + 1.5 )\
  4620. \9\9for i = 0, abs( x2 - x1 ) do\
  4621. \9\9\9pixels[#pixels + 1] = { pos, px }\
  4622. \9\9\9pos = pos + 1\
  4623. \9\9end\
  4624. \9else\
  4625. \9\9local m = dy / dx\
  4626. \9\9local c = y1 - m * x1 + .5\
  4627. \9\9local step = min( 1, 1 / abs( m ) )\
  4628. \
  4629. \9\9for x = min( x1, x2 ), max( x1, x2 ), step do\
  4630. \9\9\9pixels[#pixels + 1] = { floor( m * x + c ) * width + floor( x + 1.5 ), px }\
  4631. \9\9end\
  4632. \9end\
  4633. \
  4634. \9self:mapPixels( pixels )\
  4635. end\
  4636. \
  4637. function DrawingCanvas:drawTriangle( x1, y1, x2, y2, x3, y3, options )\
  4638. \9if options.filled then\
  4639. \9\9self:drawWeightlessLine( x1, y1, x2, y2, options )\
  4640. \9\9self:drawWeightlessLine( x1, y1, x3, y3, options )\
  4641. \9\9self:drawWeightlessLine( x2, y2, x3, y3, options )\
  4642. \
  4643. \9\9local dx1, dy1 = x2 - x1, y2 - y1\
  4644. \9\9local m1 = dy1 / dx1\
  4645. \9\9local c1 = y1 - m1 * x1\
  4646. \9\9local dx2, dy2 = x3 - x1, y3 - y1\
  4647. \9\9local m2 = dy2 / dx2\
  4648. \9\9local c2 = y3 - m2 * x3\
  4649. \9\9local dx3, dy3 = x3 - x2, y3 - y2\
  4650. \9\9local m3 = dy3 / dx3\
  4651. \9\9local c3 = y2 - m3 * x2\
  4652. \
  4653. \9\9local inf = 1/0\
  4654. \9\9local nan = inf / inf\
  4655. \
  4656. \9\9local pixels = {}\
  4657. \9\9local px = { options.colour or 1, options.textColour or 1, options.text or \" \" }\
  4658. \9\9local width = self.width\
  4659. \
  4660. \9\9for y = floor( min( y1, y2, y3 ) ), ceil( max( y1, y2, y3 ) ) do\
  4661. \9\9\9local yo = ( y ) * width\
  4662. \9\9\9local a = ( y - c1 ) / m1\
  4663. \9\9\9if x1 == x2 then\
  4664. \9\9\9\9a = y >= min( y1, y2 ) and y <= max( y1, y2 ) and x1\
  4665. \9\9\9elseif a < min( x1, x2 ) or a > max( x1, x2 ) then\
  4666. \9\9\9\9a = nil\
  4667. \9\9\9end\
  4668. \9\9\9local b = ( y - c2 ) / m2\
  4669. \9\9\9if x1 == x3 then\
  4670. \9\9\9\9b = y >= min( y1, y3 ) and y <= max( y1, y3 ) and x1\
  4671. \9\9\9elseif b < min( x1, x3 ) or b > max( x1, x3 ) then\
  4672. \9\9\9\9b = nil\
  4673. \9\9\9end\
  4674. \9\9\9local c = ( y - c3 ) / m3\
  4675. \9\9\9if x2 == x3 then\
  4676. \9\9\9\9c = y >= min( y2, y3 ) and y <= max( y2, y3 ) and x2\
  4677. \9\9\9elseif c < min( x2, x3 ) or c > max( x2, x3 ) then\
  4678. \9\9\9\9c = nil\
  4679. \9\9\9end\
  4680. \9\9\9local v1, v2 = a or b or c, c or b or a\
  4681. \9\9\9if v1 and v2 and abs( v1 - v2 ) < .00001 then v2 = b or a or c end\
  4682. \9\9\9if v1 then\
  4683. \9\9\9\9for i = floor( min( v1, v2 ) + .5 ), ceil( max( v1, v2 ) - .5 ) do\
  4684. \9\9\9\9\9pixels[#pixels + 1] = { yo + i, px }\
  4685. \9\9\9\9end\
  4686. \9\9\9end\
  4687. \9\9end\
  4688. \
  4689. \9\9self:mapPixels( pixels )\
  4690. \9else\
  4691. \9\9if not options.weight or options.weight == 1 then\
  4692. \9\9\9self:drawWeightlessLine( x1, y1, x2, y2, options )\
  4693. \9\9\9self:drawWeightlessLine( x1, y1, x3, y3, options )\
  4694. \9\9\9self:drawWeightlessLine( x2, y2, x3, y3, options )\
  4695. \9\9else\
  4696. \9\9\9self:drawLine( x1, y1, x2, y2, options )\
  4697. \9\9\9self:drawLine( x1, y1, x3, y3, options )\
  4698. \9\9\9self:drawLine( x2, y2, x3, y3, options )\
  4699. \9\9end\
  4700. \9end\
  4701. end\
  4702. \
  4703. function DrawingCanvas:drawPolygon( x, y, options )\
  4704. \9if options.filled then\
  4705. \9\9return error \"not yet supported\"\
  4706. \9else\
  4707. \9\9local points = options.points\
  4708. \9\9x = ( x or 1 ) - 1\
  4709. \9\9y = ( y or 1 ) - 1\
  4710. \9\9if #points % 2 == 1 then\
  4711. \9\9\9points[#points] = nil\
  4712. \9\9end\
  4713. \9\9for i = 1, #points / 2 do\
  4714. \9\9\9local p1x, p1y = points[i * 2 - 1], points[i * 2]\
  4715. \9\9\9local p2x, p2y = points[i * 2 + 1] or points[1], points[i * 2 + 2] or points[2]\
  4716. \9\9\9self:drawLine( p1x + x, p1y + y, p2x + x, p2y + y, options )\
  4717. \9\9end\
  4718. \9end\
  4719. end\
  4720. \
  4721. ]]\
  4722. \
  4723. require \"graphics.Canvas\"\
  4724. \
  4725. local max, min, floor, abs = math.max, math.min, math.floor, math.abs\
  4726. \
  4727. local function getpixel( options )\
  4728. \9return { options.colour or 0, options.textColour or 0, options.character or \"\" }\
  4729. end\
  4730. local function isColourOnly( options )\
  4731. \9return options.textColour == nil and options.character == nil\
  4732. end\
  4733. \
  4734. local function pointInPolygon( polyCorners, polyX, polyY, x, y, d )\
  4735. \9local oddNodes = false\
  4736. \9local j = polyCorners\
  4737. \
  4738. \9for i = 1, polyCorners do\
  4739. \9\9local polyYi = polyY[i]\
  4740. \9\9if ( ( polyYi < y and polyY[j] >= y ) or ( polyY[j] < y and polyYi >= y ) ) then\
  4741. \9\9\9if polyX[i] + d[i] * ( y - polyYi ) < x then\
  4742. \9\9\9\9oddNodes = not oddNodes\
  4743. \9\9\9end\
  4744. \9\9end\
  4745. \9\9j = i\
  4746. \9end\
  4747. \
  4748. \9return oddNodes\
  4749. end\
  4750. \
  4751. local function linewrap( str, w )\
  4752. \9if w <= 0 then\
  4753. \9\9return \"\", #str > 1 and str:sub( 2 )\
  4754. \9end\
  4755. \9for i = 1, w + 1 do\
  4756. \9\9if str:sub( i, i ) == \"\\n\" then\
  4757. \9\9\9return str:sub( 1, i - 1 ), str:sub( i + 1 )\
  4758. \9\9end\
  4759. \9end\
  4760. \9if #str <= w then\
  4761. \9\9return str\
  4762. \9end\
  4763. \9for i = w + 1, 1, -1 do\
  4764. \9\9if str:sub( i, i ):find \"%s\" then\
  4765. \9\9\9return str:sub( 1, i - 1 ) .. str:match( \"%s+\", i ), str:match( \"%S.+$\", i + 1 )\
  4766. \9\9end\
  4767. \9end\
  4768. \9return str:sub( 1, w )\
  4769. end\
  4770. \
  4771. local function wordwrap( str, w, h )\
  4772. \9local lines, line = {}\
  4773. \9while str do\
  4774. \9\9line, str = linewrap( str, w )\
  4775. \9\9lines[#lines + 1] = line\
  4776. \9end\
  4777. \9while h and #lines > math.max( h, 1 ) do\
  4778. \9\9lines[#lines] = nil\
  4779. \9end\
  4780. \9return lines\
  4781. end\
  4782. \
  4783. local function drawPixels( self, pixels, options )\
  4784. \9if isColourOnly( options ) then\
  4785. \9\9self:mapColour( pixels, options.colour or 1 )\
  4786. \9else\
  4787. \9\9self:mapStaticPixel( pixels, getpixel( options ) )\
  4788. \9end\
  4789. end\
  4790. \
  4791. local function drawRectOutline( self, x, y, width, height, options )\
  4792. \9local pixels = {}\
  4793. \9local swidth, sheight = self.width, self.height\
  4794. \9local weight = options.weight or 1\
  4795. \9local i = 1\
  4796. \
  4797. \9-- horizontal lines\
  4798. \9for n = 0, weight - 1 do\
  4799. \9\9local pos1 = ( y + n ) * swidth + x + 1\
  4800. \9\9local pos2 = ( y + height - n - 1 ) * swidth + x + 1\
  4801. \9\9for m = 1, width do\
  4802. \9\9\9if x + m <= swidth and x + m > 0 then\
  4803. \9\9\9\9pixels[i] = pos1\
  4804. \9\9\9\9pixels[i + 1] = pos2\
  4805. \9\9\9\9i = i + 2\
  4806. \9\9\9end\
  4807. \9\9\9pos1 = pos1 + 1\
  4808. \9\9\9pos2 = pos2 + 1\
  4809. \9\9end\
  4810. \9end\
  4811. \9-- vertical lines\
  4812. \9for n = weight, height - weight - 1 do\
  4813. \9\9local pos = ( y + n ) * swidth + x\
  4814. \9\9for m = 0, weight - 1 do\
  4815. \9\9\9if x + m < swidth and x + m > 0 then\
  4816. \9\9\9\9pixels[i] = pos + m + 1\
  4817. \9\9\9\9i = i + 1\
  4818. \9\9\9end\
  4819. \9\9\9if x + width - m < swidth and x + width - m > 0 then\
  4820. \9\9\9\9pixels[i] = pos + width - m\
  4821. \9\9\9\9i = i + 1\
  4822. \9\9\9end\
  4823. \9\9end\
  4824. \9end\
  4825. \
  4826. \9drawPixels( self, pixels, options )\
  4827. end\
  4828. local function drawRectFilled( self, x, y, width, height, options )\
  4829. \9local pixels = {}\
  4830. \9local swidth, sheight = self.width, self.height\
  4831. \9local i = 1\
  4832. \
  4833. \9if x < 0 then\
  4834. \9\9width = width + x\
  4835. \9\9x = 0\
  4836. \9end\
  4837. \9if x + width >= swidth then\
  4838. \9\9width = swidth - x\
  4839. \9end\
  4840. \
  4841. \9for _ = 1, height do\
  4842. \9\9local pos = y * swidth + x + 1\
  4843. \9\9y = y + 1\
  4844. \9\9for _ = 1, width do\
  4845. \9\9\9pixels[i] = pos\
  4846. \9\9\9i = i + 1\
  4847. \9\9\9pos = pos + 1\
  4848. \9\9end\
  4849. \9end\
  4850. \9drawPixels( self, pixels, options )\
  4851. end\
  4852. \
  4853. local function drawCircOutline( self, x, y, radius, options )\
  4854. \9local pixels = {}\
  4855. \9local swidth = self.width\
  4856. \9local weight = options.weight or 1\
  4857. \9local i = 1\
  4858. \
  4859. \9local r2 = radius * radius\
  4860. \
  4861. \9for py = 0, radius do\
  4862. \9\9local ix = floor( ( r2 - py ^ 2 ) ^ .5 * self.correction + .5 )\
  4863. \9\9local pos11 = ( y - py ) * swidth + x - ix + 1\
  4864. \9\9local pos21 = ( y + py ) * swidth + x - ix + 1\
  4865. \9\9local pos12 = ( y - py ) * swidth + x + ix + 1\
  4866. \9\9local pos22 = ( y + py ) * swidth + x + ix + 1\
  4867. \9\9for _ = 0, weight do\
  4868. \9\9\9pixels[i] = pos11\
  4869. \9\9\9pixels[i + 1] = pos21\
  4870. \9\9\9pixels[i + 2] = pos12\
  4871. \9\9\9pixels[i + 3] = pos22\
  4872. \9\9\9pos11 = pos11 + 1\
  4873. \9\9\9pos21 = pos21 + 1\
  4874. \9\9\9pos12 = pos12 - 1\
  4875. \9\9\9pos22 = pos22 - 1\
  4876. \9\9\9i = i + 4\
  4877. \9\9end\
  4878. \9end\
  4879. \
  4880. \9drawPixels( self, pixels, options )\
  4881. end\
  4882. local function drawCircFilled( self, x, y, radius, options )\
  4883. \9local pixels = {}\
  4884. \9local swidth = self.width\
  4885. \9local i = 1\
  4886. \
  4887. \9local r2 = radius * radius\
  4888. \
  4889. \9for py = 1, radius do\
  4890. \9\9local ix = floor( ( r2 - py ^ 2 ) ^ .5 * self.correction + .5 )\
  4891. \9\9local pos1 = ( y - py ) * swidth + max( 0, x - ix ) + 1\
  4892. \9\9local pos2 = ( y + py ) * swidth + max( 0, x - ix ) + 1\
  4893. \9\9for _ = max( 0, x - ix ), min( swidth - 1, x + ix ) do\
  4894. \9\9\9pixels[i] = pos1\
  4895. \9\9\9pixels[i + 1] = pos2\
  4896. \9\9\9pos1 = pos1 + 1\
  4897. \9\9\9pos2 = pos2 + 1\
  4898. \9\9\9i = i + 2\
  4899. \9\9end\
  4900. \9end\
  4901. \
  4902. \9local rc = floor( radius * self.correction + .5 )\
  4903. \9local pos = y * swidth + max( 0, x - rc ) + 1\
  4904. \9for _ = max( 0, x - rc ), min( swidth - 1, x + rc ) do\
  4905. \9\9pixels[i] = pos\
  4906. \9\9pos = pos + 1\
  4907. \9\9i = i + 1\
  4908. \9end\
  4909. \
  4910. \9drawPixels( self, pixels, options )\
  4911. end\
  4912. \
  4913. class \"DrawingCanvas\" extends \"Canvas\" {\
  4914. \9correction = cclite and 1.5 or 1.67;\
  4915. }\
  4916. \
  4917. function DrawingCanvas:drawPoint( x, y, options )\
  4918. \
  4919. \9local weight = options.weight or 1\
  4920. \9if weight == 1 then\
  4921. \9\9self.buffer[y * self.width + x + 1] = getpixel( options )\
  4922. \9else\
  4923. \9\9self:drawCircle( x, y, weight - 1, {\
  4924. \9\9\9colour = colour;\
  4925. \9\9\9textColour = textColour;\
  4926. \9\9\9character = character;\
  4927. \9\9\9filled = true;\
  4928. \9\9} )\
  4929. \9end\
  4930. \
  4931. end\
  4932. \
  4933. function DrawingCanvas:drawRectangle( x, y, width, height, options )\
  4934. \
  4935. \9if options.filled or options.outline == false then\
  4936. \9\9drawRectFilled( self, x, y, width, height, options )\
  4937. \9else\
  4938. \9\9drawRectOutline( self, x, y, width, height, options )\
  4939. \9end\
  4940. \
  4941. end\
  4942. \
  4943. function DrawingCanvas:drawCircle( x, y, radius, options )\
  4944. \
  4945. \9if options.filled or options.outline == false then\
  4946. \9\9drawCircFilled( self, x, y, radius, options )\
  4947. \9else\
  4948. \9\9drawCircOutline( self, x, y, radius, options )\
  4949. \9end\
  4950. \
  4951. end\
  4952. \
  4953. function DrawingCanvas:drawLine( x1, y1, x2, y2, options )\
  4954. \
  4955. \9local dx, dy = x2 - x1, y2 - y1\
  4956. \9if abs( dx ) < .0001 then\
  4957. \9\9return self:drawVerticalLine( x1, min( y1, y2 ), abs( y2 - y1 ) + 1, options )\
  4958. \9elseif abs( dy ) < .0001 then\
  4959. \9\9return self:drawHorizontalLine( min( x1, x2 ), y1, abs( x2 - x1 ) + 1, options )\
  4960. \9end\
  4961. \
  4962. \9local m = dy / dx\
  4963. \9local c = y1 - m * x1 + .5\
  4964. \9local step = min( 1, 1 / abs( m ) )\
  4965. \
  4966. \9-- draw 2 circles at ends\
  4967. \9-- semicircles, actually\
  4968. \
  4969. \9for x = min( x1, x2 ), max( x1, x2 ), step do\
  4970. \9\9local y = floor( m * x + c )\
  4971. \9\9\
  4972. \9\9-- draw line between x-r and x+r at this y\
  4973. \9end\
  4974. \
  4975. end\
  4976. \
  4977. function DrawingCanvas:drawHorizontalLine( x, y, width, options )\
  4978. \
  4979. \9local weight = options.weight or 1\
  4980. \9local swidth = self.width\
  4981. \9local pixels = {}\
  4982. \9local radius = math.floor( weight / 2 - .5 )\
  4983. \9local r2 = radius * radius\
  4984. \9local i = 1\
  4985. \
  4986. \9for r = 0, radius do\
  4987. \9\9local ix = radius - floor( ( r2 - r ^ 2 ) ^ .5 * self.correction + .5 )\
  4988. \9\9local pos1 = ( y + r ) * swidth + x + ix + 1\
  4989. \9\9local pos2 = ( y - r ) * swidth + x + ix + 1\
  4990. \9\9for _ = x + ix, x + width - ix - 1 do\
  4991. \9\9\9pixels[i] = pos1\
  4992. \9\9\9pixels[i + 1] = pos2\
  4993. \9\9\9pos1 = pos1 + 1\
  4994. \9\9\9pos2 = pos2 + 1\
  4995. \9\9\9i = i + 2\
  4996. \9\9end\
  4997. \9end\
  4998. \
  4999. \9drawPixels( self, pixels, options )\
  5000. \
  5001. end\
  5002. \
  5003. function DrawingCanvas:drawVerticalLine( x, y, size, options )\
  5004. \
  5005. \9local weight = options.weight or 1\
  5006. \9local swidth = self.width\
  5007. \9local pixels = {}\
  5008. \9local radius = math.floor( weight / 2 - .5 )\
  5009. \9local r2 = radius * radius\
  5010. \9local i = 1\
  5011. \
  5012. \9for _y = 0, size - 1 do\
  5013. \9\9if _y < radius then\
  5014. \
  5015. \9\9elseif _y >= size - radius then\
  5016. \
  5017. \9\9else\
  5018. \9\9\9local pos = ( y + _y ) * swidth + x - radius + 1\
  5019. \9\9\9for _ = 1, weight do\
  5020. \9\9\9\9pixels[i] = pos\
  5021. \9\9\9\9pos = pos + 1\
  5022. \9\9\9\9i = i + 1\
  5023. \9\9\9end\
  5024. \9\9end\
  5025. \9end\
  5026. \
  5027. \9drawPixels( self, pixels, options )\
  5028. \
  5029. end\
  5030. \
  5031. function DrawingCanvas:drawTriangle()\
  5032. \
  5033. end\
  5034. \
  5035. function DrawingCanvas:drawTopLeftTriangle()\
  5036. \
  5037. end\
  5038. \
  5039. function DrawingCanvas:drawTopRightTriangle()\
  5040. \
  5041. end\
  5042. \
  5043. function DrawingCanvas:drawBottomLeftTriangle()\
  5044. \
  5045. end\
  5046. \
  5047. function DrawingCanvas:drawBottomRightTriangle()\
  5048. \
  5049. end\
  5050. \
  5051. function DrawingCanvas:drawText( x, y, options )\
  5052. \
  5053. \9local bc = options.colour or 0\
  5054. \9local tc = options.textColour or 0\
  5055. \9local pixels = {}\
  5056. \9local width = self.width\
  5057. \9local pos = y * width + x\
  5058. \
  5059. \9for i = math.max( 1, 1 - x ), #options.text do\
  5060. \9\9if x + i > width then\
  5061. \9\9\9break\
  5062. \9\9end\
  5063. \9\9pixels[#pixels + 1] = { pos + i, { bc, tc, options.text:sub( i, i ) } }\
  5064. \9end\
  5065. \
  5066. \9self:drawPixels( pixels )\
  5067. end\
  5068. \
  5069. function DrawingCanvas:drawWrappedText( x, y, width, height, options )\
  5070. \
  5071. \9local bc = options.colour or 0\
  5072. \9local tc = options.textColour or 0\
  5073. \9local pixels = {}\
  5074. \9local alignment = options.alignment\
  5075. \9local verticalAlignment = options.verticalAlignment\
  5076. \9local lines = wordwrap( options.text, width, height )\
  5077. \9local cwidth = self.width\
  5078. \9local pos = ( ( verticalAlignment == \"centre\" and math.floor( height / 2 - #lines / 2 + .5 ) or ( verticalAlignment == \"bottom\" and height - #lines ) or 0 ) + y ) * cwidth + x\
  5079. \9local i = 1\
  5080. \
  5081. \9for line = 1, #lines do\
  5082. \9\9local offset = ( alignment == \"centre\" and math.floor( width / 2 - #lines[line] / 2 + .5 ) ) or ( alignment == \"right\" and width - #lines[line] ) or 0\
  5083. \9\9for c = 1, #lines[line] do\
  5084. \9\9\9pixels[i] = { pos + c + offset, { bc, tc, lines[line]:sub( c, c ) } }\
  5085. \9\9\9i = i + 1\
  5086. \9\9end\
  5087. \9\9pos = pos + cwidth\
  5088. \9end\
  5089. \
  5090. \9self:drawPixels( pixels )\
  5091. \
  5092. end\
  5093. \
  5094. function DrawingCanvas:drawPreformattedText( x, y, width, height, options )\
  5095. \
  5096. \9local pixels = {}\
  5097. \9local verticalAlignment = options.verticalAlignment\
  5098. \9local selectedBC = options.selectedColour or colours.blue\
  5099. \9local selectedTC = options.selectedTextColour or 1\
  5100. \9local lines = options.text\
  5101. \9local cwidth = self.width\
  5102. \9local pos = ( ( ( verticalAlignment == \"centre\" and math.floor( height / 2 - #lines / 2 + .5 ) ) or ( verticalAlignment == \"bottom\" and height - #lines ) or 0 ) + y ) * cwidth + x\
  5103. \9local i = 1\
  5104. \
  5105. \9for l = 1, #lines do\
  5106. \9\9local line = lines[l]\
  5107. \9\9local offset = ( line.alignment == \"centre\" and math.floor( width / 2 - #line / 2 + .5 ) ) or ( line.alignment == \"right\" and width - #line ) or 0\
  5108. \9\9for c = 1, #line do\
  5109. \9\9\9if c + x + offset >= 0 and c + x + offset <= cwidth then\
  5110. \9\9\9\9if line[c][3] ~= \"\\n\" then\
  5111. \9\9\9\9\9if line[c].selected then\
  5112. \9\9\9\9\9\9pixels[i] = { pos + c + offset, { selectedBC, selectedTC, line[c][3] } }\
  5113. \9\9\9\9\9else\
  5114. \9\9\9\9\9\9pixels[i] = { pos + c + offset, line[c] }\
  5115. \9\9\9\9\9end\
  5116. \9\9\9\9\9i = i + 1\
  5117. \9\9\9\9end\
  5118. \9\9\9end\
  5119. \9\9end\
  5120. \9\9pos = pos + cwidth\
  5121. \9end\
  5122. \
  5123. \9self:drawPixels( pixels )\
  5124. \
  5125. end";
  5126.     ["/lib/graphics/Image.lua"] = "\
  5127. require \"graphics.Canvas\"\
  5128. \
  5129. local col = {}\
  5130. for i = 0, 15 do\
  5131. \9col[(\"%x\"):format( i )] = 2 ^ i\
  5132. \9col[2 ^ i] = (\"%x\"):format( i )\
  5133. end\
  5134. \
  5135. class \"Image\" extends \"Canvas\" {\
  5136. \9filepath = \"\";\
  5137. }\
  5138. \
  5139. function Image:init( filepath )\
  5140. \9self.super:init( 0, 0 )\
  5141. \9if not fs.exists( filepath ) or fs.isDir( filepath ) then\
  5142. \9\9error( \"path is not a file\", 3 )\
  5143. \9end\
  5144. \9self.filepath = filepath\
  5145. \9if filepath:find \"%.nfp$\" then\
  5146. \9\9self:loadNFP()\
  5147. \9else\
  5148. \9\9self:load()\
  5149. \9end\
  5150. end\
  5151. \
  5152. function Image:loadNFP()\
  5153. \9local lines = {}\
  5154. \9local h = fs.open( self.filepath, \"r\" )\
  5155. \9local content = h.readAll()\
  5156. \9h.close()\
  5157. \9for line in content:gmatch \"[^\\n]+\" do\
  5158. \9\9lines[#lines + 1] = line\
  5159. \9end\
  5160. \
  5161. \9local width, height = #( lines[1] or \"\" ), #lines\
  5162. \9self.width = width\
  5163. \9self.height = height\
  5164. \
  5165. \9local pixels = {}\
  5166. \9local i = 1\
  5167. \9for y = 1, #lines do\
  5168. \9\9local pos = ( y - 1 ) * width\
  5169. \9\9for x = 1, #lines[y] do\
  5170. \9\9\9pixels[i] = { pos + x, { col[lines[y]:sub( x, x )] or 0, 1, \" \" } }\
  5171. \9\9\9i = i + 1\
  5172. \9\9end\
  5173. \9end\
  5174. \9self:mapPixels( pixels )\
  5175. end\
  5176. \
  5177. function Image:load()\
  5178. \
  5179. end\
  5180. \
  5181. function Image:saveNFP()\
  5182. \
  5183. end\
  5184. \
  5185. function Image:save()\
  5186. \
  5187. end";
  5188.     ["/lib/graphics/ScreenCanvas.lua"] = "\
  5189. require \"graphics.Canvas\"\
  5190. \
  5191. local col_lookup = {}\
  5192. for i = 0, 15 do\
  5193. \9col_lookup[2 ^ i] = (\"%x\"):format( i )\
  5194. end\
  5195. local concat = table.concat\
  5196. local insert = table.insert\
  5197. local remove = table.remove\
  5198. local min, max = math.min, math.max\
  5199. local unpack = unpack\
  5200. \
  5201. class \"ScreenCanvas\" extends \"Canvas\" {\
  5202. \9colour = 1;\
  5203. \9last = nil;\
  5204. }\
  5205. \
  5206. function ScreenCanvas:init( ... )\
  5207. \9self.super:init( ... )\
  5208. \9self.last = {}\
  5209. \9for i = 1, self.width * self.height do\
  5210. \9\9self.last[i] = {}\
  5211. \9end\
  5212. end\
  5213. \
  5214. function ScreenCanvas:setWidth( width )\
  5215. \9local height, buffer, raw = self.height, self.buffer, self.raw\
  5216. \9local last = self.last\
  5217. \9local px = { 0, 1, \" \" }\
  5218. \
  5219. \9while raw.width < width do\
  5220. \9\9for i = 1, height do\
  5221. \9\9\9insert( last, ( raw.width + 1 ) * i, {} )\
  5222. \9\9\9insert( buffer, ( raw.width + 1 ) * i, px )\
  5223. \9\9end\
  5224. \9\9raw.width = raw.width + 1\
  5225. \9end\
  5226. \
  5227. \9while raw.width > width do\
  5228. \9\9for i = height, 1, -1 do\
  5229. \9\9\9remove( last, raw.width * i )\
  5230. \9\9\9remove( buffer, raw.width * i )\
  5231. \9\9end\
  5232. \9\9raw.width = raw.width - 1\
  5233. \9end\
  5234. end\
  5235. \
  5236. function ScreenCanvas:setHeight( height )\
  5237. \9local width, buffer, raw = self.width, self.buffer, self.raw\
  5238. \9local last = self.last\
  5239. \9local px = { 0, 1, \" \" }\
  5240. \
  5241. \9local px = { 0, 1, \" \" }\
  5242. \9while raw.height < height do\
  5243. \9\9for i = 1, width do\
  5244. \9\9\9last[#last + 1] = {}\
  5245. \9\9\9buffer[#buffer + 1] = px\
  5246. \9\9end\
  5247. \9\9raw.height = raw.height + 1\
  5248. \9end\
  5249. \
  5250. \9while raw.height > height do\
  5251. \9\9for i = 1, width do\
  5252. \9\9\9last[#last] = nil\
  5253. \9\9\9buffer[#buffer] = nil\
  5254. \9\9end\
  5255. \9\9raw.height = raw.height - 1\
  5256. \9end\
  5257. end\
  5258. \
  5259. function ScreenCanvas:drawToTerminal( term, _x, _y )\
  5260. \9_x = ( _x or 0 ) + self.x\
  5261. \9_y = ( _y or 0 ) + self.y\
  5262. \9local pos = 1\
  5263. \9local width = self.width\
  5264. \9local buffer = self.buffer\
  5265. \9local last = self.last\
  5266. \
  5267. \9local blit, setCursorPos = term.blit, term.setCursorPos\
  5268. \
  5269. \9for y = 1, self.height do\
  5270. \9\9local px\
  5271. \9\9local text, bc, tc = {}, {}, {}\
  5272. \9\9for x = 1, width do\
  5273. \9\9\9local pxl = buffer[pos]\
  5274. \9\9\9local lst = last[pos]\
  5275. \9\9\9if pxl[1] ~= lst[1] or pxl[2] ~= lst[2] or pxl[3] ~= lst[3] then\
  5276. \9\9\9\9if not px then\
  5277. \9\9\9\9\9px = x\
  5278. \9\9\9\9\9tx = x\
  5279. \9\9\9\9\9setCursorPos( _x + x, y + _y )\
  5280. \9\9\9\9end\
  5281. \9\9\9\9bc[#bc + 1] = col_lookup[pxl[1]]\
  5282. \9\9\9\9tc[#tc + 1] = col_lookup[pxl[2]]\
  5283. \9\9\9\9text[#text + 1] = #pxl[3] == 0 and \" \" or pxl[3]\
  5284. \9\9\9\9last[pos] = buffer[pos]\
  5285. \9\9\9elseif px then\
  5286. \9\9\9\9blit( concat( text ), concat( tc ), concat( bc ) )\
  5287. \9\9\9\9px = nil\
  5288. \9\9\9\9text = {}\
  5289. \9\9\9\9tc = {}\
  5290. \9\9\9\9bc = {}\
  5291. \9\9\9end\
  5292. \9\9\9pos = pos + 1\
  5293. \9\9end\
  5294. \9\9blit( concat( text ), concat( tc ), concat( bc ) )\
  5295. \9end\
  5296. end\
  5297. \
  5298. function ScreenCanvas:drawToScreen( x, y )\
  5299. \9return self:drawToTerminal( term, x, y )\
  5300. end";
  5301.     ["/lib/graphics/TermCanvas.lua"] = "\
  5302. require \"graphics.Canvas\"\
  5303. \
  5304. local col_lookup = {}\
  5305. for i = 0, 15 do\
  5306. \9col_lookup[(\"%x\"):format( i ):byte()] = 2 ^ i\
  5307. end\
  5308. \
  5309. local isColour = term.isColour()\
  5310. \
  5311. class \"TermCanvas\" extends \"Canvas\" {\
  5312. \9colour = 32768;\
  5313. \9term_bc = 32768;\
  5314. \9term_tc = 1;\
  5315. \9term_x = 1;\
  5316. \9term_y = 1;\
  5317. \9term_cb = false;\
  5318. }\
  5319. \
  5320. function TermCanvas:getTermRedirect()\
  5321. \9local term = {}\
  5322. \
  5323. \9function term.write( s )\
  5324. \9\9s = tostring( s )\
  5325. \9\9local pos = ( self.term_y - 1 ) * self.width + self.term_x\
  5326. \9\9local pixels = {}\
  5327. \9\9local bc, tc = self.term_bc, self.term_tc\
  5328. \9\9for i = 1, math.min( #s, self.width - self.term_x + 1 ) do\
  5329. \9\9\9pixels[#pixels + 1] = { pos, { bc, tc, s:sub( i, i ) } }\
  5330. \9\9\9pos = pos + 1\
  5331. \9\9end\
  5332. \9\9self.term_x = self.term_x + #s\
  5333. \9\9self:mapPixels( pixels )\
  5334. \9end\
  5335. \9function term.blit( s, t, b )\
  5336. \9\9if #s ~= #b or #s ~= #t then\
  5337. \9\9\9return error \"arguments must be the same length\"\
  5338. \9\9end\
  5339. \9\9local pixels = {}\
  5340. \9\9local pos = ( self.term_y - 1 ) * self.width + self.term_x\
  5341. \9\9for i = 1, math.min( #s, self.width - self.term_x + 1 ) do\
  5342. \9\9\9pixels[#pixels + 1] = { pos, { col_lookup[b:byte( i )], col_lookup[t:byte( i )], s:sub( i, i ) } }\
  5343. \9\9\9pos = pos + 1\
  5344. \9\9end\
  5345. \9\9self.term_x = self.term_x + #s\
  5346. \9\9self:mapPixels( pixels )\
  5347. \9end\
  5348. \
  5349. \9function term.clear()\
  5350. \9\9self:clear( self.term_bc )\
  5351. \9end\
  5352. \9function term.clearLine()\
  5353. \9\9local px = { self.term_bc, 1, \" \" }\
  5354. \9\9local pixels = {}\
  5355. \9\9local offset = self.width * ( self.term_y - 1 )\
  5356. \9\9for i = 1, self.width do\
  5357. \9\9\9pixels[#pixels + 1] = { i + offset, px }\
  5358. \9\9end\
  5359. \9\9self:mapPixels( pixels )\
  5360. \9end\
  5361. \
  5362. \9function term.getCursorPos()\
  5363. \9\9return self.term_x, self.term_y\
  5364. \9end\
  5365. \9function term.setCursorPos( x, y )\
  5366. \9\9self.term_x = math.floor( x )\
  5367. \9\9self.term_y = math.floor( y )\
  5368. \9end\
  5369. \
  5370. \9function term.setCursorBlink( state )\
  5371. \9\9self.term_cb = state\
  5372. \9end\
  5373. \
  5374. \9function term.getSize()\
  5375. \9\9return self.width, self.height\
  5376. \9end\
  5377. \
  5378. \9function term.scroll( n )\
  5379. \9\9local buffer = self.buffer\
  5380. \9\9local offset = n * self.width\
  5381. \9\9local n, f, s = n < 0 and self.width * self.height or 1, n < 0 and 1 or self.width * self.height, n < 0 and -1 or 1\
  5382. \9\9local pixels = {}\
  5383. \9\9local px = { self.term_bc, self.term_tc, \" \" }\
  5384. \9\9for i = n, f, s do\
  5385. \9\9\9pixels[#pixels + 1] = { i, buffer[i + offset] or px }\
  5386. \9\9end\
  5387. \9\9self:mapPixels( pixels )\
  5388. \9end\
  5389. \
  5390. \9function term.isColour()\
  5391. \9\9return isColour\
  5392. \9end\
  5393. \
  5394. \9function term.setBackgroundColour( colour )\
  5395. \9\9self.term_bc = colour\
  5396. \9end\
  5397. \9function term.setTextColour( colour )\
  5398. \9\9self.term_tc = colour\
  5399. \9end\
  5400. \9function term.getBackgroundColour()\
  5401. \9\9return self.term_bc\
  5402. \9end\
  5403. \9function term.getTextColour()\
  5404. \9\9return self.term_tc\
  5405. \9end\
  5406. \
  5407. \9term.isColor = term.isColour\
  5408. \9term.setBackgroundColor = term.setBackgroundColour\
  5409. \9term.setTextColor = term.setTextColour\
  5410. \9term.getBackgroundColor = term.getBackgroundColour\
  5411. \9term.getTextColor = term.getTextColour\
  5412. \
  5413. \9return term\
  5414. end\
  5415. \
  5416. function TermCanvas:drawTo( other, x, y )\
  5417. \9self.super:drawTo( other, x, y )\
  5418. \9if self.term_cb then\
  5419. \9\9other.term_cb = true\
  5420. \9\9other.term_x = self.term_x + ( x or 1 ) - 1\
  5421. \9\9other.term_y = self.term_y + ( y or 1 ) - 1\
  5422. \9end\
  5423. end\
  5424. \
  5425. function TermCanvas:redirect()\
  5426. \9return term.redirect( self:getTermRedirect() )\
  5427. end\
  5428. \
  5429. function TermCanvas:wrap( f )\
  5430. \9local old = self:redirect()\
  5431. \9f()\
  5432. \9term.redirect( old )\
  5433. end";
  5434.     ["/lib/graphics/shader.lua"] = "\
  5435. local shader = {}\
  5436. \
  5437. shader.darken = {\
  5438. \9[colours.white] = colours.lightGrey, [colours.orange] = colours.brown, [colours.magenta] = colours.purple, [colours.lightBlue] = colours.cyan;\
  5439. \9[colours.yellow] = colours.orange, [colours.lime] = colours.green, [colours.pink] = colours.magenta, [colours.grey] = colours.black;\
  5440. \9[colours.lightGrey] = colours.grey, [colours.cyan] = colours.blue, [colours.purple] = colours.grey, [colours.blue] = colours.grey;\
  5441. \9[colours.brown] = colours.black, [colours.green] = colours.grey, [colours.red] = colours.brown, [colours.black] = colours.black;\
  5442. }\
  5443. shader.lighten = {\
  5444. \9[colours.white] = colours.white, [colours.orange] = colours.yellow, [colours.magenta] = colours.pink, [colours.lightBlue] = colours.white;\
  5445. \9[colours.yellow] = colours.white, [colours.lime] = colours.white, [colours.pink] = colours.white, [colours.grey] = colours.lightGrey;\
  5446. \9[colours.lightGrey] = colours.white, [colours.cyan] = colours.lightBlue, [colours.purple] = colours.magenta, [colours.blue] = colours.cyan;\
  5447. \9[colours.brown] = colours.red, [colours.green] = colours.lime, [colours.red] = colours.orange, [colours.black] = colours.grey;\
  5448. }\
  5449. shader.greyscale = {\
  5450. \9[colours.white] = 1, [colours.orange] = 256, [colours.magenta] = 256, [colours.lightBlue] = 256;\
  5451. \9[colours.yellow] = 1, [colours.lime] = 256, [colours.pink] = 1, [colours.grey] = 256;\
  5452. \9[colours.lightGrey] = 256, [colours.cyan] = 128, [colours.purple] = 128, [colours.blue] = 32768;\
  5453. \9[colours.brown] = 32768, [colours.green] = 128, [colours.red] = 128, [colours.black] = 32768;\
  5454. }\
  5455. shader.sepia = {\
  5456. \9[colours.white] = 1, [colours.orange] = 2, [colours.magenta] = 2, [colours.lightBlue] = 2;\
  5457. \9[colours.yellow] = 1, [colours.lime] = 2, [colours.pink] = 1, [colours.grey] = 2;\
  5458. \9[colours.lightGrey] = 2, [colours.cyan] = 16, [colours.purple] = 16, [colours.blue] = 4096;\
  5459. \9[colours.brown] = 4096, [colours.green] = 16, [colours.red] = 16, [colours.black] = 4096;\
  5460. }\
  5461. \
  5462. return shader";
  5463.     ["/lib/util/UIDrawingHelpers.lua"] = "\
  5464. -- TODO:\
  5465. \9-- add colour support\
  5466. \
  5467. local UIEventHelpers = require \"util.UIEventHelpers\"\
  5468. \
  5469. local UIDrawingHelpers = {}\
  5470. UIDrawingHelpers.scrollbar = {}\
  5471. \
  5472. function UIDrawingHelpers.scrollbar:drawScrollbars()\
  5473. \
  5474. \9local scrollright, scrollbottom = UIEventHelpers.scrollbar.active( self )\
  5475. \9local rpos, rsize = UIEventHelpers.scrollbar.getBarInfo( self,  \"right\", scrollright, scrollbottom, x, y )\
  5476. \9local bpos, bsize = UIEventHelpers.scrollbar.getBarInfo( self, \"bottom\", scrollright, scrollbottom, x, y )\
  5477. \
  5478. \9if scrollright then\
  5479. \9\9self.canvas:drawVerticalLine( self.width - 1, 0, scrollbottom and self.height - 1 or self.height, {\
  5480. \9\9\9colour = colours.grey;\
  5481. \9\9} )\
  5482. \9\9self.canvas:drawVerticalLine( self.width - 1, rpos, rsize, {\
  5483. \9\9\9colour = self.scrollbar_mounted_side == \"right\" and colours.lightBlue or colours.lightGrey\
  5484. \9\9} )\
  5485. \9end\
  5486. \9if scrollbottom then\
  5487. \9\9self.canvas:drawHorizontalLine( 0, self.height - 1, scrollright and self.width - 1 or self.width, {\
  5488. \9\9\9colour = colours.grey;\
  5489. \9\9} )\
  5490. \9\9self.canvas:drawHorizontalLine( bpos, self.height - 1, bsize, {\
  5491. \9\9\9colour = self.scrollbar_mounted_side == \"bottom\" and colours.lightBlue or colours.lightGrey\
  5492. \9\9} )\
  5493. \9end\
  5494. \9if scrollright and scrollbottom then\
  5495. \9\9self.canvas:drawPoint( self.width - 1, self.height - 1, {\
  5496. \9\9\9colour = colours.grey;\
  5497. \9\9} )\
  5498. \9end\
  5499. \
  5500. end\
  5501. \
  5502. return UIDrawingHelpers";
  5503.     ["/lib/util/UIEventHelpers.lua"] = "\
  5504. require \"Event.Event\"\
  5505. \
  5506. local clipboard = require \"clipboard\"\
  5507. \
  5508. local UIEventHelpers = {}\
  5509. UIEventHelpers.clicking = {}\
  5510. UIEventHelpers.scrollbar = {}\
  5511. UIEventHelpers.scrollbar.mixin = {}\
  5512. UIEventHelpers.textSelection = {}\
  5513. \
  5514. function UIEventHelpers.clicking:handleMouseEvent( event )\
  5515. \
  5516. \9if event.handled then return end\
  5517. \
  5518. \9if event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, self.height ) then\
  5519. \9\9self.clicking = {\
  5520. \9\9\9button = event.button;\
  5521. \9\9\9moved = false;\
  5522. \9\9}\
  5523. \9\9event.handled = true\
  5524. \9\9return \"down\"\
  5525. \
  5526. \9elseif event.name == Event.MOUSEDRAG and self.clicking then\
  5527. \9\9if not event.handled and event:isInArea( 0, 0, self.width, self.height ) then\
  5528. \9\9\9event.handled = true\
  5529. \9\9end\
  5530. \9\9self.clicking.moved = true\
  5531. \
  5532. \9elseif event.name == Event.MOUSEUP and self.clicking then\
  5533. \9\9if event.button == self.clicking.button then\
  5534. \9\9\9event.handled = true\
  5535. \9\9\9if event:isInArea( 0, 0, self.width, self.height ) and not self.clicking.moved then\
  5536. \9\9\9\9self.clicking = nil\
  5537. \9\9\9\9return \"click\"\
  5538. \9\9\9end\
  5539. \9\9\9self.clicking = nil\
  5540. \9\9\9return \"up\"\
  5541. \9\9end\
  5542. \
  5543. \9end\
  5544. \
  5545. end\
  5546. \
  5547. function UIEventHelpers.scrollbar:active()\
  5548. \
  5549. \9local cw, ch = self:getContentWidth(), self:getContentHeight()\
  5550. \9local dw, dh = self:getDisplayWidth(), self:getDisplayHeight()\
  5551. \9if cw > dw or ch > dh then\
  5552. \9\9return ch > dh - 1, cw > dw - 1\
  5553. \9end\
  5554. \9return false, false\
  5555. \
  5556. end\
  5557. \
  5558. function UIEventHelpers.scrollbar:getBarInfo( side, scrollright, scrollbottom ) -- pos, size\
  5559. \9local traysize = side == \"bottom\" and self.width - ( scrollright and 1 or 0 ) or self.height - ( scrollbottom and 1 or 0 )\
  5560. \9local contentsize = side == \"bottom\" and self:getContentWidth() or self:getContentHeight()\
  5561. \9local scale = traysize / contentsize\
  5562. \
  5563. \9local displaysize = ( side == \"bottom\" and self:getDisplayWidth() - ( scrollright and 1 or 0 ) or self:getDisplayHeight() - ( scrollbottom and 1 or 0 ) )\
  5564. \9local scroll = ( side == \"bottom\" and self:getHorizontalOffset() or self:getVerticalOffset() )\
  5565. \
  5566. \9return contentsize == 0 and 0 or math.floor( scroll * scale + .5 ), contentsize == 0 and 0 or math.floor( displaysize * scale + .5 )\
  5567. end\
  5568. \
  5569. function UIEventHelpers.scrollbar:updateScrollbarPosition( side, pos, scrollright, scrollbottom )\
  5570. \9local traysize = side == \"bottom\" and self.width - ( scrollright and 1 or 0 ) or self.height - ( scrollbottom and 1 or 0 )\
  5571. \9local contentsize = side == \"bottom\" and self:getContentWidth() or self:getContentHeight()\
  5572. \9local displaysize = ( side == \"bottom\" and self:getDisplayWidth() - ( scrollright and 1 or 0 ) or self:getDisplayHeight() - ( scrollbottom and 1 or 0 ) )\
  5573. \9local newpos = math.floor( ( pos - self.scrollbar_mounted ) / traysize * contentsize + .5 )\
  5574. \
  5575. \9if side == \"right\" then\
  5576. \9\9self:setVerticalOffset( math.max( 0, math.min( contentsize - displaysize, newpos ) ) )\
  5577. \9else\
  5578. \9\9self:setHorizontalOffset( math.max( 0, math.min( contentsize - displaysize, newpos ) ) )\
  5579. \9end\
  5580. end\
  5581. \
  5582. function UIEventHelpers.scrollbar:handleMouseEvent( event )\
  5583. \9if event.handled then return end\
  5584. \
  5585. \9local x, y = event.x, event.y\
  5586. \9local scrollright, scrollbottom = UIEventHelpers.scrollbar.active( self )\
  5587. \
  5588. \9if event.name == Event.MOUSEDOWN and event:isInArea( 0, 0, self.width, self.height ) then\
  5589. \9\9if scrollright and x == self.width - 1 then\
  5590. \9\9\9local pos, size = UIEventHelpers.scrollbar.getBarInfo( self, \"right\", scrollright, scrollbottom )\
  5591. \9\9\9if y <= pos then\
  5592. \9\9\9\9self.scrollbar_mounted = 0\
  5593. \9\9\9\9self.scrollbar_mounted_side = \"right\"\
  5594. \
  5595. \9\9\9elseif y >= pos + size then\
  5596. \9\9\9\9self.scrollbar_mounted = size - 1\
  5597. \9\9\9\9self.scrollbar_mounted_side = \"right\"\
  5598. \
  5599. \9\9\9else\
  5600. \9\9\9\9self.scrollbar_mounted = y - pos\
  5601. \9\9\9\9self.scrollbar_mounted_side = \"right\"\
  5602. \
  5603. \9\9\9end\
  5604. \9\9\9self.scrollbar_scrollright = scrollright\
  5605. \9\9\9self.scrollbar_scrollbottom = scrollbottom\
  5606. \9\9\9UIEventHelpers.scrollbar.updateScrollbarPosition( self, \"right\", y, scrollright, scrollbottom )\
  5607. \9\9\9event.handled = true\
  5608. \
  5609. \9\9elseif scrollbottom and y == self.height - 1 then\
  5610. \9\9\9local pos, size = UIEventHelpers.scrollbar.getBarInfo( self, \"bottom\", scrollright, scrollbottom )\
  5611. \9\9\9if x < pos then\
  5612. \9\9\9\9self.scrollbar_mounted = 0\
  5613. \9\9\9\9self.scrollbar_mounted_side = \"bottom\"\
  5614. \
  5615. \9\9\9elseif x >= pos + size then\
  5616. \9\9\9\9self.scrollbar_mounted = size - 1\
  5617. \9\9\9\9self.scrollbar_mounted_side = \"bottom\"\
  5618. \
  5619. \9\9\9else\
  5620. \9\9\9\9self.scrollbar_mounted = x - pos\
  5621. \9\9\9\9self.scrollbar_mounted_side = \"bottom\"\
  5622. \
  5623. \9\9\9end\
  5624. \9\9\9self.scrollbar_scrollright = scrollright\
  5625. \9\9\9self.scrollbar_scrollbottom = scrollbottom\
  5626. \9\9\9UIEventHelpers.scrollbar.updateScrollbarPosition( self, \"bottom\", x, scrollright, scrollbottom )\
  5627. \9\9\9event.handled = true\
  5628. \
  5629. \9\9end\
  5630. \9elseif event.name == Event.MOUSEDRAG and self.scrollbar_mounted then\
  5631. \9\9local side = self.scrollbar_mounted_side\
  5632. \9\9UIEventHelpers.scrollbar.updateScrollbarPosition( self, side, side == \"right\" and y or x, self.scrollbar_scrollright, self.scrollbar_scrollbottom )\
  5633. \9\9event.handled = true\
  5634. \
  5635. \9elseif event.name == Event.MOUSEUP and self.scrollbar_mounted then\
  5636. \9\9self.scrollbar_mounted = nil\
  5637. \9\9self.scrollbar_mounted_side = nil\
  5638. \9\9event.handled = true\
  5639. \9\9self.changed = true\
  5640. \
  5641. \9end\
  5642. end\
  5643. \
  5644. function UIEventHelpers.scrollbar:handleMouseScroll( event )\
  5645. \
  5646. \9if not event.handled and event.name == Event.MOUSESCROLL and event:isInArea( 0, 0, self.width, self.height ) then\
  5647. \
  5648. \9\9local right, bottom = UIEventHelpers.scrollbar.active( self )\
  5649. \9\9local max = self:getContentHeight() - self.height + ( bottom and 1 or 0 )\
  5650. \9\9if event.button == 1 and self:getVerticalOffset() < max then\
  5651. \9\9\9self:setVerticalOffset( math.max( 0, math.min( max, self:getVerticalOffset() + 1 ) ) )\
  5652. \9\9\9event.handled = true\
  5653. \9\9elseif event.button == -1 and self:getVerticalOffset() > 0 then\
  5654. \9\9\9self:setVerticalOffset( math.max( 0, math.min( max, self:getVerticalOffset() - 1 ) ) )\
  5655. \9\9\9event.handled = true\
  5656. \9\9end\
  5657. \
  5658. \9end\
  5659. end\
  5660. \
  5661. function UIEventHelpers.scrollbar.mixin:getDisplayWidth()\
  5662. \9return self.width\
  5663. end\
  5664. function UIEventHelpers.scrollbar.mixin:getDisplayHeight()\
  5665. \9return self.height\
  5666. end\
  5667. \
  5668. function UIEventHelpers.scrollbar.mixin:getContentWidth()\
  5669. \9return 0\
  5670. end\
  5671. function UIEventHelpers.scrollbar.mixin:getContentHeight()\
  5672. \9return 0\
  5673. end\
  5674. \
  5675. function UIEventHelpers.scrollbar.mixin:getHorizontalOffset()\
  5676. \9return -self.ox\
  5677. end\
  5678. function UIEventHelpers.scrollbar.mixin:getVerticalOffset()\
  5679. \9return -self.oy\
  5680. end\
  5681. function UIEventHelpers.scrollbar.mixin:setHorizontalOffset( scroll )\
  5682. \9self.ox = -scroll\
  5683. end\
  5684. function UIEventHelpers.scrollbar.mixin:setVerticalOffset( scroll )\
  5685. \9self.oy = -scroll\
  5686. end\
  5687. \
  5688. function UIEventHelpers.textSelection:getCharacter( x, y, lines, alignment, width, height )\
  5689. \
  5690. \9local line = math.max( 1, math.min( #lines, 1 + y - ( ( alignment == \"centre\" and math.floor( height / 2 - #lines / 2 + .5 ) ) or ( alignment == \"bottom\" and height - #lines ) or 0 ) ) )\
  5691. \9if not lines[line] then return nil end\
  5692. \
  5693. \9local pos = 0\
  5694. \9for l = 1, line - 1 do\
  5695. \9\9pos = pos + #lines[l]\
  5696. \9end\
  5697. \
  5698. \9local a = lines[line].alignment\
  5699. \9local char = math.max( 1, math.min( 1 + x - ( ( a == \"centre\" and math.floor( width / 2 - #lines[line] / 2 + .5 ) ) or ( a == \"right\" and width - #lines[line] ) or 0 ), #lines[line] ) )\
  5700. \
  5701. \9return pos + char, line, char\
  5702. \
  5703. end\
  5704. \
  5705. function UIEventHelpers.textSelection:deselectAll( lines )\
  5706. \
  5707. \9for l = 1, #lines do\
  5708. \9\9local line = lines[l]\
  5709. \9\9for c = 1, #line do\
  5710. \9\9\9line[c].pixel.selected = false\
  5711. \9\9end\
  5712. \9end\
  5713. \
  5714. end\
  5715. \
  5716. function UIEventHelpers.textSelection:selectRange( lines, line1, char1, line2, char2 )\
  5717. \
  5718. \9if line1 > line2 then\
  5719. \9\9line1, line2 = line2, line1\
  5720. \9\9char1, char2 = char2, char1\
  5721. \9elseif line1 == line2 and char1 > char2 then\
  5722. \9\9char1, char2 = char2, char1\
  5723. \9end\
  5724. \9for l = 1, #lines do\
  5725. \9\9local line = lines[l]\
  5726. \9\9for c = 1, #line do\
  5727. \9\9\9if l < line1 or l > line2 then\
  5728. \9\9\9\9line[c].pixel.selected = false\
  5729. \9\9\9elseif line1 == line2 then\
  5730. \9\9\9\9line[c].pixel.selected = c >= char1 and c <= char2\
  5731. \9\9\9elseif l == line1 then\
  5732. \9\9\9\9line[c].pixel.selected = c >= char1\
  5733. \9\9\9elseif l == line2 then\
  5734. \9\9\9\9line[c].pixel.selected = c <= char2\
  5735. \9\9\9else\
  5736. \9\9\9\9line[c].pixel.selected = true\
  5737. \9\9\9end\
  5738. \9\9end\
  5739. \9end\
  5740. \
  5741. end\
  5742. \
  5743. function UIEventHelpers.textSelection:getCharacterLink( x, y, lines, alignment, width, height )\
  5744. \
  5745. \9local line = math.max( 1, math.min( #lines, 1 + y - ( ( alignment == \"centre\" and math.floor( height / 2 - #lines / 2 + .5 ) ) or ( alignment == \"bottom\" and height - #lines ) or 0 ) ) )\
  5746. \9if not lines[line] then return nil end\
  5747. \
  5748. \9local pos = 0\
  5749. \9for l = 1, line - 1 do\
  5750. \9\9pos = pos + #lines[l]\
  5751. \9end\
  5752. \
  5753. \9local a = lines[line].alignment\
  5754. \9local c = 1 + x - ( ( a == \"centre\" and math.floor( width / 2 - #lines[line] / 2 + .5 ) ) or ( a == \"right\" and width - #lines[line] ) or 0 ), #lines[line]\
  5755. \
  5756. \9if lines[line][c] then\
  5757. \9\9return lines[line][c].link\
  5758. \9end\
  5759. \
  5760. end\
  5761. \
  5762. function UIEventHelpers.textSelection:handleMouseEvent( event, lines, alignment, width, height )\
  5763. \
  5764. \9-- getCharacterLink( self, event.x - self.ox, event.y - self.oy, lines, alignment, width, height )\
  5765. \
  5766. \9if event.name == Event.MOUSEDOWN then\
  5767. \9\9UIEventHelpers.textSelection.deselectAll( self, lines )\
  5768. \9\9if event.handled or not event:isInArea( 0, 0, self.width, self.height ) then\
  5769. \9\9\9self.selection = nil\
  5770. \9\9else\
  5771. \9\9\9local pos, line, char = UIEventHelpers.textSelection.getCharacter( self, event.x - self.ox, event.y - self.oy, lines, alignment, width, height )\
  5772. \9\9\9if pos then\
  5773. \9\9\9\9self.selection = {\
  5774. \9\9\9\9\9line1 = line;\
  5775. \9\9\9\9\9char1 = char;\
  5776. \9\9\9\9\9pos1 = pos;\
  5777. \9\9\9\9\9line2 = line;\
  5778. \9\9\9\9\9char2 = char;\
  5779. \9\9\9\9\9pos2 = pos;\
  5780. \9\9\9\9\9holding = true;\
  5781. \9\9\9\9\9button = event.button;\
  5782. \9\9\9\9\9initialised = false;\
  5783. \9\9\9\9}\
  5784. \9\9\9end\
  5785. \9\9\9event.handled = true\
  5786. \9\9end\
  5787. \9\9self.changed = true\
  5788. \
  5789. \9elseif not event.handled and event.name == Event.MOUSEUP and self.selection and event.button == self.selection.button then\
  5790. \9\9self.selection.holding = false\
  5791. \9\9if not self.selection.initialised then\
  5792. \9\9\9if self.onLinkPressed then\
  5793. \9\9\9\9local link = UIEventHelpers.textSelection.getCharacterLink( self, event.x - self.ox, event.y - self.oy, lines, alignment, width, height )\
  5794. \9\9\9\9if link then\
  5795. \9\9\9\9\9self:onLinkPressed( link )\
  5796. \9\9\9\9end\
  5797. \9\9\9end\
  5798. \9\9\9self.selection = nil\
  5799. \9\9end\
  5800. \
  5801. \9\9event.handled = true\
  5802. \
  5803. \9elseif not event.handled and event.name == Event.MOUSEDRAG and self.selection and self.selection.holding then\
  5804. \9\9local pos, line, char = UIEventHelpers.textSelection.getCharacter( self, event.x - self.ox, event.y - self.oy, lines, alignment, width, height )\
  5805. \9\9self.selection.pos2 = pos\
  5806. \9\9self.selection.char2 = char\
  5807. \9\9self.selection.line2 = line\
  5808. \9\9self.selection.initialised = true\
  5809. \9\9UIEventHelpers.textSelection.selectRange( self, lines, self.selection.line1, self.selection.char1, line, char )\
  5810. \9\9self.changed = true\
  5811. \
  5812. \9end\
  5813. end\
  5814. \
  5815. function UIEventHelpers.textSelection:handleKeyboardEvent( event, stream )\
  5816. \9if not event.handled and event.name == Event.KEYDOWN and self.selection and self.selection.initialised then\
  5817. \9\9if event:matchesHotkey \"ctrl-c\" or event:matchesHotkey \"ctrl-x\" then\
  5818. \9\9\9local text = \"\"\
  5819. \9\9\9local rtext = {}\
  5820. \9\9\9local pos1, pos2 = self.selection.pos1, self.selection.pos2\
  5821. \9\9\9for i = math.min( pos1, pos2 ), math.max( pos1, pos2 ) do\
  5822. \9\9\9\9text = text .. stream[i].pixel[3]\
  5823. \9\9\9\9rtext[#rtext + 1] = stream[i].pixel\
  5824. \9\9\9end\
  5825. \9\9\9clipboard.put {\
  5826. \9\9\9\9{ \"plaintext\", text };\
  5827. \9\9\9\9{ \"text\", rtext };\
  5828. \9\9\9}\
  5829. \9\9\9event.handled = true\
  5830. \9\9end\
  5831. \9end\
  5832. end\
  5833. \
  5834. return UIEventHelpers";
  5835.     ["/lib/util/markup.lua"] = "\
  5836. local col_lookup = {}\
  5837. for i = 0, 15 do\
  5838. \9col_lookup[(\"%x\"):format( i )] = 2 ^ i\
  5839. \9col_lookup[(\"%X\"):format( i )] = 2 ^ i\
  5840. end\
  5841. col_lookup[\" \"] = 0\
  5842. \
  5843. local function parseStream( text, bc, tc, allowCodes )\
  5844. \9local stream = {}\
  5845. \9local n = 1\
  5846. \9local alignment, tag = \"left\", nil\
  5847. \9local i = 1\
  5848. \9while i <= #text do\
  5849. \9\9if text:sub( i, i ) == \"@\" and allowCodes then\
  5850. \9\9\9i = i + 1\
  5851. \9\9\9local ctrl = text:sub( i, i )\
  5852. \9\9\9if ctrl == \"b\" then\
  5853. \9\9\9\9bc = col_lookup[text:sub( i + 1, i + 1 )]\
  5854. \9\9\9\9i = i + 2\
  5855. \
  5856. \9\9\9elseif ctrl == \"t\" then\
  5857. \9\9\9\9tc = col_lookup[text:sub( i + 1, i + 1 )]\
  5858. \9\9\9\9i = i + 2\
  5859. \
  5860. \9\9\9elseif ctrl == \"a\" then\
  5861. \9\9\9\9local a = text:sub( i + 1, i + 1 )\
  5862. \9\9\9\9alignment = ( a == \"c\" and \"centre\" ) or ( a == \"r\" and \"right\" ) or \"left\"\
  5863. \9\9\9\9i = i + 2\
  5864. \
  5865. \9\9\9elseif ctrl == \"m\" then\
  5866. \9\9\9\9if text:sub( i + 1, i + 1 ) == \"-\" then\
  5867. \9\9\9\9\9tag = nil\
  5868. \9\9\9\9\9i = i + 2\
  5869. \9\9\9\9else\
  5870. \9\9\9\9\9local t = text:match( \"m%((.-)%)\", i )\
  5871. \9\9\9\9\9if t then\
  5872. \9\9\9\9\9\9tag = t\
  5873. \9\9\9\9\9\9i = i + 3 + #t\
  5874. \9\9\9\9\9else\
  5875. \9\9\9\9\9\9stream[n] = { pixel = { bc, tc, \"m\" }, tag = tag, alignment = alignment }\
  5876. \9\9\9\9\9\9n = n + 1\
  5877. \9\9\9\9\9\9i = i + 1\
  5878. \9\9\9\9\9end\
  5879. \9\9\9\9end\
  5880. \
  5881. \9\9\9elseif ctrl == \"l\" then -- link\
  5882. \9\9\9\9local display, link = text:match( \"l%[(.-)%]%((.-)%)\", i )\
  5883. \9\9\9\9if display then\
  5884. \9\9\9\9\9i = i + 5 + #display + #link\
  5885. \9\9\9\9\9for c = 1, #display do\
  5886. \9\9\9\9\9\9stream[n] = { pixel = { bc, tc, display:sub( c, c ) }, tag = tag, alignment = alignment, link = link }\
  5887. \9\9\9\9\9\9n = n + 1\
  5888. \9\9\9\9\9end\
  5889. \9\9\9\9else\
  5890. \9\9\9\9\9stream[n] = { pixel = { bc, tc, \"l\" }, tag = tag, alignment = alignment }\
  5891. \9\9\9\9\9n = n + 1\
  5892. \9\9\9\9\9i = i + 1\
  5893. \9\9\9\9end\
  5894. \
  5895. \9\9\9else\
  5896. \9\9\9\9stream[n] = { pixel = { bc, tc, text:sub( i, i ) }, tag = tag, alignment = alignment }\
  5897. \9\9\9\9n = n + 1\
  5898. \9\9\9\9i = i + 1\
  5899. \9\9\9end\
  5900. \9\9else\
  5901. \9\9\9stream[n] = { pixel = { bc, tc, text:sub( i, i ) }, tag = tag, alignment = alignment }\
  5902. \9\9\9n = n + 1\
  5903. \9\9\9i = i + 1\
  5904. \9\9end\
  5905. \9end\
  5906. \9return stream\
  5907. end\
  5908. \
  5909. local function wrapStreamLine( stream, pos, width )\
  5910. \9for i = pos, pos + math.min( width or #stream - pos, #stream - pos ) do\
  5911. \9\9if stream[i].pixel[3] == \"\\n\" then\
  5912. \9\9\9return i\
  5913. \9\9end\
  5914. \9end\
  5915. \9if not width or #stream - pos + 1 <= width then\
  5916. \9\9return #stream\
  5917. \9end\
  5918. \9for i = pos + width, pos, -1 do\
  5919. \9\9if stream[i].pixel[3]:find \"%s\" then\
  5920. \9\9\9while stream[i + 1].pixel[3]:find \"%s\" do\
  5921. \9\9\9\9i = i + 1\
  5922. \9\9\9end\
  5923. \9\9\9return i\
  5924. \9\9end\
  5925. \9end\
  5926. \9return pos + width - 1\
  5927. end\
  5928. \
  5929. local function wrapStream( stream, width, height )\
  5930. \9local lines, pos = {}, 1\
  5931. \9while pos <= #stream do\
  5932. \9\9local newpos = wrapStreamLine( stream, pos, width )\
  5933. \9\9lines[#lines + 1] = { pos, newpos }\
  5934. \9\9pos = newpos + 1\
  5935. \9end\
  5936. \9local t, d = {}, {}\
  5937. \9if height then\
  5938. \9\9error( height )\
  5939. \9end\
  5940. \9for i = 1, math.min( height or #lines, #lines ) do\
  5941. \9\9local alignment = stream[lines[i][1]].alignment\
  5942. \9\9t[i] = { alignment = alignment }\
  5943. \9\9d[i] = { alignment = alignment }\
  5944. \9\9local p = 1\
  5945. \9\9for n = lines[i][1], lines[i][2] do\
  5946. \9\9\9t[i][p] = stream[n]\
  5947. \9\9\9d[i][p] = stream[n].pixel\
  5948. \9\9\9p = p + 1\
  5949. \9\9end\
  5950. \9end\
  5951. \9return t, d, stream\
  5952. end\
  5953. \
  5954. local markup = {}\
  5955. \
  5956. function markup.parse( text, bc, tc, width, height, allowCodes )\
  5957. \9return wrapStream( parseStream( text, bc, tc, allowCodes ), width, height )\
  5958. end\
  5959. \
  5960. return markup";
  5961.     ["/minify.lua"] = "local function _W(f) local e=setmetatable({}, {__index = getfenv()}) return setfenv(f,e)() or e end\
  5962. Utils=_W(function()\
  5963. return {\
  5964. \9CreateLookup = function(tbl)\
  5965. \9\9for _, v in ipairs(tbl) do\
  5966. \9\9\9tbl[v] = true\
  5967. \9\9end\
  5968. \9\9return tbl\
  5969. \9end\
  5970. }\
  5971. end)\
  5972. Constants=_W(function()\
  5973. --- Lexer constants\
  5974. -- @module lexer.Constants\
  5975. \
  5976. createLookup = Utils.CreateLookup\
  5977. \
  5978. --- List of white chars\
  5979. WhiteChars = createLookup{' ', '\\n', '\\t', '\\r'}\
  5980. \
  5981. --- Lookup of escape characters\
  5982. EscapeLookup = {['\\r'] = '\\\\r', ['\\n'] = '\\\\n', ['\\t'] = '\\\\t', ['\"'] = '\\\\\"', [\"'\"] = \"\\\\'\"}\
  5983. \
  5984. --- Lookup of lower case characters\
  5985. LowerChars = createLookup{\
  5986. \9'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',\
  5987. \9'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'\
  5988. }\
  5989. \
  5990. --- Lookup of upper case characters\
  5991. UpperChars = createLookup{\
  5992. \9'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',\
  5993. \9'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'\
  5994. }\
  5995. \
  5996. --- Lookup of digits\
  5997. Digits = createLookup{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}\
  5998. \
  5999. --- Lookup of hex digits\
  6000. HexDigits = createLookup{\
  6001. \9'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\
  6002. \9'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'\
  6003. }\
  6004. \
  6005. --- Lookup of valid symbols\
  6006. Symbols = createLookup{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'}\
  6007. \
  6008. --- Lookup of valid keywords\
  6009. Keywords = createLookup{\
  6010. \9'and', 'break', 'do', 'else', 'elseif',\
  6011. \9'end', 'false', 'for', 'function', 'goto', 'if',\
  6012. \9'in', 'local', 'nil', 'not', 'or', 'repeat',\
  6013. \9'return', 'then', 'true', 'until', 'while',\
  6014. }\
  6015. \
  6016. --- Keywords that end a block\
  6017. StatListCloseKeywords = createLookup{'end', 'else', 'elseif', 'until'}\
  6018. \
  6019. --- Unary operators\
  6020. UnOps = createLookup{'-', 'not', '#'}\
  6021. end)\
  6022. Scope=_W(function()\
  6023. --- Holds variables for one scope\
  6024. -- This implementation is inefficient. Instead of using hashes,\
  6025. -- a linear search is used instead to look up variables\
  6026. -- @module lexer.Scope\
  6027. \
  6028. local keywords = Constants.Keywords\
  6029. \
  6030. --- Holds the data for one variable\
  6031. -- @table Variable\
  6032. -- @tfield Scope Scope The parent scope\
  6033. -- @tfield string Name The name of the variable\
  6034. -- @tfield boolean IsGlobal Is the variable global\
  6035. -- @tfield boolean CanRename If the variable can be renamed\
  6036. -- @tfield int References Number of references\
  6037. \
  6038. --- Holds variables for one scope\
  6039. -- @type Scope\
  6040. -- @tfield ?|Scope Parent The parent scope\
  6041. -- @tfield table Locals A list of locals variables\
  6042. -- @tfield table Globals A list of global variables\
  6043. -- @tfield table Children A list of children @{Scope|scopes}\
  6044. \
  6045. local Scope = {}\
  6046. \
  6047. --- Add a local to this scope\
  6048. -- @tparam Variable variable The local object\
  6049. function Scope:AddLocal(variable)\
  6050. \9table.insert(self.Locals, variable)\
  6051. end\
  6052. \
  6053. --- Create a @{Variable} and add it to the scope\
  6054. -- @tparam string name The name of the local\
  6055. -- @treturn Variable The created local\
  6056. function Scope:CreateLocal(name)\
  6057. \9local variable = self:GetLocal(name)\
  6058. \9if variable then return variable end\
  6059. \
  6060. \9variable = {\
  6061. \9\9Scope = self,\
  6062. \9\9Name= name,\
  6063. \9\9IsGlobal = false,\
  6064. \9\9CanRename = true,\
  6065. \9\9References = 1,\
  6066. \9}\
  6067. \
  6068. \9self:AddLocal(variable)\
  6069. \9return variable\
  6070. end\
  6071. \
  6072. --- Get a local variable\
  6073. -- @tparam string name The name of the local\
  6074. -- @treturn ?|Variable The variable\
  6075. function Scope:GetLocal(name)\
  6076. \9for k, var in pairs(self.Locals) do\
  6077. \9\9if var.Name == name then return var end\
  6078. \9end\
  6079. \
  6080. \9if self.Parent then\
  6081. \9\9return self.Parent:GetLocal(name)\
  6082. \9end\
  6083. end\
  6084. \
  6085. --- Find an local variable by its old name\
  6086. -- @tparam string name The old name of the local\
  6087. -- @treturn ?|Variable The local variable\
  6088. function Scope:GetOldLocal(name)\
  6089. \9if self.oldLocalNamesMap[name] then\
  6090. \9\9return self.oldLocalNamesMap[name]\
  6091. \9end\
  6092. \9return self:GetLocal(name)\
  6093. end\
  6094. \
  6095. --- Rename a local variable\
  6096. -- @tparam string|Variable oldName The old variable name\
  6097. -- @tparam string newName The new variable name\
  6098. function Scope:RenameLocal(oldName, newName)\
  6099. \9oldName = type(oldName) == 'string' and oldName or oldName.Name\
  6100. \9local found = false\
  6101. \9local var = self:GetLocal(oldName)\
  6102. \9if var then\
  6103. \9\9var.Name = newName\
  6104. \9\9self.oldLocalNamesMap[oldName] = var\
  6105. \9\9found = true\
  6106. \9end\
  6107. \9if not found and self.Parent then\
  6108. \9\9self.Parent:RenameLocal(oldName, newName)\
  6109. \9end\
  6110. end\
  6111. \
  6112. --- Add a global to this scope\
  6113. -- @tparam Variable name The name of the global\
  6114. function Scope:AddGlobal(name)\
  6115. \9table.insert(self.Globals, name)\
  6116. end\
  6117. \
  6118. --- Create a @{Variable} and add it to the scope\
  6119. -- @tparam string name The name of the global\
  6120. -- @treturn Variable The created global\
  6121. function Scope:CreateGlobal(name)\
  6122. \9local variable = self:GetGlobal(name)\
  6123. \9if variable then return variable end\
  6124. \
  6125. \9variable = {\
  6126. \9\9Scope = self,\
  6127. \9\9Name= name,\
  6128. \9\9IsGlobal = true,\
  6129. \9\9CanRename = true,\
  6130. \9\9References = 1,\
  6131. \9}\
  6132. \
  6133. \9self:AddGlobal(variable)\
  6134. \9return variable\
  6135. end\
  6136. \
  6137. --- Get a global variable\
  6138. -- @tparam string name The name of the global\
  6139. -- @treturn ?|Variable The variable\
  6140. function Scope:GetGlobal(name)\
  6141. \9for k, v in pairs(self.Globals) do\
  6142. \9\9if v.Name == name then return v end\
  6143. \9end\
  6144. \
  6145. \9if self.Parent then\
  6146. \9\9return self.Parent:GetGlobal(name)\
  6147. \9end\
  6148. end\
  6149. \
  6150. --- Find a Global by its old name\
  6151. -- @tparam string name The old name of the global\
  6152. -- @treturn ?|Variable The variable\
  6153. function Scope:GetOldGlobal(name)\
  6154. \9if self.oldGlobalNamesMap[name] then\
  6155. \9\9return self.oldGlobalNamesMap[name]\
  6156. \9end\
  6157. \9return self:GetGlobal(name)\
  6158. end\
  6159. \
  6160. --- Rename a global variable\
  6161. -- @tparam string|Variable oldName The old variable name\
  6162. -- @tparam string newName The new variable name\
  6163. function Scope:RenameGlobal(oldName, newName)\
  6164. \9oldName = type(oldName) == 'string' and oldName or oldName.Name\
  6165. \9local found = false\
  6166. \9local var = self:GetGlobal(oldName)\
  6167. \9if var then\
  6168. \9\9var.Name = newName\
  6169. \9\9self.oldGlobalNamesMap[oldName] = var\
  6170. \9\9found = true\
  6171. \9end\
  6172. \9if not found and self.Parent then\
  6173. \9\9self.Parent:RenameGlobal(oldName, newName)\
  6174. \9end\
  6175. end\
  6176. \
  6177. --- Get a variable by name\
  6178. -- @tparam string name The name of the variable\
  6179. -- @treturn ?|Variable The found variable\
  6180. -- @fixme This is a very inefficient implementation, as with @{Scope:GetLocal} and @{Scope:GetGlocal}\
  6181. function Scope:GetVariable(name)\
  6182. \9return self:GetLocal(name) or self:GetGlobal(name)\
  6183. end\
  6184. \
  6185. --- Find an variable by its old name\
  6186. -- @tparam string name The old name of the variable\
  6187. -- @treturn ?|Variable The variable\
  6188. function Scope:GetOldVariable(name)\
  6189. \9return self:GetOldLocal(name) or self:GetOldGlobal(name)\
  6190. end\
  6191. \
  6192. --- Rename a variable\
  6193. -- @tparam string|Variable oldName The old variable name\
  6194. -- @tparam string newName The new variable name\
  6195. function Scope:RenameVariable(oldName, newName)\
  6196. \9oldName = type(oldName) == 'string' and oldName or oldName.Name\
  6197. \9if self:GetLocal(oldName) then\
  6198. \9\9self:RenameLocal(oldName, newName)\
  6199. \9else\
  6200. \9\9self:RenameGlobal(oldName, newName)\
  6201. \9end\
  6202. end\
  6203. \
  6204. --- Get all variables in the scope\
  6205. -- @treturn table A list of @{Variable|variables}\
  6206. function Scope:GetAllVariables()\
  6207. \9return self:getVars(true, self:getVars(true))\
  6208. end\
  6209. \
  6210. --- Get all variables\
  6211. -- @tparam boolean top If this values is the 'top' of the function stack\
  6212. -- @tparam table ret Table to fill with return values (optional)\
  6213. -- @treturn table The variables\
  6214. -- @local\
  6215. function Scope:getVars(top, ret)\
  6216. \9local ret = ret or {}\
  6217. \9if top then\
  6218. \9\9for k, v in pairs(self.Children) do\
  6219. \9\9\9v:getVars(true, ret)\
  6220. \9\9end\
  6221. \9else\
  6222. \9\9for k, v in pairs(self.Locals) do\
  6223. \9\9\9table.insert(ret, v)\
  6224. \9\9end\
  6225. \9\9for k, v in pairs(self.Globals) do\
  6226. \9\9\9table.insert(ret, v)\
  6227. \9\9end\
  6228. \9\9if self.Parent then\
  6229. \9\9\9self.Parent:getVars(false, ret)\
  6230. \9\9end\
  6231. \9end\
  6232. \9return ret\
  6233. end\
  6234. \
  6235. --- Rename all locals to smaller values\
  6236. -- @tparam string validNameChars All characters that can be used to make a variable name\
  6237. -- @fixme Some of the string generation happens a lot, this could be looked at\
  6238. function Scope:ObfuscateLocals(validNameChars)\
  6239. \9-- Use values sorted for letter frequency instead\
  6240. \9local startChars = validNameChars or \"etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ\"\
  6241. \9local otherChars = validNameChars or \"etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ\"\
  6242. \
  6243. \9local startCharsLength, otherCharsLength = #startChars, #otherChars\
  6244. \9local index = 0\
  6245. \9local floor = math.floor\
  6246. \9for _, var in pairs(self.Locals) do\
  6247. \9\9local name\
  6248. \
  6249. \9\9repeat\
  6250. \9\9\9if index < startCharsLength then\
  6251. \9\9\9\9index = index + 1\
  6252. \9\9\9\9name = startChars:sub(index, index)\
  6253. \9\9\9else\
  6254. \9\9\9\9if index < startCharsLength then\
  6255. \9\9\9\9\9index = index + 1\
  6256. \9\9\9\9\9name = startChars:sub(index, index)\
  6257. \9\9\9\9else\
  6258. \9\9\9\9\9local varIndex = floor(index / startCharsLength)\
  6259. \9\9\9\9\9local offset = index % startCharsLength\
  6260. \9\9\9\9\9name = startChars:sub(offset, offset)\
  6261. \
  6262. \9\9\9\9\9while varIndex > 0 do\
  6263. \9\9\9\9\9\9offset = varIndex % otherCharsLength\
  6264. \9\9\9\9\9\9name = otherChars:sub(offset, offset) .. name\
  6265. \9\9\9\9\9\9varIndex = floor(varIndex / otherCharsLength)\
  6266. \9\9\9\9\9end\
  6267. \9\9\9\9\9index = index + 1\
  6268. \9\9\9\9end\
  6269. \9\9\9end\
  6270. \9\9until not (keywords[name] or self:GetVariable(name))\
  6271. \9\9self:RenameLocal(var.Name, name)\
  6272. \9end\
  6273. end\
  6274. \
  6275. --- Converts the scope to a string\
  6276. -- No, it actually just returns '&lt;scope&gt;'\
  6277. -- @treturn string '&lt;scope&gt;'\
  6278. function Scope:ToString()\
  6279. \9return '<Scope>'\
  6280. end\
  6281. \
  6282. --- Create a new scope\
  6283. -- @tparam Scope parent The parent scope\
  6284. -- @treturn Scope The created scope\
  6285. local function NewScope(parent)\
  6286. \9local scope = setmetatable({\
  6287. \9\9Parent = parent,\
  6288. \9\9Locals = { },\
  6289. \9\9Globals = { },\
  6290. \9\9oldLocalNamesMap = { },\
  6291. \9\9oldGlobalNamesMap = { },\
  6292. \9\9Children = { },\
  6293. \9}, { __index = Scope })\
  6294. \
  6295. \9if parent then\
  6296. \9\9table.insert(parent.Children, scope)\
  6297. \9end\
  6298. \
  6299. \9return scope\
  6300. end\
  6301. \
  6302. return NewScope\
  6303. end)\
  6304. TokenList=_W(function()\
  6305. --- Provides utilities for reading tokens from a 'stream'\
  6306. -- @module lexer.TokenList\
  6307. \
  6308. --- Stores a list of tokens\
  6309. -- @type TokenList\
  6310. -- @tfield table tokens List of tokens\
  6311. -- @tfield number pointer Pointer to the current\
  6312. -- @tfield table savedPointers A save point\
  6313. local TokenList = {}\
  6314. \
  6315. --- Get this element in the token list\
  6316. -- @tparam int offset The offset in the token list\
  6317. function TokenList:Peek(offset)\
  6318. \9local tokens = self.tokens\
  6319. \9offset = offset or 0\
  6320. \9return tokens[math.min(#tokens, self.pointer+offset)]\
  6321. end\
  6322. \
  6323. --- Get the next token in the list\
  6324. -- @tparam table tokenList Add the token onto this table\
  6325. -- @treturn Token The token\
  6326. function TokenList:Get(tokenList)\
  6327. \9local tokens = self.tokens\
  6328. \9local pointer = self.pointer\
  6329. \9local token = tokens[pointer]\
  6330. \9self.pointer = math.min(pointer + 1, #tokens)\
  6331. \9if tokenList then\
  6332. \9\9table.insert(tokenList, token)\
  6333. \9end\
  6334. \9return token\
  6335. end\
  6336. \
  6337. --- Check if the next token is of a type\
  6338. -- @tparam string type The type to compare it with\
  6339. -- @treturn bool If the type matches\
  6340. function TokenList:Is(type)\
  6341. \9return self:Peek().Type == type\
  6342. end\
  6343. \
  6344. --- Save position in a stream\
  6345. function TokenList:Save()\
  6346. \9table.insert(self.savedPointers, self.pointer)\
  6347. end\
  6348. \
  6349. --- Remove the last position in the stream\
  6350. function TokenList:Commit()\
  6351. \9local savedPointers = self.savedPointers\
  6352. \9savedPointers[#savedPointers] = nil\
  6353. end\
  6354. \
  6355. --- Restore to the previous save point\
  6356. function TokenList:Restore()\
  6357. \9local savedPointers = self.savedPointers\
  6358. \9local sPLength = #savedPointers\
  6359. \9self.pointer = savedP[sPLength]\
  6360. \9savedPointers[sPLength] = nil\
  6361. end\
  6362. \
  6363. --- Check if the next token is a symbol and return it\
  6364. -- @tparam string symbol Symbol to check (Optional)\
  6365. -- @tparam table tokenList Add the token onto this table\
  6366. -- @treturn[0] ?|token If symbol is not specified, return the token\
  6367. -- @treturn[1] boolean If symbol is specified, return true if it matches\
  6368. function TokenList:ConsumeSymbol(symbol, tokenList)\
  6369. \9local token = self:Peek()\
  6370. \9if token.Type == 'Symbol' then\
  6371. \9\9if symbol then\
  6372. \9\9\9if token.Data == symbol then\
  6373. \9\9\9\9self:Get(tokenList)\
  6374. \9\9\9\9return true\
  6375. \9\9\9else\
  6376. \9\9\9\9return nil\
  6377. \9\9\9end\
  6378. \9\9else\
  6379. \9\9\9self:Get(tokenList)\
  6380. \9\9\9return token\
  6381. \9\9end\
  6382. \9else\
  6383. \9\9return nil\
  6384. \9end\
  6385. end\
  6386. \
  6387. --- Check if the next token is a keyword and return it\
  6388. -- @tparam string kw Keyword to check (Optional)\
  6389. -- @tparam table tokenList Add the token onto this table\
  6390. -- @treturn[0] ?|token If kw is not specified, return the token\
  6391. -- @treturn[1] boolean If kw is specified, return true if it matches\
  6392. function TokenList:ConsumeKeyword(kw, tokenList)\
  6393. \9local token = self:Peek()\
  6394. \9if token.Type == 'Keyword' and token.Data == kw then\
  6395. \9\9self:Get(tokenList)\
  6396. \9\9return true\
  6397. \9else\
  6398. \9\9return nil\
  6399. \9end\
  6400. end\
  6401. \
  6402. --- Check if the next token matches is a keyword\
  6403. -- @tparam string kw The particular keyword\
  6404. -- @treturn boolean If it matches or not\
  6405. function TokenList:IsKeyword(kw)\
  6406. \9local token = self:Peek()\
  6407. \9return token.Type == 'Keyword' and token.Data == kw\
  6408. end\
  6409. \
  6410. --- Check if the next token matches is a symbol\
  6411. -- @tparam string symbol The particular symbol\
  6412. -- @treturn boolean If it matches or not\
  6413. function TokenList:IsSymbol(symbol)\
  6414. \9local token = self:Peek()\
  6415. \9return token.Type == 'Symbol' and token.Data == symbol\
  6416. end\
  6417. \
  6418. --- Check if the next token is an end of file\
  6419. -- @treturn boolean If the next token is an end of file\
  6420. function TokenList:IsEof()\
  6421. \9return self:Peek().Type == 'Eof'\
  6422. end\
  6423. \
  6424. --- Produce a string off all tokens\
  6425. -- @tparam boolean includeLeading Include the leading whitespace\
  6426. -- @treturn string The resulting string\
  6427. function TokenList:Print(includeLeading)\
  6428. \9includeLeading = (includeLeading == nil and true or includeLeading)\
  6429. \
  6430. \9local out = \"\"\
  6431. \9for _, token in ipairs(self.tokens) do\
  6432. \9\9if includeLeading then\
  6433. \9\9\9for _, whitespace in ipairs(token.LeadingWhite) do\
  6434. \9\9\9\9out = out .. whitespace:Print() .. \"\\n\"\
  6435. \9\9\9end\
  6436. \9\9end\
  6437. \9\9out = out .. token:Print() .. \"\\n\"\
  6438. \9end\
  6439. \
  6440. \9return out\
  6441. end\
  6442. \
  6443. return TokenList\
  6444. end)\
  6445. Parse=_W(function()\
  6446. --- The main lua parser and lexer.\
  6447. -- LexLua returns a Lua token stream, with tokens that preserve\
  6448. -- all whitespace formatting information.\
  6449. -- ParseLua returns an AST, internally relying on LexLua.\
  6450. -- @module lexer.Parse\
  6451. \
  6452. local createLookup = Utils.CreateLookup\
  6453. \
  6454. local lowerChars = Constants.LowerChars\
  6455. local upperChars = Constants.UpperChars\
  6456. local digits = Constants.Digits\
  6457. local symbols = Constants.Symbols\
  6458. local hexDigits = Constants.HexDigits\
  6459. local keywords = Constants.Keywords\
  6460. local statListCloseKeywords = Constants.StatListCloseKeywords\
  6461. local unops = Constants.UnOps\
  6462. local setmeta = setmetatable\
  6463. \
  6464. --- One token\
  6465. -- @table Token\
  6466. -- @tparam string Type The token type\
  6467. -- @param Data Data about the token\
  6468. -- @tparam string CommentType The type of comment  (Optional)\
  6469. -- @tparam number Line Line number (Optional)\
  6470. -- @tparam number Char Character number (Optional)\
  6471. local Token = {}\
  6472. \
  6473. --- Creates a string representation of the token\
  6474. -- @treturn string The resulting string\
  6475. function Token:Print()\
  6476. \9return \"<\"..(self.Type .. string.rep(' ', math.max(3, 12-#self.Type)))..\"  \"..(self.Data or '')..\" >\"\
  6477. end\
  6478. \
  6479. local tokenMeta = { __index = Token }\
  6480. \
  6481. --- Create a list of @{Token|tokens} from a Lua source\
  6482. -- @tparam string src Lua source code\
  6483. -- @treturn TokenList The list of @{Token|tokens}\
  6484. local function LexLua(src)\
  6485. \9--token dump\
  6486. \9local tokens = {}\
  6487. \
  6488. \9do -- Main bulk of the work\
  6489. \9\9--line / char / pointer tracking\
  6490. \9\9local pointer = 1\
  6491. \9\9local line = 1\
  6492. \9\9local char = 1\
  6493. \
  6494. \9\9--get / peek functions\
  6495. \9\9local function get()\
  6496. \9\9\9local c = src:sub(pointer,pointer)\
  6497. \9\9\9if c == '\\n' then\
  6498. \9\9\9\9char = 1\
  6499. \9\9\9\9line = line + 1\
  6500. \9\9\9else\
  6501. \9\9\9\9char = char + 1\
  6502. \9\9\9end\
  6503. \9\9\9pointer = pointer + 1\
  6504. \9\9\9return c\
  6505. \9\9end\
  6506. \9\9local function peek(n)\
  6507. \9\9\9n = n or 0\
  6508. \9\9\9return src:sub(pointer+n,pointer+n)\
  6509. \9\9end\
  6510. \9\9local function consume(chars)\
  6511. \9\9\9local c = peek()\
  6512. \9\9\9for i = 1, #chars do\
  6513. \9\9\9\9if c == chars:sub(i,i) then return get() end\
  6514. \9\9\9end\
  6515. \9\9end\
  6516. \
  6517. \9\9--shared stuff\
  6518. \9\9local function generateError(err)\
  6519. \9\9\9error(\">> :\"..line..\":\"..char..\": \"..err, 0)\
  6520. \9\9end\
  6521. \
  6522. \9\9local function tryGetLongString()\
  6523. \9\9\9local start = pointer\
  6524. \9\9\9if peek() == '[' then\
  6525. \9\9\9\9local equalsCount = 0\
  6526. \9\9\9\9local depth = 1\
  6527. \9\9\9\9while peek(equalsCount+1) == '=' do\
  6528. \9\9\9\9\9equalsCount = equalsCount + 1\
  6529. \9\9\9\9end\
  6530. \9\9\9\9if peek(equalsCount+1) == '[' then\
  6531. \9\9\9\9\9--start parsing the string. Strip the starting bit\
  6532. \9\9\9\9\9for _ = 0, equalsCount+1 do get() end\
  6533. \
  6534. \9\9\9\9\9--get the contents\
  6535. \9\9\9\9\9local contentStart = pointer\
  6536. \9\9\9\9\9while true do\
  6537. \9\9\9\9\9\9--check for eof\
  6538. \9\9\9\9\9\9if peek() == '' then\
  6539. \9\9\9\9\9\9\9generateError(\"Expected `]\"..string.rep('=', equalsCount)..\"]` near <eof>.\", 3)\
  6540. \9\9\9\9\9\9end\
  6541. \
  6542. \9\9\9\9\9\9--check for the end\
  6543. \9\9\9\9\9\9local foundEnd = true\
  6544. \9\9\9\9\9\9if peek() == ']' then\
  6545. \9\9\9\9\9\9\9for i = 1, equalsCount do\
  6546. \9\9\9\9\9\9\9\9if peek(i) ~= '=' then foundEnd = false end\
  6547. \9\9\9\9\9\9\9end\
  6548. \9\9\9\9\9\9\9if peek(equalsCount+1) ~= ']' then\
  6549. \9\9\9\9\9\9\9\9foundEnd = false\
  6550. \9\9\9\9\9\9\9end\
  6551. \9\9\9\9\9\9else\
  6552. \9\9\9\9\9\9\9if peek() == '[' then\
  6553. \9\9\9\9\9\9\9\9-- is there an embedded long string?\
  6554. \9\9\9\9\9\9\9\9local embedded = true\
  6555. \9\9\9\9\9\9\9\9for i = 1, equalsCount do\
  6556. \9\9\9\9\9\9\9\9\9if peek(i) ~= '=' then\
  6557. \9\9\9\9\9\9\9\9\9\9embedded = false\
  6558. \9\9\9\9\9\9\9\9\9\9break\
  6559. \9\9\9\9\9\9\9\9\9end\
  6560. \9\9\9\9\9\9\9\9end\
  6561. \9\9\9\9\9\9\9\9if peek(equalsCount + 1) == '[' and embedded then\
  6562. \9\9\9\9\9\9\9\9\9-- oh look, there was\
  6563. \9\9\9\9\9\9\9\9\9depth = depth + 1\
  6564. \9\9\9\9\9\9\9\9\9for i = 1, (equalsCount + 2) do\
  6565. \9\9\9\9\9\9\9\9\9\9get()\
  6566. \9\9\9\9\9\9\9\9\9end\
  6567. \9\9\9\9\9\9\9\9end\
  6568. \9\9\9\9\9\9\9end\
  6569. \9\9\9\9\9\9\9foundEnd = false\
  6570. \9\9\9\9\9\9end\
  6571. \
  6572. \9\9\9\9\9\9if foundEnd then\
  6573. \9\9\9\9\9\9\9depth = depth - 1\
  6574. \9\9\9\9\9\9\9if depth == 0 then\
  6575. \9\9\9\9\9\9\9\9break\
  6576. \9\9\9\9\9\9\9else\
  6577. \9\9\9\9\9\9\9\9for i = 1, equalsCount + 2 do\
  6578. \9\9\9\9\9\9\9\9\9get()\
  6579. \9\9\9\9\9\9\9\9end\
  6580. \9\9\9\9\9\9\9end\
  6581. \9\9\9\9\9\9else\
  6582. \9\9\9\9\9\9\9get()\
  6583. \9\9\9\9\9\9end\
  6584. \9\9\9\9\9end\
  6585. \
  6586. \9\9\9\9\9--get the interior string\
  6587. \9\9\9\9\9local contentString = src:sub(contentStart, pointer-1)\
  6588. \
  6589. \9\9\9\9\9--found the end. Get rid of the trailing bit\
  6590. \9\9\9\9\9for i = 0, equalsCount+1 do get() end\
  6591. \
  6592. \9\9\9\9\9--get the exterior string\
  6593. \9\9\9\9\9local longString = src:sub(start, pointer-1)\
  6594. \
  6595. \9\9\9\9\9--return the stuff\
  6596. \9\9\9\9\9return contentString, longString\
  6597. \9\9\9\9else\
  6598. \9\9\9\9\9return nil\
  6599. \9\9\9\9end\
  6600. \9\9\9else\
  6601. \9\9\9\9return nil\
  6602. \9\9\9end\
  6603. \9\9end\
  6604. \
  6605. \9\9--main token emitting loop\
  6606. \9\9while true do\
  6607. \9\9\9--get leading whitespace. The leading whitespace will include any comments\
  6608. \9\9\9--preceding the token. This prevents the parser needing to deal with comments\
  6609. \9\9\9--separately.\
  6610. \9\9\9local leading = { }\
  6611. \9\9\9local leadingWhite = ''\
  6612. \9\9\9local longStr = false\
  6613. \9\9\9while true do\
  6614. \9\9\9\9local c = peek()\
  6615. \9\9\9\9if c == '#' and peek(1) == '!' and line == 1 then\
  6616. \9\9\9\9\9-- #! shebang for linux scripts\
  6617. \9\9\9\9\9get()\
  6618. \9\9\9\9\9get()\
  6619. \9\9\9\9\9leadingWhite = \"#!\"\
  6620. \9\9\9\9\9while peek() ~= '\\n' and peek() ~= '' do\
  6621. \9\9\9\9\9\9leadingWhite = leadingWhite .. get()\
  6622. \9\9\9\9\9end\
  6623. \
  6624. \9\9\9\9\9table.insert(leading, setmeta({\
  6625. \9\9\9\9\9\9Type = 'Comment',\
  6626. \9\9\9\9\9\9CommentType = 'Shebang',\
  6627. \9\9\9\9\9\9Data = leadingWhite,\
  6628. \9\9\9\9\9\9Line = line,\
  6629. \9\9\9\9\9\9Char = char\
  6630. \9\9\9\9\9}, tokenMeta))\
  6631. \9\9\9\9\9leadingWhite = \"\"\
  6632. \9\9\9\9end\
  6633. \9\9\9\9if c == ' ' or c == '\\t' then\
  6634. \9\9\9\9\9--whitespace\
  6635. \9\9\9\9\9--leadingWhite = leadingWhite..get()\
  6636. \9\9\9\9\9local c2 = get() -- ignore whitespace\
  6637. \9\9\9\9\9table.insert(leading, setmeta({\
  6638. \9\9\9\9\9\9Type = 'Whitespace',\
  6639. \9\9\9\9\9\9Line = line,\
  6640. \9\9\9\9\9\9Char = char,\
  6641. \9\9\9\9\9\9Data = c2\
  6642. \9\9\9\9\9}, tokenMeta))\
  6643. \9\9\9\9elseif c == '\\n' or c == '\\r' then\
  6644. \9\9\9\9\9local nl = get()\
  6645. \9\9\9\9\9if leadingWhite ~= \"\" then\
  6646. \9\9\9\9\9\9table.insert(leading, setmeta({\
  6647. \9\9\9\9\9\9\9Type = 'Comment',\
  6648. \9\9\9\9\9\9\9CommentType = longStr and 'LongComment' or 'Comment',\
  6649. \9\9\9\9\9\9\9Data = leadingWhite,\
  6650. \9\9\9\9\9\9\9Line = line,\
  6651. \9\9\9\9\9\9\9Char = char,\
  6652. \9\9\9\9\9\9}, tokenMeta))\
  6653. \9\9\9\9\9\9leadingWhite = \"\"\
  6654. \9\9\9\9\9end\
  6655. \9\9\9\9\9table.insert(leading, setmeta({\
  6656. \9\9\9\9\9\9Type = 'Whitespace',\
  6657. \9\9\9\9\9\9Line = line,\
  6658. \9\9\9\9\9\9Char = char,\
  6659. \9\9\9\9\9\9Data = nl,\
  6660. \9\9\9\9\9}, tokenMeta))\
  6661. \9\9\9\9elseif c == '-' and peek(1) == '-' then\
  6662. \9\9\9\9\9--comment\
  6663. \9\9\9\9\9get()\
  6664. \9\9\9\9\9get()\
  6665. \9\9\9\9\9leadingWhite = leadingWhite .. '--'\
  6666. \9\9\9\9\9local _, wholeText = tryGetLongString()\
  6667. \9\9\9\9\9if wholeText then\
  6668. \9\9\9\9\9\9leadingWhite = leadingWhite..wholeText\
  6669. \9\9\9\9\9\9longStr = true\
  6670. \9\9\9\9\9else\
  6671. \9\9\9\9\9\9while peek() ~= '\\n' and peek() ~= '' do\
  6672. \9\9\9\9\9\9\9leadingWhite = leadingWhite..get()\
  6673. \9\9\9\9\9\9end\
  6674. \9\9\9\9\9end\
  6675. \9\9\9\9else\
  6676. \9\9\9\9\9break\
  6677. \9\9\9\9end\
  6678. \9\9\9end\
  6679. \9\9\9if leadingWhite ~= \"\" then\
  6680. \9\9\9\9table.insert(leading, setmeta(\
  6681. \9\9\9\9{\
  6682. \9\9\9\9\9Type = 'Comment',\
  6683. \9\9\9\9\9CommentType = longStr and 'LongComment' or 'Comment',\
  6684. \9\9\9\9\9Data = leadingWhite,\
  6685. \9\9\9\9\9Line = line,\
  6686. \9\9\9\9\9Char = char,\
  6687. \9\9\9\9}, tokenMeta))\
  6688. \9\9\9end\
  6689. \
  6690. \9\9\9--get the initial char\
  6691. \9\9\9local thisLine = line\
  6692. \9\9\9local thisChar = char\
  6693. \9\9\9local errorAt = \":\"..line..\":\"..char..\":> \"\
  6694. \9\9\9local c = peek()\
  6695. \
  6696. \9\9\9--symbol to emit\
  6697. \9\9\9local toEmit = nil\
  6698. \
  6699. \9\9\9--branch on type\
  6700. \9\9\9if c == '' then\
  6701. \9\9\9\9--eof\
  6702. \9\9\9\9toEmit = { Type = 'Eof' }\
  6703. \
  6704. \9\9\9elseif upperChars[c] or lowerChars[c] or c == '_' then\
  6705. \9\9\9\9--ident or keyword\
  6706. \9\9\9\9local start = pointer\
  6707. \9\9\9\9repeat\
  6708. \9\9\9\9\9get()\
  6709. \9\9\9\9\9c = peek()\
  6710. \9\9\9\9until not (upperChars[c] or lowerChars[c] or digits[c] or c == '_')\
  6711. \9\9\9\9local dat = src:sub(start, pointer-1)\
  6712. \9\9\9\9if keywords[dat] then\
  6713. \9\9\9\9\9toEmit = {Type = 'Keyword', Data = dat}\
  6714. \9\9\9\9else\
  6715. \9\9\9\9\9toEmit = {Type = 'Ident', Data = dat}\
  6716. \9\9\9\9end\
  6717. \
  6718. \9\9\9elseif digits[c] or (peek() == '.' and digits[peek(1)]) then\
  6719. \9\9\9\9--number const\
  6720. \9\9\9\9local start = pointer\
  6721. \9\9\9\9if c == '0' and peek(1) == 'x' then\
  6722. \9\9\9\9\9get();get()\
  6723. \9\9\9\9\9while hexDigits[peek()] do get() end\
  6724. \9\9\9\9\9if consume('Pp') then\
  6725. \9\9\9\9\9\9consume('+-')\
  6726. \9\9\9\9\9\9while digits[peek()] do get() end\
  6727. \9\9\9\9\9end\
  6728. \9\9\9\9else\
  6729. \9\9\9\9\9while digits[peek()] do get() end\
  6730. \9\9\9\9\9if consume('.') then\
  6731. \9\9\9\9\9\9while digits[peek()] do get() end\
  6732. \9\9\9\9\9end\
  6733. \9\9\9\9\9if consume('Ee') then\
  6734. \9\9\9\9\9\9consume('+-')\
  6735. \9\9\9\9\9\9while digits[peek()] do get() end\
  6736. \9\9\9\9\9end\
  6737. \9\9\9\9end\
  6738. \9\9\9\9toEmit = {Type = 'Number', Data = src:sub(start, pointer-1)}\
  6739. \
  6740. \9\9\9elseif c == '\\'' or c == '\\\"' then\
  6741. \9\9\9\9local start = pointer\
  6742. \9\9\9\9--string const\
  6743. \9\9\9\9local delim = get()\
  6744. \9\9\9\9local contentStart = pointer\
  6745. \9\9\9\9while true do\
  6746. \9\9\9\9\9local c = get()\
  6747. \9\9\9\9\9if c == '\\\\' then\
  6748. \9\9\9\9\9\9get() --get the escape char\
  6749. \9\9\9\9\9elseif c == delim then\
  6750. \9\9\9\9\9\9break\
  6751. \9\9\9\9\9elseif c == '' then\
  6752. \9\9\9\9\9\9generateError(\"Unfinished string near <eof>\")\
  6753. \9\9\9\9\9end\
  6754. \9\9\9\9end\
  6755. \9\9\9\9local content = src:sub(contentStart, pointer-2)\
  6756. \9\9\9\9local constant = src:sub(start, pointer-1)\
  6757. \9\9\9\9toEmit = {Type = 'String', Data = constant, Constant = content}\
  6758. \
  6759. \9\9\9elseif c == '[' then\
  6760. \9\9\9\9local content, wholetext = tryGetLongString()\
  6761. \9\9\9\9if wholetext then\
  6762. \9\9\9\9\9toEmit = {Type = 'String', Data = wholetext, Constant = content}\
  6763. \9\9\9\9else\
  6764. \9\9\9\9\9get()\
  6765. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = '['}\
  6766. \9\9\9\9end\
  6767. \
  6768. \9\9\9elseif consume('>=<') then\
  6769. \9\9\9\9if consume('=') then\
  6770. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = c..'='}\
  6771. \9\9\9\9else\
  6772. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = c}\
  6773. \9\9\9\9end\
  6774. \
  6775. \9\9\9elseif consume('~') then\
  6776. \9\9\9\9if consume('=') then\
  6777. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = '~='}\
  6778. \9\9\9\9else\
  6779. \9\9\9\9\9generateError(\"Unexpected symbol `~` in source.\", 2)\
  6780. \9\9\9\9end\
  6781. \
  6782. \9\9\9elseif consume('.') then\
  6783. \9\9\9\9if consume('.') then\
  6784. \9\9\9\9\9if consume('.') then\
  6785. \9\9\9\9\9\9toEmit = {Type = 'Symbol', Data = '...'}\
  6786. \9\9\9\9\9else\
  6787. \9\9\9\9\9\9toEmit = {Type = 'Symbol', Data = '..'}\
  6788. \9\9\9\9\9end\
  6789. \9\9\9\9else\
  6790. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = '.'}\
  6791. \9\9\9\9end\
  6792. \
  6793. \9\9\9elseif consume(':') then\
  6794. \9\9\9\9if consume(':') then\
  6795. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = '::'}\
  6796. \9\9\9\9else\
  6797. \9\9\9\9\9toEmit = {Type = 'Symbol', Data = ':'}\
  6798. \9\9\9\9end\
  6799. \
  6800. \9\9\9elseif symbols[c] then\
  6801. \9\9\9\9get()\
  6802. \9\9\9\9toEmit = {Type = 'Symbol', Data = c}\
  6803. \
  6804. \9\9\9else\
  6805. \9\9\9\9local contents, all = tryGetLongString()\
  6806. \9\9\9\9if contents then\
  6807. \9\9\9\9\9toEmit = {Type = 'String', Data = all, Constant = contents}\
  6808. \9\9\9\9else\
  6809. \9\9\9\9\9generateError(\"Unexpected Symbol `\"..c..\"` in source.\", 2)\
  6810. \9\9\9\9end\
  6811. \9\9\9end\
  6812. \
  6813. \9\9\9--add the emitted symbol, after adding some common data\
  6814. \9\9\9toEmit.LeadingWhite = leading -- table of leading whitespace/comments\
  6815. \
  6816. \9\9\9toEmit.Line = thisLine\
  6817. \9\9\9toEmit.Char = thisChar\
  6818. \9\9\9tokens[#tokens+1] = setmeta(toEmit, tokenMeta)\
  6819. \
  6820. \9\9\9--halt after eof has been emitted\
  6821. \9\9\9if toEmit.Type == 'Eof' then break end\
  6822. \9\9end\
  6823. \9end\
  6824. \
  6825. \9--public interface:\
  6826. \9local tokenList = setmetatable({\
  6827. \9\9tokens = tokens,\
  6828. \9\9savedPointers = {},\
  6829. \9\9pointer = 1\
  6830. \9}, {__index = TokenList})\
  6831. \
  6832. \9return tokenList\
  6833. end\
  6834. \
  6835. --- Create a AST tree from a Lua Source\
  6836. -- @tparam TokenList tok List of tokens from @{LexLua}\
  6837. -- @treturn table The AST tree\
  6838. local function ParseLua(tok)\
  6839. \9--- Generate an error\
  6840. \9-- @tparam string msg The error message\
  6841. \9-- @raise The produces error message\
  6842. \9local function GenerateError(msg)\
  6843. \9\9local err = \">> :\"..tok:Peek().Line..\":\"..tok:Peek().Char..\": \"..msg..\"\\n\"\
  6844. \9\9--find the line\
  6845. \9\9local lineNum = 0\
  6846. \9\9if type(src) == 'string' then\
  6847. \9\9\9for line in src:gmatch(\"[^\\n]*\\n?\") do\
  6848. \9\9\9\9if line:sub(-1,-1) == '\\n' then line = line:sub(1,-2) end\
  6849. \9\9\9\9lineNum = lineNum+1\
  6850. \9\9\9\9if lineNum == tok:Peek().Line then\
  6851. \9\9\9\9\9err = err..\">> `\"..line:gsub('\\t','    ')..\"`\\n\"\
  6852. \9\9\9\9\9for i = 1, tok:Peek().Char do\
  6853. \9\9\9\9\9\9local c = line:sub(i,i)\
  6854. \9\9\9\9\9\9if c == '\\t' then\
  6855. \9\9\9\9\9\9\9err = err..'    '\
  6856. \9\9\9\9\9\9else\
  6857. \9\9\9\9\9\9\9err = err..' '\
  6858. \9\9\9\9\9\9end\
  6859. \9\9\9\9\9end\
  6860. \9\9\9\9\9err = err..\"   ^^^^\"\
  6861. \9\9\9\9\9break\
  6862. \9\9\9\9end\
  6863. \9\9\9end\
  6864. \9\9end\
  6865. \9\9error(err)\
  6866. \9end\
  6867. \
  6868. \9local ParseExpr,\
  6869. \9      ParseStatementList,\
  6870. \9      ParseSimpleExpr,\
  6871. \9      ParsePrimaryExpr,\
  6872. \9      ParseSuffixedExpr\
  6873. \
  6874. \9--- Parse the function definition and its arguments\
  6875. \9-- @tparam Scope.Scope scope The current scope\
  6876. \9-- @tparam table tokenList A table to fill with tokens\
  6877. \9-- @treturn Node A function Node\
  6878. \9local function ParseFunctionArgsAndBody(scope, tokenList)\
  6879. \9\9local funcScope = Scope(scope)\
  6880. \9\9if not tok:ConsumeSymbol('(', tokenList) then\
  6881. \9\9\9GenerateError(\"`(` expected.\")\
  6882. \9\9end\
  6883. \
  6884. \9\9--arg list\
  6885. \9\9local argList = {}\
  6886. \9\9local isVarArg = false\
  6887. \9\9while not tok:ConsumeSymbol(')', tokenList) do\
  6888. \9\9\9if tok:Is('Ident') then\
  6889. \9\9\9\9local arg = funcScope:CreateLocal(tok:Get(tokenList).Data)\
  6890. \9\9\9\9argList[#argList+1] = arg\
  6891. \9\9\9\9if not tok:ConsumeSymbol(',', tokenList) then\
  6892. \9\9\9\9\9if tok:ConsumeSymbol(')', tokenList) then\
  6893. \9\9\9\9\9\9break\
  6894. \9\9\9\9\9else\
  6895. \9\9\9\9\9\9GenerateError(\"`)` expected.\")\
  6896. \9\9\9\9\9end\
  6897. \9\9\9\9end\
  6898. \9\9\9elseif tok:ConsumeSymbol('...', tokenList) then\
  6899. \9\9\9\9isVarArg = true\
  6900. \9\9\9\9if not tok:ConsumeSymbol(')', tokenList) then\
  6901. \9\9\9\9\9GenerateError(\"`...` must be the last argument of a function.\")\
  6902. \9\9\9\9end\
  6903. \9\9\9\9break\
  6904. \9\9\9else\
  6905. \9\9\9\9GenerateError(\"Argument name or `...` expected\")\
  6906. \9\9\9end\
  6907. \9\9end\
  6908. \
  6909. \9\9--body\
  6910. \9\9local body = ParseStatementList(funcScope)\
  6911. \
  6912. \9\9--end\
  6913. \9\9if not tok:ConsumeKeyword('end', tokenList) then\
  6914. \9\9\9GenerateError(\"`end` expected after function body\")\
  6915. \9\9end\
  6916. \
  6917. \9\9return {\
  6918. \9\9\9AstType   = 'Function',\
  6919. \9\9\9Scope     = funcScope,\
  6920. \9\9\9Arguments = argList,\
  6921. \9\9\9Body      = body,\
  6922. \9\9\9VarArg    = isVarArg,\
  6923. \9\9\9Tokens    = tokenList,\
  6924. \9\9}\
  6925. \9end\
  6926. \
  6927. \9--- Parse a simple expression\
  6928. \9-- @tparam Scope.Scope scope The current scope\
  6929. \9-- @treturn Node the resulting node\
  6930. \9function ParsePrimaryExpr(scope)\
  6931. \9\9local tokenList = {}\
  6932. \
  6933. \9\9if tok:ConsumeSymbol('(', tokenList) then\
  6934. \9\9\9local ex = ParseExpr(scope)\
  6935. \9\9\9if not tok:ConsumeSymbol(')', tokenList) then\
  6936. \9\9\9\9GenerateError(\"`)` Expected.\")\
  6937. \9\9\9end\
  6938. \
  6939. \9\9\9return {\
  6940. \9\9\9\9AstType = 'Parentheses',\
  6941. \9\9\9\9Inner   = ex,\
  6942. \9\9\9\9Tokens  = tokenList,\
  6943. \9\9\9}\
  6944. \
  6945. \9\9elseif tok:Is('Ident') then\
  6946. \9\9\9local id = tok:Get(tokenList)\
  6947. \9\9\9local var = scope:GetLocal(id.Data)\
  6948. \9\9\9if not var then\
  6949. \9\9\9\9var = scope:GetGlobal(id.Data)\
  6950. \9\9\9\9if not var then\
  6951. \9\9\9\9\9var = scope:CreateGlobal(id.Data)\
  6952. \9\9\9\9else\
  6953. \9\9\9\9\9var.References = var.References + 1\
  6954. \9\9\9\9end\
  6955. \9\9\9else\
  6956. \9\9\9\9var.References = var.References + 1\
  6957. \9\9\9end\
  6958. \
  6959. \9\9\9return {\
  6960. \9\9\9\9AstType  = 'VarExpr',\
  6961. \9\9\9\9Name     = id.Data,\
  6962. \9\9\9\9Variable = var,\
  6963. \9\9\9\9Tokens   = tokenList,\
  6964. \9\9\9}\
  6965. \9\9else\
  6966. \9\9\9GenerateError(\"primary expression expected\")\
  6967. \9\9end\
  6968. \9end\
  6969. \
  6970. \9--- Parse some table related expressions\
  6971. \9-- @tparam Scope.Scope scope The current scope\
  6972. \9-- @tparam boolean onlyDotColon Only allow '.' or ':' nodes\
  6973. \9-- @treturn Node The resulting node\
  6974. \9function ParseSuffixedExpr(scope, onlyDotColon)\
  6975. \9\9--base primary expression\
  6976. \9\9local prim = ParsePrimaryExpr(scope)\
  6977. \
  6978. \9\9while true do\
  6979. \9\9\9local tokenList = {}\
  6980. \
  6981. \9\9\9if tok:IsSymbol('.') or tok:IsSymbol(':') then\
  6982. \9\9\9\9local symb = tok:Get(tokenList).Data\
  6983. \9\9\9\9if not tok:Is('Ident') then\
  6984. \9\9\9\9\9GenerateError(\"<Ident> expected.\")\
  6985. \9\9\9\9end\
  6986. \9\9\9\9local id = tok:Get(tokenList)\
  6987. \
  6988. \9\9\9\9prim = {\
  6989. \9\9\9\9\9AstType  = 'MemberExpr',\
  6990. \9\9\9\9\9Base     = prim,\
  6991. \9\9\9\9\9Indexer  = symb,\
  6992. \9\9\9\9\9Ident    = id,\
  6993. \9\9\9\9\9Tokens   = tokenList,\
  6994. \9\9\9\9}\
  6995. \
  6996. \9\9\9elseif not onlyDotColon and tok:ConsumeSymbol('[', tokenList) then\
  6997. \9\9\9\9local ex = ParseExpr(scope)\
  6998. \9\9\9\9if not tok:ConsumeSymbol(']', tokenList) then\
  6999. \9\9\9\9\9GenerateError(\"`]` expected.\")\
  7000. \9\9\9\9end\
  7001. \
  7002. \9\9\9\9prim = {\
  7003. \9\9\9\9\9AstType  = 'IndexExpr',\
  7004. \9\9\9\9\9Base     = prim,\
  7005. \9\9\9\9\9Index    = ex,\
  7006. \9\9\9\9\9Tokens   = tokenList,\
  7007. \9\9\9\9}\
  7008. \
  7009. \9\9\9elseif not onlyDotColon and tok:ConsumeSymbol('(', tokenList) then\
  7010. \9\9\9\9local args = {}\
  7011. \9\9\9\9while not tok:ConsumeSymbol(')', tokenList) do\
  7012. \9\9\9\9\9args[#args+1] = ParseExpr(scope)\
  7013. \9\9\9\9\9if not tok:ConsumeSymbol(',', tokenList) then\
  7014. \9\9\9\9\9\9if tok:ConsumeSymbol(')', tokenList) then\
  7015. \9\9\9\9\9\9\9break\
  7016. \9\9\9\9\9\9else\
  7017. \9\9\9\9\9\9\9GenerateError(\"`)` Expected.\")\
  7018. \9\9\9\9\9\9end\
  7019. \9\9\9\9\9end\
  7020. \9\9\9\9end\
  7021. \
  7022. \9\9\9\9prim = {\
  7023. \9\9\9\9\9AstType   = 'CallExpr',\
  7024. \9\9\9\9\9Base      = prim,\
  7025. \9\9\9\9\9Arguments = args,\
  7026. \9\9\9\9\9Tokens    = tokenList,\
  7027. \9\9\9\9}\
  7028. \
  7029. \9\9\9elseif not onlyDotColon and tok:Is('String') then\
  7030. \9\9\9\9--string call\
  7031. \9\9\9\9prim = {\
  7032. \9\9\9\9\9AstType    = 'StringCallExpr',\
  7033. \9\9\9\9\9Base       = prim,\
  7034. \9\9\9\9\9Arguments  = { tok:Get(tokenList) },\
  7035. \9\9\9\9\9Tokens     = tokenList,\
  7036. \9\9\9\9}\
  7037. \
  7038. \9\9\9elseif not onlyDotColon and tok:IsSymbol('{') then\
  7039. \9\9\9\9--table call\
  7040. \9\9\9\9local ex = ParseSimpleExpr(scope)\
  7041. \9\9\9\9-- FIX: ParseExpr(scope) parses the table AND and any following binary expressions.\
  7042. \9\9\9\9-- We just want the table\
  7043. \
  7044. \9\9\9\9prim = {\
  7045. \9\9\9\9\9AstType   = 'TableCallExpr',\
  7046. \9\9\9\9\9Base      = prim,\
  7047. \9\9\9\9\9Arguments = { ex },\
  7048. \9\9\9\9\9Tokens    = tokenList,\
  7049. \9\9\9\9}\
  7050. \
  7051. \9\9\9else\
  7052. \9\9\9\9break\
  7053. \9\9\9end\
  7054. \9\9end\
  7055. \9\9return prim\
  7056. \9end\
  7057. \
  7058. \9--- Parse a simple expression (strings, numbers, booleans, varargs)\
  7059. \9-- @tparam Scope.Scope scope The current scope\
  7060. \9-- @treturn Node The resulting node\
  7061. \9function ParseSimpleExpr(scope)\
  7062. \9\9local tokenList = {}\
  7063. \
  7064. \9\9if tok:Is('Number') then\
  7065. \9\9\9return {\
  7066. \9\9\9\9AstType = 'NumberExpr',\
  7067. \9\9\9\9Value   = tok:Get(tokenList),\
  7068. \9\9\9\9Tokens  = tokenList,\
  7069. \9\9\9}\
  7070. \
  7071. \9\9elseif tok:Is('String') then\
  7072. \9\9\9return {\
  7073. \9\9\9\9AstType = 'StringExpr',\
  7074. \9\9\9\9Value   = tok:Get(tokenList),\
  7075. \9\9\9\9Tokens  = tokenList,\
  7076. \9\9\9}\
  7077. \
  7078. \9\9elseif tok:ConsumeKeyword('nil', tokenList) then\
  7079. \9\9\9return {\
  7080. \9\9\9\9AstType = 'NilExpr',\
  7081. \9\9\9\9Tokens  = tokenList,\
  7082. \9\9\9}\
  7083. \
  7084. \9\9elseif tok:IsKeyword('false') or tok:IsKeyword('true') then\
  7085. \9\9\9return {\
  7086. \9\9\9\9AstType = 'BooleanExpr',\
  7087. \9\9\9\9Value   = (tok:Get(tokenList).Data == 'true'),\
  7088. \9\9\9\9Tokens  = tokenList,\
  7089. \9\9\9}\
  7090. \
  7091. \9\9elseif tok:ConsumeSymbol('...', tokenList) then\
  7092. \9\9\9return {\
  7093. \9\9\9\9AstType  = 'DotsExpr',\
  7094. \9\9\9\9Tokens   = tokenList,\
  7095. \9\9\9}\
  7096. \
  7097. \9\9elseif tok:ConsumeSymbol('{', tokenList) then\
  7098. \9\9\9local entryList = {}\
  7099. \9\9\9local v = {\
  7100. \9\9\9\9AstType = 'ConstructorExpr',\
  7101. \9\9\9\9EntryList = entryList,\
  7102. \9\9\9\9Tokens  = tokenList,\
  7103. \9\9\9}\
  7104. \
  7105. \9\9\9while true do\
  7106. \9\9\9\9if tok:IsSymbol('[', tokenList) then\
  7107. \9\9\9\9\9--key\
  7108. \9\9\9\9\9tok:Get(tokenList)\
  7109. \9\9\9\9\9local key = ParseExpr(scope)\
  7110. \9\9\9\9\9if not tok:ConsumeSymbol(']', tokenList) then\
  7111. \9\9\9\9\9\9GenerateError(\"`]` Expected\")\
  7112. \9\9\9\9\9end\
  7113. \9\9\9\9\9if not tok:ConsumeSymbol('=', tokenList) then\
  7114. \9\9\9\9\9\9GenerateError(\"`=` Expected\")\
  7115. \9\9\9\9\9end\
  7116. \9\9\9\9\9local value = ParseExpr(scope)\
  7117. \9\9\9\9\9entryList[#entryList+1] = {\
  7118. \9\9\9\9\9\9Type  = 'Key',\
  7119. \9\9\9\9\9\9Key   = key,\
  7120. \9\9\9\9\9\9Value = value,\
  7121. \9\9\9\9\9}\
  7122. \
  7123. \9\9\9\9elseif tok:Is('Ident') then\
  7124. \9\9\9\9\9--value or key\
  7125. \9\9\9\9\9local lookahead = tok:Peek(1)\
  7126. \9\9\9\9\9if lookahead.Type == 'Symbol' and lookahead.Data == '=' then\
  7127. \9\9\9\9\9\9--we are a key\
  7128. \9\9\9\9\9\9local key = tok:Get(tokenList)\
  7129. \9\9\9\9\9\9if not tok:ConsumeSymbol('=', tokenList) then\
  7130. \9\9\9\9\9\9\9GenerateError(\"`=` Expected\")\
  7131. \9\9\9\9\9\9end\
  7132. \9\9\9\9\9\9local value = ParseExpr(scope)\
  7133. \9\9\9\9\9\9entryList[#entryList+1] = {\
  7134. \9\9\9\9\9\9\9Type  = 'KeyString',\
  7135. \9\9\9\9\9\9\9Key   = key.Data,\
  7136. \9\9\9\9\9\9\9Value = value,\
  7137. \9\9\9\9\9\9}\
  7138. \
  7139. \9\9\9\9\9else\
  7140. \9\9\9\9\9\9--we are a value\
  7141. \9\9\9\9\9\9local value = ParseExpr(scope)\
  7142. \9\9\9\9\9\9entryList[#entryList+1] = {\
  7143. \9\9\9\9\9\9\9Type = 'Value',\
  7144. \9\9\9\9\9\9\9Value = value,\
  7145. \9\9\9\9\9\9}\
  7146. \
  7147. \9\9\9\9\9end\
  7148. \9\9\9\9elseif tok:ConsumeSymbol('}', tokenList) then\
  7149. \9\9\9\9\9break\
  7150. \
  7151. \9\9\9\9else\
  7152. \9\9\9\9\9--value\
  7153. \9\9\9\9\9local value = ParseExpr(scope)\
  7154. \9\9\9\9\9entryList[#entryList+1] = {\
  7155. \9\9\9\9\9\9Type = 'Value',\
  7156. \9\9\9\9\9\9Value = value,\
  7157. \9\9\9\9\9}\
  7158. \9\9\9\9end\
  7159. \
  7160. \9\9\9\9if tok:ConsumeSymbol(';', tokenList) or tok:ConsumeSymbol(',', tokenList) then\
  7161. \9\9\9\9\9--all is good\
  7162. \9\9\9\9elseif tok:ConsumeSymbol('}', tokenList) then\
  7163. \9\9\9\9\9break\
  7164. \9\9\9\9else\
  7165. \9\9\9\9\9GenerateError(\"`}` or table entry Expected\")\
  7166. \9\9\9\9end\
  7167. \9\9\9end\
  7168. \9\9\9return v\
  7169. \
  7170. \9\9elseif tok:ConsumeKeyword('function', tokenList) then\
  7171. \9\9\9local func = ParseFunctionArgsAndBody(scope, tokenList)\
  7172. \
  7173. \9\9\9func.IsLocal = true\
  7174. \9\9\9return func\
  7175. \
  7176. \9\9else\
  7177. \9\9\9return ParseSuffixedExpr(scope)\
  7178. \9\9end\
  7179. \9end\
  7180. \
  7181. \9local unopprio = 8\
  7182. \9local priority = {\
  7183. \9\9['+'] = {6,6},\
  7184. \9\9['-'] = {6,6},\
  7185. \9\9['%'] = {7,7},\
  7186. \9\9['/'] = {7,7},\
  7187. \9\9['*'] = {7,7},\
  7188. \9\9['^'] = {10,9},\
  7189. \9\9['..'] = {5,4},\
  7190. \9\9['=='] = {3,3},\
  7191. \9\9['<'] = {3,3},\
  7192. \9\9['<='] = {3,3},\
  7193. \9\9['~='] = {3,3},\
  7194. \9\9['>'] = {3,3},\
  7195. \9\9['>='] = {3,3},\
  7196. \9\9['and'] = {2,2},\
  7197. \9\9['or'] = {1,1},\
  7198. \9}\
  7199. \
  7200. \9--- Parse an expression\
  7201. \9-- @tparam Skcope.Scope scope The current scope\
  7202. \9-- @tparam int level Current level (Optional)\
  7203. \9-- @treturn Node The resulting node\
  7204. \9function ParseExpr(scope, level)\
  7205. \9\9level = level or 0\
  7206. \9\9--base item, possibly with unop prefix\
  7207. \9\9local exp\
  7208. \9\9if unops[tok:Peek().Data] then\
  7209. \9\9\9local tokenList = {}\
  7210. \9\9\9local op = tok:Get(tokenList).Data\
  7211. \9\9\9exp = ParseExpr(scope, unopprio)\
  7212. \
  7213. \9\9\9local nodeEx = {\
  7214. \9\9\9\9AstType = 'UnopExpr',\
  7215. \9\9\9\9Rhs     = exp,\
  7216. \9\9\9\9Op      = op,\
  7217. \9\9\9\9OperatorPrecedence = unopprio,\
  7218. \9\9\9\9Tokens  = tokenList,\
  7219. \9\9\9}\
  7220. \
  7221. \9\9\9exp = nodeEx\
  7222. \9\9else\
  7223. \9\9\9exp = ParseSimpleExpr(scope)\
  7224. \9\9end\
  7225. \
  7226. \9\9--next items in chain\
  7227. \9\9while true do\
  7228. \9\9\9local prio = priority[tok:Peek().Data]\
  7229. \9\9\9if prio and prio[1] > level then\
  7230. \9\9\9\9local tokenList = {}\
  7231. \9\9\9\9local op = tok:Get(tokenList).Data\
  7232. \9\9\9\9local rhs = ParseExpr(scope, prio[2])\
  7233. \
  7234. \9\9\9\9local nodeEx = {\
  7235. \9\9\9\9\9AstType = 'BinopExpr',\
  7236. \9\9\9\9\9Lhs     = exp,\
  7237. \9\9\9\9\9Op      = op,\
  7238. \9\9\9\9\9OperatorPrecedence = prio[1],\
  7239. \9\9\9\9\9Rhs     = rhs,\
  7240. \9\9\9\9\9Tokens  = tokenList,\
  7241. \9\9\9\9}\
  7242. \
  7243. \9\9\9\9exp = nodeEx\
  7244. \9\9\9else\
  7245. \9\9\9\9break\
  7246. \9\9\9end\
  7247. \9\9end\
  7248. \
  7249. \9\9return exp\
  7250. \9end\
  7251. \
  7252. \9--- Parse a statement (if, for, while, etc...)\
  7253. \9-- @tparam Scope.Scope scope The current scope\
  7254. \9-- @treturn Node The resulting node\
  7255. \9local function ParseStatement(scope)\
  7256. \9\9local stat = nil\
  7257. \9\9local tokenList = {}\
  7258. \9\9if tok:ConsumeKeyword('if', tokenList) then\
  7259. \9\9\9--setup\
  7260. \9\9\9local clauses = {}\
  7261. \9\9\9local nodeIfStat = {\
  7262. \9\9\9\9AstType = 'IfStatement',\
  7263. \9\9\9\9Clauses = clauses,\
  7264. \9\9\9}\
  7265. \9\9\9--clauses\
  7266. \9\9\9repeat\
  7267. \9\9\9\9local nodeCond = ParseExpr(scope)\
  7268. \
  7269. \9\9\9\9if not tok:ConsumeKeyword('then', tokenList) then\
  7270. \9\9\9\9\9GenerateError(\"`then` expected.\")\
  7271. \9\9\9\9end\
  7272. \9\9\9\9local nodeBody = ParseStatementList(scope)\
  7273. \9\9\9\9clauses[#clauses+1] = {\
  7274. \9\9\9\9\9Condition = nodeCond,\
  7275. \9\9\9\9\9Body = nodeBody,\
  7276. \9\9\9\9}\
  7277. \9\9\9until not tok:ConsumeKeyword('elseif', tokenList)\
  7278. \
  7279. \9\9\9--else clause\
  7280. \9\9\9if tok:ConsumeKeyword('else', tokenList) then\
  7281. \9\9\9\9local nodeBody = ParseStatementList(scope)\
  7282. \9\9\9\9clauses[#clauses+1] = {\
  7283. \9\9\9\9\9Body = nodeBody,\
  7284. \9\9\9\9}\
  7285. \9\9\9end\
  7286. \
  7287. \9\9\9--end\
  7288. \9\9\9if not tok:ConsumeKeyword('end', tokenList) then\
  7289. \9\9\9\9GenerateError(\"`end` expected.\")\
  7290. \9\9\9end\
  7291. \
  7292. \9\9\9nodeIfStat.Tokens = tokenList\
  7293. \9\9\9stat = nodeIfStat\
  7294. \9\9elseif tok:ConsumeKeyword('while', tokenList) then\
  7295. \9\9\9--condition\
  7296. \9\9\9local nodeCond = ParseExpr(scope)\
  7297. \
  7298. \9\9\9--do\
  7299. \9\9\9if not tok:ConsumeKeyword('do', tokenList) then\
  7300. \9\9\9\9return GenerateError(\"`do` expected.\")\
  7301. \9\9\9end\
  7302. \
  7303. \9\9\9--body\
  7304. \9\9\9local nodeBody = ParseStatementList(scope)\
  7305. \
  7306. \9\9\9--end\
  7307. \9\9\9if not tok:ConsumeKeyword('end', tokenList) then\
  7308. \9\9\9\9GenerateError(\"`end` expected.\")\
  7309. \9\9\9end\
  7310. \
  7311. \9\9\9--return\
  7312. \9\9\9stat = {\
  7313. \9\9\9\9AstType = 'WhileStatement',\
  7314. \9\9\9\9Condition = nodeCond,\
  7315. \9\9\9\9Body      = nodeBody,\
  7316. \9\9\9\9Tokens    = tokenList,\
  7317. \9\9\9}\
  7318. \9\9elseif tok:ConsumeKeyword('do', tokenList) then\
  7319. \9\9\9--do block\
  7320. \9\9\9local nodeBlock = ParseStatementList(scope)\
  7321. \9\9\9if not tok:ConsumeKeyword('end', tokenList) then\
  7322. \9\9\9\9GenerateError(\"`end` expected.\")\
  7323. \9\9\9end\
  7324. \
  7325. \9\9\9stat = {\
  7326. \9\9\9\9AstType = 'DoStatement',\
  7327. \9\9\9\9Body    = nodeBlock,\
  7328. \9\9\9\9Tokens  = tokenList,\
  7329. \9\9\9}\
  7330. \9\9elseif tok:ConsumeKeyword('for', tokenList) then\
  7331. \9\9\9--for block\
  7332. \9\9\9if not tok:Is('Ident') then\
  7333. \9\9\9\9GenerateError(\"<ident> expected.\")\
  7334. \9\9\9end\
  7335. \9\9\9local baseVarName = tok:Get(tokenList)\
  7336. \9\9\9if tok:ConsumeSymbol('=', tokenList) then\
  7337. \9\9\9\9--numeric for\
  7338. \9\9\9\9local forScope = Scope(scope)\
  7339. \9\9\9\9local forVar = forScope:CreateLocal(baseVarName.Data)\
  7340. \
  7341. \9\9\9\9local startEx = ParseExpr(scope)\
  7342. \9\9\9\9if not tok:ConsumeSymbol(',', tokenList) then\
  7343. \9\9\9\9\9GenerateError(\"`,` Expected\")\
  7344. \9\9\9\9end\
  7345. \9\9\9\9local endEx = ParseExpr(scope)\
  7346. \9\9\9\9local stepEx\
  7347. \9\9\9\9if tok:ConsumeSymbol(',', tokenList) then\
  7348. \9\9\9\9\9stepEx = ParseExpr(scope)\
  7349. \9\9\9\9end\
  7350. \9\9\9\9if not tok:ConsumeKeyword('do', tokenList) then\
  7351. \9\9\9\9\9GenerateError(\"`do` expected\")\
  7352. \9\9\9\9end\
  7353. \
  7354. \9\9\9\9local body = ParseStatementList(forScope)\
  7355. \9\9\9\9if not tok:ConsumeKeyword('end', tokenList) then\
  7356. \9\9\9\9\9GenerateError(\"`end` expected\")\
  7357. \9\9\9\9end\
  7358. \
  7359. \9\9\9\9stat = {\
  7360. \9\9\9\9\9AstType  = 'NumericForStatement',\
  7361. \9\9\9\9\9Scope    = forScope,\
  7362. \9\9\9\9\9Variable = forVar,\
  7363. \9\9\9\9\9Start    = startEx,\
  7364. \9\9\9\9\9End      = endEx,\
  7365. \9\9\9\9\9Step     = stepEx,\
  7366. \9\9\9\9\9Body     = body,\
  7367. \9\9\9\9\9Tokens   = tokenList,\
  7368. \9\9\9\9}\
  7369. \9\9\9else\
  7370. \9\9\9\9--generic for\
  7371. \9\9\9\9local forScope = Scope(scope)\
  7372. \
  7373. \9\9\9\9local varList = { forScope:CreateLocal(baseVarName.Data) }\
  7374. \9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7375. \9\9\9\9\9if not tok:Is('Ident') then\
  7376. \9\9\9\9\9\9GenerateError(\"for variable expected.\")\
  7377. \9\9\9\9\9end\
  7378. \9\9\9\9\9varList[#varList+1] = forScope:CreateLocal(tok:Get(tokenList).Data)\
  7379. \9\9\9\9end\
  7380. \9\9\9\9if not tok:ConsumeKeyword('in', tokenList) then\
  7381. \9\9\9\9\9GenerateError(\"`in` expected.\")\
  7382. \9\9\9\9end\
  7383. \9\9\9\9local generators = {ParseExpr(scope)}\
  7384. \9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7385. \9\9\9\9\9generators[#generators+1] = ParseExpr(scope)\
  7386. \9\9\9\9end\
  7387. \
  7388. \9\9\9\9if not tok:ConsumeKeyword('do', tokenList) then\
  7389. \9\9\9\9\9GenerateError(\"`do` expected.\")\
  7390. \9\9\9\9end\
  7391. \
  7392. \9\9\9\9local body = ParseStatementList(forScope)\
  7393. \9\9\9\9if not tok:ConsumeKeyword('end', tokenList) then\
  7394. \9\9\9\9\9GenerateError(\"`end` expected.\")\
  7395. \9\9\9\9end\
  7396. \
  7397. \9\9\9\9stat = {\
  7398. \9\9\9\9\9AstType      = 'GenericForStatement',\
  7399. \9\9\9\9\9Scope        = forScope,\
  7400. \9\9\9\9\9VariableList = varList,\
  7401. \9\9\9\9\9Generators   = generators,\
  7402. \9\9\9\9\9Body         = body,\
  7403. \9\9\9\9\9Tokens       = tokenList,\
  7404. \9\9\9\9}\
  7405. \9\9\9end\
  7406. \9\9elseif tok:ConsumeKeyword('repeat', tokenList) then\
  7407. \9\9\9local body = ParseStatementList(scope)\
  7408. \
  7409. \9\9\9if not tok:ConsumeKeyword('until', tokenList) then\
  7410. \9\9\9\9GenerateError(\"`until` expected.\")\
  7411. \9\9\9end\
  7412. \
  7413. \9\9\9cond = ParseExpr(body.Scope)\
  7414. \
  7415. \9\9\9stat = {\
  7416. \9\9\9\9AstType   = 'RepeatStatement',\
  7417. \9\9\9\9Condition = cond,\
  7418. \9\9\9\9Body      = body,\
  7419. \9\9\9\9Tokens    = tokenList,\
  7420. \9\9\9}\
  7421. \9\9elseif tok:ConsumeKeyword('function', tokenList) then\
  7422. \9\9\9if not tok:Is('Ident') then\
  7423. \9\9\9\9GenerateError(\"Function name expected\")\
  7424. \9\9\9end\
  7425. \9\9\9local name = ParseSuffixedExpr(scope, true) --true => only dots and colons\
  7426. \
  7427. \9\9\9local func = ParseFunctionArgsAndBody(scope, tokenList)\
  7428. \
  7429. \9\9\9func.IsLocal = false\
  7430. \9\9\9func.Name    = name\
  7431. \9\9\9stat = func\
  7432. \9\9elseif tok:ConsumeKeyword('local', tokenList) then\
  7433. \9\9\9if tok:Is('Ident') then\
  7434. \9\9\9\9local varList = { tok:Get(tokenList).Data }\
  7435. \9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7436. \9\9\9\9\9if not tok:Is('Ident') then\
  7437. \9\9\9\9\9\9GenerateError(\"local var name expected\")\
  7438. \9\9\9\9\9end\
  7439. \9\9\9\9\9varList[#varList+1] = tok:Get(tokenList).Data\
  7440. \9\9\9\9end\
  7441. \
  7442. \9\9\9\9local initList = {}\
  7443. \9\9\9\9if tok:ConsumeSymbol('=', tokenList) then\
  7444. \9\9\9\9\9repeat\
  7445. \9\9\9\9\9\9initList[#initList+1] = ParseExpr(scope)\
  7446. \9\9\9\9\9until not tok:ConsumeSymbol(',', tokenList)\
  7447. \9\9\9\9end\
  7448. \
  7449. \9\9\9\9--now patch var list\
  7450. \9\9\9\9--we can't do this before getting the init list, because the init list does not\
  7451. \9\9\9\9--have the locals themselves in scope.\
  7452. \9\9\9\9for i, v in pairs(varList) do\
  7453. \9\9\9\9\9varList[i] = scope:CreateLocal(v)\
  7454. \9\9\9\9end\
  7455. \
  7456. \9\9\9\9stat = {\
  7457. \9\9\9\9\9AstType   = 'LocalStatement',\
  7458. \9\9\9\9\9LocalList = varList,\
  7459. \9\9\9\9\9InitList  = initList,\
  7460. \9\9\9\9\9Tokens    = tokenList,\
  7461. \9\9\9\9}\
  7462. \
  7463. \9\9\9elseif tok:ConsumeKeyword('function', tokenList) then\
  7464. \9\9\9\9if not tok:Is('Ident') then\
  7465. \9\9\9\9\9GenerateError(\"Function name expected\")\
  7466. \9\9\9\9end\
  7467. \9\9\9\9local name = tok:Get(tokenList).Data\
  7468. \9\9\9\9local localVar = scope:CreateLocal(name)\
  7469. \
  7470. \9\9\9\9local func = ParseFunctionArgsAndBody(scope, tokenList)\
  7471. \
  7472. \9\9\9\9func.Name    = localVar\
  7473. \9\9\9\9func.IsLocal = true\
  7474. \9\9\9\9stat = func\
  7475. \
  7476. \9\9\9else\
  7477. \9\9\9\9GenerateError(\"local var or function def expected\")\
  7478. \9\9\9end\
  7479. \9\9elseif tok:ConsumeSymbol('::', tokenList) then\
  7480. \9\9\9if not tok:Is('Ident') then\
  7481. \9\9\9\9GenerateError('Label name expected')\
  7482. \9\9\9end\
  7483. \9\9\9local label = tok:Get(tokenList).Data\
  7484. \9\9\9if not tok:ConsumeSymbol('::', tokenList) then\
  7485. \9\9\9\9GenerateError(\"`::` expected\")\
  7486. \9\9\9end\
  7487. \9\9\9stat = {\
  7488. \9\9\9\9AstType = 'LabelStatement',\
  7489. \9\9\9\9Label   = label,\
  7490. \9\9\9\9Tokens  = tokenList,\
  7491. \9\9\9}\
  7492. \9\9elseif tok:ConsumeKeyword('return', tokenList) then\
  7493. \9\9\9local exList = {}\
  7494. \9\9\9if not tok:IsKeyword('end') then\
  7495. \9\9\9\9-- Use PCall as this may produce an error\
  7496. \9\9\9\9local st, firstEx = pcall(function() return ParseExpr(scope) end)\
  7497. \9\9\9\9if st then\
  7498. \9\9\9\9\9exList[1] = firstEx\
  7499. \9\9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7500. \9\9\9\9\9\9exList[#exList+1] = ParseExpr(scope)\
  7501. \9\9\9\9\9end\
  7502. \9\9\9\9end\
  7503. \9\9\9end\
  7504. \9\9\9stat = {\
  7505. \9\9\9\9AstType   = 'ReturnStatement',\
  7506. \9\9\9\9Arguments = exList,\
  7507. \9\9\9\9Tokens    = tokenList,\
  7508. \9\9\9}\
  7509. \9\9elseif tok:ConsumeKeyword('break', tokenList) then\
  7510. \9\9\9stat = {\
  7511. \9\9\9\9AstType = 'BreakStatement',\
  7512. \9\9\9\9Tokens  = tokenList,\
  7513. \9\9\9}\
  7514. \9\9elseif tok:ConsumeKeyword('goto', tokenList) then\
  7515. \9\9\9if not tok:Is('Ident') then\
  7516. \9\9\9\9GenerateError(\"Label expected\")\
  7517. \9\9\9end\
  7518. \9\9\9local label = tok:Get(tokenList).Data\
  7519. \9\9\9stat = {\
  7520. \9\9\9\9AstType = 'GotoStatement',\
  7521. \9\9\9\9Label   = label,\
  7522. \9\9\9\9Tokens  = tokenList,\
  7523. \9\9\9}\
  7524. \9\9else\
  7525. \9\9\9--statementParseExpr\
  7526. \9\9\9local suffixed = ParseSuffixedExpr(scope)\
  7527. \
  7528. \9\9\9--assignment or call?\
  7529. \9\9\9if tok:IsSymbol(',') or tok:IsSymbol('=') then\
  7530. \9\9\9\9--check that it was not parenthesized, making it not an lvalue\
  7531. \9\9\9\9if (suffixed.ParenCount or 0) > 0 then\
  7532. \9\9\9\9\9GenerateError(\"Can not assign to parenthesized expression, is not an lvalue\")\
  7533. \9\9\9\9end\
  7534. \
  7535. \9\9\9\9--more processing needed\
  7536. \9\9\9\9local lhs = { suffixed }\
  7537. \9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7538. \9\9\9\9\9lhs[#lhs+1] = ParseSuffixedExpr(scope)\
  7539. \9\9\9\9end\
  7540. \
  7541. \9\9\9\9--equals\
  7542. \9\9\9\9if not tok:ConsumeSymbol('=', tokenList) then\
  7543. \9\9\9\9\9GenerateError(\"`=` Expected.\")\
  7544. \9\9\9\9end\
  7545. \
  7546. \9\9\9\9--rhs\
  7547. \9\9\9\9local rhs = {ParseExpr(scope)}\
  7548. \9\9\9\9while tok:ConsumeSymbol(',', tokenList) do\
  7549. \9\9\9\9\9rhs[#rhs+1] = ParseExpr(scope)\
  7550. \9\9\9\9end\
  7551. \
  7552. \9\9\9\9--done\
  7553. \9\9\9\9stat = {\
  7554. \9\9\9\9\9AstType = 'AssignmentStatement',\
  7555. \9\9\9\9\9Lhs     = lhs,\
  7556. \9\9\9\9\9Rhs     = rhs,\
  7557. \9\9\9\9\9Tokens  = tokenList,\
  7558. \9\9\9\9}\
  7559. \
  7560. \9\9\9elseif suffixed.AstType == 'CallExpr' or\
  7561. \9\9\9\9   suffixed.AstType == 'TableCallExpr' or\
  7562. \9\9\9\9   suffixed.AstType == 'StringCallExpr'\
  7563. \9\9\9then\
  7564. \9\9\9\9--it's a call statement\
  7565. \9\9\9\9stat = {\
  7566. \9\9\9\9\9AstType    = 'CallStatement',\
  7567. \9\9\9\9\9Expression = suffixed,\
  7568. \9\9\9\9\9Tokens     = tokenList,\
  7569. \9\9\9\9}\
  7570. \9\9\9else\
  7571. \9\9\9\9GenerateError(\"Assignment Statement Expected\")\
  7572. \9\9\9end\
  7573. \9\9end\
  7574. \
  7575. \9\9if tok:IsSymbol(';') then\
  7576. \9\9\9stat.Semicolon = tok:Get( stat.Tokens )\
  7577. \9\9end\
  7578. \9\9return stat\
  7579. \9end\
  7580. \
  7581. \9--- Parse a a list of statements\
  7582. \9-- @tparam Scope.Scope scope The current scope\
  7583. \9-- @treturn Node The resulting node\
  7584. \9function ParseStatementList(scope)\
  7585. \9\9local body = {}\
  7586. \9\9local nodeStatlist   = {\
  7587. \9\9\9Scope   = Scope(scope),\
  7588. \9\9\9AstType = 'Statlist',\
  7589. \9\9\9Body    = body,\
  7590. \9\9\9Tokens  = {},\
  7591. \9\9}\
  7592. \
  7593. \9\9while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do\
  7594. \9\9\9local nodeStatement = ParseStatement(nodeStatlist.Scope)\
  7595. \9\9\9--stats[#stats+1] = nodeStatement\
  7596. \9\9\9body[#body + 1] = nodeStatement\
  7597. \9\9end\
  7598. \
  7599. \9\9if tok:IsEof() then\
  7600. \9\9\9local nodeEof = {}\
  7601. \9\9\9nodeEof.AstType = 'Eof'\
  7602. \9\9\9nodeEof.Tokens  = { tok:Get() }\
  7603. \9\9\9body[#body + 1] = nodeEof\
  7604. \9\9end\
  7605. \
  7606. \9\9--nodeStatlist.Body = stats\
  7607. \9\9return nodeStatlist\
  7608. \9end\
  7609. \
  7610. \9return ParseStatementList(Scope())\
  7611. end\
  7612. \
  7613. --- @export\
  7614. return { LexLua = LexLua, ParseLua = ParseLua }\
  7615. end)\
  7616. Rebuild=_W(function()\
  7617. --- Rebuild source code from an AST\
  7618. -- Does not preserve whitespace\
  7619. -- @module lexer.Rebuild\
  7620. \
  7621. local lowerChars = Constants.LowerChars\
  7622. local upperChars = Constants.UpperChars\
  7623. local digits = Constants.Digits\
  7624. local symbols = Constants.Symbols\
  7625. \
  7626. --- Join two statements together\
  7627. -- @tparam string left The left statement\
  7628. -- @tparam string right The right statement\
  7629. -- @tparam string sep The string used to separate the characters\
  7630. -- @treturn string The joined strings\
  7631. local function JoinStatements(left, right, sep)\
  7632. \9sep = sep or ' '\
  7633. \9local leftEnd, rightStart = left:sub(-1,-1), right:sub(1,1)\
  7634. \9if upperChars[leftEnd] or lowerChars[leftEnd] or leftEnd == '_' then\
  7635. \9\9if not (rightStart == '_' or upperChars[rightStart] or lowerChars[rightStart] or digits[rightStart]) then\
  7636. \9\9\9--rightStart is left symbol, can join without seperation\
  7637. \9\9\9return left .. right\
  7638. \9\9else\
  7639. \9\9\9return left .. sep .. right\
  7640. \9\9end\
  7641. \9elseif digits[leftEnd] then\
  7642. \9\9if rightStart == '(' then\
  7643. \9\9\9--can join statements directly\
  7644. \9\9\9return left .. right\
  7645. \9\9elseif symbols[rightStart] then\
  7646. \9\9\9return left .. right\
  7647. \9\9else\
  7648. \9\9\9return left .. sep .. right\
  7649. \9\9end\
  7650. \9elseif leftEnd == '' then\
  7651. \9\9return left .. right\
  7652. \9else\
  7653. \9\9if rightStart == '(' then\
  7654. \9\9\9--don't want to accidentally call last statement, can't join directly\
  7655. \9\9\9return left .. sep .. right\
  7656. \9\9else\
  7657. \9\9\9return left .. right\
  7658. \9\9end\
  7659. \9end\
  7660. end\
  7661. \
  7662. --- Returns the minified version of an AST. Operations which are performed:\
  7663. --  - All comments and whitespace are ignored\
  7664. --  - All local variables are renamed\
  7665. -- @tparam Node ast The AST tree\
  7666. -- @treturn string The minified string\
  7667. -- @todo Ability to control minification level\
  7668. local function Minify(ast)\
  7669. \9local formatStatlist, formatExpr\
  7670. \9local count = 0\
  7671. \9local function joinStatements(left, right, set)\
  7672. \9\9if count > 150 then\
  7673. \9\9\9count = 0\
  7674. \9\9\9return left .. \"\\n\" .. right\
  7675. \9\9else\
  7676. \9\9\9return JoinStatements(left, right, sep)\
  7677. \9\9end\
  7678. \9end\
  7679. \
  7680. \9formatExpr = function(expr, precedence)\
  7681. \9\9local precedence = precedence or 0\
  7682. \9\9local currentPrecedence = 0\
  7683. \9\9local skipParens = false\
  7684. \9\9local out = \"\"\
  7685. \9\9if expr.AstType == 'VarExpr' then\
  7686. \9\9\9if expr.Variable then\
  7687. \9\9\9\9out = out..expr.Variable.Name\
  7688. \9\9\9else\
  7689. \9\9\9\9out = out..expr.Name\
  7690. \9\9\9end\
  7691. \
  7692. \9\9elseif expr.AstType == 'NumberExpr' then\
  7693. \9\9\9out = out..expr.Value.Data\
  7694. \
  7695. \9\9elseif expr.AstType == 'StringExpr' then\
  7696. \9\9\9out = out..expr.Value.Data\
  7697. \
  7698. \9\9elseif expr.AstType == 'BooleanExpr' then\
  7699. \9\9\9out = out..tostring(expr.Value)\
  7700. \
  7701. \9\9elseif expr.AstType == 'NilExpr' then\
  7702. \9\9\9out = joinStatements(out, \"nil\")\
  7703. \
  7704. \9\9elseif expr.AstType == 'BinopExpr' then\
  7705. \9\9\9currentPrecedence = expr.OperatorPrecedence\
  7706. \9\9\9out = joinStatements(out, formatExpr(expr.Lhs, currentPrecedence))\
  7707. \9\9\9out = joinStatements(out, expr.Op)\
  7708. \9\9\9out = joinStatements(out, formatExpr(expr.Rhs))\
  7709. \9\9\9if expr.Op == '^' or expr.Op == '..' then\
  7710. \9\9\9\9currentPrecedence = currentPrecedence - 1\
  7711. \9\9\9end\
  7712. \
  7713. \9\9\9if currentPrecedence < precedence then\
  7714. \9\9\9\9skipParens = false\
  7715. \9\9\9else\
  7716. \9\9\9\9skipParens = true\
  7717. \9\9\9end\
  7718. \9\9elseif expr.AstType == 'UnopExpr' then\
  7719. \9\9\9out = joinStatements(out, expr.Op)\
  7720. \9\9\9out = joinStatements(out, formatExpr(expr.Rhs))\
  7721. \
  7722. \9\9elseif expr.AstType == 'DotsExpr' then\
  7723. \9\9\9out = out..\"...\"\
  7724. \
  7725. \9\9elseif expr.AstType == 'CallExpr' then\
  7726. \9\9\9out = out..formatExpr(expr.Base)\
  7727. \9\9\9out = out..\"(\"\
  7728. \9\9\9for i = 1, #expr.Arguments do\
  7729. \9\9\9\9out = out..formatExpr(expr.Arguments[i])\
  7730. \9\9\9\9if i ~= #expr.Arguments then\
  7731. \9\9\9\9\9out = out..\",\"\
  7732. \9\9\9\9end\
  7733. \9\9\9end\
  7734. \9\9\9out = out..\")\"\
  7735. \
  7736. \9\9elseif expr.AstType == 'TableCallExpr' then\
  7737. \9\9\9out = out..formatExpr(expr.Base)\
  7738. \9\9\9out = out..formatExpr(expr.Arguments[1])\
  7739. \
  7740. \9\9elseif expr.AstType == 'StringCallExpr' then\
  7741. \9\9\9out = out..formatExpr(expr.Base)\
  7742. \9\9\9out = out..expr.Arguments[1].Data\
  7743. \
  7744. \9\9elseif expr.AstType == 'IndexExpr' then\
  7745. \9\9\9out = out..formatExpr(expr.Base)..\"[\"..formatExpr(expr.Index)..\"]\"\
  7746. \
  7747. \9\9elseif expr.AstType == 'MemberExpr' then\
  7748. \9\9\9out = out..formatExpr(expr.Base)..expr.Indexer..expr.Ident.Data\
  7749. \
  7750. \9\9elseif expr.AstType == 'Function' then\
  7751. \9\9\9expr.Scope:ObfuscateLocals()\
  7752. \9\9\9out = out..\"function(\"\
  7753. \9\9\9if #expr.Arguments > 0 then\
  7754. \9\9\9\9for i = 1, #expr.Arguments do\
  7755. \9\9\9\9\9out = out..expr.Arguments[i].Name\
  7756. \9\9\9\9\9if i ~= #expr.Arguments then\
  7757. \9\9\9\9\9\9out = out..\",\"\
  7758. \9\9\9\9\9elseif expr.VarArg then\
  7759. \9\9\9\9\9\9out = out..\",...\"\
  7760. \9\9\9\9\9end\
  7761. \9\9\9\9end\
  7762. \9\9\9elseif expr.VarArg then\
  7763. \9\9\9\9out = out..\"...\"\
  7764. \9\9\9end\
  7765. \9\9\9out = out..\")\"\
  7766. \9\9\9out = joinStatements(out, formatStatlist(expr.Body))\
  7767. \9\9\9out = joinStatements(out, \"end\")\
  7768. \
  7769. \9\9elseif expr.AstType == 'ConstructorExpr' then\
  7770. \9\9\9out = out..\"{\"\
  7771. \9\9\9for i = 1, #expr.EntryList do\
  7772. \9\9\9\9local entry = expr.EntryList[i]\
  7773. \9\9\9\9if entry.Type == 'Key' then\
  7774. \9\9\9\9\9out = out..\"[\"..formatExpr(entry.Key)..\"]=\"..formatExpr(entry.Value)\
  7775. \9\9\9\9elseif entry.Type == 'Value' then\
  7776. \9\9\9\9\9out = out..formatExpr(entry.Value)\
  7777. \9\9\9\9elseif entry.Type == 'KeyString' then\
  7778. \9\9\9\9\9out = out..entry.Key..\"=\"..formatExpr(entry.Value)\
  7779. \9\9\9\9end\
  7780. \9\9\9\9if i ~= #expr.EntryList then\
  7781. \9\9\9\9\9out = out..\",\"\
  7782. \9\9\9\9end\
  7783. \9\9\9end\
  7784. \9\9\9out = out..\"}\"\
  7785. \
  7786. \9\9elseif expr.AstType == 'Parentheses' then\
  7787. \9\9\9out = out..\"(\"..formatExpr(expr.Inner)..\")\"\
  7788. \
  7789. \9\9end\
  7790. \9\9if not skipParens then\
  7791. \9\9\9out = string.rep('(', expr.ParenCount or 0) .. out\
  7792. \9\9\9out = out .. string.rep(')', expr.ParenCount or 0)\
  7793. \9\9end\
  7794. \9\9count = count + #out\
  7795. \9\9return out\
  7796. \9end\
  7797. \
  7798. \9local formatStatement = function(statement)\
  7799. \9\9local out = ''\
  7800. \9\9if statement.AstType == 'AssignmentStatement' then\
  7801. \9\9\9for i = 1, #statement.Lhs do\
  7802. \9\9\9\9out = out..formatExpr(statement.Lhs[i])\
  7803. \9\9\9\9if i ~= #statement.Lhs then\
  7804. \9\9\9\9\9out = out..\",\"\
  7805. \9\9\9\9end\
  7806. \9\9\9end\
  7807. \9\9\9if #statement.Rhs > 0 then\
  7808. \9\9\9\9out = out..\"=\"\
  7809. \9\9\9\9for i = 1, #statement.Rhs do\
  7810. \9\9\9\9\9out = out..formatExpr(statement.Rhs[i])\
  7811. \9\9\9\9\9if i ~= #statement.Rhs then\
  7812. \9\9\9\9\9\9out = out..\",\"\
  7813. \9\9\9\9\9end\
  7814. \9\9\9\9end\
  7815. \9\9\9end\
  7816. \
  7817. \9\9elseif statement.AstType == 'CallStatement' then\
  7818. \9\9\9out = formatExpr(statement.Expression)\
  7819. \
  7820. \9\9elseif statement.AstType == 'LocalStatement' then\
  7821. \9\9\9out = out..\"local \"\
  7822. \9\9\9for i = 1, #statement.LocalList do\
  7823. \9\9\9\9out = out..statement.LocalList[i].Name\
  7824. \9\9\9\9if i ~= #statement.LocalList then\
  7825. \9\9\9\9\9out = out..\",\"\
  7826. \9\9\9\9end\
  7827. \9\9\9end\
  7828. \9\9\9if #statement.InitList > 0 then\
  7829. \9\9\9\9out = out..\"=\"\
  7830. \9\9\9\9for i = 1, #statement.InitList do\
  7831. \9\9\9\9\9out = out..formatExpr(statement.InitList[i])\
  7832. \9\9\9\9\9if i ~= #statement.InitList then\
  7833. \9\9\9\9\9\9out = out..\",\"\
  7834. \9\9\9\9\9end\
  7835. \9\9\9\9end\
  7836. \9\9\9end\
  7837. \
  7838. \9\9elseif statement.AstType == 'IfStatement' then\
  7839. \9\9\9out = joinStatements(\"if\", formatExpr(statement.Clauses[1].Condition))\
  7840. \9\9\9out = joinStatements(out, \"then\")\
  7841. \9\9\9out = joinStatements(out, formatStatlist(statement.Clauses[1].Body))\
  7842. \9\9\9for i = 2, #statement.Clauses do\
  7843. \9\9\9\9local st = statement.Clauses[i]\
  7844. \9\9\9\9if st.Condition then\
  7845. \9\9\9\9\9out = joinStatements(out, \"elseif\")\
  7846. \9\9\9\9\9out = joinStatements(out, formatExpr(st.Condition))\
  7847. \9\9\9\9\9out = joinStatements(out, \"then\")\
  7848. \9\9\9\9else\
  7849. \9\9\9\9\9out = joinStatements(out, \"else\")\
  7850. \9\9\9\9end\
  7851. \9\9\9\9out = joinStatements(out, formatStatlist(st.Body))\
  7852. \9\9\9end\
  7853. \9\9\9out = joinStatements(out, \"end\")\
  7854. \
  7855. \9\9elseif statement.AstType == 'WhileStatement' then\
  7856. \9\9\9out = joinStatements(\"while\", formatExpr(statement.Condition))\
  7857. \9\9\9out = joinStatements(out, \"do\")\
  7858. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7859. \9\9\9out = joinStatements(out, \"end\")\
  7860. \
  7861. \9\9elseif statement.AstType == 'DoStatement' then\
  7862. \9\9\9out = joinStatements(out, \"do\")\
  7863. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7864. \9\9\9out = joinStatements(out, \"end\")\
  7865. \
  7866. \9\9elseif statement.AstType == 'ReturnStatement' then\
  7867. \9\9\9out = \"return\"\
  7868. \9\9\9for i = 1, #statement.Arguments do\
  7869. \9\9\9\9out = joinStatements(out, formatExpr(statement.Arguments[i]))\
  7870. \9\9\9\9if i ~= #statement.Arguments then\
  7871. \9\9\9\9\9out = out..\",\"\
  7872. \9\9\9\9end\
  7873. \9\9\9end\
  7874. \
  7875. \9\9elseif statement.AstType == 'BreakStatement' then\
  7876. \9\9\9out = \"break\"\
  7877. \
  7878. \9\9elseif statement.AstType == 'RepeatStatement' then\
  7879. \9\9\9out = \"repeat\"\
  7880. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7881. \9\9\9out = joinStatements(out, \"until\")\
  7882. \9\9\9out = joinStatements(out, formatExpr(statement.Condition))\
  7883. \
  7884. \9\9elseif statement.AstType == 'Function' then\
  7885. \9\9\9statement.Scope:ObfuscateLocals()\
  7886. \9\9\9if statement.IsLocal then\
  7887. \9\9\9\9out = \"local\"\
  7888. \9\9\9end\
  7889. \9\9\9out = joinStatements(out, \"function \")\
  7890. \9\9\9if statement.IsLocal then\
  7891. \9\9\9\9out = out..statement.Name.Name\
  7892. \9\9\9else\
  7893. \9\9\9\9out = out..formatExpr(statement.Name)\
  7894. \9\9\9end\
  7895. \9\9\9out = out..\"(\"\
  7896. \9\9\9if #statement.Arguments > 0 then\
  7897. \9\9\9\9for i = 1, #statement.Arguments do\
  7898. \9\9\9\9\9out = out..statement.Arguments[i].Name\
  7899. \9\9\9\9\9if i ~= #statement.Arguments then\
  7900. \9\9\9\9\9\9out = out..\",\"\
  7901. \9\9\9\9\9elseif statement.VarArg then\
  7902. \9\9\9\9\9\9out = out..\",...\"\
  7903. \9\9\9\9\9end\
  7904. \9\9\9\9end\
  7905. \9\9\9elseif statement.VarArg then\
  7906. \9\9\9\9out = out..\"...\"\
  7907. \9\9\9end\
  7908. \9\9\9out = out..\")\"\
  7909. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7910. \9\9\9out = joinStatements(out, \"end\")\
  7911. \
  7912. \9\9elseif statement.AstType == 'GenericForStatement' then\
  7913. \9\9\9statement.Scope:ObfuscateLocals()\
  7914. \9\9\9out = \"for \"\
  7915. \9\9\9for i = 1, #statement.VariableList do\
  7916. \9\9\9\9out = out..statement.VariableList[i].Name\
  7917. \9\9\9\9if i ~= #statement.VariableList then\
  7918. \9\9\9\9\9out = out..\",\"\
  7919. \9\9\9\9end\
  7920. \9\9\9end\
  7921. \9\9\9out = out..\" in\"\
  7922. \9\9\9for i = 1, #statement.Generators do\
  7923. \9\9\9\9out = joinStatements(out, formatExpr(statement.Generators[i]))\
  7924. \9\9\9\9if i ~= #statement.Generators then\
  7925. \9\9\9\9\9out = joinStatements(out, ',')\
  7926. \9\9\9\9end\
  7927. \9\9\9end\
  7928. \9\9\9out = joinStatements(out, \"do\")\
  7929. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7930. \9\9\9out = joinStatements(out, \"end\")\
  7931. \
  7932. \9\9elseif statement.AstType == 'NumericForStatement' then\
  7933. \9\9\9statement.Scope:ObfuscateLocals()\
  7934. \9\9\9out = \"for \"\
  7935. \9\9\9out = out..statement.Variable.Name..\"=\"\
  7936. \9\9\9out = out..formatExpr(statement.Start)..\",\"..formatExpr(statement.End)\
  7937. \9\9\9if statement.Step then\
  7938. \9\9\9\9out = out..\",\"..formatExpr(statement.Step)\
  7939. \9\9\9end\
  7940. \9\9\9out = joinStatements(out, \"do\")\
  7941. \9\9\9out = joinStatements(out, formatStatlist(statement.Body))\
  7942. \9\9\9out = joinStatements(out, \"end\")\
  7943. \9\9elseif statement.AstType == 'LabelStatement' then\
  7944. \9\9\9out = \"::\" .. statement.Label .. \"::\"\
  7945. \9\9elseif statement.AstType == 'GotoStatement' then\
  7946. \9\9\9out = \"goto \" .. statement.Label\
  7947. \9\9elseif statement.AstType == 'Comment' then\
  7948. \9\9\9-- ignore\
  7949. \9\9elseif statement.AstType == 'Eof' then\
  7950. \9\9\9-- ignore\
  7951. \9\9else\
  7952. \9\9\9error(\"Unknown AST Type: \" .. statement.AstType)\
  7953. \9\9end\
  7954. \9\9count = count + #out\
  7955. \9\9return out\
  7956. \9end\
  7957. \
  7958. \9formatStatlist = function(statList)\
  7959. \9\9local out = ''\
  7960. \9\9statList.Scope:ObfuscateLocals()\
  7961. \9\9for _, stat in pairs(statList.Body) do\
  7962. \9\9\9out = joinStatements(out, formatStatement(stat), ';')\
  7963. \9\9end\
  7964. \9\9return out\
  7965. \9end\
  7966. \
  7967. \9return formatStatlist(ast)\
  7968. end\
  7969. \
  7970. local push, pop = os.queueEvent, coroutine.yield\
  7971. \
  7972. --- Minify a string\
  7973. -- @tparam string input The input string\
  7974. -- @treturn string The minifyied string\
  7975. local function MinifyString(input)\
  7976. \9local lex = Parse.LexLua(input)\
  7977. \9push(\"sleep\") pop(\"sleep\") -- Minifying often takes too long\
  7978. \
  7979. \9lex = Parse.ParseLua(lex)\
  7980. \9push(\"sleep\") pop(\"sleep\")\
  7981. \
  7982. \9return Minify(lex)\
  7983. end\
  7984. \
  7985. --- Minify a file\
  7986. -- @tparam string inputFile File to read from\
  7987. -- @tparam string outputFile File to write to (Defaults to inputFile)\
  7988. local function MinifyFile(inputFile, outputFile)\
  7989. \9outputFile = outputFile or inputFile\
  7990. \
  7991. \9local input = fs.open(inputFile, \"r\")\
  7992. \9local contents = input.readAll()\
  7993. \9input.close()\
  7994. \
  7995. \9contents = MinifyString(contents)\
  7996. \
  7997. \9local result = fs.open(outputFile, \"w\")\
  7998. \9result.write(contents)\
  7999. \9result.close()\
  8000. end\
  8001. \
  8002. --- @export\
  8003. return {\
  8004. \9JoinStatements = JoinStatements,\
  8005. \9Minify = Minify,\
  8006. \9MinifyString = MinifyString,\
  8007. \9MinifyFile = MinifyFile,\
  8008. }\
  8009. end)";
  8010.     ["/run.lua"] = "\
  8011. local files, main = ...\
  8012. \
  8013. if type( files ) ~= \"table\" or type( main ) ~= \"string\" then\
  8014. \9if shell and shell.getRunningProgram() == \"Flare/run.lua\" then\
  8015. \9\9return printError \"Use Flare/bin/debug to run a Flare project\"\
  8016. \9else\
  8017. \9\9return error( \"expected table files, string path, got \" .. type( files ) .. \", \" .. type( main ) )\
  8018. \9end\
  8019. end\
  8020. \
  8021. local libpath = \"Flare/lib;Flare/lib/elements\"\
  8022. local env = setmetatable( {}, { __index = _ENV or getfenv() } )\
  8023. local loaded, results = {}, {}\
  8024. local class\
  8025. \
  8026. local function extends( new )\
  8027. \9if not class.isClass( env[new] ) then\
  8028. \9\9return error( new .. \" is not a class\" )\
  8029. \9end\
  8030. \9local last = class.last()\
  8031. \9last:extends( env[new] )\
  8032. \9return function( t )\
  8033. \9\9last:mixin( t )\
  8034. \9end\
  8035. end\
  8036. \
  8037. local function loadf( content, name )\
  8038. \9local f, err = load( content, name, nil, env )\
  8039. \9if not f then\
  8040. \9\9return error( err, 0 )\
  8041. \9end\
  8042. \9return f\
  8043. end\
  8044. \
  8045. local function loadfile( path, name )\
  8046. \9local h = fs.open( path, \"r\" )\
  8047. \9local content = h.readAll() -- existence is already checked, so open() must work\
  8048. \9h.close()\
  8049. \
  8050. \9return loadf( content, name )\
  8051. end\
  8052. \
  8053. local function require( name )\
  8054. \9if loaded[name] then\
  8055. \9\9return results[name]\
  8056. \9end\
  8057. \9loaded[name] = true\
  8058. \9if files[name] then\
  8059. \9\9local ok, data = pcall( loadf( files[name], name ), name )\
  8060. \9\9if not ok then\
  8061. \9\9\9return error( data, 0 )\
  8062. \9\9end\
  8063. \9\9results[name] = data\
  8064. \9\9return data\
  8065. \9else\
  8066. \9\9for path in libpath:gmatch \"[^;]+\" do\
  8067. \9\9\9local file, fpath = path .. \"/\" .. name:gsub( \"%.\", \"/\" ), nil\
  8068. \9\9\9if fs.exists( file .. \".lua\" ) and not fs.isDir( file .. \".lua\" ) then\
  8069. \9\9\9\9fpath = file .. \".lua\"\
  8070. \9\9\9elseif fs.exists( file .. \"/init.lua\" ) and not fs.isDir( file .. \"/init.lua\" ) then\
  8071. \9\9\9\9fpath = file .. \"/init.lua\"\
  8072. \9\9\9end\
  8073. \9\9\9if fpath then\
  8074. \9\9\9\9local ok, data = pcall( loadfile( fpath, name ), name )\
  8075. \9\9\9\9if not ok then\
  8076. \9\9\9\9\9return error( data, 0 )\
  8077. \9\9\9\9end\
  8078. \9\9\9\9results[name] = data\
  8079. \9\9\9\9return data\
  8080. \9\9\9end\
  8081. \9\9end\
  8082. \9\9return error( \"file not found: '\" .. name .. \"'\" )\
  8083. \9end\
  8084. end\
  8085. \
  8086. class = require \"class\"\
  8087. \
  8088. env.require = require\
  8089. env.class = class\
  8090. env.extends = extends\
  8091. \
  8092. local screen, view\
  8093. local running = true\
  8094. local held = {}\
  8095. \
  8096. require \"UIView\"\
  8097. require \"Timer\"\
  8098. require \"graphics.ScreenCanvas\"\
  8099. \
  8100. require \"Event.Event\"\
  8101. require \"Event.MouseEvent\"\
  8102. require \"Event.KeyboardEvent\"\
  8103. require \"Event.TextEvent\"\
  8104. require \"Timer\"\
  8105. \
  8106. local Timer = env.Timer\
  8107. local Event = env.Event\
  8108. local MouseEvent = env.MouseEvent\
  8109. local KeyboardEvent = env.KeyboardEvent\
  8110. local TextEvent = env.TextEvent\
  8111. \
  8112. local application = {\
  8113. \9view = env.UIView( 0, 0, term.getSize() );\
  8114. \9terminateable = true;\
  8115. }\
  8116. \
  8117. function application:stop()\
  8118. \9running = false\
  8119. end\
  8120. \
  8121. application.view.canvas = application.view.canvas:cloneAs( env.ScreenCanvas )\
  8122. screen = application.view.canvas\
  8123. view = application.view\
  8124. \
  8125. local function getEventObject( ev )\
  8126. \9if ev[1] == \"key\" then\
  8127. \9\9held[keys.getName( ev[2] ) or ev[2]] = true\
  8128. \9\9return KeyboardEvent( Event.KEYDOWN, ev[2], held, { isRepeat = ev[3] } )\
  8129. \9elseif ev[1] == \"key_up\" then\
  8130. \9\9held[keys.getName( ev[2] ) or ev[2]] = nil\
  8131. \9\9return KeyboardEvent( Event.KEYUP, ev[2], held, {} )\
  8132. \9elseif ev[1] == \"mouse_click\" then\
  8133. \9\9return MouseEvent( Event.MOUSEDOWN, ev[3] - 1, ev[4] - 1, ev[2], true, {} )\
  8134. \9elseif ev[1] == \"mouse_up\" then\
  8135. \9\9return MouseEvent( Event.MOUSEUP, ev[3] - 1, ev[4] - 1, ev[2], true, {} )\
  8136. \9elseif ev[1] == \"mouse_scroll\" then\
  8137. \9\9return MouseEvent( Event.MOUSESCROLL, ev[3] - 1, ev[4] - 1, ev[2], true, {} )\
  8138. \9elseif ev[1] == \"mouse_drag\" then\
  8139. \9\9return MouseEvent( Event.MOUSEDRAG, ev[3] - 1, ev[4] - 1, ev[2], true, {} )\
  8140. \9elseif ev[1] == \"char\" then\
  8141. \9\9return TextEvent( Event.TEXT, ev[2], {} )\
  8142. \9elseif ev[1] == \"paste\" then\
  8143. \9\9return TextEvent( Event.PASTE, ev[2], {} )\
  8144. \9else\
  8145. \9\9return Event( ev[1], { unpack( ev, 2 ) } )\
  8146. \9end\
  8147. end\
  8148. local function redraw()\
  8149. \9if view.changed then\
  8150. \9\9screen:clear()\
  8151. \9\9view:draw()\
  8152. \9\9screen:drawToScreen()\
  8153. \9\9if screen.cursor then\
  8154. \9\9\9local cursor = screen.cursor\
  8155. \9\9\9term.setCursorPos( cursor.x + 1, cursor.y + 1 )\
  8156. \9\9\9term.setTextColour( cursor.colour )\
  8157. \9\9\9term.setCursorBlink( true )\
  8158. \9\9else\
  8159. \9\9\9term.setCursorBlink( false )\
  8160. \9\9end\
  8161. \9end\
  8162. end\
  8163. \
  8164. env.application = application\
  8165. \
  8166. require \"init\"\
  8167. \
  8168. if type( application.load ) == \"function\" then\
  8169. \9application:load( select( 3, ... ) )\
  8170. end\
  8171. \
  8172. while running do\
  8173. \9Timer.step()\
  8174. \9redraw()\
  8175. \9local event = { coroutine.yield() }\
  8176. \9if event[1] == \"term_resize\" then\
  8177. \9\9view.width, view.height = term.getSize()\
  8178. \9elseif event[1] == \"terminate\" and application.terminateable then\
  8179. \9\9running = false\
  8180. \9elseif event[1] ~= \"timer\" or not Timer.update( event[1], event[2] ) then\
  8181. \9\9view:handle( getEventObject( event ) )\
  8182. \9end\
  8183. \9view:update( Timer.getDelta() )\
  8184. end\
  8185. ";
  8186. }
  8187. fs.delete "Flare"
  8188. for k, v in pairs( files ) do
  8189.     print( "Installing Flare file " .. k )
  8190.     local h = fs.open( "Flare" .. k, "w" )
  8191.     if h then
  8192.         h.write( v )
  8193.         h.close()
  8194.     end
  8195. end
Add Comment
Please, Sign In to add comment