ComputerMan123

Titanium

Feb 9th, 2017
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local files = {
  2. ["MThemeManager.ti"]="--[[\
  3.    The MThemeManager mixin \"should\" be used by classes that want to manage objects which are themeable, the main example being the 'Application' class.\
  4. ]]\
  5. \
  6. class \"MThemeManager\" abstract() {\
  7.    themes = {}\
  8. }\
  9. \
  10. --[[\
  11.    @instance\
  12.    @desc Adds the given theme into this objects `themes` table and re-groups the themes\
  13.    @param <Theme Instance - theme>\
  14. ]]\
  15. function MThemeManager:addTheme( theme )\
  16.    self:removeTheme( theme )\
  17.    table.insert( self.themes, theme )\
  18. \
  19.    self:groupRules()\
  20. end\
  21. \
  22. --[[\
  23.    @instance\
  24.    @desc Removes the given theme from this objects `themes` table. Returns true if a theme was removed, false otherwise.\
  25. \
  26.          Re-groups the themes afterwards\
  27.    @param <Instance 'Theme'/string name - target>\
  28.    @return <boolean - success>\
  29. ]]\
  30. function MThemeManager:removeTheme( target )\
  31.    local searchName = ( type( target ) == \"string\" and true ) or ( not Titanium.typeOf( target, \"Theme\", true ) and error \"Invalid target to remove\" )\
  32.    local themes = self.themes\
  33.    for i = 1, #themes do\
  34.        if ( searchName and themes[ i ].name == target ) or ( not searchName and themes[ i ] == target ) then\
  35.            table.remove( themes, i )\
  36.            self:groupRules()\
  37. \
  38.            return true\
  39.        end\
  40.    end\
  41. end\
  42. \
  43. --[[\
  44.    @instance\
  45.    @desc Adds a theme instance named 'name' and imports the file contents from 'location' to this object\
  46.    @param <string - name>, <string - location>\
  47. ]]\
  48. function MThemeManager:importTheme( name, location )\
  49.    self:addTheme( Theme.fromFile( name, location ) )\
  50. end\
  51. \
  52. --[[\
  53.    @instance\
  54.    @desc Merges all the themes together into one theme, and groups properties by query to avoid running identical queries multiple times.\
  55. \
  56.          Saves grouped rules to 'rules', and calls :dispatchThemeRules\
  57. ]]\
  58. function MThemeManager:groupRules()\
  59.    local themes, outputRules = self.themes, {}\
  60.    for i = 1, #themes do\
  61.        for type, rules in pairs( themes[ i ].rules ) do\
  62.            if not outputRules[ type ] then outputRules[ type ] = {} end\
  63. \
  64.            local outputRulesType = outputRules[ type ]\
  65.            for query, rules in pairs( rules ) do\
  66.                if not outputRulesType[ query ] then outputRulesType[ query ] = {} end\
  67. \
  68.                local outputRulesQuery = outputRulesType[ query ]\
  69.                for r = 1, #rules do\
  70.                    outputRulesQuery[ #outputRulesQuery + 1 ] = rules[ r ]\
  71.                end\
  72.            end\
  73.        end\
  74.    end\
  75. \
  76.    self.rules = outputRules\
  77.    self:dispatchThemeRules()\
  78. end\
  79. \
  80. --[[\
  81.    @instance\
  82.    @desc Calls :retrieveThemes on the child nodes, meaning they will re-fetch their rules from the manager after clearing any current ones.\
  83. ]]\
  84. function MThemeManager:dispatchThemeRules()\
  85.    local nodes = self.collatedNodes\
  86.    for i = 1, #nodes do nodes[ i ]:retrieveThemes() end\
  87. end",
  88. ["MCallbackManager.ti"]="--[[\
  89.    @instance callbacks - table (def. {}) - The callbacks set on this instance\
  90. \
  91.    The callback manager is a mixin \"that\" can be used by classes that want to provide an easy way for a developer to assign actions on certain conditions.\
  92. \
  93.    These conditions may include node specific callbacks, like a button click or input submission.\
  94. ]]\
  95. \
  96. class \"MCallbackManager\" abstract() {\
  97.    callbacks = {}\
  98. }\
  99. \
  100. --[[\
  101.    @instance\
  102.    @desc Assigns a function 'fn' to 'callbackName'.\
  103.    @param <string - name>, <function - fn>, [string - id]\
  104. ]]\
  105. function MCallbackManager:on( callbackName, fn, id )\
  106.    if not ( type( callbackName ) == \"string\" and type( fn ) == \"function\" ) or ( id and type( id ) ~= \"string\" ) then\
  107.        return error \"Expected string, function, [string]\"\
  108.    end\
  109. \
  110.    local callbacks = self.callbacks\
  111.    if not callbacks[ callbackName ] then callbacks[ callbackName ] = {} end\
  112. \
  113.    table.insert( callbacks[ callbackName ], { fn, id } )\
  114. \
  115.    return self\
  116. end\
  117. \
  118. --[[\
  119.    @instance\
  120.    @desc Removes all callbacks for a certain condition. If an id is provided only callbacks matching that id will be executed.\
  121.    @param <string - callbackName>, [string - id]\
  122. ]]\
  123. function MCallbackManager:off( callbackName, id )\
  124.    if id then\
  125.        local callbacks = self.callbacks[ callbackName ]\
  126. \
  127.        if callbacks then\
  128.            for i = #callbacks, 1, -1 do\
  129.                if callbacks[ i ][ 2 ] == id then\
  130.                    table.remove( callbacks, i )\
  131.                end\
  132.            end\
  133.        end\
  134.    else self.callbacks[ callbackName ] = nil end\
  135. \
  136.    return self\
  137. end\
  138. \
  139. --[[\
  140.    @instance\
  141.    @desc Executes all assigned functions for 'callbackName' with 'self' and the arguments passed to this function.\
  142.    @param <string - callbackName>, [vararg - ...]\
  143. ]]\
  144. function MCallbackManager:executeCallbacks( callbackName, ... )\
  145.    local callbacks = self.callbacks[ callbackName ]\
  146. \
  147.    if callbacks then\
  148.        for i = 1, #callbacks do callbacks[ i ][ 1 ]( self, ... ) end\
  149.    end\
  150. end\
  151. \
  152. --[[\
  153.    @instance\
  154.    @desc Returns true if there are any callbacks for 'target' exist\
  155.    @param <string - target>\
  156.    @return <boolean - callbacksExist>\
  157. ]]\
  158. function MCallbackManager:canCallback( target )\
  159.    local cbs = self.callbacks[ target ]\
  160.    return cbs and #cbs > 0\
  161. end\
  162. ",
  163. ["Checkbox.ti"]="--[[\
  164.    @instance checkedMark - string (def. \"x\") - The single character used when the checkbox is checked\
  165.    @instance uncheckedMark - string (def. \" \") - The single character used when the checkbox is not checked\
  166. \
  167.    The checkbox is a node that can be toggled on and off.\
  168. \
  169.    When the checkbox is toggled, the 'toggle' callback will be fired due to mixing in MTogglable\
  170. ]]\
  171. \
  172. class \"Checkbox\" extends \"Node\" mixin \"MActivatable\" mixin \"MTogglable\" {\
  173.    checkedMark = \"x\";\
  174.    uncheckedMark = \" \";\
  175. \
  176.    allowMouse = true;\
  177. }\
  178. \
  179. --[[\
  180.    @constructor\
  181.    @desc Resolves arguments and calls super constructor\
  182.    @param <number - X>, <number - Y>\
  183. ]]\
  184. function Checkbox:__init__( ... )\
  185.    self:resolve( ... )\
  186.    self:super()\
  187. \
  188.    self:register(\"checkedMark\", \"uncheckedMark\")\
  189. end\
  190. \
  191. --[[\
  192.    @instance\
  193.    @desc Sets the checkbox to 'active' when clicked\
  194.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  195. ]]\
  196. function Checkbox:onMouseClick( event, handled, within )\
  197.    if not handled then\
  198.        self.active = within\
  199. \
  200.        if within then\
  201.            event.handled = true\
  202.        end\
  203.    end\
  204. end\
  205. \
  206. --[[\
  207.    @instance\
  208.    @desc Sets the checkbox to inactive when the mouse button is released. If released on checkbox while active 'onToggle' callback is fired and the checkbox is toggled.\
  209.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  210. ]]\
  211. function Checkbox:onMouseUp( event, handled, within )\
  212.    if not handled and within and self.active then\
  213.        self:toggle( event, handled, within )\
  214. \
  215.        event.handled = true\
  216.    end\
  217. \
  218.    self.active = false\
  219. end\
  220. \
  221. --[[\
  222.    @instance\
  223.    @desc If a label which specifies this node as its 'labelFor' paramater is clicked this function will be called, causing the checkbox to toggle\
  224.    @param <Label Instance - label>, <MouseEvent - event>, <boolean - handled>, <boolean - within>\
  225. ]]\
  226. function Checkbox:onLabelClicked( label, event, handled, within )\
  227.    self:toggle( event, handled, within, label )\
  228.    event.handled = true\
  229. end\
  230. \
  231. --[[\
  232.    @instance\
  233.    @desc Draws the checkbox to the canvas\
  234.    @param [boolean - force]\
  235. ]]\
  236. function Checkbox:draw( force )\
  237.    local raw = self.raw\
  238.    if raw.changed or force then\
  239.        local toggled, tc, bg = self.toggled\
  240.        if not self.enabled then\
  241.            tc, bg = raw.disabledColour, raw.disabledBackgroundColour\
  242.        elseif toggled then\
  243.            tc, bg = raw.toggledColour, raw.toggledBackgroundColour\
  244.        elseif self.active then\
  245.            tc, bg = raw.activeColour, raw.activeBackgroundColour\
  246.        end\
  247. \
  248.        raw.canvas:drawPoint( 1, 1, toggled and raw.checkedMark or raw.uncheckedMark, tc, bg )\
  249.        raw.changed = false\
  250.    end\
  251. end\
  252. \
  253. configureConstructor( {\
  254.    orderedArguments = { \"X\", \"Y\" },\
  255.    argumentTypes = {\
  256.        checkedMark = \"string\",\
  257.        uncheckedMark = \"string\"\
  258.    }\
  259. }, true, true )\
  260. ",
  261. ["Pane.ti"]="--[[\
  262.    A pane is a very simple node that simply draws a box at 'X', 'Y' with dimensions 'width', 'height'.\
  263. ]]\
  264. \
  265. class \"Pane\" extends \"Node\" {\
  266.    backgroundColour = colours.black;\
  267. \
  268.    allowMouse = true;\
  269.    useAnyCallbacks = true;\
  270. }\
  271. \
  272. --[[\
  273.    @instance\
  274.    @desc Resolves arguments and calls super constructor.\
  275. ]]\
  276. function Pane:__init__( ... )\
  277.    self:resolve( ... )\
  278.    self:super()\
  279. end\
  280. \
  281. --[[\
  282.    @instance\
  283.    @desc Clears the canvas, the canvas background colour becomes 'backgroundColour' during the clear.\
  284.    @param [boolean - force]\
  285. ]]\
  286. function Pane:draw( force )\
  287.    local raw = self.raw\
  288.    if raw.changed or force then\
  289.        raw.canvas:clear()\
  290.        raw.changed = false\
  291.    end\
  292. end\
  293. \
  294. --[[\
  295.    @instance\
  296.    @desc Handles any mouse events cast onto this node to prevent nodes under it being affected by them.\
  297.    @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\
  298. ]]\
  299. function Pane:onMouse( event, handled, within )\
  300.    if not within or handled then return end\
  301. \
  302.    event.handled = true\
  303. end\
  304. \
  305. configureConstructor({\
  306.    orderedArguments = {\"X\", \"Y\", \"width\", \"height\", \"backgroundColour\"}\
  307. }, true)\
  308. ",
  309. ["Page.ti"]="--[[\
  310.    The Page node is used by PageContainer nodes to hold content.\
  311. \
  312.    The width and height of the page is automatically defined when setting a parent on the page.\
  313. ]]\
  314. \
  315. class \"Page\" extends \"ScrollContainer\"\
  316. \
  317. --[[\
  318.    @setter\
  319.    @desc Sets the parent, and adjusts the width and height of the page to match that of the parent\
  320.    @param <Node Instance - parent>\
  321. ]]\
  322. function Page:setParent( parent )\
  323.    self.super:setParent( parent )\
  324. \
  325.    self.width = parent.width\
  326.    self.height = parent.height\
  327. end\
  328. \
  329. configureConstructor {\
  330.    orderedArguments = { \"id\" },\
  331.    requiredArguments = { \"id\" }\
  332. }\
  333. ",
  334. ["MNodeContainer.ti"]="local function resetNode( self, node )\
  335.    node:queueAreaReset()\
  336. \
  337.    node.parent = nil\
  338.    node.application = nil\
  339. \
  340.    if self.focusedNode == node then\
  341.        node.focused = false\
  342.    end\
  343. \
  344.    node:executeCallbacks \"remove\"\
  345. \
  346.    self.changed = true\
  347.    self:clearCollatedNodes()\
  348. end\
  349. \
  350. class \"MNodeContainer\" abstract() {\
  351.    nodes = {}\
  352. }\
  353. \
  354. --[[\
  355.    @instance\
  356.    @desc Adds a node to the object. This node will have its object and parent (this) set\
  357.    @param <Instance 'Node' - node>\
  358.    @return 'param1 (node)'\
  359. ]]\
  360. function MNodeContainer:addNode( node )\
  361.    if not Titanium.typeOf( node, \"Node\", true ) then\
  362.        return error( \"Cannot add '\"..tostring( node )..\"' as Node on '\"..tostring( self )..\"'\" )\
  363.    end\
  364. \
  365.    node.parent = self\
  366.    if Titanium.typeOf( self, \"Application\", true ) then\
  367.        node.application = self\
  368.        self.needsThemeUpdate = true\
  369.    else\
  370.        if Titanium.typeOf( self.application, \"Application\", true ) then\
  371.            node.application = self.application\
  372.            self.application.needsThemeUpdate = true\
  373.        end\
  374.    end\
  375. \
  376.    self.changed = true\
  377.    self:clearCollatedNodes()\
  378. \
  379.    table.insert( self.nodes, node )\
  380.    node:retrieveThemes()\
  381. \
  382.    if node.focused then node:focus() end\
  383.    return node\
  384. end\
  385. \
  386. --[[\
  387.    @instance\
  388.    @desc Removes a node matching the name* provided OR, if a node object is passed the actual node. Returns false if not found or (true and node)\
  389.    @param <Instance 'Node'/string name - target>\
  390.    @return <boolean - success>, [node - removedNode**]\
  391. \
  392.    *Note: In order for the node to be removed its 'name' field must match the 'name' parameter.\
  393.    **Note: Removed node will only be returned if a node was removed (and thus success 'true')\
  394. ]]\
  395. function MNodeContainer:removeNode( target )\
  396.    local searchName = type( target ) == \"string\"\
  397. \
  398.    if not searchName and not Titanium.typeOf( target, \"Node\", true ) then\
  399.        return error( \"Cannot perform search for node using target '\"..tostring( target )..\"' to remove.\" )\
  400.    end\
  401. \
  402.    local nodes, node, nodeName = self.nodes, nil\
  403.    for i = 1, #nodes do\
  404.        node = nodes[ i ]\
  405. \
  406.        if ( searchName and node.id == target ) or ( not searchName and node == target ) then\
  407.            resetNode( self, node )\
  408. \
  409.            table.remove( nodes, i )\
  410.            return true, node\
  411.        end\
  412.    end\
  413. \
  414.    return false\
  415. end\
  416. \
  417. --[[\
  418.    @instance\
  419.    @desc Resets and removes every node from the instance\
  420. ]]\
  421. function MNodeContainer:clearNodes()\
  422.    local nodes = self.nodes\
  423.    for i = #nodes, 1, -1 do\
  424.        resetNode( self, nodes[ i ] )\
  425.        table.remove( nodes, i )\
  426.    end\
  427. end\
  428. \
  429. --[[\
  430.    @instance\
  431.    @desc Searches for (and returns) a node with the 'id' specified. If 'recursive' is true and a node that contains others is found, the node will also be searched.\
  432.    @param <string - id>, [boolean - recursive]\
  433.    @return [Node Instance - node]\
  434. ]]\
  435. function MNodeContainer:getNode( id, recursive )\
  436.    local nodes, node = recursive and self.collatedNodes or self.nodes\
  437. \
  438.    for i = 1, #nodes do\
  439.        node = nodes[ i ]\
  440.        if node.id == id then\
  441.            return node\
  442.        end\
  443.    end\
  444. end\
  445. \
  446. --[[\
  447.    @instance\
  448.    @desc Returns true if the mouse event passed is in bounds of a visible child node\
  449.    @param <MouseEvent - event>\
  450.    @return [boolean - insideBounds]\
  451. ]]\
  452. function MNodeContainer:isMouseColliding( event )\
  453.    local eX, eY, nodes = event.X - self.X + 1, event.Y - self.Y + 1, self.nodes\
  454.    for i = 1, #nodes do\
  455.        local node = nodes[ i ]\
  456.        local nodeX, nodeY = node.X, node.Y\
  457. \
  458.        if node.visible and eX >= nodeX and eX <= nodeX + node.width - 1 and eY >= nodeY and eY <= nodeY + node.height - 1 then\
  459.            return true\
  460.        end\
  461.    end\
  462. \
  463.    return false\
  464. end\
  465. \
  466. --[[\
  467.    @instance\
  468.    @desc Returns a 'NodeQuery' instance containing the nodes that matched the query and methods to manipulate\
  469.    @param <string - query>\
  470.    @return <NodeQuery Instance - Query Result>\
  471. ]]\
  472. function MNodeContainer:query( query )\
  473.    return NodeQuery( self, query )\
  474. end\
  475. \
  476. --[[\
  477.    @instance\
  478.    @desc Clears the collatedNodes of all parents forcing them to update their collatedNodes cache on next retrieval\
  479. ]]\
  480. function MNodeContainer:clearCollatedNodes()\
  481.    self.collatedNodes = false\
  482. \
  483.    local parent = self.parent\
  484.    if parent then\
  485.        parent:clearCollatedNodes()\
  486.    end\
  487. end\
  488. \
  489. --[[\
  490.    @instance\
  491.    @desc If no collatedNodes (or the collateNodes are empty), the nodes are collated (:collate) and returned.\
  492.    @return <table - collatedNodes>\
  493. ]]\
  494. function MNodeContainer:getCollatedNodes()\
  495.    if not self.collatedNodes or #self.collatedNodes == 0 then\
  496.        self:collate()\
  497.    end\
  498. \
  499.    return self.collatedNodes\
  500. end\
  501. \
  502. --[[\
  503.    @instance\
  504.    @desc Caches all nodes under this container (and child containers) in 'collatedNodes'.\
  505.          This list maybe out of date if 'collate' isn't called before usage. Caching is not automatic.\
  506.    @param [table - collated]\
  507. ]]\
  508. function MNodeContainer:collate( collated )\
  509.    local collated = collated or {}\
  510. \
  511.    local nodes, node = self.nodes\
  512.    for i = 1, #nodes do\
  513.        node = nodes[ i ]\
  514.        collated[ #collated + 1 ] = node\
  515. \
  516.        local collatedNode = node.collatedNodes\
  517.        if collatedNode then\
  518.            for i = 1, #collatedNode do\
  519.                collated[ #collated + 1 ] = collatedNode[ i ]\
  520.            end\
  521.        end\
  522.    end\
  523. \
  524.    self.collatedNodes = collated\
  525. end\
  526. \
  527. --[[\
  528.    @instance\
  529.    @desc Sets the enabled property of the node to 'enabled'. Sets node's 'changed' to true.\
  530.    @param <boolean - enabled>\
  531. ]]\
  532. function MNodeContainer:setEnabled( enabled )\
  533.    self.super:setEnabled( enabled )\
  534.    if self.parentEnabled then\
  535.        local nodes = self.nodes\
  536.        for i = 1, #nodes do\
  537.            nodes[ i ].parentEnabled = enabled\
  538.        end\
  539.    end\
  540. end\
  541. \
  542. --[[\
  543.    @instance\
  544.    @desc Updates all direct children with the new 'parentEnabled' property (found using 'enabled')\
  545.    @param <boolean - enabled>\
  546. ]]\
  547. function MNodeContainer:setParentEnabled( enabled )\
  548.    self.super:setParentEnabled( enabled )\
  549. \
  550.    local newEnabled, nodes = self.enabled, self.nodes\
  551.    for i = 1, #nodes do\
  552.        nodes[ i ].parentEnabled = newEnabled\
  553.    end\
  554. end\
  555. \
  556. \
  557. --[[\
  558.    @instance\
  559.    @desc Iterates over child nodes to ensure that nodes added to this container prior to Application set are updated (with the new Application)\
  560.    @param <Application - app>\
  561. ]]\
  562. function MNodeContainer:setApplication( app )\
  563.    self.application = app\
  564. \
  565.    local nodes = self.nodes\
  566.    for i = 1, #nodes do\
  567.        nodes[ i ].application = app\
  568.    end\
  569. end\
  570. \
  571. --[[\
  572.    @instance\
  573.    @desc Clears the area provided and queues a redraw for child nodes intersecting the area.\
  574.          The content of the child node will not be update, it's content will only be drawn to it's parent.\
  575.    @param <number - x>, <number - y>, <number - width>, <number - height>\
  576. ]]\
  577. function MNodeContainer:redrawArea( x, y, width, height, xOffset, yOffset )\
  578.    y = y > 0 and y or 1\
  579.    x = x > 0 and x or 1\
  580.    if y + height - 1 > self.height then height = self.height - y + 1 end\
  581.    if x + width - 1 > self.width then width = self.width - x + 1 end\
  582. \
  583.    if not self.canvas then return end\
  584.    self.canvas:clearArea( x, y, width, height )\
  585. \
  586.    local nodes, node, nodeX, nodeY = self.nodes\
  587.    for i = 1, #nodes do\
  588.        node = nodes[ i ]\
  589.        nodeX, nodeY = node.X + ( xOffset or 0 ), node.Y + ( yOffset or 0 )\
  590. \
  591.        if not ( nodeX + node.width - 1 < x or nodeX > x + width or nodeY + node.height - 1 < y or nodeY > y + height ) then\
  592.            node.needsRedraw = true\
  593.        end\
  594.    end\
  595. \
  596.    local parent = self.parent\
  597.    if parent then\
  598.        parent:redrawArea( self.X + x - 1, self.Y + y - 1, width, height )\
  599.    end\
  600. end\
  601. \
  602. --[[\
  603.    @instance\
  604.    @desc Appends nodes loaded via TML to the Applications nodes.\
  605.    @param <string - path>\
  606. ]]\
  607. function MNodeContainer:importFromTML( path )\
  608.    TML.fromFile( self, path )\
  609.    self.changed = true\
  610. end\
  611. \
  612. --[[\
  613.    @instance\
  614.    @desc Removes all nodes from the Application and inserts those loaded via TML\
  615.    @param <string - path>\
  616. ]]\
  617. function MNodeContainer:replaceWithTML( path )\
  618.    local nodes, node = self.nodes\
  619.    for i = #nodes, 1, -1 do\
  620.        node = nodes[ i ]\
  621.        node.parent = nil\
  622.        node.application = nil\
  623. \
  624.        table.remove( nodes, i )\
  625.    end\
  626. \
  627.    self:importFromTML( path )\
  628. end\
  629. ",
  630. ["KeyEvent.ti"]="--[[\
  631.    @instance main - string (def. \"KEY\") - The main type of the event, should remain unchanged\
  632.    @instance sub - string (def. false) - The sub type of the event. If the key has been released (key_up), sub will be \"UP\", otherwise it will be \"DOWN\"\
  633.    @instance keyCode - number (def. false) - The keycode that represents the key pressed\
  634.    @instance keyName - string (def. false) - The name that represents the key pressed (keys.getName)\
  635.    @instance held - boolean (def. nil) - If true, the event was fired as a result of the key being held\
  636. ]]\
  637. \
  638. class \"KeyEvent\" extends \"Event\" {\
  639.    main = \"KEY\";\
  640. \
  641.    sub = false;\
  642. \
  643.    keyCode = false;\
  644.    keyName = false;\
  645. }\
  646. \
  647. function KeyEvent:__init__( name, key, held, sub )\
  648.    self.name = name\
  649.    self.sub = sub or name == \"key_up\" and \"UP\" or \"DOWN\"\
  650.    self.held = held\
  651. \
  652.    self.keyCode = key\
  653.    self.keyName = keys.getName( key )\
  654. \
  655.    self.data = { name, key, held }\
  656. end\
  657. ",
  658. ["MKeyHandler.ti"]="--[[\
  659.    The key handler mixin \"facilitates\" common features of objects that utilize key events. The mixin \"can\" manage hotkeys and will check them for validity\
  660.    when a key event is caught.\
  661. ]]\
  662. \
  663. class \"MKeyHandler\" abstract() {\
  664.    static = {\
  665.        keyAlias = {}\
  666.    };\
  667. \
  668.    keys = {};\
  669.    hotkeys = {};\
  670. }\
  671. \
  672. --[[\
  673.    @instance\
  674.    @desc 'Handles' a key by updating its status in 'keys'. If the event was a key down, it's status will be set to false if not held and true if it is.\
  675.          If the event is a key down, the key's status will be set to nil (use this to detect if a key is not pressed).\
  676.          The registered hotkeys will be updated everytime this function is called.\
  677.    @param <KeyEvent - event>\
  678. ]]\
  679. function MKeyHandler:handleKey( event )\
  680.    local keyCode = event.keyCode\
  681.    if event.sub == \"DOWN\" then\
  682.        self.keys[ keyCode ] = event.held\
  683.        self:checkHotkeys( keyCode )\
  684.    else\
  685.        self.keys[ keyCode ] = nil\
  686.        self:checkHotkeys()\
  687.    end\
  688. end\
  689. \
  690. --[[\
  691.    @instance\
  692.    @desc Returns true if a key is pressed (regardless of held state) and false otherwise\
  693.    @param <number - keyCode>\
  694.    @return <boolean - isPressed>\
  695. ]]\
  696. function MKeyHandler:isPressed( keyCode )\
  697.    return self.keys[ keyCode ] ~= nil\
  698. end\
  699. \
  700. --[[\
  701.    @instance\
  702.    @desc Returns true if the key is pressed and held, or false otherwise\
  703.    @param <number - keyCode>\
  704.    @return <boolean - isHeld>\
  705. ]]\
  706. function MKeyHandler:isHeld( keyCode )\
  707.    return self.keys[ keyCode ]\
  708. end\
  709. \
  710. --[[\
  711.    @instance\
  712.    @desc Breaks 'hotkey' into key names and check their status. The last element of the hotkey must be pressed last (be the active key)\
  713.          Hotkey format \"leftCtrl-leftShift-t\" (keyName-keyName-keyName)\
  714.    @param <string - hotkey>, [number - key]\
  715.    @return <boolean - hotkeyMatch>\
  716. ]]\
  717. function MKeyHandler:matchesHotkey( hotkey, key )\
  718.    for segment in hotkey:gmatch \"(%w-)%-\" do\
  719. \9\9if self.keys[ keys[ segment ] ] == nil then\
  720. \9\9\9return false\
  721.        end\
  722. \9end\
  723. \
  724. \9return key == keys[ hotkey:gsub( \".+%-\", \"\" ) ]\
  725. end\
  726. \
  727. --[[\
  728.    @instance\
  729.    @desc Registers a hotkey by adding it's callback and hotkey string to the handlers 'hotkeys'.\
  730.    @param <string - name>, <string - hotkey>, <function - callback>\
  731. ]]\
  732. function MKeyHandler:registerHotkey( name, hotkey, callback )\
  733.    if not ( type( name ) == \"string\" and type( hotkey ) == \"string\" and type( callback ) == \"function\" ) then\
  734.        return error \"Expected string, string, function\"\
  735.    end\
  736. \
  737.    self.hotkeys[ name ] = { hotkey, callback }\
  738. end\
  739. \
  740. --[[\
  741.    @instance\
  742.    @desc Iterates through the registered hotkeys and checks for matches using 'matchesHotkey'. If a hotkey matches it's registered callback is invoked\
  743.    @param [number - key]\
  744. ]]\
  745. function MKeyHandler:checkHotkeys( key )\
  746.    for _, hotkey in pairs( self.hotkeys ) do\
  747.        if self:matchesHotkey( hotkey[ 1 ], key ) then\
  748.            hotkey[ 2 ]( self, key )\
  749.        end\
  750.    end\
  751. end\
  752. ",
  753. ["TML.ti"]="--[[\
  754.    @local\
  755.    @desc Creates a table of arguments using the classes constructor configuration. This table is then unpacked (and the result returned)\
  756.    @param <Class Base - class>, <table - target>\
  757.    @return [var - args...]\
  758. ]]\
  759. local function formArguments( class, target )\
  760.    local reg = class:getRegistry()\
  761.    local constructor, alias, args = reg.constructor, reg.alias, target.arguments\
  762.    local returnArguments, trailingTable, dynamics = {}, {}, {}\
  763. \
  764.    if not constructor then return nil end\
  765.    local argumentTypes = constructor.argumentTypes\
  766. \
  767.    local ordered, set, target = constructor.orderedArguments, {}\
  768.    for i = 1, #ordered do\
  769.        target = ordered[ i ]\
  770.        local argType = argumentTypes[ alias[ target ] or target ]\
  771. \
  772.        local val = args[ target ]\
  773.        if val then\
  774.            local escaped, rest = val:match \"^(%%*)%$(.*)$\"\
  775.            if not escaped or #escaped % 2 ~= 0 then\
  776.                returnArguments[ i ] = XMLParser.convertArgType( val, argType )\
  777.            else\
  778.                returnArguments[ i ] = argType == \"string\" and \"\" or ( argType == \"number\" and 1 or ( argType == \"boolean\" ) ) or error \"invalid argument type\"\
  779.                dynamics[ target ] = DynamicEqParser( rest )\
  780.            end\
  781.        end\
  782. \
  783.        set[ ordered[ i ] ] = true\
  784.    end\
  785. \
  786.    for argName, argValue in pairs( args ) do\
  787.        if not set[ argName ] then\
  788.            trailingTable[ argName ] = XMLParser.convertArgType( argValue, argumentTypes[ alias[ argName ] or argName ] )\
  789.        end\
  790.    end\
  791. \
  792.    if next( trailingTable ) then\
  793.        returnArguments[ #ordered + 1 ] = trailingTable\
  794.    end\
  795. \
  796.    return class( unpack( returnArguments, 1, next(trailingTable) and #ordered + 1 or #ordered ) ), dynamics\
  797. end\
  798. \
  799. --[[\
  800.    The TML class \"is\" used to parse an XML tree into Titanium nodes.\
  801. ]]\
  802. \
  803. class \"TML\" {\
  804.    tree = false;\
  805.    parent = false;\
  806. }\
  807. \
  808. --[[\
  809.    @constructor\
  810.    @desc Constructs the TML instance by storing the parent and tree on 'self' and then parsing the tree.\
  811.    @param <Class Instance - parent>, <table - tree>\
  812. ]]\
  813. function TML:__init__( parent, source )\
  814.    self.parent = parent\
  815.    self.tree = XMLParser( source ).tree\
  816. \
  817.    self:parseTree()\
  818. end\
  819. \
  820. --[[\
  821.    @instance\
  822.    @desc Parses 'self.tree' by creating and adding node instances to their parents.\
  823. ]]\
  824. function TML:parseTree()\
  825.    local queue = { { self.parent, self.tree } }\
  826. \
  827.    local i, toSetup, parent, tree = 1, {}\
  828.    while i <= #queue do\
  829.        parent, tree = queue[ i ][ 1 ], queue[ i ][ 2 ]\
  830. \
  831.        local target\
  832.        for t = 1, #tree do\
  833.            target = tree[ t ]\
  834. \
  835.            if parent:can \"addTMLObject\" then\
  836.                local obj, children = parent:addTMLObject( target )\
  837.                if obj and children then\
  838.                    table.insert( queue, { obj, children } )\
  839.                end\
  840.            else\
  841.                local classArg = target.arguments[\"class\"]\
  842.                if classArg then target.arguments[\"class\"] = nil end\
  843. \
  844.                local itemClass = Titanium.getClass( target.type ) or error( \"Failed to spawn XML tree. Failed to find class '\"..target.type..\"'\" )\
  845.                if not Titanium.typeOf( itemClass, \"Node\" ) and target.type ~= \"Page\" then --TODO: Remove this page exception when issue #28 is resolved\
  846.                    error(\"Failed to spawn XML tree. Class '\"..target.type..\"' is not a valid node\")\
  847.                end\
  848. \
  849.                local itemInstance, dynamics = formArguments( itemClass, target )\
  850.                if classArg then\
  851.                    itemInstance.classes = type( itemInstance.classes ) == \"table\" and itemInstance.classes or {}\
  852.                    for className in classArg:gmatch \"%S+\" do\
  853.                        itemInstance.classes[ className ] = true\
  854.                    end\
  855.                end\
  856. \
  857.                if target.children then\
  858.                    table.insert( queue, { itemInstance, target.children } )\
  859.                end\
  860. \
  861.                toSetup[ #toSetup + 1 ] = { itemInstance, dynamics }\
  862.                if parent:can \"addNode\" then\
  863.                    parent:addNode( itemInstance )\
  864.                else\
  865.                    return error(\"Failed to spawn XML tree. \"..tostring( parent )..\" cannot contain nodes.\")\
  866.                end\
  867.            end\
  868.        end\
  869. \
  870.        i = i + 1\
  871.    end\
  872. \
  873.    for i = 1, #toSetup do\
  874.        local instance = toSetup[ i ][ 1 ]\
  875. \
  876.        for property, config in pairs( toSetup[ i ][ 2 ] ) do\
  877.            --TODO: Resolve node queries\
  878. \
  879.            instance:dynamicallyLinkProperty( property, config:resolveStacks( instance ), config.output )\
  880.        end\
  881.    end\
  882. end\
  883. \
  884. --[[\
  885.    @static\
  886.    @desc Reads the data from 'path' and creates a TML instance with the contents as the source (arg #2)\
  887.    @param <Class Instance - parent>, <string - path>\
  888.    @return <TML Instance - instance>\
  889. ]]\
  890. function TML.static.fromFile( parent, path )\
  891.    if not Titanium.isInstance( parent ) then\
  892.        return error \"Expected Titanium instance as first argument (parent)\"\
  893.    end\
  894. \
  895.    if not fs.exists( path ) then return error( \"Path \"..tostring( path )..\" cannot be found\" ) end\
  896. \
  897.    local h = fs.open( path, \"r\" )\
  898.    local content = h.readAll()\
  899.    h.close()\
  900. \
  901.    return TML( parent, content )\
  902. end\
  903. ",
  904. ["MThemeable.ti"]="local function doesLevelMatch( target, criteria, noAttr )\
  905.    if ( ( criteria.type and target.__type == criteria.type ) or criteria.type == \"*\" or not criteria.type ) and noAttr then\
  906.        return true\
  907.    end\
  908. \
  909.    if ( criteria.type and target.__type ~= criteria.type and criteria.type ~= \"*\" ) or ( criteria.id and target.id ~= criteria.id ) or ( criteria.classes and not target:hasClass( criteria.classes ) ) then\
  910.        return false\
  911.    end\
  912. \
  913.    return true\
  914. end\
  915. \
  916. local function doParentsMatch( parents, level, criteria, noAttr )\
  917.    for i = level, #parents do\
  918.        local parent = parents[ i ]\
  919.        if doesLevelMatch( parent, criteria, noAttr ) then\
  920.            return true, i\
  921.        end\
  922.    end\
  923. \
  924.    return false\
  925. end\
  926. \
  927. local function doesMatchQuery( node, queryString, noAttr )\
  928.    -- Get a parsed version of the query\
  929.    local query = QueryParser( queryString ).query[ 1 ]\
  930. \
  931.    -- Collate the nodes parents once here\
  932.    local last, levels = node, {}\
  933.    while true do\
  934.        local p = last.parent\
  935.        if p then\
  936.            levels[ #levels + 1 ] = p\
  937.            last = p\
  938.        else break end\
  939.    end\
  940. \
  941. \
  942.    -- If the last part of the query (the node part) does not match the node, return false\
  943.    if not doesLevelMatch( node, query[ #query ], noAttr ) then\
  944.        return false\
  945.    end\
  946. \
  947. \
  948.    -- Work backwards from the end of the query (-1), to the beginning.\
  949.    local parentLevel = 1\
  950.    for i = #query - 1, 1, -1 do\
  951.        local part = query[ i ]\
  952.        if part.direct then\
  953.            if doesLevelMatch( levels[ parentLevel ], part, noAttr ) then\
  954.                parentLevel = parentLevel + 1\
  955.            else return false end\
  956.        else\
  957.            local success, levels = doParentsMatch( levels, parentLevel, part, noAttr )\
  958.            if success then\
  959.                parentLevel = parentLevel + levels\
  960.            else return false end\
  961.        end\
  962.    end\
  963. \
  964.    return true\
  965. end\
  966. \
  967. --[[\
  968.    The MThemeable mixin \"facilitates\" the use of themes on objects.\
  969.    It allows properties to be registered allowing the object to monitor property changes and apply them correctly.\
  970. \
  971.    The mixin \"stores\" all properties set directly on the object in `mainValues`. These values are prioritised over values from themes unless the theme rule is designated as 'important'.\
  972. \
  973.    This mixin \"no\" longer handles property links as this functionality has been replaced by a more robust system 'MPropertyManager'.\
  974. ]]\
  975. \
  976. class \"MThemeable\" abstract() {\
  977.    isUpdating = false;\
  978.    hooked = false;\
  979. \
  980.    properties = {};\
  981.    classes = {};\
  982.    applicableRules = {};\
  983. \
  984.    mainValues = {}; --TODO: Preserve dynamic values, instead of the dynamic value output so that they can be restored fully\
  985.    defaultValues = {};\
  986. }\
  987. \
  988. --[[\
  989.    @instance\
  990.    @desc Registers the properties provided. These properties are monitored for changes.\
  991.    @param <string - property>, ...\
  992. ]]\
  993. function MThemeable:register( ... )\
  994.    if self.hooked then return error \"Cannot register new properties while hooked. Unhook the theme handler before registering new properties\" end\
  995. \
  996.    local args = { ... }\
  997.    for i = 1, #args do\
  998.        self.properties[ args[ i ] ] = true\
  999.    end\
  1000. end\
  1001. \
  1002. --[[\
  1003.    @instance\
  1004.    @desc Unregisters properties provided\
  1005.    @param <string - property>, ...\
  1006. ]]\
  1007. function MThemeable:unregister( ... )\
  1008.    if self.hooked then return error \"Cannot unregister properties while hooked. Unhook the theme handler before unregistering properties\" end\
  1009. \
  1010.    local args = { ... }\
  1011.    for i = 1, #args do\
  1012.        self.properties[ args[ i ] ] = nil\
  1013.    end\
  1014. end\
  1015. \
  1016. --[[\
  1017.    @instance\
  1018.    @desc Hooks into the instance by creating watch instructions that inform the mixin \"of\" property changes.\
  1019. ]]\
  1020. function MThemeable:hook()\
  1021.    if self.hooked then return error \"Failed to hook theme handler. Already hooked\" end\
  1022. \
  1023.    for property in pairs( self.properties ) do\
  1024.        self:watchProperty( property, function( _, __, value )\
  1025.            if self.isUpdating then return end\
  1026. \
  1027.            self.mainValues[ property ] = value\
  1028.            return self:fetchPropertyValue( property )\
  1029.        end, \"THEME_HOOK_\" .. self.__ID )\
  1030. \
  1031.        self[ self.__resolved[ property ] and \"mainValues\" or \"defaultValues\" ][ property ] = self[ property ]\
  1032.    end\
  1033. \
  1034.    self.hooked = true\
  1035. end\
  1036. \
  1037. --[[\
  1038.    @instance\
  1039.    @desc Removes the watch instructions originating from this mixin (identified by 'THEME_HOOK_<ID>' name)\
  1040. ]]\
  1041. function MThemeable:unhook()\
  1042.    if not self.hooked then return error \"Failed to unhook theme handler. Already unhooked\" end\
  1043.    self:unwatchProperty( \"*\", \"THEME_HOOK_\" .. self.__ID )\
  1044. \
  1045.    self.hooked = false\
  1046. end\
  1047. \
  1048. --[[\
  1049.    @instance\
  1050.    @desc Returns the value for the property given. The value is found by checking themes for property values (taking into account 'important' rules). If no rule is found in the themes, the\
  1051.          value from 'mainValues' is returned instead.\
  1052.    @param <string - property>\
  1053.    @return <any - value>, <table - rule>\
  1054. ]]\
  1055. function MThemeable:fetchPropertyValue( property )\
  1056.    local newValue = self.mainValues[ property ]\
  1057.    local requireImportant = newValue ~= nil\
  1058. \
  1059.    local rules, r, usedRule = self.applicableRules\
  1060.    for i = 1, #rules do\
  1061.        r = rules[ i ]\
  1062.        if r.property == property and ( not requireImportant or r.important ) then\
  1063.            newValue = r.value\
  1064.            usedRule = r\
  1065. \
  1066.            if r.important then requireImportant = true end\
  1067.        end\
  1068.    end\
  1069. \
  1070.    return newValue, usedRule\
  1071. end\
  1072. \
  1073. --[[\
  1074.    @instance\
  1075.    @desc Fetches the value from the application by checking themes for valid rules. If a theme value is found it is applied directly (this does trigger the setter)\
  1076.    @param <string - property>\
  1077. ]]\
  1078. function MThemeable:updateProperty( property )\
  1079.    if not self.properties[ property ] then\
  1080.        return error( \"Failed to update property '\"..tostring( property )..\"'. Property not registered\" )\
  1081.    end\
  1082. \
  1083.    --TODO: Look into removing old dynamic values when the theme rule is no longer used. Manual dynamicValues need to be distinguished from those created automatically. Ugh.\
  1084.    local new, rule = self:fetchPropertyValue( property )\
  1085.    self.isUpdating = true\
  1086.    if new ~= nil then\
  1087.        if rule and rule.isDynamic then\
  1088.            if self.binds[ property ] then self:unlinkProperties( self, property ) end\
  1089. \
  1090.            self:dynamicallyLinkProperty( property, new:resolveStacks( self ), new.output )\
  1091.        else\
  1092.            self[ property ] = new\
  1093.        end\
  1094.    else\
  1095.        self[ property ] = self.defaultValues[ property ]\
  1096.    end\
  1097. \
  1098.    self.isUpdating = false\
  1099. end\
  1100. \
  1101. --[[\
  1102.    @instance\
  1103.    @desc Stores rules that can be applied to this node (excluding class \"and\" ids) in 'applicableRules'. These rules are then filtered by id and classes into 'applicableRules'.\
  1104. \
  1105.          If 'preserveOld', the old rules will NOT be cleared.\
  1106.    @param [boolean - preserveOld]\
  1107. ]]\
  1108. function MThemeable:retrieveThemes( preserveOld )\
  1109.    if not preserveOld then self.rules = {} end\
  1110.    if not self.application then return false end\
  1111. \
  1112.    local types, aliases\
  1113. \
  1114.    local selfRules, targetRules = self.rules, self.application.rules\
  1115. \
  1116.    if not targetRules then return end\
  1117.    for _, value in pairs { targetRules.ANY, targetRules[ self.__type ] } do\
  1118.        local q = 1\
  1119.        for query, properties in pairs( value ) do\
  1120.            if doesMatchQuery( self, query, true ) then\
  1121.                if not selfRules[ query ] then selfRules[ query ] = {} end\
  1122.                local rules, prop = selfRules[ query ]\
  1123.                for i = 1, #properties do\
  1124.                    prop = properties[ i ]\
  1125. \
  1126.                    if prop.computeType then\
  1127.                        if not aliases then\
  1128.                            local reg = Titanium.getClass( self.__type ).getRegistry()\
  1129.                            aliases = reg.alias\
  1130. \
  1131.                            local constructor = reg.constructor\
  1132.                            if constructor then\
  1133.                                types = constructor.argumentTypes or {}\
  1134.                            else types = {} end\
  1135.                        end\
  1136. \
  1137.                        rules[ #rules + 1 ] = { property = prop.property, important = prop.important, value = XMLParser.convertArgType( prop.value, types[ aliases[ prop.property ] or prop.property ] ) }\
  1138.                    else\
  1139.                        rules[ #rules + 1 ] = prop\
  1140.                    end\
  1141.                end\
  1142.            end\
  1143.        end\
  1144.    end\
  1145. \
  1146.    self:filterThemes()\
  1147. end\
  1148. \
  1149. --[[\
  1150.    @instance\
  1151.    @desc Checks each owned rule, only applying the rules that have queries that match exactly (owned rules are not dependent on classes/ids, where as applicableRules are)\
  1152. ]]\
  1153. function MThemeable:filterThemes()\
  1154.    local aRules = {}\
  1155.    for query, properties in pairs( self.rules ) do\
  1156.        if doesMatchQuery( self, query ) then\
  1157.            -- The query is an exact match, add the properties to 'applicableRules', where the node will fetch it's properties\
  1158.            for i = 1, #properties do aRules[ #aRules + 1 ] = properties[ i ] end\
  1159.        end\
  1160.    end\
  1161. \
  1162.    self.applicableRules = aRules\
  1163.    self:updateProperties()\
  1164. end\
  1165. \
  1166. --[[\
  1167.    @instance\
  1168.    @desc Updates each registered property\
  1169. ]]\
  1170. function MThemeable:updateProperties()\
  1171.    for property in pairs( self.properties ) do\
  1172.        self:updateProperty( property )\
  1173.    end\
  1174. end\
  1175. \
  1176. --[[\
  1177.    @instance\
  1178.    @desc Adds class 'class' and updated TML properties\
  1179.    @param <string - class>\
  1180. ]]\
  1181. function MThemeable:addClass( class )\
  1182.    self.classes[ class ] = true\
  1183.    self:filterThemes()\
  1184. end\
  1185. \
  1186. --[[\
  1187.    @instance\
  1188.    @desc Removes class 'class' and updated TML properties\
  1189.    @param <string - class>\
  1190. ]]\
  1191. function MThemeable:removeClass( class )\
  1192.    self.classes[ class ] = nil\
  1193.    self:filterThemes()\
  1194. end\
  1195. \
  1196. --[[\
  1197.    @instance\
  1198.    @desc Shortcut method to set class \"if\" 'has' is truthy or remove it otherwise (updates properties too)\
  1199.    @param <string - class>, [var - has]\
  1200. ]]\
  1201. function MThemeable:setClass( class, has )\
  1202.    self.classes[ class ] = has and true or nil\
  1203.    self:filterThemes()\
  1204. end\
  1205. \
  1206. --[[\
  1207.    @instance\
  1208.    @desc Returns true if:\
  1209.          - Param passed is a table and all values inside the table are set as classes on this object\
  1210.          - Param is string and this object has that class\
  1211.    @param <string|table - class>\
  1212.    @return <boolean - has>\
  1213. ]]\
  1214. function MThemeable:hasClass( t )\
  1215.    if type( t ) == \"string\" then\
  1216.        return self.classes[ t ]\
  1217.    elseif type( t ) == \"table\" then\
  1218.        for i = 1, #t do\
  1219.            if not self.classes[ t[ i ] ] then\
  1220.                return false\
  1221.            end\
  1222.        end\
  1223. \
  1224.        return true\
  1225.    else\
  1226.        return error(\"Invalid target '\"..tostring( t )..\"' for class check\")\
  1227.    end\
  1228. end\
  1229. ",
  1230. ["Class.lua"]="--[[\
  1231.    Titanium Class System - Version 1.1\
  1232. \
  1233.    Copyright (c) Harry Felton 2016\
  1234. ]]\
  1235. \
  1236. local classes, classRegistry, currentClass, currentRegistry = {}, {}\
  1237. local reserved = {\
  1238.    static = true,\
  1239.    super = true,\
  1240.    __type = true,\
  1241.    isCompiled = true,\
  1242.    compile = true\
  1243. }\
  1244. \
  1245. local missingClassLoader\
  1246. \
  1247. local getters = setmetatable( {}, { __index = function( self, name )\
  1248.    self[ name ] = \"get\" .. name:sub( 1, 1 ):upper() .. name:sub( 2 )\
  1249. \
  1250.    return self[ name ]\
  1251. end })\
  1252. \
  1253. local setters = setmetatable( {}, { __index = function( self, name )\
  1254.    self[ name ] = \"set\"..name:sub(1, 1):upper()..name:sub(2)\
  1255. \
  1256.    return self[ name ]\
  1257. end })\
  1258. \
  1259. local isNumber = {}\
  1260. for i = 0, 15 do isNumber[2 ^ i] = true end\
  1261. \
  1262. --[[ Constants ]]--\
  1263. local ERROR_BUG = \"\\nPlease report this via GitHub @ hbomb79/Titanium\"\
  1264. local ERROR_GLOBAL = \"Failed to %s to %s\\n\"\
  1265. local ERROR_NOT_BUILDING = \"No class is currently being built. Declare a class before invoking '%s'\"\
  1266. \
  1267. --[[ Helper functions ]]--\
  1268. local function throw( ... )\
  1269.    return error( table.concat( { ... }, \"\\n\" ) , 2 )\
  1270. end\
  1271. \
  1272. local function verifyClassEntry( target )\
  1273.    return type( target ) == \"string\" and type( classes[ target ] ) == \"table\" and type( classRegistry[ target ] ) == \"table\"\
  1274. end\
  1275. \
  1276. local function verifyClassObject( target, autoCompile )\
  1277.    if not Titanium.isClass( target ) then\
  1278.        return false\
  1279.    end\
  1280. \
  1281.    if autoCompile and not target:isCompiled() then\
  1282.        target:compile()\
  1283.    end\
  1284. \
  1285.    return true\
  1286. end\
  1287. \
  1288. local function isBuilding( ... )\
  1289.    if type( currentRegistry ) == \"table\" or type( currentClass ) == \"table\" then\
  1290.        if not ( currentRegistry and currentClass ) then\
  1291.            throw(\"Failed to validate currently building class objects\", \"The 'currentClass' and 'currentRegistry' variables are not both set\\n\", \"currentClass: \"..tostring( currentClass ), \"currentRegistry: \"..tostring( currentRegistry ), ERROR_BUG)\
  1292.        end\
  1293.        return true\
  1294.    end\
  1295. \
  1296.    if #({ ... }) > 0 then\
  1297.        return throw( ... )\
  1298.    else\
  1299.        return false\
  1300.    end\
  1301. end\
  1302. \
  1303. local function getClass( target )\
  1304.    if verifyClassEntry( target ) then\
  1305.        return classes[ target ]\
  1306.    elseif missingClassLoader then\
  1307.        local oC, oCReg = currentClass, currentRegistry\
  1308.        currentClass, currentRegistry = nil, nil\
  1309. \
  1310.        missingClassLoader( target )\
  1311.        local c = classes[ target ]\
  1312.        if not verifyClassObject( c, true ) then\
  1313.            throw(\"Failed to load missing class '\"..target..\"'.\\n\", \"The missing class loader failed to load class '\"..target..\"'.\\n\")\
  1314.        end\
  1315. \
  1316.        currentClass, currentRegistry = oC, oCReg\
  1317. \
  1318.        return c\
  1319.    else throw(\"Class '\"..target..\"' not found\") end\
  1320. end\
  1321. \
  1322. local function deepCopy( source )\
  1323.    if type( source ) == \"table\" then\
  1324.        local copy = {}\
  1325.        for key, value in next, source, nil do\
  1326.            copy[ deepCopy( key ) ] = deepCopy( value )\
  1327.        end\
  1328.        return copy\
  1329.    else\
  1330.        return source\
  1331.    end\
  1332. end\
  1333. \
  1334. local function propertyCatch( tbl )\
  1335.    if type( tbl ) == \"table\" then\
  1336.        if tbl.static then\
  1337.            if type( tbl.static ) ~= \"table\" then\
  1338.                throw(\"Invalid entity found in trailing property table\", \"Expected type 'table' for entity 'static'. Found: \"..tostring( tbl.static ), \"\\nThe 'static' entity is for storing static variables, refactor your class declaration.\")\
  1339.            end\
  1340. \
  1341. \
  1342.            local cStatic, cOwnedStatics = currentRegistry.static, currentRegistry.ownedStatics\
  1343.            for key, value in pairs( tbl.static ) do\
  1344.                if reserved[ key ] then\
  1345.                    throw(\
  1346.                        \"Failed to set static key '\"..key..\"' on building class '\"..currentRegistry.type..\"'\",\
  1347.                        \"'\"..key..\"' is reserved by Titanium for internal processes.\"\
  1348.                    )\
  1349.                end\
  1350. \
  1351.                cStatic[ key ] = value\
  1352.                cOwnedStatics[ key ] = type( value ) == \"nil\" and nil or true\
  1353.            end\
  1354. \
  1355.            tbl.static = nil\
  1356.        end\
  1357. \
  1358.        local cKeys, cOwned = currentRegistry.keys, currentRegistry.ownedKeys\
  1359.        for key, value in pairs( tbl ) do\
  1360.            cKeys[ key ] = value\
  1361.            cOwned[ key ] = type( value ) == \"nil\" and nil or true\
  1362.        end\
  1363.    elseif type( tbl ) ~= \"nil\" then\
  1364.        throw(\"Invalid trailing entity caught\\n\", \"An invalid object was caught trailing the class declaration for '\"..currentRegistry.type..\"'.\\n\", \"Object: '\"..tostring( tbl )..\"' (\"..type( tbl )..\")\"..\"\\n\", \"Expected [tbl | nil]\")\
  1365.    end\
  1366. end\
  1367. \
  1368. local function createFunctionWrapper( fn, superLevel )\
  1369.    return function( instance, ... )\
  1370.        local oldSuper = instance:setSuper( superLevel )\
  1371. \
  1372.        local v = { fn( ... ) }\
  1373. \
  1374.        instance.super = oldSuper\
  1375. \
  1376.        return unpack( v )\
  1377.    end\
  1378. end\
  1379. \
  1380. \
  1381. --[[ Local Functions ]]--\
  1382. local function compileSupers( targets )\
  1383.    local inheritedKeys, superMatrix = {}, {}, {}\
  1384.    local function compileSuper( target, id )\
  1385.        local factories = {}\
  1386.        local targetType = target.__type\
  1387.        local targetReg = classRegistry[ targetType ]\
  1388. \
  1389.        for key, value in pairs( targetReg.keys ) do\
  1390.            if not reserved[ key ] then\
  1391.                local toInsert = value\
  1392.                if type( value ) == \"function\" then\
  1393.                    factories[ key ] = function( instance, ... )\
  1394.                        --print(\"Super factory for \"..key..\"\\nArgs: \"..( function( args ) local s = \"\"; for i = 1, #args do s = s .. \" - \" .. tostring( args[ i ] ) .. \"\\n\" end return s end )( { ... } ))\
  1395.                        local oldSuper = instance:setSuper( id + 1 )\
  1396.                        local v = { value( instance, ... ) }\
  1397. \
  1398.                        instance.super = oldSuper\
  1399.                        return unpack( v )\
  1400.                    end\
  1401. \
  1402.                    toInsert = factories[ key ]\
  1403.                end\
  1404. \
  1405.                inheritedKeys[ key ] = toInsert\
  1406.            end\
  1407.        end\
  1408. \
  1409.        -- Handle inheritance\
  1410.        for key, value in pairs( inheritedKeys ) do\
  1411.            if type( value ) == \"function\" and not factories[ key ] then\
  1412.                factories[ key ] = value\
  1413.            end\
  1414.        end\
  1415. \
  1416.        superMatrix[ id ] = { factories, targetReg }\
  1417.    end\
  1418. \
  1419.    for id = #targets, 1, -1 do compileSuper( targets[ id ], id ) end\
  1420. \
  1421.    return inheritedKeys, function( instance )\
  1422.        local matrix, matrixReady = {}\
  1423.        local function generateMatrix( target, id )\
  1424.            local superTarget, matrixTbl, matrixMt = superMatrix[ id ], {}, {}\
  1425.            local factories, reg = superTarget[ 1 ], superTarget[ 2 ]\
  1426. \
  1427.            matrixTbl.__type = reg.type\
  1428. \
  1429.            local raw, owned, wrapCache, factory, upSuper = reg.raw, reg.ownedKeys, {}\
  1430. \
  1431.            function matrixMt:__tostring()\
  1432.                return \"[\"..reg.type..\"] Super #\"..id..\" of '\"..instance.__type..\"' instance\"\
  1433.            end\
  1434.            function matrixMt:__newindex( k, v )\
  1435.                if not matrixReady and k == \"super\" then\
  1436.                    upSuper = v\
  1437.                    return\
  1438.                end\
  1439. \
  1440.                throw(\"Cannot set keys on super. Illegal action.\")\
  1441.            end\
  1442.            function matrixMt:__index( k )\
  1443.                factory = factories[ k ]\
  1444.                if factory then\
  1445.                    if not wrapCache[ k ] then\
  1446.                        wrapCache[ k ] = (function( _, ... )\
  1447.                            return factory( instance, ... )\
  1448.                        end)\
  1449.                    end\
  1450. \
  1451.                    return wrapCache[ k ]\
  1452.                else\
  1453.                    if k == \"super\" then\
  1454.                        return upSuper\
  1455.                    else\
  1456.                        return throw(\"Cannot lookup value for key '\"..k..\"' on super\", \"Only functions can be accessed from supers.\")\
  1457.                    end\
  1458.                end\
  1459.            end\
  1460.            function matrixMt:__call( instance, ... )\
  1461.                local init = self.__init__\
  1462.                if type( init ) == \"function\" then\
  1463.                    return init( self, ... )\
  1464.                else\
  1465.                    throw(\"Failed to execute super constructor. __init__ method not found\")\
  1466.                end\
  1467.            end\
  1468. \
  1469.            setmetatable( matrixTbl, matrixMt )\
  1470.            return matrixTbl\
  1471.        end\
  1472. \
  1473.        local last = matrix\
  1474.        for id = 1, #targets do\
  1475.            last.super = generateMatrix( targets[ id ], id )\
  1476.            last = last.super\
  1477.        end\
  1478. \
  1479.        martixReady = true\
  1480.        return matrix\
  1481.    end\
  1482. end\
  1483. local function mergeValues( a, b )\
  1484.    if type( a ) == \"table\" and type( b ) == \"table\" then\
  1485.        local merged = deepCopy( a ) or throw( \"Invalid base table for merging.\" )\
  1486. \
  1487.        if #b == 0 and next( b ) then\
  1488.            for key, value in pairs( b ) do merged[ key ] = value end\
  1489.        elseif #b > 0 then\
  1490.            for i = 1, #b do table.insert( merged, i, b[ i ] ) end\
  1491.        end\
  1492. \
  1493.        return merged\
  1494.    end\
  1495. \
  1496.    return b == nil and a or b\
  1497. end\
  1498. local constructorTargets = { \"orderedArguments\", \"requiredArguments\", \"argumentTypes\", \"useProxy\" }\
  1499. local function compileConstructor( superReg )\
  1500.    local constructorConfiguration = {}\
  1501. \
  1502.    local superConfig, currentConfig = superReg.constructor, currentRegistry.constructor\
  1503.    if not currentConfig and superConfig then\
  1504.        currentRegistry.constructor = superConfig\
  1505.        return\
  1506.    elseif currentConfig and not superConfig then\
  1507.        superConfig = {}\
  1508.    elseif not currentConfig and not superConfig then\
  1509.        return\
  1510.    end\
  1511. \
  1512.    local constructorKey\
  1513.    for i = 1, #constructorTargets do\
  1514.        constructorKey = constructorTargets[ i ]\
  1515.        if not ( ( constructorKey == \"orderedArguments\" and currentConfig.clearOrdered ) or ( constructorKey == \"requiredArguments\" and currentConfig.clearRequired ) ) then\
  1516.            currentConfig[ constructorKey ] = mergeValues( superConfig[ constructorKey ], currentConfig[ constructorKey ] )\
  1517.        end\
  1518.    end\
  1519. end\
  1520. local function compileCurrent()\
  1521.    isBuilding(\
  1522.        \"Cannot compile current class.\",\
  1523.        \"No class is being built at time of call. Declare a class be invoking 'compileCurrent'\"\
  1524.    )\
  1525.    local ownedKeys, ownedStatics, allMixins = currentRegistry.ownedKeys, currentRegistry.ownedStatics, currentRegistry.allMixins\
  1526. \
  1527.    -- Mixins\
  1528.    local cConstructor = currentRegistry.constructor\
  1529.    for target in pairs( currentRegistry.mixins ) do\
  1530.        allMixins[ target ] = true\
  1531.        local reg = classRegistry[ target ]\
  1532. \
  1533.        local t = { { reg.keys, currentRegistry.keys, ownedKeys }, { reg.static, currentRegistry.static, ownedStatics }, { reg.alias, currentRegistry.alias, currentRegistry.alias } }\
  1534.        for i = 1, #t do\
  1535.            local source, target, owned = t[ i ][ 1 ], t[ i ][ 2 ], t[ i ][ 3 ]\
  1536.            for key, value in pairs( source ) do\
  1537.                if not owned[ key ] then\
  1538.                    target[ key ] = value\
  1539.                end\
  1540.            end\
  1541.        end\
  1542. \
  1543.        local constructor = reg.constructor\
  1544.        if constructor then\
  1545.            if constructor.clearOrdered then cConstructor.orderedArguments = nil end\
  1546.            if constructor.clearRequired then cConstructor.requiredArguments = nil end\
  1547. \
  1548.            local target\
  1549.            for i = 1, #constructorTargets do\
  1550.                target = constructorTargets[ i ]\
  1551.                cConstructor[ target ] = mergeValues( cConstructor[ target ], constructor and constructor[ target ] )\
  1552.            end\
  1553.        end\
  1554.    end\
  1555. \
  1556.    -- Supers\
  1557.    local superKeys\
  1558.    if currentRegistry.super then\
  1559.        local supers = {}\
  1560. \
  1561.        local last, c, newC = currentRegistry.super.target\
  1562.        while last do\
  1563.            c = getClass( last, true )\
  1564. \
  1565.            supers[ #supers + 1 ] = c\
  1566.            newC = classRegistry[ last ].super\
  1567.            last = newC and newC.target or false\
  1568.        end\
  1569. \
  1570.        superKeys, currentRegistry.super.matrix = compileSupers( supers )\
  1571. \
  1572.        -- Inherit alias from previous super\
  1573.        local currentAlias = currentRegistry.alias\
  1574.        for alias, redirect in pairs( classRegistry[ supers[ 1 ].__type ].alias ) do\
  1575.            if currentAlias[ alias ] == nil then\
  1576.                currentAlias[ alias ] = redirect\
  1577.            end\
  1578.        end\
  1579. \
  1580.        for mName in pairs( classRegistry[ supers[ 1 ].__type ].allMixins ) do\
  1581.            allMixins[ mName ] = true\
  1582.        end\
  1583. \
  1584.        compileConstructor( classRegistry[ supers[ 1 ].__type ] )\
  1585.    end\
  1586. \
  1587.    -- Generate instance function wrappers\
  1588.    local instanceWrappers, instanceVariables = {}, {}\
  1589.    for key, value in pairs( currentRegistry.keys ) do\
  1590.        if type( value ) == \"function\" then\
  1591.            instanceWrappers[ key ] = true\
  1592.            instanceVariables[ key ] = createFunctionWrapper( value, 1 )\
  1593.        else\
  1594.            instanceVariables[ key ] = value\
  1595.        end\
  1596.    end\
  1597.    if superKeys then\
  1598.        for key, value in pairs( superKeys ) do\
  1599.            if not instanceVariables[ key ] then\
  1600.                if type( value ) == \"function\" then\
  1601.                    instanceWrappers[ key ] = true\
  1602.                    instanceVariables[ key ] = function( _, ... ) return value( ... ) end\
  1603.                else\
  1604.                    instanceVariables[ key ] = value\
  1605.                end\
  1606.            end\
  1607.        end\
  1608.    end\
  1609. \
  1610.    -- Finish compilation\
  1611.    currentRegistry.initialWrappers = instanceWrappers\
  1612.    currentRegistry.initialKeys = instanceVariables\
  1613.    currentRegistry.compiled = true\
  1614. \
  1615.    currentRegistry = nil\
  1616.    currentClass = nil\
  1617. \
  1618. end\
  1619. local function spawn( target, ... )\
  1620.    if not verifyClassEntry( target ) then\
  1621.        throw(\
  1622.            \"Failed to spawn class instance of '\"..tostring( target )..\"'\",\
  1623.            \"A class entity named '\"..tostring( target )..\"' doesn't exist.\"\
  1624.        )\
  1625.    end\
  1626. \
  1627.    local classEntry, classReg = classes[ target ], classRegistry[ target ]\
  1628.    if classReg.abstract or not classReg.compiled then\
  1629.        throw(\
  1630.            \"Failed to instantiate class '\"..classReg.type..\"'\",\
  1631.            \"Class '\"..classReg.type..\"' \"..(classReg.abstract and \"is abstract. Cannot instantiate abstract class.\" or \"has not been compiled. Cannot instantiate.\")\
  1632.        )\
  1633.    end\
  1634. \
  1635.    local wrappers, wrapperCache = deepCopy( classReg.initialWrappers ), {}\
  1636.    local raw = deepCopy( classReg.initialKeys )\
  1637.    local alias = classReg.alias\
  1638. \
  1639.    local instanceID = string.sub( tostring( raw ), 8 )\
  1640. \
  1641.    local supers = {}\
  1642.    local function indexSupers( last, ID )\
  1643.        while last.super do\
  1644.            supers[ ID ] = last.super\
  1645.            last = last.super\
  1646.            ID = ID + 1\
  1647.        end\
  1648.    end\
  1649. \
  1650.    local instanceObj, instanceMt = { raw = raw, __type = target, __instance = true, __ID = instanceID }, { __metatable = {} }\
  1651.    local getting, useGetters, setting, useSetters = {}, true, {}, true\
  1652.    function instanceMt:__index( k )\
  1653.        local k = alias[ k ] or k\
  1654. \
  1655.        local getFn = getters[ k ]\
  1656.        if useGetters and not getting[ k ] and wrappers[ getFn ] then\
  1657.            getting[ k ] = true\
  1658.            local v = self[ getFn ]( self )\
  1659.            getting[ k ] = nil\
  1660. \
  1661.            return v\
  1662.        elseif wrappers[ k ] then\
  1663.            if not wrapperCache[ k ] then\
  1664.                wrapperCache[ k ] = function( ... )\
  1665.                    --print(\"Wrapper for \"..k..\". Arguments: \"..( function( args ) local s = \"\"; for i = 1, #args do s = s .. \" - \" .. tostring( args[ i ] ) .. \"\\n\" end return s end )( { ... } ) )\
  1666.                    return raw[ k ]( self, ... )\
  1667.                end\
  1668.            end\
  1669. \
  1670.            return wrapperCache[ k ]\
  1671.        else return raw[ k ] end\
  1672.    end\
  1673. \
  1674.    function instanceMt:__newindex( k, v )\
  1675.        local k = alias[ k ] or k\
  1676. \
  1677.        local setFn = setters[ k ]\
  1678.        if useSetters and not setting[ k ] and wrappers[ setFn ] then\
  1679.            setting[ k ] = true\
  1680.            self[ setFn ]( self, v )\
  1681.            setting[ k ] = nil\
  1682.        elseif type( v ) == \"function\" and useSetters then\
  1683.            wrappers[ k ] = true\
  1684.            raw[ k ] = createFunctionWrapper( v, 1 )\
  1685.        else\
  1686.            wrappers[ k ] = nil\
  1687.            raw[ k ] = v\
  1688.        end\
  1689.    end\
  1690. \
  1691.    function instanceMt:__tostring()\
  1692.        return \"[Instance] \"..target..\" (\"..instanceID..\")\"\
  1693.    end\
  1694. \
  1695.    if classReg.super then\
  1696.        instanceObj.super = classReg.super.matrix( instanceObj ).super\
  1697.        indexSupers( instanceObj, 1 )\
  1698.    end\
  1699. \
  1700.    local old\
  1701.    function instanceObj:setSuper( target )\
  1702.        old, instanceObj.super = instanceObj.super, supers[ target ]\
  1703.        return old\
  1704.    end\
  1705. \
  1706.    local function setSymKey( key, value )\
  1707.        useSetters = false\
  1708.        instanceObj[ key ] = value\
  1709.        useSetters = true\
  1710.    end\
  1711. \
  1712.    local resolved\
  1713.    local resolvedArguments = {}\
  1714.    function instanceObj:resolve( ... )\
  1715.        if resolved then return false end\
  1716. \
  1717.        local args, config = { ... }, classReg.constructor\
  1718.        if not config then\
  1719.            throw(\"Failed to resolve \"..tostring( instance )..\" constructor arguments. No configuration has been set via 'configureConstructor'.\")\
  1720.        end\
  1721. \
  1722.        local configRequired, configOrdered, configTypes, configProxy = config.requiredArguments, config.orderedArguments, config.argumentTypes or {}, config.useProxy or {}\
  1723. \
  1724.        local argumentsRequired = {}\
  1725.        if configRequired then\
  1726.            local target = type( configRequired ) == \"table\" and configRequired or configOrdered\
  1727. \
  1728.            for i = 1, #target do argumentsRequired[ target[ i ] ] = true end\
  1729.        end\
  1730. \
  1731.        local orderedMatrix = {}\
  1732.        for i = 1, #configOrdered do orderedMatrix[ configOrdered[ i ] ] = i end\
  1733. \
  1734.        local proxyAll, proxyMatrix = type( configProxy ) == \"boolean\" and configProxy, {}\
  1735.        if not proxyAll then\
  1736.            for i = 1, #configProxy do proxyMatrix[ configProxy[ i ] ] = true end\
  1737.        end\
  1738. \
  1739.        local function handleArgument( position, name, value )\
  1740.            local desiredType = configTypes[ name ]\
  1741.            if desiredType == \"colour\" or desiredType == \"color\" then\
  1742.                --TODO: Check if number is valid (maybe?)\
  1743.                desiredType = \"number\"\
  1744.            end\
  1745. \
  1746.            if desiredType and type( value ) ~= desiredType then\
  1747.                return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. Invalid type for argument '\"..name..\"'. Type \"..configTypes[ name ]..\" expected, \"..type( value )..\" was received.\")\
  1748.            end\
  1749. \
  1750.            resolvedArguments[ name ], argumentsRequired[ name ] = true, nil\
  1751.            if proxyAll or proxyMatrix[ name ] then\
  1752.                self[ name ] = value\
  1753.            else\
  1754.                setSymKey( name, value )\
  1755.            end\
  1756.        end\
  1757. \
  1758.        for iter, value in pairs( args ) do\
  1759.            if configOrdered[ iter ] then\
  1760.                handleArgument( iter, configOrdered[ iter ], value )\
  1761.            elseif type( value ) == \"table\" then\
  1762.                for key, v in pairs( value ) do\
  1763.                    handleArgument( orderedMatrix[ key ], key, v )\
  1764.                end\
  1765.            else\
  1766.                return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. Invalid argument found at ordered position \"..iter..\".\")\
  1767.            end\
  1768.        end\
  1769. \
  1770.        if next( argumentsRequired ) then\
  1771.            local str, name = \"\"\
  1772.            local function append( cnt )\
  1773.                str = str ..\"- \"..cnt..\"\\n\"\
  1774.            end\
  1775. \
  1776.            return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. The following required arguments were not provided:\\n\\n\"..(function()\
  1777.                str = \"Ordered:\\n\"\
  1778.                for i = 1, #configOrdered do\
  1779.                    name = configOrdered[ i ]\
  1780.                    if argumentsRequired[ name ] then\
  1781.                        append( name .. \" [#\"..i..\"]\" )\
  1782.                        argumentsRequired[ name ] = nil\
  1783.                    end\
  1784.                end\
  1785. \
  1786.                if next( argumentsRequired ) then\
  1787.                    str = str .. \"\\nTrailing:\\n\"\
  1788.                    for name, _ in pairs( argumentsRequired ) do append( name ) end\
  1789.                end\
  1790. \
  1791.                return str\
  1792.            end)())\
  1793.        end\
  1794. \
  1795.        resolved = true\
  1796.        return true\
  1797.    end\
  1798.    instanceObj.__resolved = resolvedArguments\
  1799. \
  1800.    function instanceObj:can( method )\
  1801.        return wrappers[ method ] or false\
  1802.    end\
  1803. \
  1804.    local locked = { __index = true, __newindex = true }\
  1805.    function instanceObj:setMetaMethod( method, fn )\
  1806.        if type( method ) ~= \"string\" then\
  1807.            throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Expected string for argument #1, got '\"..tostring( method )..\"' of type \"..type( method ) )\
  1808.        elseif type( fn ) ~= \"function\" then\
  1809.            throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Expected function for argument #2, got '\"..tostring( fn )..\"' of type \"..type( fn ) )\
  1810.        end\
  1811. \
  1812.        method = \"__\"..method\
  1813.        if locked[ method ] then\
  1814.            throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Metamethod locked\" )\
  1815.        end\
  1816. \
  1817.        instanceMt[ method ] = fn\
  1818.    end\
  1819. \
  1820.    function instanceObj:lockMetaMethod( method )\
  1821.        if type( method ) ~= \"string\" then\
  1822.            throw( \"Failed to lock metamethod '\"..tostring( method )..\"'\", \"Expected string, got '\"..tostring( method )..\"' of type \"..type( method ) )\
  1823.        end\
  1824. \
  1825.        locked[ \"__\"..method ] = true\
  1826.    end\
  1827. \
  1828.    setmetatable( instanceObj, instanceMt )\
  1829.    if type( instanceObj.__init__ ) == \"function\" then instanceObj:__init__( ... ) end\
  1830. \
  1831.    for mName in pairs( classReg.allMixins ) do\
  1832.        if type( instanceObj[ mName ] ) == \"function\" then instanceObj[ mName ]( instanceObj ) end\
  1833.    end\
  1834. \
  1835.    if type( instanceObj.__postInit__ ) == \"function\" then instanceObj:__postInit__( ... ) end\
  1836. \
  1837.    return instanceObj\
  1838. end\
  1839. \
  1840. \
  1841. --[[ Global functions ]]--\
  1842. \
  1843. function class( name )\
  1844.    if isBuilding() then\
  1845.        throw(\
  1846.            \"Failed to declare class '\"..tostring( name )..\"'\",\
  1847.            \"A new class cannot be declared until the currently building class has been compiled.\",\
  1848.            \"\\nCompile '\"..tostring( currentRegistry.type )..\"' before declaring '\"..tostring( name )..\"'\"\
  1849.        )\
  1850.    end\
  1851. \
  1852.    local function nameErr( reason )\
  1853.        throw( \"Failed to declare class '\"..tostring( name )..\"'\\n\", string.format( \"Class name %s is not valid. %s\", tostring( name ), reason ) )\
  1854.    end\
  1855. \
  1856.    if type( name ) ~= \"string\" then\
  1857.        nameErr \"Class names must be a string\"\
  1858.    elseif not name:find \"%a\" then\
  1859.        nameErr \"No alphabetic characters could be found\"\
  1860.    elseif name:find \"%d\" then\
  1861.        nameErr \"Class names cannot contain digits\"\
  1862.    elseif classes[ name ] then\
  1863.        nameErr \"A class with that name already exists\"\
  1864.    elseif reserved[ name ] then\
  1865.        nameErr (\"'\"..name..\"' is reserved for Titanium processes\")\
  1866.    else\
  1867.        local char = name:sub( 1, 1 )\
  1868.        if char ~= char:upper() then\
  1869.            nameErr \"Class names must begin with an uppercase character\"\
  1870.        end\
  1871.    end\
  1872. \
  1873.    local classReg = {\
  1874.        type = name,\
  1875. \
  1876.        static = {},\
  1877.        keys = {},\
  1878.        ownedStatics = {},\
  1879.        ownedKeys = {},\
  1880. \
  1881.        initialWrappers = {},\
  1882.        initialKeys = {},\
  1883. \
  1884.        mixins = {},\
  1885.        allMixins = {},\
  1886.        alias = {},\
  1887. \
  1888.        constructor = {},\
  1889.        super = false,\
  1890. \
  1891.        compiled = false,\
  1892.        abstract = false\
  1893.    }\
  1894. \
  1895.    -- Class metatable\
  1896.    local classMt = { __metatable = {} }\
  1897.    function classMt:__tostring()\
  1898.        return (classReg.compiled and \"[Compiled] \" or \"\") .. \"Class '\"..name..\"'\"\
  1899.    end\
  1900. \
  1901.    local keys, owned = classReg.keys, classReg.ownedKeys\
  1902.    local staticKeys, staticOwned = classReg.static, classReg.ownedStatics\
  1903.    function classMt:__newindex( k, v )\
  1904.        if classReg.compiled then\
  1905.            throw(\
  1906.                \"Failed to set key on class base.\", \"\",\
  1907.                \"This class base is compiled, once a class base is compiled new keys cannot be added to it\",\
  1908.                \"\\nPerhaps you meant to set the static key '\"..name..\".static.\"..k..\"' instead.\"\
  1909.            )\
  1910.        end\
  1911. \
  1912.        keys[ k ] = v\
  1913.        owned[ k ] = type( v ) == \"nil\" and nil or true\
  1914.    end\
  1915.    function classMt:__index( k )\
  1916.        if owned[ k ] then\
  1917.            throw (\
  1918.                \"Access to key '\"..k..\"' denied.\",\
  1919.                \"Instance keys cannot be accessed from a class base, regardless of compiled state\",\
  1920.                classReg.ownedStatics[ k ] and \"\\nPerhaps you meant to access the static variable '\" .. name .. \".static.\".. k .. \"' instead\" or nil\
  1921.            )\
  1922.        elseif staticOwned[ k ] then\
  1923.            return staticKeys[ k ]\
  1924.        end\
  1925.    end\
  1926.    function classMt:__call( ... )\
  1927.        return spawn( name, ... )\
  1928.    end\
  1929. \
  1930.    -- Static metatable\
  1931.    local staticMt = { __index = staticKeys }\
  1932.    function staticMt:__newindex( k, v )\
  1933.        staticKeys[ k ] = v\
  1934.        staticOwned[ k ] = type( v ) == \"nil\" and nil or true\
  1935.    end\
  1936. \
  1937.    -- Class object\
  1938.    local classObj = { __type = name }\
  1939.    classObj.static = setmetatable( {}, staticMt )\
  1940.    classObj.compile = compileCurrent\
  1941. \
  1942.    function classObj:isCompiled() return classReg.compiled end\
  1943. \
  1944.    function classObj:getRegistry() return classReg end\
  1945. \
  1946.    setmetatable( classObj, classMt )\
  1947. \
  1948.    -- Export\
  1949.    currentRegistry = classReg\
  1950.    classRegistry[ name ] = classReg\
  1951. \
  1952.    currentClass = classObj\
  1953.    classes[ name ] = classObj\
  1954. \
  1955.    _G[ name ] = classObj\
  1956. \
  1957.    return propertyCatch\
  1958. end\
  1959. \
  1960. function extends( name )\
  1961.    isBuilding(\
  1962.        string.format( ERROR_GLOBAL, \"extend\", \"target class '\"..tostring( name )..\"'\" ), \"\",\
  1963.        string.format( ERROR_NOT_BUILDING, \"extends\" )\
  1964.    )\
  1965. \
  1966.    currentRegistry.super = {\
  1967.        target = name\
  1968.    }\
  1969.    return propertyCatch\
  1970. end\
  1971. \
  1972. function mixin( name )\
  1973.    if type( name ) ~= \"string\" then\
  1974.        throw(\"Invalid mixin target '\"..tostring( name )..\"'\")\
  1975.    end\
  1976. \
  1977.    isBuilding(\
  1978.        string.format( ERROR_GLOBAL, \"mixin\", \"target class '\".. name ..\"'\" ),\
  1979.        string.format( ERROR_NOT_BUILDING, \"mixin\" )\
  1980.    )\
  1981. \
  1982.    local mixins = currentRegistry.mixins\
  1983.    if mixins[ name ] then\
  1984.        throw(\
  1985.            string.format( ERROR_GLOBAL, \"mixin class '\".. name ..\"'\", \"class '\"..currentRegistry.type)\
  1986.            \"'\".. name ..\"' has already been mixed in to this target class.\"\
  1987.        )\
  1988.    end\
  1989. \
  1990.    if not getClass( name, true ) then\
  1991.        throw(\
  1992.            string.format( ERROR_GLOBAL, \"mixin class '\".. name ..\"'\", \"class '\"..currentRegistry.type ),\
  1993.            \"The mixin class '\".. name ..\"' failed to load\"\
  1994.        )\
  1995.    end\
  1996. \
  1997.    mixins[ name ] = true\
  1998.    return propertyCatch\
  1999. end\
  2000. \
  2001. function abstract()\
  2002.    isBuilding(\
  2003.        \"Failed to enforce abstract class policy\\n\",\
  2004.        string.format( ERROR_NOT_BUILDING, \"abstract\" )\
  2005.    )\
  2006. \
  2007.    currentRegistry.abstract = true\
  2008.    return propertyCatch\
  2009. end\
  2010. \
  2011. function alias( target )\
  2012.    local FAIL_MSG = \"Failed to implement alias targets\\n\"\
  2013.    isBuilding( FAIL_MSG, string.format( ERROR_NOT_BUILDING, \"alias\" ) )\
  2014. \
  2015.    local tbl = type( target ) == \"table\" and target or (\
  2016.        type( target ) == \"string\" and (\
  2017.            type( _G[ target ] ) == \"table\" and _G[ target ] or throw( FAIL_MSG, \"Failed to find '\"..tostring( target )..\"' table in global environment.\" )\
  2018.        ) or throw( FAIL_MSG, \"Expected type table as target, got '\"..tostring( target )..\"' of type \"..type( target ) )\
  2019.    )\
  2020. \
  2021.    local cAlias = currentRegistry.alias\
  2022.    for alias, redirect in pairs( tbl ) do\
  2023.        cAlias[ alias ] = redirect\
  2024.    end\
  2025. \
  2026.    return propertyCatch\
  2027. end\
  2028. \
  2029. function configureConstructor( config, clearOrdered, clearRequired )\
  2030.    isBuilding(\
  2031.        \"Failed to configure class constructor\\n\",\
  2032.        string.format( ERROR_NOT_BUILDING, \"configureConstructor\" )\
  2033.    )\
  2034. \
  2035.    if type( config ) ~= \"table\" then\
  2036.        throw (\
  2037.            \"Failed to configure class constructor\\n\",\
  2038.            \"Expected type 'table' as first argument\"\
  2039.        )\
  2040.    end\
  2041. \
  2042.    local constructor = {\
  2043.        clearOrdered = clearOrdered or nil,\
  2044.        clearRequired = clearRequired or nil\
  2045.    }\
  2046.    for key, value in pairs( config ) do constructor[ key ] = value end\
  2047. \
  2048.    currentRegistry.constructor = constructor\
  2049.    return propertyCatch\
  2050. end\
  2051. \
  2052. --[[ Class Library ]]--\
  2053. Titanium = {}\
  2054. \
  2055. function Titanium.getGetterName( property ) return getters[ property ] end\
  2056. \
  2057. function Titanium.getSetterName( property ) return setters[ property ] end\
  2058. \
  2059. function Titanium.getClass( name )\
  2060.    return classes[ name ]\
  2061. end\
  2062. \
  2063. function Titanium.getClasses()\
  2064.    return classes\
  2065. end\
  2066. \
  2067. function Titanium.isClass( target )\
  2068.    return type( target ) == \"table\" and type( target.__type ) == \"string\" and verifyClassEntry( target.__type )\
  2069. end\
  2070. \
  2071. function Titanium.isInstance( target )\
  2072.    return Titanium.isClass( target ) and target.__instance\
  2073. end\
  2074. \
  2075. function Titanium.typeOf( target, classType, instance )\
  2076.    if not Titanium.isClass( target ) or ( instance and not Titanium.isInstance( target ) ) then\
  2077.        return false\
  2078.    end\
  2079. \
  2080.    local targetReg = classRegistry[ target.__type ]\
  2081. \
  2082.    return targetReg.type == classType or ( targetReg.super and Titanium.typeOf( classes[ targetReg.super.target ], classType ) ) or false\
  2083. end\
  2084. \
  2085. function Titanium.mixesIn( target, mixinName )\
  2086.    if not Titanium.isClass( target ) then return false end\
  2087. \
  2088.    return classRegistry[ target.__type ].allMixins[ mixinName ]\
  2089. end\
  2090. \
  2091. function Titanium.setClassLoader( fn )\
  2092.    if type( fn ) ~= \"function\" then\
  2093.        throw( \"Failed to set class loader\", \"Value '\"..tostring( fn )..\"' is invalid, expected function\" )\
  2094.    end\
  2095. \
  2096.    missingClassLoader = fn\
  2097. end\
  2098. \
  2099. local preprocessTargets = {\"class\", \"extends\", \"alias\", \"mixin\"}\
  2100. function Titanium.preprocess( text )\
  2101.    local keyword\
  2102.    for i = 1, #preprocessTargets do\
  2103.        keyword = preprocessTargets[ i ]\
  2104. \
  2105.        for value in text:gmatch( keyword .. \" ([_%a][_%w]*)%s\" ) do\
  2106.            text = text:gsub( keyword .. \" \" .. value, keyword..\" \\\"\"..value..\"\\\"\" )\
  2107.        end\
  2108.    end\
  2109. \
  2110.    for name in text:gmatch( \"abstract class (\\\".[^%s]+\\\")\" ) do\
  2111.        text = text:gsub( \"abstract class \"..name, \"class \"..name..\" abstract()\" )\
  2112.    end\
  2113. \
  2114.    return text\
  2115. end\
  2116. ",
  2117. ["Titanium.lua"]="--[[\
  2118.    Event declaration\
  2119.    =================\
  2120. \
  2121.    Titanium needs to know what class types to spawn when an event is spawned, for flexibility this can be edited whenever you see fit. The matrix\
  2122.    starts blank, so we define basic events here. (on event type 'key', spawn instance of type 'value')\
  2123. ]]\
  2124. Event.static.matrix = {\
  2125.    mouse_click = MouseEvent,\
  2126.    mouse_drag = MouseEvent,\
  2127.    mouse_up = MouseEvent,\
  2128.    mouse_scroll = MouseEvent,\
  2129. \
  2130.    key = KeyEvent,\
  2131.    key_up = KeyEvent,\
  2132. \
  2133.    char = CharEvent\
  2134. }\
  2135. \
  2136. --[[\
  2137.    Image Parsing\
  2138.    =============\
  2139. \
  2140.    Titaniums Image class parses image files based on their extension, two popular formats (nfp and default) are supported by default, however this can be expanded like you see here.\
  2141.    These functions are expected to return the dimensions of the image and, a buffer (2D table) of pixels to be drawn directly to the images canvas. Pixels that do not exist in the image\
  2142.    need not be acounted for, Titanium will automatically fill those as 'blank' pixels by setting them as 'transparent'.\
  2143. \
  2144.    See the default functions below for good examples of image parsing.\
  2145. ]]\
  2146. \
  2147. Image.setImageParser(\"\", function( stream ) -- Default CC images, no extension\
  2148.    -- Break image into lines, find the maxwidth of the image (the length of the longest line)\
  2149.    local hex = TermCanvas.static.hex\
  2150.    width, lines, pixels = 1, {}, {}\
  2151.    for line in stream:gmatch \"([^\\n]*)\\n?\" do\
  2152.        width = math.max( width, #line )\
  2153.        lines[ #lines + 1 ] = line\
  2154.    end\
  2155. \
  2156.    -- Iterate each line, forming a buffer of pixels with missing information (whitespace) being left nil\
  2157.    for l = 1, #lines do\
  2158.        local y, line = width * ( l - 1 ), lines[ l ]\
  2159. \
  2160.        for i = 1, width do\
  2161.            local colour = hex[ line:sub( i, i ) ]\
  2162.            pixels[ y + i ] = { \" \", colour, colour }\
  2163.        end\
  2164.    end\
  2165. \
  2166.    return width, #lines, pixels\
  2167. end).setImageParser(\"nfp\", function( stream ) -- NFP images, .nfp extension\
  2168.    --TODO: Look into nfp file format and write parser\
  2169. end)\
  2170. \
  2171. --[[\
  2172.    Tween setup\
  2173.    ===========\
  2174. \
  2175.    The following blocks of code define the functions that will be invoked when an animation that used that type of easing is updated. These functions\
  2176.    are adjusted versions (the algorithm has remained the same, however code formatting and variable names are largely changed to match Titanium) of\
  2177.    the easing functions published by kikito on GitHub. Refer to 'LICENSE' in this project root for more information (and Enrique's license).\
  2178. \
  2179.    The functions are passed 4 arguments, these arguments are listed below:\
  2180.    - clock: This argument contains the current clock time of the Tween being updated, this is used to tell how far through the animation we are (in seconds)\
  2181.    - initial: The value of the property being animated at the instantiation of the tween. This is usually added as a Y-Axis transformation.\
  2182.    - change: The difference of the initial and final property value. ie: How much the value will have to change to match the final from where it was as instantiation.\
  2183.    - duration: The total duration of the running Tween.\
  2184. \
  2185.    Certain functions are passed extra arguments. The Tween class doesn't pass these in, however custom animation engines could invoke these easing functions\
  2186.    through `Tween.static.easing.<easingType>`.\
  2187. ]]\
  2188. \
  2189. local abs, pow, asin, sin, sqrt, pi = math.abs, math.pow, math.asin, math.sin, math.sqrt, math.pi\
  2190. local easing = Tween.static.easing\
  2191. -- Linear easing function\
  2192. Tween.addEasing(\"linear\", function( clock, initial, change, duration )\
  2193.    return change * clock / duration + initial\
  2194. end)\
  2195. \
  2196. -- Quad easing functions\
  2197. Tween.addEasing(\"inQuad\", function( clock, initial, change, duration )\
  2198.    return change * pow( clock / duration, 2 ) + initial\
  2199. end).addEasing(\"outQuad\", function( clock, initial, change, duration )\
  2200.    local clock = clock / duration\
  2201.    return -change * clock * ( clock - 2 ) + initial\
  2202. end).addEasing(\"inOutQuad\", function( clock, initial, change, duration )\
  2203.    local clock = clock / duration * 2\
  2204.    if clock < 1 then\
  2205.        return change / 2 * pow( clock, 2 ) + initial\
  2206.    end\
  2207. \
  2208.    return -change / 2 * ( ( clock - 1 ) * ( clock - 3 ) - 1 ) + initial\
  2209. end).addEasing(\"outInQuad\", function( clock, initial, change, duration )\
  2210.    if clock < duration / 2 then\
  2211.        return easing.outQuad( clock * 2, initial, change / 2, duration )\
  2212.    end\
  2213. \
  2214.    return easing.inQuad( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration)\
  2215. end)\
  2216. \
  2217. -- Cubic easing functions\
  2218. Tween.addEasing(\"inCubic\", function( clock, initial, change, duration )\
  2219.    return change * pow( clock / duration, 3 ) + initial\
  2220. end).addEasing(\"outCubic\", function( clock, initial, change, duration )\
  2221.    return change * ( pow( clock / duration - 1, 3 ) + 1 ) + initial\
  2222. end).addEasing(\"inOutCubic\", function( clock, initial, change, duration )\
  2223.    local clock = clock / duration * 2\
  2224.    if clock < 1 then\
  2225.        return change / 2 * clock * clock * clock + initial\
  2226.    end\
  2227. \
  2228.    clock = clock - 2\
  2229.    return change / 2 * (clock * clock * clock + 2) + initial\
  2230. end).addEasing(\"outInCubic\", function( clock, initial, change, duration )\
  2231.    if clock < duration / 2 then\
  2232.        return easing.outCubic( clock * 2, initial, change / 2, duration )\
  2233.    end\
  2234. \
  2235.    return easing.inCubic( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2236. end)\
  2237. \
  2238. -- Quart easing functions\
  2239. Tween.addEasing(\"inQuart\", function( clock, initial, change, duration )\
  2240.    return change * pow( clock / duration, 4 ) + initial\
  2241. end).addEasing(\"outQuart\", function( clock, initial, change, duration )\
  2242.    return -change * ( pow( clock / duration - 1, 4 ) - 1 ) + initial\
  2243. end).addEasing(\"inOutQuart\", function( clock, initial, change, duration )\
  2244.    local clock = clock / duration * 2\
  2245.    if clock < 1 then\
  2246.        return change / 2 * pow(clock, 4) + initial\
  2247.    end\
  2248. \
  2249.    return -change / 2 * ( pow( clock - 2, 4 ) - 2 ) + initial\
  2250. end).addEasing(\"outInQuart\", function( clock, initial, change, duration )\
  2251.    if clock < duration / 2 then\
  2252.        return easing.outQuart( clock * 2, initial, change / 2, duration )\
  2253.    end\
  2254. \
  2255.    return easing.inQuart( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2256. end)\
  2257. \
  2258. -- Quint easing functions\
  2259. Tween.addEasing(\"inQuint\", function( clock, initial, change, duration )\
  2260.    return change * pow( clock / duration, 5 ) + initial\
  2261. end).addEasing(\"outQuint\", function( clock, initial, change, duration )\
  2262.    return change * ( pow( clock / duration - 1, 5 ) + 1 ) + initial\
  2263. end).addEasing(\"inOutQuint\", function( clock, initial, change, duration )\
  2264.    local clock = clock / duration * 2\
  2265.    if clock < 1 then\
  2266.        return change / 2 * pow( clock, 5 ) + initial\
  2267.    end\
  2268. \
  2269.    return change / 2 * (pow( clock - 2, 5 ) + 2 ) + initial\
  2270. end).addEasing(\"outInQuint\", function( clock, initial, change, duration )\
  2271.    if clock < duration / 2 then\
  2272.        return easing.outQuint( clock * 2, initial, change / 2, duration )\
  2273.    end\
  2274. \
  2275.    return easing.inQuint( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2276. end)\
  2277. \
  2278. -- Sine easing functions\
  2279. Tween.addEasing(\"inSine\", function( clock, initial, change, duration )\
  2280.    return -change * cos( clock / duration * ( pi / 2 ) ) + change + initial\
  2281. end).addEasing(\"outSine\", function( clock, initial, change, duration )\
  2282.    return change * sin( clock / duration * ( pi / 2 ) ) + initial\
  2283. end).addEasing(\"inOutSine\", function( clock, initial, change, duration )\
  2284.    return -change / 2 * ( cos( pi * clock / duration ) - 1 ) + initial\
  2285. end).addEasing(\"outInSine\", function( clock, initial, change, duration )\
  2286.    if clock < duration / 2 then\
  2287.        return easing.outSine( clock * 2, initial, change / 2, duration )\
  2288.    end\
  2289. \
  2290.    return easing.inSine( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2291. end)\
  2292. \
  2293. -- Expo easing functions\
  2294. Tween.addEasing(\"inExpo\", function( clock, initial, change, duration )\
  2295.    if clock == 0 then\
  2296.        return initial\
  2297.    end\
  2298.    return change * pow( 2, 10 * ( clock / duration - 1 ) ) + initial - change * 0.001\
  2299. end).addEasing(\"outExpo\", function( clock, initial, change, duration )\
  2300.    if clock == duration then\
  2301.        return initial + change\
  2302.    end\
  2303. \
  2304.    return change * 1.001 * ( -pow( 2, -10 * clock / duration ) + 1 ) + initial\
  2305. end).addEasing(\"inOutExpo\", function( clock, initial, change, duration )\
  2306.    if clock == 0 then\
  2307.        return initial\
  2308.    elseif clock == duration then\
  2309.        return initial + change\
  2310.    end\
  2311. \
  2312.    local clock = clock / duration * 2\
  2313.    if clock < 1 then\
  2314.        return change / 2 * pow( 2, 10 * ( clock - 1 ) ) + initial - change * 0.0005\
  2315.    end\
  2316. \
  2317.    return change / 2 * 1.0005 * ( -pow( 2, -10 * ( clock - 1 ) ) + 2 ) + initial\
  2318. end).addEasing(\"outInExpo\", function( clock, initial, change, duration )\
  2319.    if clock < duration / 2 then\
  2320.        return easing.outExpo( clock * 2, initial, change / 2, duration )\
  2321.    end\
  2322. \
  2323.    return easing.inExpo( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2324. end)\
  2325. \
  2326. -- Circ easing functions\
  2327. Tween.addEasing(\"inCirc\", function( clock, initial, change, duration )\
  2328.    return -change * ( sqrt( 1 - pow( clock / duration, 2 ) ) - 1 ) + initial\
  2329. end).addEasing(\"outCirc\", function( clock, initial, change, duration )\
  2330.    return change * sqrt( 1 - pow( clock / duration - 1, 2 ) ) + initial\
  2331. end).addEasing(\"inOutCirc\", function( clock, initial, change, duration )\
  2332.    local clock = clock / duration * 2\
  2333.    if clock < 1 then\
  2334.        return -change / 2 * ( sqrt( 1 - clock * clock ) - 1 ) + initial\
  2335.    end\
  2336. \
  2337.    clock = clock - 2\
  2338.    return change / 2 * ( sqrt( 1 - clock * clock ) + 1 ) + initial\
  2339. end).addEasing(\"outInCirc\", function( clock, initial, change, duration )\
  2340.    if clock < duration / 2 then\
  2341.        return easing.outCirc( clock * 2, initial, change / 2, duration )\
  2342.    end\
  2343. \
  2344.    return easing.inCirc( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2345. end)\
  2346. \
  2347. -- Elastic easing functions\
  2348. local function calculatePAS(p,a,change,duration)\
  2349.  local p, a = p or duration * 0.3, a or 0\
  2350.  if a < abs( change ) then\
  2351.      return p, change, p / 4 -- p, a, s\
  2352.  end\
  2353. \
  2354.  return p, a, p / ( 2 * pi ) * asin( change / a ) -- p,a,s\
  2355. end\
  2356. \
  2357. Tween.addEasing(\"inElastic\", function( clock, initial, change, duration, amplitude, period )\
  2358.    if clock == 0 then return initial end\
  2359. \
  2360.    local clock, s = clock / duration\
  2361.    if clock == 1 then\
  2362.        return initial + change\
  2363.    end\
  2364. \
  2365.    clock, p, a, s = clock - 1, calculatePAS( p, a, change, duration )\
  2366.    return -( a * pow( 2, 10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) ) + initial\
  2367. end).addEasing(\"outElastic\", function( clock, initial, change, duration, amplitude, period )\
  2368.    if clock == 0 then\
  2369.        return initial\
  2370.    end\
  2371.    local clock, s = clock / duration\
  2372. \
  2373.    if clock == 1 then\
  2374.        return initial + change\
  2375.    end\
  2376. \
  2377.    local p,a,s = calculatePAS( period, amplitude, change, duration )\
  2378.    return a * pow( 2, -10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) + change + initial\
  2379. end).addEasing(\"inOutElastic\", function( clock, initial, change, duration, amplitude, period )\
  2380.    if clock == 0 then return initial end\
  2381. \
  2382.    local clock = clock / duration * 2\
  2383.    if clock == 2 then return initial + change end\
  2384. \
  2385.    local clock, p, a, s = clock - 1, calculatePAS( period, amplitude, change, duration )\
  2386.    if clock < 0 then\
  2387.        return -0.5 * ( a * pow( 2, 10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) ) + initial\
  2388.    end\
  2389. \
  2390.    return a * pow( 2, -10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) * 0.5 + change + initial\
  2391. end).addEasing(\"outInElastic\", function( clock, initial, change, duration, amplitude, period )\
  2392.    if clock < duration / 2 then\
  2393.        return easing.outElastic( clock * 2, initial, change / 2, duration, amplitude, period )\
  2394.    end\
  2395. \
  2396.    return easing.inElastic( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration, amplitude, period )\
  2397. end)\
  2398. \
  2399. -- Back easing functions\
  2400. Tween.addEasing(\"inBack\", function( clock, initial, change, duration, s )\
  2401.    local s, clock = s or 1.70158, clock / duration\
  2402. \
  2403.    return change * clock * clock * ( ( s + 1 ) * clock - s ) + initial\
  2404. end).addEasing(\"outBack\", function( clock, initial, change, duration, s )\
  2405.    local s, clock = s or 1.70158, clock / duration - 1\
  2406. \
  2407.    return change * ( clock * clock * ( ( s + 1 ) * clock + s ) + 1 ) + initial\
  2408. end).addEasing(\"inOutBack\", function( clock, initial, change, duration, s )\
  2409.    local s, clock = ( s or 1.70158 ) * 1.525, clock / duration * 2\
  2410.    if clock < 1 then\
  2411.        return change / 2 * ( clock * clock * ( ( s + 1 ) * clock - s ) ) + initial\
  2412.    end\
  2413. \
  2414.    clock = clock - 2\
  2415.    return change / 2 * ( clock * clock * ( ( s + 1 ) * clock + s ) + 2 ) + initial\
  2416. end).addEasing(\"outInBack\", function( clock, initial, change, duration, s )\
  2417.    if clock < duration / 2 then\
  2418.        return easing.outBack( clock * 2, initial, change / 2, duration, s )\
  2419.    end\
  2420. \
  2421.    return easing.inBack( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration, s )\
  2422. end)\
  2423. \
  2424. -- Bounce easing functions\
  2425. Tween.addEasing(\"inBounce\", function( clock, initial, change, duration )\
  2426.    return change - easing.outBounce( duration - clock, 0, change, duration ) + initial\
  2427. end).addEasing(\"outBounce\", function( clock, initial, change, duration )\
  2428.    local clock = clock / duration\
  2429.    if clock < 1 / 2.75 then\
  2430.        return change * ( 7.5625 * clock * clock ) + initial\
  2431.    elseif clock < 2 / 2.75 then\
  2432.        clock = clock - ( 1.5 / 2.75 )\
  2433.        return change * ( 7.5625 * clock * clock + 0.75 ) + initial\
  2434.    elseif clock < 2.5 / 2.75 then\
  2435.        clock = clock - ( 2.25 / 2.75 )\
  2436.        return change * ( 7.5625 * clock * clock + 0.9375 ) + initial\
  2437.    end\
  2438. \
  2439.    clock = clock - (2.625 / 2.75)\
  2440.    return change * (7.5625 * clock * clock + 0.984375) + initial\
  2441. end).addEasing(\"inOutBounce\", function( clock, initial, change, duration )\
  2442.    if clock < duration / 2 then\
  2443.        return easing.inBounce( clock * 2, 0, change, duration ) * 0.5 + initial\
  2444.    end\
  2445. \
  2446.    return easing.outBounce( clock * 2 - duration, 0, change, duration ) * 0.5 + change * .5 + initial\
  2447. end).addEasing(\"outInBounce\", function( clock, initial, change, duration )\
  2448.    if clock < duration / 2 then\
  2449.        return easing.outBounce( clock * 2, initial, change / 2, duration )\
  2450.    end\
  2451. \
  2452.    return easing.inBounce( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
  2453. end)\
  2454. ",
  2455. ["RadioButton.ti"]="--[[\
  2456.    @static groups - table (def. {}) - The current radio button groups\
  2457.    @instance group - string (def. false) - The group the radio button belongs to\
  2458. \
  2459.    A radio button belongs to a group. Anytime a radio button in the same group is selected, all others are deselected. This means only one radio button\
  2460.    is selected at a time inside of a group. The value of the selected radio button can be retrieved using 'RadioButton.static.getValue'\
  2461. \
  2462.    When the radio button is selected, the 'select' callback is fired\
  2463. ]]\
  2464. class \"RadioButton\" extends \"Checkbox\" {\
  2465.    static = {\
  2466.        groups = {}\
  2467.    };\
  2468. \
  2469.    group = false;\
  2470. }\
  2471. \
  2472. --[[\
  2473.    @constructor\
  2474.    @desc Constructs the instance, and selects the radio button if 'toggled' is set\
  2475.    @param [number - X], [number - Y], <string - group>\
  2476. ]]\
  2477. function RadioButton:__init__( ... )\
  2478.    self:super( ... )\
  2479. \
  2480.    if self.toggled then\
  2481.        RadioButton.deselectInGroup( self.group, self )\
  2482.    end\
  2483. end\
  2484. \
  2485. --[[\
  2486.    @instance\
  2487.    @desc Deselects every radio button in the group, toggles this radio button\
  2488. ]]\
  2489. function RadioButton:select()\
  2490.    RadioButton.deselectInGroup( self.group )\
  2491. \
  2492.    self.toggled = true\
  2493.    self:executeCallbacks \"select\"\
  2494. end\
  2495. \
  2496. --[[\
  2497.    @instance\
  2498.    @desc If the radio button is active, and the mouse click occured on this node, the radio button is selected (:select)\
  2499.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  2500. ]]\
  2501. function RadioButton:onMouseUp( event, handled, within )\
  2502.    if not handled and within and self.active then\
  2503.        self:select( event, handled, within )\
  2504. \
  2505.        event.handled = true\
  2506.    end\
  2507. \
  2508.    self.active = false\
  2509. end\
  2510. \
  2511. --[[\
  2512.    @instance\
  2513.    @desc If an assigned label (labelFor set as this nodes ID on a label) is clicked, this radio button is selected\
  2514.    @param <Label Instance - label>, <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  2515. ]]\
  2516. function RadioButton:onLabelClicked( label, event, handled, within )\
  2517.    self:select( event, handled, within, label )\
  2518.    event.handled = true\
  2519. end\
  2520. \
  2521. --[[\
  2522.    @instance\
  2523.    @desc Updates the radio button group by removing the button from the old group and adding it to the new one\
  2524.    @param <string - group>\
  2525. ]]\
  2526. function RadioButton:setGroup( group )\
  2527.    if self.group then\
  2528.        RadioButton.removeFromGroup( self, self.group )\
  2529.    end\
  2530.    self.group = group\
  2531. \
  2532.    RadioButton.addToGroup( self, group )\
  2533. end\
  2534. \
  2535. --[[\
  2536.    @static\
  2537.    @desc Adds the node provided to the group given\
  2538.    @param <Node Instance - node>, <string - group>\
  2539. ]]\
  2540. function RadioButton.static.addToGroup( node, group )\
  2541.    local g = RadioButton.groups[ group ]\
  2542.    if type( g ) == \"table\" then\
  2543.        RadioButton.removeFromGroup( node, group )\
  2544. \
  2545.        table.insert( g, node )\
  2546.    else\
  2547.        RadioButton.groups[ group ] = { node }\
  2548.    end\
  2549. end\
  2550. \
  2551. --[[\
  2552.    @static\
  2553.    @desc Removes the node provided from the group given if present\
  2554.    @param <Node Instance - node>, <string - group>\
  2555. ]]\
  2556. function RadioButton.static.removeFromGroup( node, group )\
  2557.    local index = RadioButton.isInGroup( node, group )\
  2558.    if index then\
  2559.        table.remove( RadioButton.groups[ group ], index )\
  2560. \
  2561.        if #RadioButton.groups[ group ] == 0 then\
  2562.            RadioButton.groups[ group ] = nil\
  2563.        end\
  2564.    end\
  2565. end\
  2566. \
  2567. --[[\
  2568.    @static\
  2569.    @desc Returns true if 'node' is inside 'group'\
  2570.    @param <Node Instance - node>, <string - group>\
  2571.    @return <boolean - isInsideGroup>\
  2572. ]]\
  2573. function RadioButton.static.isInGroup( node, group )\
  2574.    local g = RadioButton.groups[ group ]\
  2575.    for i = 1, #g do\
  2576.        if g[ i ] == node then return i end\
  2577.    end\
  2578. \
  2579.    return false\
  2580. end\
  2581. \
  2582. --[[\
  2583.    @static\
  2584.    @desc If no 'target', deselects every node inside 'group'. If a 'target' is given, every node BUT the 'target' is deselected inside the group\
  2585.    @param <string - group>, [Node Instance - target]\
  2586. ]]\
  2587. function RadioButton.static.deselectInGroup( group, target )\
  2588.    local g = RadioButton.groups[ group ]\
  2589. \
  2590.    for i = 1, #g do if ( not target or ( target and g[ i ] ~= target ) ) then g[ i ].toggled = false end end\
  2591. end\
  2592. \
  2593. --[[\
  2594.    @static\
  2595.    @desc Returns the value of the selected radio button inside of the group given (if one is selected)\
  2596.    @param <string - group>\
  2597.    @return <string - value> - If a radio button is selected, it's value is returned\
  2598. ]]\
  2599. function RadioButton.static.getValue( group )\
  2600.    local g = RadioButton.groups[ group ]\
  2601.    if g then\
  2602.        local radio\
  2603.        for i = 1, #g do\
  2604.            radio = g[ i ]\
  2605.            if radio.toggled then return radio.value end\
  2606.        end\
  2607.    end\
  2608. end\
  2609. \
  2610. configureConstructor({\
  2611.    orderedArguments = { \"X\", \"Y\", \"group\" },\
  2612.    requiredArguments = { \"group\" },\
  2613.    argumentTypes = { group = \"string\" },\
  2614.    useProxy = { \"group\" }\
  2615. }, true, true )\
  2616. ",
  2617. ["DynamicEqLexer.ti"]="--[[\
  2618.    A lexer that processes dynamic value equations into tokens used by DynamicEqParser\
  2619. ]]\
  2620. \
  2621. class \"DynamicEqLexer\" extends \"Lexer\"\
  2622. \
  2623. --[[\
  2624.    @instance\
  2625.    @desc Finds a valid number in the current stream. Returns 'true' if one was found, 'nil' otherwise\
  2626.    @return <boolean - true> - Found a valid Lua number\
  2627. ]]\
  2628. function DynamicEqLexer:lexNumber()\
  2629.    local stream = self:trimStream()\
  2630.    local exp, following = stream:match \"^%d*%.?%d+(e)([-+]?%d*)\"\
  2631. \
  2632.    if exp and exp ~= \"\" then\
  2633.        if following and following ~= \"\" then\
  2634.            self:pushToken { type = \"NUMBER\", value = self:consumePattern \"^%d*%.?%d+e[-+]?%d*\" }\
  2635.            return true\
  2636.        else self:throw \"Invalid number. Expected digit after 'e'\" end\
  2637.    elseif stream:find \"^%d*%.?%d+\" then\
  2638.        self:pushToken { type = \"NUMBER\", value = self:consumePattern \"^%d*%.?%d+\" }\
  2639.        return true\
  2640.    end\
  2641. end\
  2642. \
  2643. --[[\
  2644.    @instance\
  2645.    @desc The main token creator\
  2646. ]]\
  2647. function DynamicEqLexer:tokenize()\
  2648.    local stream = self:trimStream()\
  2649.    local first = stream:sub( 1, 1 )\
  2650. \
  2651.    if stream:find \"^%b{}\" then\
  2652.        self:pushToken { type = \"QUERY\", value = self:consumePattern \"^%b{}\" }\
  2653.    elseif not self:lexNumber() then\
  2654.        if first == \"'\" or first == '\"' then\
  2655.            self:pushToken { type = \"STRING\", value = self:consumeString( first ), surroundedBy = first }\
  2656.        elseif stream:find \"^and\" then\
  2657.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^and\", binary = true }\
  2658.        elseif stream:find \"^or\" then\
  2659.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^or\", binary = true }\
  2660.        elseif stream:find \"^not\" then\
  2661.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^not\", unary = true }\
  2662.        elseif stream:find \"^[#]\" then\
  2663.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^[#]\", unary = true }\
  2664.        elseif stream:find \"^[/%*%%]\" then\
  2665.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^[/%*%%]\", binary = true }\
  2666.        elseif stream:find \"^%.%.\" then\
  2667.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^%.%.\", binary = true }\
  2668.        elseif stream:find \"^%=%=\" then\
  2669.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^%=%=\", binary = true }\
  2670.        elseif stream:find \"^[%+%-]\" then\
  2671.            self:pushToken { type = \"OPERATOR\", value = self:consumePattern \"^[%+%-]\", ambiguos = true }\
  2672.        elseif stream:find \"^[%(%)]\" then\
  2673.            self:pushToken { type = \"PAREN\", value = self:consumePattern \"^[%(%)]\" }\
  2674.        elseif stream:find \"^%.\" then\
  2675.            self:pushToken { type = \"DOT\", value = self:consumePattern \"^%.\" }\
  2676.        elseif stream:find \"^%w+\" then\
  2677.            self:pushToken { type = \"NAME\", value = self:consumePattern \"^%w+\" }\
  2678.        else\
  2679.            self:throw(\"Unexpected block '\".. ( stream:match( \"%S+\" ) or \"\" ) ..\"'\")\
  2680.        end\
  2681.    end\
  2682. end\
  2683. ",
  2684. ["XMLParser.ti"]="--[[\
  2685.    The XMLParser class \"is\" used to handle the lexing and parsing of XMLParser source into a parse tree.\
  2686. ]]\
  2687. \
  2688. class \"XMLParser\" extends \"Parser\" {\
  2689.    tokens = false;\
  2690.    tree = false;\
  2691. }\
  2692. \
  2693. --[[\
  2694.    @constructor\
  2695.    @desc Creates a 'Lexer' instance with the source and stores the tokens provided. Invokes 'parse' once lexical analysis complete.\
  2696. ]]\
  2697. function XMLParser:__init__( source )\
  2698.    local lex = XMLLexer( source )\
  2699.    self:super( lex.tokens )\
  2700. end\
  2701. \
  2702. --[[\
  2703.    @instance\
  2704.    @desc Iterates through every token and constructs a tree of XML layers\
  2705. ]]\
  2706. function XMLParser:parse()\
  2707.    local stack, top, token = {{}}, false, self:stepForward()\
  2708.    local isTagOpen, settingAttribute\
  2709. \
  2710.    while token do\
  2711.        if settingAttribute then\
  2712.            if token.type == \"XML_ATTRIBUTE_VALUE\" or token.type == \"XML_STRING_ATTRIBUTE_VALUE\" then\
  2713.                top.arguments[ settingAttribute ] = token.value\
  2714.                settingAttribute = false\
  2715.            else\
  2716.                self:throw( \"Unexpected \"..token.type..\". Expected attribute value following XML_ASSIGNMENT token.\" )\
  2717.            end\
  2718.        else\
  2719.            if token.type == \"XML_OPEN\" then\
  2720.                if isTagOpen then\
  2721.                    self:throw \"Unexpected XML_OPEN token. Expected XML attributes or end of tag.\"\
  2722.                end\
  2723.                isTagOpen = true\
  2724. \
  2725.                top = { type = token.value, arguments = {} }\
  2726.                table.insert( stack, top )\
  2727.            elseif token.type == \"XML_END\" then\
  2728.                local toClose = table.remove( stack )\
  2729.                top = stack[ #stack ]\
  2730. \
  2731.                if not top then\
  2732.                    self:throw(\"Nothing to close with XML_END of type '\"..token.value..\"'\")\
  2733.                elseif toClose.type ~= token.value then\
  2734.                    self:throw(\"Tried to close \"..toClose.type..\" with XML_END of type '\"..token.value..\"'\")\
  2735.                end\
  2736. \
  2737.                if not top.children then top.children = {} end\
  2738.                table.insert( top.children, toClose )\
  2739.            elseif token.type == \"XML_END_CLOSE\" then\
  2740.                top = stack[ #stack - 1 ]\
  2741. \
  2742.                if not top then\
  2743.                    self:throw(\"Unexpected XML_END_CLOSE tag (/>)\")\
  2744.                end\
  2745. \
  2746.                if not top.children then top.children = {} end\
  2747.                table.insert( top.children, table.remove( stack ) )\
  2748.            elseif token.type == \"XML_CLOSE\" then\
  2749.                isTagOpen = false\
  2750.            elseif token.type == \"XML_ATTRIBUTE\" then\
  2751.                local next = self:stepForward()\
  2752. \
  2753.                if next.type == \"XML_ASSIGNMENT\" then\
  2754.                    settingAttribute = token.value\
  2755.                else\
  2756.                    top.arguments[ token.value ] = true\
  2757.                    self.position = self.position - 1\
  2758.                end\
  2759.            elseif token.type == \"XML_CONTENT\" then\
  2760.                if not top.type then\
  2761.                    self:throw(\"Unexpected XML_CONTENT. Invalid content: \"..token.value)\
  2762.                end\
  2763. \
  2764.                top.content = token.value\
  2765.            else\
  2766.                self:throw(\"Unexpected \"..token.type)\
  2767.            end\
  2768.        end\
  2769. \
  2770.        if token.type == \"XML_END\" or token.type == \"XML_END_CLOSE\" then\
  2771.            isTagOpen = false\
  2772.        end\
  2773. \
  2774.        if top.content and top.children then\
  2775.            self:throw \"XML layers cannot contain child nodes and XML_CONTENT at the same time\"\
  2776.        end\
  2777. \
  2778.        token = self:stepForward()\
  2779.    end\
  2780.    self.tree = stack[ 1 ].children\
  2781. end\
  2782. \
  2783. --[[\
  2784.    @static\
  2785.    @desc When lexing the XML arguments they are all stored as strings as a result of the string operations to find tokens.\
  2786.          This function converts a value to the type given (#2)\
  2787.    @param <var - argumentValue>, <string - desiredType>\
  2788.    @return <desiredType* - value>\
  2789. \
  2790.    *Note: desiredType is passed as type string, however the return is the value type defined inside the string. eg: desiredType: \"number\" will return a number, not a string.\
  2791. ]]\
  2792. function XMLParser.static.convertArgType( argumentValue, desiredType )\
  2793.    local vType = type( argumentValue )\
  2794.    argumentValue = vType == \"number\" and math.ceil( argumentValue ) or argumentValue\
  2795. \
  2796.    if not desiredType or not argumentValue or vType == desiredType then\
  2797.        return argumentValue\
  2798.    end\
  2799. \
  2800.    if desiredType == \"string\" then\
  2801.        return tostring( argumentValue )\
  2802.    elseif desiredType == \"number\" then\
  2803.        return tonumber( argumentValue ) and math.ceil( tonumber( argumentValue ) ) or error( \"Failed to cast argument to number. Value: \"..tostring( argumentValue )..\" is not a valid number\" )\
  2804.    elseif desiredType == \"boolean\" then\
  2805.        if argumentValue == \"true\" then return true\
  2806.        elseif argumentValue == \"false\" then return false\
  2807.        else\
  2808.            return error( \"Failed to cast argument to boolean. Value: \"..tostring( argumentValue )..\" is not a valid boolean (true or false)\" )\
  2809.        end\
  2810.    elseif desiredType == \"colour\" or desiredType == \"color\" then\
  2811.        if argumentValue == \"transparent\" or argumentValue == \"trans\" then\
  2812.            return 0\
  2813.        end\
  2814.        return tonumber( argumentValue ) or colours[ argumentValue ] or colors[ argumentValue ] or error( \"Failed to cast argument to colour (number). Value: \"..tostring( argumentValue )..\" is not a valid colour\" )\
  2815.    else\
  2816.        return error( \"Failed to cast argument. Unknown target type '\"..tostring( desiredType )..\"'\" )\
  2817.    end\
  2818. end\
  2819. ",
  2820. ["Component.ti"]="--[[\
  2821.    @instance width - number (def. 1) - The objects width, defines the width of the canvas.\
  2822.    @instance height - number (def. 1) - The objects width, defines the height of the canvas.\
  2823.    @instance X - number (def. 1) - The objects X position\
  2824.    @instance Y - number (def. 1) - The objects Y position\
  2825.    @instance changed - boolean (def. true) - If true, the node will be redrawn by it's parent. This propagates upto the application, before being drawn to the CraftOS term object. Set to false after draw.\
  2826.    @instance backgroundChar - string (def. \" \") - Defines the character used when redrawing the canvas. Can be set to \"nil\" to use no character at all.\
  2827. \
  2828.    A Component is an object that can be respresented visually.\
  2829. ]]\
  2830. \
  2831. class \"Component\" abstract() mixin \"MPropertyManager\" {\
  2832.    width = 1;\
  2833.    height = 1;\
  2834.    X = 1;\
  2835.    Y = 1;\
  2836. \
  2837.    changed = true;\
  2838. \
  2839.    backgroundChar = \" \";\
  2840. }\
  2841. \
  2842. --[[\
  2843.    @instance\
  2844.    @desc Redraws the area that 'self' occupies inside it's parent\
  2845. ]]\
  2846. function Component:queueAreaReset()\
  2847.    local parent = self.parent\
  2848.    if parent then\
  2849.        parent:redrawArea( self.X, self.Y, self.width, self.height )\
  2850.    end\
  2851. \
  2852.    self.changed = true\
  2853. end\
  2854. \
  2855. --[[\
  2856.    @instance\
  2857.    @desc Accepts either a property, or a property-value table to set on the instance\
  2858.    @param <string - property>, <any - value> - If setting just one property\
  2859.    @param <table - properties> - Setting multiple properties, using format { property = value }\
  2860. ]]\
  2861. function Component:set( properties, value )\
  2862.    if type( properties ) == \"string\" then\
  2863.        self[ properties ] = value\
  2864.    elseif type( properties ) == \"table\" then\
  2865.        for property, val in pairs( properties ) do\
  2866.            self[ property ] = val\
  2867.        end\
  2868.    else return error \"Expected table or string\"end\
  2869. \
  2870.    return self\
  2871. end\
  2872. \
  2873. --[[\
  2874.    @setter\
  2875.    @desc Resets the area the node previously occupied before moving the node's X position\
  2876.    @param <number - X>\
  2877. ]]\
  2878. function Component:setX( X )\
  2879.    self:queueAreaReset()\
  2880.    self.X = X\
  2881. end\
  2882. \
  2883. --[[\
  2884.    @setter\
  2885.    @desc Resets the area the node previously occupied before moving the node's Y position\
  2886.    @param <number - Y>\
  2887. ]]\
  2888. function Component:setY( Y )\
  2889.    self:queueAreaReset()\
  2890.    self.Y = Y\
  2891. end\
  2892. \
  2893. --[[\
  2894.    @setter\
  2895.    @desc Resets the area the node previously occupied before changing the nodes width\
  2896.    @param <number - width>\
  2897. ]]\
  2898. function Component:setWidth( width )\
  2899.    self:queueAreaReset()\
  2900. \
  2901.    self.width = width\
  2902.    self.canvas.width = width\
  2903. end\
  2904. \
  2905. --[[\
  2906.    @setter\
  2907.    @desc Resets the area the node previously occupied before changing the nodes height\
  2908.    @param <number - height>\
  2909. ]]\
  2910. function Component:setHeight( height )\
  2911.    self:queueAreaReset()\
  2912. \
  2913.    self.height = height\
  2914.    self.canvas.height = height\
  2915. end\
  2916. \
  2917. --[[\
  2918.    @setter\
  2919.    @desc Changes the colour of the canvas and the node, and queues a redraw\
  2920.    @param <number - colour>\
  2921. ]]\
  2922. function Component:setColour( colour )\
  2923.    self.colour = colour\
  2924.    self.canvas.colour = colour\
  2925. \
  2926.    self.changed = true\
  2927. end\
  2928. \
  2929. --[[\
  2930.    @setter\
  2931.    @desc Changes the background colour of the canvas and the node, and queues a redraw\
  2932.    @param <number - backgroundColour>\
  2933. ]]\
  2934. function Component:setBackgroundColour( backgroundColour )\
  2935.    self.backgroundColour = backgroundColour\
  2936.    self.canvas.backgroundColour = backgroundColour\
  2937. \
  2938.    self.changed = true\
  2939. end\
  2940. \
  2941. --[[\
  2942.    @setter\
  2943.    @desc Changes the transparency of the canvas and node, and queues a redraw\
  2944.    @param <boolean - transparent>\
  2945. ]]\
  2946. function Component:setTransparent( transparent )\
  2947.    self.transparent = transparent\
  2948.    self.canvas.transparent = transparent\
  2949. \
  2950.    self.changed = true\
  2951. end\
  2952. \
  2953. --[[\
  2954.    @setter\
  2955.    @desc Changes the canvas and nodes background character, and queues a redraw\
  2956.    @param <string - backgroundChar>\
  2957. ]]\
  2958. function Component:setBackgroundChar( backgroundChar )\
  2959.    if backgroundChar == \"nil\" then\
  2960.        backgroundChar = nil\
  2961.    end\
  2962. \
  2963.    self.backgroundChar = backgroundChar\
  2964.    self.canvas.backgroundChar = backgroundChar\
  2965. \
  2966.    self.changed = true\
  2967. end\
  2968. \
  2969. --[[\
  2970.    @setter\
  2971.    @desc Changes the backgroundTextColour of the canvas and node, and queues a redraw\
  2972.    @param <number - backgroundTextColour>\
  2973. ]]\
  2974. function Component:setBackgroundTextColour( backgroundTextColour )\
  2975.    self.backgroundTextColour = backgroundTextColour\
  2976.    self.canvas.backgroundTextColour = backgroundTextColour\
  2977. \
  2978.    self.changed = true\
  2979. end\
  2980. \
  2981. configureConstructor {\
  2982.    orderedArguments = { \"X\", \"Y\", \"width\", \"height\" },\
  2983.    argumentTypes = { X = \"number\", Y = \"number\", width = \"number\", height = \"number\", colour = \"colour\", backgroundColour = \"colour\", backgroundTextColour = \"colour\", transparent = \"boolean\" }\
  2984. } alias {\
  2985.    color = \"colour\",\
  2986.    backgroundColor = \"backgroundColour\"\
  2987. }\
  2988. ",
  2989. ["Application.ti"]="--[[\
  2990.    @instance width - number (def. 51) - The applications width, defines the width of the canvas.\
  2991.    @instance height - number (def. 19) - The applications width, defines the height of the canvas.\
  2992.    @instance threads - table (def. {}) - The threads currently stored on the Application. Includes stopped threads (due to finish, or exception).\
  2993.    @instance timers - table (def. {}) - The currently running timers. Timers that have finished are removed from the table. Repeating timers are simply re-queued via :schedule.\
  2994.    @instance running - boolean (def. false) - The current state of the application loop. If false, the application loop will stop.\
  2995.    @instance terminatable - boolean (def. false) - If true, the application will exit (running = false) when the 'terminate' event is caught inside the event loop.\
  2996.    @instance focusedNode - Node (def. nil) - If present, contains the currently focused node. This node is used to determine the application caret information using :getCaretInfo.\
  2997. \
  2998.    An Application object is the entry point to a Titanium Application. The Application derives a lot of it's functionality from\
  2999.    it's mixins. However, application-wide node focusing, threads, timers, animations and the event loop are all handled by this class.\
  3000. \
  3001.    This is why it is considered the heart of a Titanium project - without it, the project would simply not run due to the lack of a yielding\
  3002.    event-loop.\
  3003. ]]\
  3004. \
  3005. class \"Application\" extends \"Component\" mixin \"MThemeManager\" mixin \"MKeyHandler\" mixin \"MCallbackManager\" mixin \"MAnimationManager\" mixin \"MNodeContainer\" {\
  3006.    width = 51;\
  3007.    height = 19;\
  3008. \
  3009.    threads = {};\
  3010.    timers = {};\
  3011. \
  3012.    running = false;\
  3013.    terminatable = false;\
  3014. }\
  3015. \
  3016. --[[\
  3017.    @constructor\
  3018.    @desc Constructs an instance of the Application by setting all necessary unique properties on it\
  3019.    @param [number - width], [number - height]\
  3020.    @return <nil>\
  3021. ]]\
  3022. function Application:__init__( ... )\
  3023.    self:resolve( ... )\
  3024.    self.canvas = TermCanvas( self )\
  3025. \
  3026.    self:setMetaMethod(\"add\", function( a, b )\
  3027.        local t = a ~= self and a or b\
  3028. \
  3029.        if Titanium.typeOf( t, \"Node\", true ) then\
  3030.            return self:addNode( t )\
  3031.        elseif Titanium.typeOf( t, \"Thread\", true ) then\
  3032.            return self:addThread( t )\
  3033.        end\
  3034. \
  3035.        error \"Invalid targets for application '__add'. Expected node or thread.\"\
  3036.    end)\
  3037. end\
  3038. \
  3039. --[[\
  3040.    @instance\
  3041.    @desc Focuses the node provided application wide. The node will control the application caret and will have it's 'focused' property set to true\
  3042.          Also, the 'focus' callback will be called on the application, passing the node focused.\
  3043.    @param <Node Instance - node>\
  3044. ]]\
  3045. function Application:focusNode( node )\
  3046.    if not Titanium.typeOf( node, \"Node\", true ) then\
  3047.        return error \"Failed to update application focused node. Invalid node object passed.\"\
  3048.    end\
  3049. \
  3050.    self:unfocusNode()\
  3051.    self.focusedNode = node\
  3052.    node.changed = true\
  3053. \
  3054. \
  3055.    node:executeCallbacks( \"focus\", self )\
  3056. end\
  3057. \
  3058. --[[\
  3059.    @instance\
  3060.    @desc If called with no arguments, the currently focused node will be unfocused, and the 'unfocus' callback will be executed\
  3061. \
  3062.          If called with the targetNode argument, the currently focused node will only be unfocused if it *is* that node. If the focused node\
  3063.          is NOT the targetNode, the function will return. If it is, it will be unfocused and the 'unfocus' callback executed.\
  3064.    @param [Node Instance - targetNode]\
  3065. ]]\
  3066. function Application:unfocusNode( targetNode )\
  3067.    local node = self.focusedNode\
  3068.    if not node or ( targetNode ~= node ) then return end\
  3069. \
  3070.    self.focusedNode = nil\
  3071. \
  3072.    node.raw.focused = false\
  3073.    node.changed = true\
  3074. \
  3075.    node:executeCallbacks( \"unfocus\", self )\
  3076. end\
  3077. \
  3078. --[[\
  3079.    @instance\
  3080.    @desc Adds a new thread named 'name' running 'func'. This thread will receive events caught by the Application engine\
  3081.    @param <threadObj - Thread Instance>\
  3082.    @return [threadObj | error]\
  3083. ]]\
  3084. function Application:addThread( threadObj )\
  3085.    if not Titanium.typeOf( threadObj, \"Thread\", true ) then\
  3086.        error( \"Failed to add thread, object '\"..tostring( threadObj )..\"' is invalid. Thread Instance required\")\
  3087.    end\
  3088. \
  3089.    table.insert( self.threads, threadObj )\
  3090. \
  3091.    return threadObj\
  3092. end\
  3093. \
  3094. --[[\
  3095.    @instance\
  3096.    @desc Removes the thread named 'name'*\
  3097.    @param <Thread Instance - target> - Used when removing the thread provided\
  3098.    @param <string - target> - Used when removing the thread using the name provided\
  3099.    @return <boolean - success>, [node - removedThread**]\
  3100. \
  3101.    *Note: In order for the thread to be removed its 'id' field must match the 'id' parameter.\
  3102.    **Note: Removed thread will only be returned if a thread was removed (and thus success 'true')\
  3103. ]]\
  3104. function Application:removeThread( target )\
  3105.    if not Titanium.typeOf( target, \"Thread\", true ) then\
  3106.        return error( \"Cannot perform search for thread using target '\"..tostring( target )..\"'.\" )\
  3107.    end\
  3108. \
  3109.    local searchID = type( target ) == \"string\"\
  3110.    local threads, thread, threadID = self.threads\
  3111.    for i = 1, #threads do\
  3112.        thread = threads[ i ]\
  3113. \
  3114.        if ( searchID and thread.id == target ) or ( not searchID and thread == target ) then\
  3115.            thread:stop()\
  3116. \
  3117.            table.remove( threads, i )\
  3118.            return true, thread\
  3119.        end\
  3120.    end\
  3121. \
  3122.    return false\
  3123. end\
  3124. \
  3125. --[[\
  3126.    @instance\
  3127.    @desc Ships events to threads, if the thread requires a Titanium event, that will be passed instead.\
  3128.    @param <AnyEvent - eventObj>, <vararg - eData>\
  3129. ]]\
  3130. function Application:handleThreads( eventObj, ... )\
  3131.    local threads = self.threads\
  3132. \
  3133.    local thread\
  3134.    for i = 1, #threads do\
  3135.        thread = threads[ i ]\
  3136. \
  3137.        if thread.titaniumEvents then\
  3138.            thread:handle( eventObj )\
  3139.        else\
  3140.            thread:handle( ... )\
  3141.        end\
  3142.    end\
  3143. end\
  3144. \
  3145. --[[\
  3146.    @instance\
  3147.    @desc Queues the execution of 'fn' after 'time' seconds.\
  3148.    @param <function - fn>, <number - time>, [boolean - repeating], [string - name]\
  3149.    @return <number - timerID>\
  3150. ]]\
  3151. function Application:schedule( fn, time, repeating, name )\
  3152.    local timers = self.timers\
  3153.    if name then\
  3154.        self:unschedule( name )\
  3155.    end\
  3156. \
  3157.    local ID = os.startTimer( time ) --TODO: Use timer util to re-use timer IDs\
  3158.    self.timers[ ID ] = { fn, time, repeating, name }\
  3159. \
  3160.    return ID\
  3161. end\
  3162. \
  3163. --[[\
  3164.    @instance\
  3165.    @desc Unschedules the execution of a function using the name attached. If no name was assigned when scheduling, the timer cannot be cancelled using this method.\
  3166.    @param <string - name>\
  3167.    @return <boolean - success>\
  3168. ]]\
  3169. function Application:unschedule( name )\
  3170.    local timers = self.timers\
  3171.    for timerID, timerDetails in next, timers do\
  3172.        if timerDetails[ 4 ] == name then\
  3173.            os.cancelTimer( timerID )\
  3174.            timers[ timerID ] = nil\
  3175. \
  3176.            return true\
  3177.        end\
  3178.    end\
  3179. \
  3180.    return false\
  3181. end\
  3182. \
  3183. --[[\
  3184.    @instance\
  3185.    @desc Returns the position of the application, used when calculating the absolute position of a child node relative to the term object\
  3186.    @return <number - X>, <number - Y>\
  3187. ]]\
  3188. function Application:getAbsolutePosition()\
  3189.    return self.X, self.Y\
  3190. end\
  3191. \
  3192. --[[\
  3193.    @instance\
  3194.    @desc Begins the program loop\
  3195. ]]\
  3196. function Application:start()\
  3197.    self:restartAnimationTimer()\
  3198.    self.running = true\
  3199.    while self.running do\
  3200.        self:draw()\
  3201.        local event = { coroutine.yield() }\
  3202.        local eName = event[ 1 ]\
  3203. \
  3204.        if eName == \"timer\" then\
  3205.            local timerID = event[ 2 ]\
  3206.            if timerID == self.timer then\
  3207.                self:updateAnimations()\
  3208.            elseif self.timers[ timerID ] then\
  3209.                local timerDetails = self.timers[ timerID ]\
  3210.                if timerDetails[ 3 ] then\
  3211.                    self:schedule( unpack( timerDetails ) )\
  3212.                end\
  3213. \
  3214.                self.timers[ timerID ] = nil\
  3215.                timerDetails[ 1 ]( self, timerID )\
  3216.            end\
  3217.        elseif eName == \"terminate\" and self.terminatable then\
  3218.            printError \"Application Terminated\"\
  3219.            self:stop()\
  3220.        end\
  3221. \
  3222.        self:handle( unpack( event ) )\
  3223.    end\
  3224. end\
  3225. \
  3226. --[[\
  3227.    @instance\
  3228.    @desc Draws changed nodes (or all nodes if 'force' is true)\
  3229.    @param [boolean - force]\
  3230. ]]\
  3231. function Application:draw( force )\
  3232.    if not self.changed and not force then return end\
  3233. \
  3234.    local canvas = self.canvas\
  3235.    local nodes, node = self.nodes\
  3236. \
  3237.    for i = 1, #nodes do\
  3238.        node = nodes[ i ]\
  3239.        if node.needsRedraw and node.visible then\
  3240.            node:draw( force )\
  3241. \
  3242.            node.canvas:drawTo( canvas, node.X, node.Y )\
  3243.            node.needsRedraw = false\
  3244.        end\
  3245.    end\
  3246.    self.changed = false\
  3247. \
  3248.    local focusedNode, caretEnabled, caretX, caretY, caretColour = self.focusedNode\
  3249.    if focusedNode and focusedNode:can \"getCaretInfo\" then\
  3250.        caretEnabled, caretX, caretY, caretColour = focusedNode:getCaretInfo()\
  3251.    end\
  3252. \
  3253.    term.setCursorBlink( caretEnabled or false )\
  3254.    canvas:draw( force )\
  3255. \
  3256.    if caretEnabled then\
  3257.        term.setTextColour( caretColour or self.colour or 32768 )\
  3258.        term.setCursorPos( caretX or 1, caretY or 1 )\
  3259.    end\
  3260. end\
  3261. \
  3262. --[[\
  3263.    @instance\
  3264.    @desc Spawns a Titanium event instance and ships it to nodes and threads.\
  3265.    @param <table - event>\
  3266. ]]\
  3267. function Application:handle( eName, ... )\
  3268.    local eventObject = Event.spawn( eName, ... )\
  3269.    if eventObject.main == \"KEY\" then self:handleKey( eventObject ) end\
  3270. \
  3271.    local nodes, node = self.nodes\
  3272.    for i = #nodes, 1, -1 do\
  3273.        node = nodes[ i ]\
  3274.        -- The node will update itself depending on the event. Once all are updated they are drawn if changed.\
  3275.        if node then node:handle( eventObject ) end\
  3276.    end\
  3277. \
  3278.    self:executeCallbacks( eName, eventObject )\
  3279.    self:handleThreads( eventObject, eName, ... )\
  3280. end\
  3281. \
  3282. --[[\
  3283.    @instance\
  3284.    @desc Stops the program loop\
  3285. ]]\
  3286. function Application:stop()\
  3287.    if self.running then\
  3288.        self.running = false\
  3289.        os.queueEvent( \"ti_app_close\" )\
  3290.    else\
  3291.        return error \"Application already stopped\"\
  3292.    end\
  3293. end\
  3294. ",
  3295. ["Terminal.ti"]="local function isThreadRunning( obj )\
  3296.    if not obj.thread then return false end\
  3297. \
  3298.    return obj.thread.running\
  3299. end\
  3300. \
  3301. --[[\
  3302.    The terminal class \"is\" a node designed to emulate term programs. For example, the CraftOS shell can be run inside of this\
  3303.    node, with full functionality.\
  3304. \
  3305.    This could potentially be used to embed Titanium applications, however a more sophisticated approach is in the works.\
  3306. ]]\
  3307. \
  3308. class \"Terminal\" extends \"Node\" mixin \"MFocusable\" {\
  3309.    static = {\
  3310.        focusedEvents = {\
  3311.            MOUSE = true,\
  3312.            KEY = true,\
  3313.            CHAR = true\
  3314.        }\
  3315.    };\
  3316. \
  3317.    canvas = true;\
  3318.    displayThreadStatus = true;\
  3319. }\
  3320. \
  3321. \
  3322. --[[\
  3323.    @instance\
  3324.    @desc Creates a terminal instance and creating a custom redirect canvas (the program being run inside the terminal requires a term redirect)\
  3325.    @param [number - X], [number - Y], [number - width], [number - height], [function - chunk]\
  3326. ]]\
  3327. function Terminal:__init__( ... )\
  3328.    self:resolve( ... )\
  3329.    self:super()\
  3330. \
  3331.    self.canvas = RedirectCanvas( self )\
  3332.    self.redirect = self.canvas:getTerminalRedirect()\
  3333. end\
  3334. \
  3335. --[[\
  3336.    @instance\
  3337.    @desc 'Wraps' the chunk (self.chunk - function) by creating a Thread instance with the chunk as its function (coroutine).\
  3338.          The embedded program is then started by resuming the coroutine with 'titanium_terminal_start'.\
  3339. \
  3340.          A chunk must be set on the terminal node for this function to succeed. This function is automatically executed\
  3341.          when a chunk is set (self.chunk = fChunk, or self:setChunk( fChunk ) ).\
  3342. ]]\
  3343. function Terminal:wrapChunk()\
  3344.    if type( self.chunk ) ~= \"function\" then\
  3345.        return error \"Cannot wrap chunk. No chunk function set.\"\
  3346.    end\
  3347. \
  3348.    self.canvas:resetTerm()\
  3349. \
  3350.    self.thread = Thread( self.chunk )\
  3351.    self:resume( GenericEvent \"titanium_terminal_start\" )\
  3352. end\
  3353. \
  3354. --[[\
  3355.    @instance\
  3356.    @desc Resumes the terminal with the given event. If the event is a mouse event its co-ordinates should have been adjusted to accomodate the terminal location\
  3357.          This is done automatically if the event is delivered via 'self:handle'.\
  3358. \
  3359.          The terminal (thread) is then resumed with this event. If the thread crashes, the 'exception' callback is executed with the thread. Access the exception using\
  3360.          'thread.exception'.\
  3361. \
  3362.          If the thread finished (gracefully), the 'finish' callback will be executed, with the thread AND a boolean (true), to indicate graceful finish\
  3363.          If the thread did not finish gracefully, the above will occur, however the boolean will be false as opposed to true.\
  3364.    @param <Event Instance - event>\
  3365. ]]\
  3366. function Terminal:resume( event )\
  3367.    if not isThreadRunning( self ) then return end\
  3368. \
  3369.    if not Titanium.typeOf( event, \"Event\", true ) then\
  3370.        return error \"Invalid event object passed to resume terminal thread\"\
  3371.    end\
  3372. \
  3373.    local thread, old = self.thread, term.redirect( self.redirect )\
  3374.    thread:filterHandle( event )\
  3375.    term.redirect( old )\
  3376. \
  3377.    if not thread.running then\
  3378.        if type( thread.exception ) == \"string\" then\
  3379.            if self.displayThreadStatus then\
  3380.                self:emulate(function() printError( \"Thread Crashed: \" .. tostring( thread.exception ) ) end)\
  3381.            end\
  3382. \
  3383.            self:executeCallbacks(\"exception\", thread)\
  3384.        else\
  3385.            if self.displayThreadStatus then\
  3386.                self:emulate(function() print \"Finished\" end)\
  3387.            end\
  3388. \
  3389.            self:executeCallbacks(\"finish\", thread, true)\
  3390.        end\
  3391. \
  3392.        self:executeCallbacks(\"finish\", thread, false)\
  3393.    end\
  3394. \
  3395.    self.changed = true\
  3396. end\
  3397. \
  3398. --[[\
  3399.    @instance\
  3400.    @desc Allows a custom function to be executed with the terminals redirect being used, with error catching.\
  3401.    @param <function - fn>\
  3402. ]]\
  3403. function Terminal:emulate( fn )\
  3404.    if type( fn ) ~= \"function\" then\
  3405.        return error(\"Failed to emulate function. '\"..tostring( fn )..\"' is not valid\")\
  3406.    end\
  3407. \
  3408.    local old = term.redirect( self.redirect )\
  3409.    local ok, err = pcall( fn )\
  3410.    term.redirect( old )\
  3411. \
  3412.    if not ok then\
  3413.        return error(\"Failed to emulate function. Reason: \"..tostring( err ), 3)\
  3414.    end\
  3415. end\
  3416. \
  3417. --[[\
  3418.    @setter\
  3419.    @desc Sets the chunk on the instance, and wraps the chunk using 'wrapChunk'.\
  3420.    @param <function - chunk>\
  3421. ]]\
  3422. function Terminal:setChunk( chunk )\
  3423.    self.chunk = chunk\
  3424.    self:wrapChunk()\
  3425. end\
  3426. \
  3427. --[[\
  3428.    @instance\
  3429.    @desc Provides the information required by the nodes application to draw the application caret.\
  3430.    @return <boolean - caretEnabled>, <number - caretX>, <number - caretY>, <colour - caretColour>\
  3431. ]]\
  3432. function Terminal:getCaretInfo()\
  3433.    local c = self.canvas\
  3434.    return isThreadRunning( self ) and c.tCursor, c.tX + self.X - 1, c.tY + self.Y - 1, c.tColour\
  3435. end\
  3436. \
  3437. --[[\
  3438.    @instance\
  3439.    @desc If a MouseEvent is received, it's position is adjusted to become relative to this node before being passed to the terminal thread.\
  3440.    @param <Event Instance - eventObj>\
  3441. ]]\
  3442. function Terminal:handle( eventObj )\
  3443.    if not isThreadRunning( self ) then self:unfocus(); return end\
  3444. \
  3445.    if eventObj.main == \"MOUSE\" then\
  3446.        if not eventObj.handled and eventObj:withinParent( self ) then self:focus() else self:unfocus() end\
  3447.        eventObj = eventObj:clone( self )\
  3448.    elseif eventObj.handled then\
  3449.        return\
  3450.    end\
  3451. \
  3452.    if Terminal.focusedEvents[ eventObj.main ] and not self.focused then return end\
  3453.    self:resume( eventObj )\
  3454. end\
  3455. \
  3456. --[[\
  3457.    @instance\
  3458.    @desc The terminal node has no need to draw any custom graphics to it's canvas - the running thread does all the drawing.\
  3459.          The parent node automatically draws the node canvas to it's own, so there is no need to run any draw code here.\
  3460. ]]\
  3461. function Terminal:draw() end\
  3462. \
  3463. configureConstructor({\
  3464.    orderedArguments = { \"X\", \"Y\", \"width\", \"height\", \"chunk\" },\
  3465.    argumentTypes = { chunk = \"function\" },\
  3466.    useProxy = { \"chunk\" }\
  3467. }, true)\
  3468. ",
  3469. ["Input.ti"]="--[[\
  3470.    @instance position - number (def. 0) - The position of the caret, dictates the position new characters are added\
  3471.    @instance scroll - number (def. 0) - The scroll position of the input, used when the content is longer than the width of the node\
  3472.    @instance value - string (def. \"\") - The value currently held by the input\
  3473.    @instance selection - number, boolean (def. false) - If a number, the end of the selection. If false, no selection made\
  3474.    @instance selectedColour - colour (def. false) - The colour of selected text\
  3475.    @instance selectedBackgroundColour - colour (def. false) - The background colour of selected text\
  3476.    @instance placeholder - string (def. false) - The text displayed when the input is unfocused and has no value\
  3477.    @instance placeholderColour - colour (def. 256) - The colour used when displaying the placeholder text\
  3478.    @instance limit - number (def. 0) - If greater than 0, the amount of text entered will be limited to that number. If 0, no limit is set.\
  3479.    @instance mask - string (def. \"\") - If not set to \"\", the character will be used instead of the characters displayed at draw time. Doesn't affect the actual value, only how it is displayed (ie: password forms)\
  3480. \
  3481.    When the text is changed, the 'change' callback is executed. When the 'enter' key is pressed, the 'trigger' callback will be executed.\
  3482. \
  3483.    The Input class \"provides\" the user with the ability to insert a single line of text, see EditableTextContainer for multi-line text input.\
  3484. ]]\
  3485. \
  3486. local stringRep, stringSub = string.rep, string.sub\
  3487. class \"Input\" extends \"Node\" mixin \"MActivatable\" mixin \"MFocusable\" {\
  3488.    position = 0;\
  3489.    scroll = 0;\
  3490.    value = \"\";\
  3491. \
  3492.    selection = false;\
  3493.    selectedColour = false;\
  3494.    selectedBackgroundColour = colours.lightBlue;\
  3495. \
  3496.    placeholder = false;\
  3497.    placeholderColour = 256;\
  3498. \
  3499.    allowMouse = true;\
  3500.    allowKey = true;\
  3501.    allowChar = true;\
  3502. \
  3503.    limit = 0;\
  3504.    mask = \"\";\
  3505. }\
  3506. \
  3507. --[[\
  3508.    @constructor\
  3509.    @desc Constructs the instance by resolving arguments and registering used properties\
  3510. ]]\
  3511. function Input:__init__( ... )\
  3512.    self:resolve( ... )\
  3513.    self:register( \"width\", \"selectedColour\", \"selectedBackgroundColour\", \"limit\" )\
  3514. \
  3515.    self:super()\
  3516. end\
  3517. \
  3518. --[[\
  3519.    @instance\
  3520.    @desc Sets the input to active if clicked on, sets active and focused to false if the mouse click was not on the input.\
  3521.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  3522. ]]\
  3523. function Input:onMouseClick( event, handled, within )\
  3524.    if within and not handled then\
  3525.        if event.button ~= 1 then return end\
  3526.        if self.focused then\
  3527.            local application, pos, width, scroll = self.application, self.position, self.width, self.scroll\
  3528.            local clickedPos = math.min( #self.value, event.X - self.X + self.scroll )\
  3529. \
  3530.            if application:isPressed( keys.leftShift ) or application:isPressed( keys.rightShift ) then\
  3531.                if clickedPos ~= pos then\
  3532.                    self.selection = clickedPos\
  3533.                else self.selection = false end\
  3534.            else self.position, self.selection = clickedPos, false end\
  3535.        end\
  3536. \
  3537.        self.active, event.handled = true, true\
  3538.    else\
  3539.        self.active = false\
  3540.        self:unfocus()\
  3541.    end\
  3542. end\
  3543. \
  3544. --[[\
  3545.    @instance\
  3546.    @desc If a mouse drag occurs while the input is focused, the selection will be moved to the mouse drag location, creating a selection between the cursor position and the drag position\
  3547.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  3548. ]]\
  3549. function Input:onMouseDrag( event, handled, within )\
  3550.    if not self.focused or handled then return end\
  3551.    self.selection = math.min( #self.value, event.X - self.X + self.scroll )\
  3552.    event.handled = true\
  3553. end\
  3554. \
  3555. --[[\
  3556.    @instance\
  3557.    @desc If the mouse up missed the input or the event was already handled, active and false are set to false.\
  3558.          If within and not handled and input is active focused is set to true. Active is set to false on all conditions.\
  3559.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  3560. ]]\
  3561. function Input:onMouseUp( event, handled, within )\
  3562.    if ( not within or handled ) and self.focused then\
  3563.        self:unfocus()\
  3564.    elseif within and not handled and self.active and not self.focused then\
  3565.        self:focus()\
  3566.    end\
  3567. \
  3568.    self.active = false\
  3569. end\
  3570. \
  3571. --[[\
  3572.    @instance\
  3573.    @desc Catches char events and inserts the character pressed into the input's value.\
  3574.    @param <CharEvent Instance - event>, <boolean - handled>\
  3575. ]]\
  3576. function Input:onChar( event, handled )\
  3577.    if not self.focused or handled then return end\
  3578. \
  3579.    local value, position, selection = self.value, self.position, self.selection\
  3580.    if selection then\
  3581.        local start, stop = math.min( selection, position ), math.max( selection, position )\
  3582.        start = start > stop and start - 1 or start\
  3583. \
  3584.        self.value, self.selection = stringSub( value, 1, start ) .. event.char .. stringSub( value, stop + ( start < stop and 1 or 2 ) ), false\
  3585.        self.position = start + 1\
  3586.        self.changed = true\
  3587.    else\
  3588.        if self.limit > 0 and #value >= self.limit then return end\
  3589. \
  3590.        self.value = stringSub( value, 1, position ) .. event.char .. stringSub( value, position + 1 )\
  3591.        self.position = self.position + 1\
  3592.    end\
  3593. \
  3594.    self:executeCallbacks \"change\"\
  3595. \
  3596.    event.handled = true\
  3597. end\
  3598. \
  3599. --[[\
  3600.    @instance\
  3601.    @desc Catches key down events and performs an action depending on the key pressed\
  3602.    @param <KeyEvent Instance - event>, <boolean - handled>\
  3603. ]]\
  3604. function Input:onKeyDown( event, handled )\
  3605.    if not self.focused or handled then return end\
  3606. \
  3607.    local value, position = self.value, self.position\
  3608.    local valueLen = #value\
  3609.    if event.sub == \"DOWN\" then\
  3610.        local key, selection, position, application = event.keyName, self.selection, self.position, self.application\
  3611.        local isPressed, start, stop = application:isPressed( keys.leftShift ) or application:isPressed( keys.rightShift )\
  3612. \
  3613.        if selection then\
  3614.            start, stop = selection < position and selection or position, selection > position and selection + 1 or position + 1\
  3615.        else start, stop = position - 1, position end\
  3616. \
  3617.        if key == \"enter\" then\
  3618.            self:executeCallbacks( \"trigger\", self.value, self.selection and self:getSelectedValue() )\
  3619.        elseif selection then\
  3620.            if key == \"delete\" or key == \"backspace\" then\
  3621.                self.value = stringSub( value, 1, start ) .. stringSub( value, stop )\
  3622.                self.position = start\
  3623.                self.selection = false\
  3624.            elseif not isPressed and ( key == \"left\" or key == \"right\" ) then\
  3625.                self.position = key == \"left\" and start + 1 or key == \"right\" and stop - 2\
  3626.                self.selection = false\
  3627.            end\
  3628.        end\
  3629. \
  3630.        local cSelection = self.selection or self.position\
  3631.        local function set( offset )\
  3632.            if isPressed then self.selection = cSelection + offset\
  3633.            else self.position = self.position + offset; self.selection = false end\
  3634.        end\
  3635. \
  3636.        if key == \"left\" then set( -1 )\
  3637.        elseif key == \"right\" then set( 1 ) else\
  3638.            if key == \"home\" then\
  3639.                set( isPressed and -cSelection or -position )\
  3640.            elseif key == \"end\" then\
  3641.                set( isPressed and valueLen - cSelection or valueLen - position )\
  3642.            elseif key == \"backspace\" and isPressed then\
  3643.                self.value, self.position = stringSub( self.value, stop + 1 ), 0\
  3644.            end\
  3645.        end\
  3646. \
  3647.        if not isPressed then\
  3648.            if key == \"backspace\" and start >= 0 and not selection then\
  3649.                self.value = stringSub( value, 1, start ) .. stringSub( value, stop + 1 )\
  3650.                self.position = start\
  3651.            elseif key == \"delete\" and not selection then\
  3652.                self.value, self.changed = stringSub( value, 1, stop ) .. stringSub( value, stop + 2 ), true\
  3653.            end\
  3654.        end\
  3655.    end\
  3656. end\
  3657. \
  3658. --[[\
  3659.    @instance\
  3660.    @desc If an assigned label (labelFor set as this nodes ID on a label) is clicked, this input is focused\
  3661.    @param <Label Instance - label>, <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  3662. ]]\
  3663. function Input:onLabelClicked( label, event, handled, within )\
  3664.    self:focus()\
  3665.    event.handled = true\
  3666. end\
  3667. \
  3668. --[[\
  3669.    @instance\
  3670.    @desc Draws the inputs background and text to the parent canvas\
  3671.    @param [boolean - force]\
  3672. ]]\
  3673. function Input:draw( force )\
  3674.    local raw = self.raw\
  3675.    if raw.changed or force then\
  3676.        local canvas, tc, bg = raw.canvas, raw.colour, raw.backgroundColour\
  3677.        if raw.focused then tc, bg = raw.focusedColour, raw.focusedBackgroundColour\
  3678.        elseif raw.active then tc, bg = raw.activeColour, raw.activeBackgroundColour end\
  3679. \
  3680.        canvas:clear( bg )\
  3681. \
  3682.        local position, width, value, selection, placeholder = self.position, self.width, self.mask ~= \"\" and stringRep( self.mask, #self.value ) or self.value, self.selection, self.placeholder\
  3683.        if self.focused or not placeholder or #value > 0 then\
  3684.            if self.selection then\
  3685.                local start, stop = selection < position and selection or position, selection > position and selection + 1 or position + 1\
  3686.                if start < stop then stop = stop - 1 end\
  3687. \
  3688.                local startPos = -self.scroll + 1\
  3689. \
  3690.                canvas:drawTextLine( startPos, 1, stringSub( value, 1, start + 1 ), tc, bg )\
  3691.                canvas:drawTextLine( startPos + start, 1, stringSub( value, start + 1, stop ), self.focused and self.selectedColour or tc, self.focused and self.selectedBackgroundColour or bg )\
  3692.                canvas:drawTextLine( startPos + stop, 1, stringSub( value, stop + 1 ), tc, bg )\
  3693.            else\
  3694.                canvas:drawTextLine( -self.scroll + 1, 1, value, tc, bg )\
  3695.            end\
  3696.        else canvas:drawTextLine( 1, 1, stringSub( placeholder, 1, self.width ), self.placeholderColour, bg ) end\
  3697. \
  3698.        raw.changed = false\
  3699.    end\
  3700. end\
  3701. \
  3702. --[[\
  3703.    @instance\
  3704.    @desc Attempts to reposition the scroll of the input box depending on the position indicator\
  3705.    @param <number - indicator>\
  3706. ]]\
  3707. function Input:repositionScroll( indicator )\
  3708.    local limit = self.limit\
  3709.    local isLimit = limit > 0\
  3710. \
  3711.    if indicator >= self.width and indicator > ( self.scroll + self.width - 1 ) then\
  3712.        self.scroll = math.min( indicator - self.width + 1, #self.value - self.width + 1 ) - ( isLimit and indicator >= limit and 1 or 0 )\
  3713.    elseif indicator <= self.scroll then\
  3714.        self.scroll = math.max( self.scroll - ( self.scroll - indicator ), 0 )\
  3715.    else self.scroll = math.max( math.min( self.scroll, #self.value - self.width + 1 ), 0 ) end\
  3716. end\
  3717. \
  3718. --[[\
  3719.    @instance\
  3720.    @desc If the given selection is a number, it will be adjusted to fit within the bounds of the input and set. If not, the value will be raw set.\
  3721.    @param <number|boolean - selection>\
  3722. ]]\
  3723. function Input:setSelection( selection )\
  3724.    if type( selection ) == \"number\" then\
  3725.        local newSelection = math.max( math.min( selection, #self.value ), 0 )\
  3726.        self.selection = newSelection ~= self.position and newSelection or false\
  3727.    else self.selection = selection end\
  3728. \
  3729.    self:repositionScroll( self.selection or self.position )\
  3730.    self.changed = true\
  3731. end\
  3732. \
  3733. --[[\
  3734.    @instance\
  3735.    @desc Returns the value of the input that is selected\
  3736.    @return <string - selectedValue>\
  3737. ]]\
  3738. function Input:getSelectedValue()\
  3739.    local selection, position = self.selection, self.position\
  3740.    return stringSub( self.value, ( selection < position and selection or position ) + 1, ( selection > position and selection or position ) )\
  3741. end\
  3742. \
  3743. --[[\
  3744.    @instance\
  3745.    @desc If the given position is equal to the (inputs) selection, the selection will be reset.\
  3746.          If not equal, the value will be adjusted to fit inside the bounds of the input and then set.\
  3747.    @param <number - pos>\
  3748. ]]\
  3749. function Input:setPosition( pos )\
  3750.    if self.selection == pos then self.selection = false end\
  3751.    self.position, self.changed = math.max( math.min( pos, #self.value ), 0 ), true\
  3752. \
  3753.    self:repositionScroll( self.position )\
  3754. end\
  3755. \
  3756. --[[\
  3757.    @instance\
  3758.    @desc When called, returns the state of the caret, its position (absolute) and colour.\
  3759.    @return <boolean - caretEnabled>, <number - caretX>, <number - caretY>, <colour - caretColour>\
  3760. ]]\
  3761. function Input:getCaretInfo()\
  3762.    local sX, sY = self:getAbsolutePosition()\
  3763.    local limit = self.limit\
  3764. \
  3765.    return not self.selection and ( limit <= 0 or self.position < limit ), sX + ( self.position - self.scroll ), sY, self.focusedColour\
  3766. end\
  3767. \
  3768. \
  3769. configureConstructor({\
  3770.    orderedArguments = { \"X\", \"Y\", \"width\" },\
  3771.    argumentTypes = { value = \"string\", position = \"number\", selection = \"number\", placeholder = \"string\", placeholderColour = \"colour\", selectedColour = \"colour\", selectedBackgroundColour = \"colour\", limit = \"number\", mask = \"string\" },\
  3772.    useProxy = { \"toggled\" }\
  3773. }, true)\
  3774. ",
  3775. ["NodeQuery.ti"]="local function format( original, symbol, final )\
  3776.    local wrapper = type( original ) == \"string\" and '\"' or \"\"\
  3777.    local finalWrapper = type( final ) == \"string\" and '\"' or \"\"\
  3778. \
  3779.    return (\"return %s%s%s %s %s%s%s\"):format( wrapper, tostring( original ), wrapper, symbol, finalWrapper, tostring( final ), finalWrapper )\
  3780. end\
  3781. \
  3782. local function testCondition( node, condition )\
  3783.    local fn, err = loadstring( format( node[ condition.property ], condition.symbol, condition.value ) )\
  3784.    if fn then return fn() end\
  3785. \
  3786.    return fn()\
  3787. end\
  3788. \
  3789. local function queryScope( scope, section, results )\
  3790.    local last = {}\
  3791. \
  3792.    local node\
  3793.    for i = 1, #scope do\
  3794.        node = scope[ i ]\
  3795. \
  3796.        if ( not section.id or node.id == section.id ) and\
  3797.        ( not section.type or section.type == \"*\" or node.__type == section.type ) and\
  3798.        ( not section.classes or node:hasClass( section.classes ) ) then\
  3799.            local condition, failed = section.condition\
  3800.            if condition then\
  3801.                local conditionPart\
  3802.                for c = 1, #condition do\
  3803.                    if not testCondition( node, condition[ c ] ) then\
  3804.                        failed = true\
  3805.                        break\
  3806.                    end\
  3807.                end\
  3808.            end\
  3809. \
  3810.            if not failed then\
  3811.                last[ #last + 1 ] = node\
  3812.            end\
  3813.        end\
  3814.    end\
  3815. \
  3816.    return last\
  3817. end\
  3818. \
  3819. local function createScope( results, direct )\
  3820.    local scope = {}\
  3821.    for i = 1, #results do\
  3822.        local innerScope = direct and results[ i ].nodes or results[ i ].collatedNodes\
  3823. \
  3824.        for r = 1, #innerScope do\
  3825.            scope[ #scope + 1 ] = innerScope[ r ]\
  3826.        end\
  3827.    end\
  3828. \
  3829.    return scope\
  3830. end\
  3831. \
  3832. local function performQuery( query, base )\
  3833.    local lastResults, section = base\
  3834. \
  3835.    for i = 1, #query do\
  3836.        section = query[ i ]\
  3837.        lastResults = queryScope( createScope( lastResults, section.direct ), section )\
  3838.    end\
  3839. \
  3840.    return lastResults\
  3841. end\
  3842. \
  3843. --[[\
  3844.    @static supportedMethods - table (def. { ... }) - Methods inside the table are automatically implemented on NodeQuery instances at instantiation. When called, the method is executed on all nodes in the result set with all arguments being passed\
  3845.    @instance result - table (def. false) - All nodes that matched the query\
  3846.    @instance parent - Instance (def. false) - The Titanium instance that the NodeQuery will begin searching at\
  3847. ]]\
  3848. \
  3849. class \"NodeQuery\" {\
  3850.    static = { supportedMethods = { \"addClass\", \"removeClass\", \"setClass\", \"set\", \"animate\", \"on\", \"off\" } };\
  3851.    result = false;\
  3852. \
  3853.    parent = false;\
  3854. }\
  3855. \
  3856. --[[\
  3857.    @constructor\
  3858.    @desc Constructs the NodeQuery instance by parsing 'queryString' and executing the query.\
  3859. \
  3860.          Supported methods configured via NodeQuery.static.supportedMethods are then implemented on the instance.\
  3861. ]]\
  3862. function NodeQuery:__init__( parent, queryString )\
  3863.    if not ( Titanium.isInstance( parent ) and type( queryString ) == \"string\" ) then\
  3864.        return error \"Node query requires Titanium instance and string query\"\
  3865.    end\
  3866.    self.parent = parent\
  3867. \
  3868.    self.parsedQuery = QueryParser( queryString ).query\
  3869.    self.result = self:query()\
  3870. \
  3871.    local sup = NodeQuery.supportedMethods\
  3872.    for i = 1, #sup do\
  3873.        self[ sup[ i ] ] = function( self, ... ) self:executeOnNodes( sup[ i ], ... ) end\
  3874.    end\
  3875. end\
  3876. \
  3877. --[[\
  3878.    @static\
  3879.    @desc Returns a table containing the nodes matching the conditions set in 'query'\
  3880.    @return <table - results>\
  3881. ]]\
  3882. function NodeQuery:query()\
  3883.    local query, results = self.parsedQuery, {}\
  3884.    if type( query ) ~= \"table\" then return error( \"Cannot perform query. Invalid query object passed\" ) end\
  3885. \
  3886.    local parent = { self.parent }\
  3887.    for i = 1, #query do\
  3888.        local res = performQuery( query[ i ], parent )\
  3889. \
  3890.        for r = 1, #res do\
  3891.            results[ #results + 1 ] = res[ r ]\
  3892.        end\
  3893.    end\
  3894. \
  3895.    return results\
  3896. end\
  3897. \
  3898. --[[\
  3899.    @instance\
  3900.    @desc Returns true if the class 'class' exists on all nodes in the result set, false otherwise\
  3901.    @param <table|string - class>\
  3902.    @return <boolean - hasClass>\
  3903. ]]\
  3904. function NodeQuery:hasClass( class )\
  3905.    local nodes = self.result\
  3906.    for i = 1, #nodes do\
  3907.        if not nodes[ i ]:hasClass( class ) then\
  3908.            return false\
  3909.        end\
  3910.    end\
  3911. \
  3912.    return true\
  3913. end\
  3914. \
  3915. --[[\
  3916.    @instance\
  3917.    @desc The function 'fn' will be called once for each node in the result set, with the node being passed each time (essentially iterates over each node in the result set)\
  3918.    @param <function - fn>\
  3919. ]]\
  3920. function NodeQuery:each( fn )\
  3921.    local nodes = self.result\
  3922.    for i = 1, #nodes do\
  3923.        fn( nodes[ i ] )\
  3924.    end\
  3925. end\
  3926. \
  3927. --[[\
  3928.    @instance\
  3929.    @desc Iterates over each node in the result set, calling 'fnName' with arguments '...' on each\
  3930.    @param <string - fnName>, [vararg - ...]\
  3931. ]]\
  3932. function NodeQuery:executeOnNodes( fnName, ... )\
  3933.    local nodes, node = self.result\
  3934.    for i = 1, #nodes do\
  3935.        node = nodes[ i ]\
  3936. \
  3937.        if node:can( fnName ) then\
  3938.            node[ fnName ]( node, ... )\
  3939.        end\
  3940.    end\
  3941. end\
  3942. ",
  3943. ["Thread.ti"]="--[[\
  3944.    @instance running - boolean (def. false) - Indicates whether or not the thread is running. When false, calls to :handle will be rejected.\
  3945.    @instance func - function (def. false) - The function to wrap inside of a coroutine.\
  3946.    @instance co - coroutine (def. false) - The coroutine object automatically created by the Thread instance when it is started\
  3947.    @instance filter - string (def. false) - If set, only events that match will be handled. If titanium events are enabled, the :is() function will be used.\
  3948.    @instance exception - string (def. false) - If the thread crashes, coroutine.resume will catch the error and it will be stored inside of this property.\
  3949.    @instance titaniumEvents - boolean (def. false) - If 'true', events passed to this thread will NOT be converted to CC events and will remain event instances\
  3950. \
  3951.    The Thread object is a simple class \"used\" to wrap a function (chunk) in a coroutine.\
  3952. \
  3953.    This object can then be added to Application instances, via :addThread, and removed using the :removeThread\
  3954.    counterpart. This allows for easy 'multitasking', much like the ComputerCraft parallel API.\
  3955. \
  3956.    When resuming a thread, a titanium event should be passed via ':filterHandle'. Failing to do so will cause unexpected side-effects for threads\
  3957.    that don't use 'titaniumEvents'. As a rule, ':handle' shouldn't be called manually.\
  3958. ]]\
  3959. \
  3960. class \"Thread\" mixin \"MCallbackManager\" {\
  3961.    running = false;\
  3962. \
  3963.    func = false;\
  3964.    co = false;\
  3965. \
  3966.    filter = false;\
  3967.    exception = false;\
  3968. \
  3969.    titaniumEvents = false;\
  3970. }\
  3971. \
  3972. --[[\
  3973.    @instance\
  3974.    @desc Constructs the instance and starts the thread by invoking ':start'\
  3975.    @param <function - func>, [boolean - titaniumEvents], [string - id]\
  3976. ]]\
  3977. function Thread:__init__( ... )\
  3978.    self:resolve( ... )\
  3979.    self:start()\
  3980. end\
  3981. \
  3982. --[[\
  3983.    @instance\
  3984.    @desc Starts the thread by setting 'running' to true, resetting 'filter' and wrapping 'func' in a coroutine\
  3985. ]]\
  3986. function Thread:start()\
  3987.    self.co = coroutine.create( self.func )\
  3988.    self.running = true\
  3989.    self.filter = false\
  3990. end\
  3991. \
  3992. --[[\
  3993.    @instance\
  3994.    @desc Stops the thread by setting 'running' to false, preventing events from being handled\
  3995. ]]\
  3996. function Thread:stop()\
  3997.    self.running = false\
  3998. end\
  3999. \
  4000. --[[\
  4001.    @instance\
  4002.    @desc The preferred way of delivering events to a thread. Processes the given event, passing relevant information to ':handle' depending on the value of 'titaniumEvents'.\
  4003. \
  4004.          If 'titaniumEvents' is true, the event will be passed as is. If 'false', the event data will be unpacked before being sent.\
  4005.    @param <Event Instance - eventObj>\
  4006. ]]\
  4007. function Thread:filterHandle( eventObj )\
  4008.    if self.titaniumEvents then\
  4009.        self:handle( eventObj )\
  4010.    else\
  4011.        self:handle( unpack( eventObj.data ) )\
  4012.    end\
  4013. end\
  4014. \
  4015. --[[\
  4016.    @instance\
  4017.    @desc The raw handle method, shouldn't be called manually. Passes the given argument(s) to the coroutine. The first argument is assumed to be the event\
  4018.          itself (either the CC event name, or the event instance) and is used to determine if the event matches the filter (if set).\
  4019.    @param <Event Instance - eventObj> - Expected arguments when 'titaniumEvents' is true\
  4020.    @param <string - eventName>, <eventDetails - ...> - Expected arguments when 'titaniumEvents' is false\
  4021. ]]\
  4022. function Thread:handle( ... )\
  4023.    if not self.running then return false end\
  4024. \
  4025.    local tEvents, cFilter, eMain, co, ok, filter = self.titaniumEvents, self.filter, select( 1, ... ), self.co\
  4026.    if tEvents then\
  4027.        if not cFilter or ( eMain:is( cFilter ) or eMain:is( \"terminate\" ) ) then\
  4028.            ok, filter = coroutine.resume( co, eMain )\
  4029.        else return end\
  4030.    else\
  4031.        if not cFilter or ( eMain == cFilter or eMain == \"terminate\" ) then\
  4032.            ok, filter = coroutine.resume( co, ... )\
  4033.        else return end\
  4034.    end\
  4035. \
  4036.    if ok then\
  4037.        if coroutine.status( co ) == \"dead\" then\
  4038.            self.running = false\
  4039.        end\
  4040. \
  4041.        self.filter = filter\
  4042.    else\
  4043.        self.exception = filter\
  4044.        self.running = false\
  4045.    end\
  4046. end\
  4047. \
  4048. --[[\
  4049.    @setter\
  4050.    @desc Updates 'running' property and invokes the 'finish' callback. If the thread crashed, the exception is passed to the callback too.\
  4051.    @param <boolean - running>\
  4052. ]]\
  4053. function Thread:setRunning( running )\
  4054.    self.running = running\
  4055. \
  4056.    if not running then\
  4057.        self:executeCallbacks( \"finish\", self.exception )\
  4058.    end\
  4059. end\
  4060. \
  4061. configureConstructor {\
  4062.    orderedArguments = { \"func\", \"titaniumEvents\", \"id\" },\
  4063.    requiredArguments = { \"func\" }\
  4064. }\
  4065. ",
  4066. ["XMLLexer.ti"]="--[[\
  4067.    @instance openTag - boolean (def. false) - If true, the lexer is currently inside of an XML tag\
  4068.    @instance definingAttribute - boolean (def. false) - If true, the lexer is currently inside an opening XML tag and is trying to find attributes (XML_ATTRIBUTE_VALUE)\
  4069.    @instance currentAttribute - boolean (def. false) - If true, the lexer will take the next token as an attribute value (after '=')\
  4070. \
  4071.    A lexer than processes XML content into tokens used by XMLParser\
  4072. ]]\
  4073. class \"XMLLexer\" extends \"Lexer\" {\
  4074.    openTag = false;\
  4075.    definingAttribute = false;\
  4076.    currentAttribute = false;\
  4077. }\
  4078. \
  4079. --[[\
  4080.    @instance\
  4081.    @desc Converts the stream into tokens by way of pattern matching\
  4082. ]]\
  4083. function XMLLexer:tokenize()\
  4084.    self:trimStream()\
  4085.    local stream, openTag, currentAttribute, definingAttribute = self:trimStream(), self.openTag, self.currentAttribute, self.definingAttribute\
  4086.    local first = stream:sub( 1, 1 )\
  4087. \
  4088.    if stream:find \"^<(%w+)\" then\
  4089.        self:pushToken({type = \"XML_OPEN\", value = self:consumePattern \"^<(%w+)\"})\
  4090.        self.openTag = true\
  4091.    elseif stream:find \"^</(%w+)>\" then\
  4092.        self:pushToken({type = \"XML_END\", value = self:consumePattern \"^</(%w+)>\"})\
  4093.        self.openTag = false\
  4094.    elseif stream:find \"^/>\" then\
  4095.        self:pushToken({type = \"XML_END_CLOSE\"})\
  4096.        self:consume( 2 )\
  4097.        self.openTag = false\
  4098.    elseif openTag and stream:find \"^%w+\" then\
  4099.        self:pushToken({type = definingAttribute and \"XML_ATTRIBUTE_VALUE\" or \"XML_ATTRIBUTE\", value = self:consumePattern \"^%w+\"})\
  4100. \
  4101.        if not definingAttribute then\
  4102.            self.currentAttribute = true\
  4103.            return\
  4104.        end\
  4105.    elseif not openTag and stream:find \"^([^<]+)\" then\
  4106.        local content = self:consumePattern \"^([^<]+)\"\
  4107. \
  4108.        local newlines = select( 2, content:gsub(\"\\n\", \"\") )\
  4109.        if newlines then self:newline( newlines ) end\
  4110. \
  4111.        self:pushToken({type = \"XML_CONTENT\", value = content })\
  4112.    elseif first == \"=\" then\
  4113.        self:pushToken({type = \"XML_ASSIGNMENT\", value = \"=\"})\
  4114.        self:consume( 1 )\
  4115. \
  4116.        if currentAttribute then\
  4117.            self.definingAttribute = true\
  4118.        end\
  4119. \
  4120.        return\
  4121.    elseif first == \"'\" or first == \"\\\"\" then\
  4122.        self:pushToken({type = definingAttribute and \"XML_STRING_ATTRIBUTE_VALUE\" or \"XML_STRING\", value = self:consumeString( first )})\
  4123.    elseif first == \">\" then\
  4124.        self:pushToken({type = \"XML_CLOSE\"})\
  4125.        self.openTag = false\
  4126.        self:consume( 1 )\
  4127.    else\
  4128.        self:throw(\"Unexpected block '\"..stream:match(\"(.-)%s\")..\"'\")\
  4129.    end\
  4130. \
  4131.    if self.currentAttribute then self.currentAttribute = false end\
  4132.    if self.definingAttribute then self.definingAttribute = false end\
  4133. end\
  4134. ",
  4135. ["MAnimationManager.ti"]="--[[\
  4136.    @instance animations - table (def. {}) - The current animations attached to this instance\
  4137.    @instance animationTimer - number, boolean (def. false) - If false, no animation timer is set. If a number, represents the ID of the timer that will update the animations every tick\
  4138.    @instance time - number (def. false) - Represents the current time (os.clock). Used to calculate deltaTime (dt) when updating each Tween\
  4139. ]]\
  4140. \
  4141. class \"MAnimationManager\" abstract() {\
  4142.    animations = {};\
  4143.    animationTimer = false;\
  4144. \
  4145.    time = false;\
  4146. }\
  4147. \
  4148. --[[\
  4149.    @desc When the animation timer ticks, update animations attached to this application and requeue the timer if more animations must occur.\
  4150. ]]\
  4151. function MAnimationManager:updateAnimations()\
  4152.    local dt = os.clock() - self.time\
  4153. \
  4154.    local anims, anim = self.animations\
  4155.    for i = #anims, 1, -1 do\
  4156.        anim = anims[ i ]\
  4157. \
  4158.        if anim:update( dt ) then\
  4159.            if type( anim.promise ) == \"function\" then\
  4160.                anim:promise( self )\
  4161.            end\
  4162. \
  4163.            self:removeAnimation( anim )\
  4164.        end\
  4165.    end\
  4166. \
  4167.    self.timer = false\
  4168.    if #anims > 0 then self:restartAnimationTimer() end\
  4169. end\
  4170. \
  4171. --[[\
  4172.    @instance\
  4173.    @desc Adds an animation to this object, on update this animation will be updated\
  4174.    @param <Tween Instance - animation>\
  4175. ]]\
  4176. function MAnimationManager:addAnimation( animation )\
  4177.    if not Titanium.typeOf( animation, \"Tween\", true ) then\
  4178.        return error(\"Failed to add animation to manager. '\"..tostring( animation )..\"' is invalid, Tween instance expected\")\
  4179.    end\
  4180. \
  4181.    self:removeAnimation( animation.name )\
  4182.    table.insert( self.animations, animation )\
  4183. \
  4184.    if not self.timer then\
  4185.        self:restartAnimationTimer()\
  4186.    end\
  4187. \
  4188.    return animation\
  4189. end\
  4190. \
  4191. --[[\
  4192.    @instance\
  4193.    @desc Removes an animation from this object, it will stop receiving updates from this object\
  4194.    @param <string - animation> - The name of the animation to remove\
  4195.    @param <Tween Instance - animation> - The animation instance to remove\
  4196.    @return <Tween Instance - animation> - The removed animation. If nil, no animation removed\
  4197. ]]\
  4198. function MAnimationManager:removeAnimation( animation )\
  4199.    local searchName\
  4200.    if type( animation ) == \"string\" then\
  4201.        searchName = true\
  4202.    elseif not Titanium.typeOf( animation, \"Tween\", true ) then\
  4203.        return error(\"Failed to remove animation from manager. '\"..tostring( animation )..\"' is invalid, Tween instance expected\")\
  4204.    end\
  4205. \
  4206.    local anims = self.animations\
  4207.    for i = 1, #anims do\
  4208.        if ( searchName and anims[ i ].name == animation ) or ( not searchName and anims[ i ] == animation ) then\
  4209.            return table.remove( anims, i )\
  4210.        end\
  4211.    end\
  4212. end\
  4213. \
  4214. --[[\
  4215.    @instance\
  4216.    @desc When an animation is queued the timer is created for 'time' (0.05). This replaces the currently running timer (if any).\
  4217.          The objects 'time' is then updated to the current time (os.clock)\
  4218.    @param [number - time]\
  4219. ]]\
  4220. function MAnimationManager:restartAnimationTimer( time )\
  4221.    if self.timer then\
  4222.        os.cancelTimer( self.timer )\
  4223.    end\
  4224. \
  4225.    self.time = os.clock()\
  4226.    self.timer = os.startTimer( type( time ) == \"number\" and time or .05 )\
  4227. end\
  4228. ",
  4229. ["Theme.ti"]="local function getTagDetails( rule )\
  4230.    return ( rule.arguments.id and ( \"#\" .. rule.arguments.id ) or \"\" ) .. (function( classString ) local classes = \"\"; for className in classString:gmatch(\"%S+\") do classes = classes .. \".\"..className end; return classes end)( rule.arguments[\"class\"] or \"\" )\
  4231. end\
  4232. \
  4233. local function splitXMLTheme( queue, tree )\
  4234.    for i = 1, #tree do\
  4235.        local children = tree[ i ].children\
  4236.        if children then\
  4237.            for c = 1, #children do\
  4238.                local type = tree[ i ].type\
  4239.                queue[ #queue + 1 ] = { ( type == \"Any\" and \"*\" or type ) .. getTagDetails( tree[ i ] ), children[ c ], tree[ i ] }\
  4240.            end\
  4241.        end\
  4242.    end\
  4243. \
  4244.    return queue\
  4245. end\
  4246. \
  4247. --[[\
  4248.    @instance name - string (def. false) - The name of the theme. A name should always be set on the instance, and is a required constructor argument\
  4249.    @instance rules - table (def. {}) - The rules of this theme, generated via Theme.static.parse.\
  4250. \
  4251.    The Theme class \"is\" a basic class \"designed\" to hold styling rules.\
  4252. \
  4253.    Themes are added to objects using the MThemeManager mixin (or a custom implementation). These themes then dictate the appearance of objects that utilize 'MThemeable'.\
  4254. ]]\
  4255. \
  4256. class \"Theme\" {\
  4257.    name = false;\
  4258. \
  4259.    rules = {};\
  4260. }\
  4261. \
  4262. --[[\
  4263.    @constructor\
  4264.    @desc Constructs the Theme by setting the name and, if 'source' is provided, parsing it and storing the result in 'rules'\
  4265.    @param <string - name>, [string - source]\
  4266. ]]\
  4267. function Theme:__init__( name, source )\
  4268.    self.name = type( name ) == \"string\" and name or error(\"Failed to initialise Theme. Name '\"..tostring( name )..\"' is invalid, expected string.\")\
  4269. \
  4270.    if source then self.rules = Theme.parse( source ) end\
  4271. end\
  4272. \
  4273. --[[\
  4274.    @static\
  4275.    @desc Parses XML source code by lexing/parsing it into an XML tree. The XML is then parsed into theme rules\
  4276.    @param <string - source>\
  4277.    @return <table - rules>\
  4278. ]]\
  4279. function Theme.static.parse( source )\
  4280.    local queue, rawRules, q = splitXMLTheme( {}, XMLParser( source ).tree ), {}, 1\
  4281. \
  4282.    local function processQueueEntry( entry )\
  4283.        local queryPrefix, rule = entry[ 1 ], entry[ 2 ]\
  4284.        local children = rule.children\
  4285. \
  4286.        if children then\
  4287.            for c = 1, #children do\
  4288.                if not Titanium.getClass( rule.type ) and rule.type ~= \"Any\" then\
  4289.                    return error( \"Failed to generate theme data. Child target '\"..rule.type..\"' doesn't exist as a Titanium class\" )\
  4290.                end\
  4291. \
  4292.                local type = rule.type\
  4293.                queue[ #queue + 1 ] = { queryPrefix .. \" \" .. ( rule.arguments.direct and \"> \" or \"\" ) .. ( type == \"Any\" and \"*\" or type ) .. getTagDetails( rule ), children[ c ], rule }\
  4294.            end\
  4295.        elseif rule.content then\
  4296.            local ownerType = entry[ 3 ].type\
  4297.            local dynamic = rule.arguments.dynamic\
  4298. \
  4299.            local ruleTarget, computeType, value = ownerType, false, rule.content\
  4300.            if ownerType == \"Any\" then\
  4301.                ruleTarget, computeType = \"ANY\", true\
  4302.            elseif not dynamic then\
  4303.                local parentReg = Titanium.getClass( ownerType ).getRegistry()\
  4304.                local argumentTypes = parentReg.constructor and parentReg.constructor.argumentTypes or {}\
  4305. \
  4306.                value = XMLParser.convertArgType( value, argumentTypes[ parentReg.alias[ rule.type ] or rule.type ] )\
  4307.            end\
  4308. \
  4309.            if dynamic then\
  4310.                value = DynamicEqParser( rule.content )\
  4311.            end\
  4312. \
  4313.            if not rawRules[ ruleTarget ] then rawRules[ ruleTarget ] = {} end\
  4314.            if not rawRules[ ruleTarget ][ queryPrefix ] then rawRules[ ruleTarget ][ queryPrefix ] = {} end\
  4315.            table.insert( rawRules[ ruleTarget ][ queryPrefix ], {\
  4316.                computeType = not dynamic and computeType or nil,\
  4317.                property = rule.type,\
  4318.                value = value,\
  4319.                important = rule.arguments.important,\
  4320.                isDynamic = dynamic\
  4321.            })\
  4322.        else\
  4323.            return error( \"Failed to generate theme data. Invalid theme rule found. No value (XML_CONTENT) has been set for tag '\"..rule.type..\"'\" )\
  4324.        end\
  4325.    end\
  4326. \
  4327.    while q <= #queue do\
  4328.        processQueueEntry( queue[ q ] )\
  4329.        q = q + 1\
  4330.    end\
  4331. \
  4332.    return rawRules\
  4333. end\
  4334. \
  4335. --[[\
  4336.    @static\
  4337.    @desc Creates a Theme instance with the name passed and the source as the contents of the file at 'path'.\
  4338.    @param <string - name>, <string - path>\
  4339.    @return <Theme Instance - Theme>\
  4340. ]]\
  4341. function Theme.static.fromFile( name, path )\
  4342.    if not fs.exists( path ) then\
  4343.        return error( \"Path '\"..tostring( path )..\"' cannot be found\" )\
  4344.    end\
  4345. \
  4346.    local h = fs.open( path, \"r\" )\
  4347.    local content = h.readAll()\
  4348.    h.close()\
  4349. \
  4350.    return Theme( name, content )\
  4351. end\
  4352. ",
  4353. ["PageContainer.ti"]="--[[\
  4354.    The PageContainer serves as a container that shows one 'page' at a time. Preset (or completely custom) animated transitions can be used when\
  4355.    a new page is selected.\
  4356. ]]\
  4357. \
  4358. class \"PageContainer\" extends \"Container\" {\
  4359.    scroll = 0;\
  4360. \
  4361.    animationDuration = 0.25;\
  4362.    animationEasing = \"outQuad\";\
  4363.    customAnimation = false;\
  4364.    selectedPage = false;\
  4365. \
  4366.    pageIndexes = {};\
  4367. }\
  4368. \
  4369. --[[\
  4370.    @instance\
  4371.    @desc Intercepts the draw call, adding the x scroll to the x offset\
  4372.    @param [boolean - force], [number - offsetX], [number - offsetY]\
  4373. ]]\
  4374. function PageContainer:draw( force, offsetX, offsetY )\
  4375.    if not self.selectedPage then\
  4376.        self.canvas:drawTextLine( 1, 1, \"No page selected\", 16384, 1 )\
  4377.    else\
  4378.        return self.super:draw( force, ( offsetX or 0 ) - self.scroll, offsetY )\
  4379.    end\
  4380. end\
  4381. \
  4382. --[[\
  4383.    @instance\
  4384.    @desc If a MOUSE event is handled, it's X co-ordinate is adjusted using the scroll offset of the page container.\
  4385.    @param <Event Instance - eventObj>\
  4386.    @return <boolean - propagate>\
  4387. ]]\
  4388. function PageContainer:handle( eventObj )\
  4389.    if not self.super.super:handle( eventObj ) then return end\
  4390. \
  4391.    local clone\
  4392.    if eventObj.main == \"MOUSE\" then\
  4393.        clone = eventObj:clone( self )\
  4394.        clone.X = clone.X + self.scroll\
  4395.        clone.isWithin = clone.isWithin and eventObj:withinParent( self ) or false\
  4396.    end\
  4397. \
  4398.    self:shipEvent( clone or eventObj )\
  4399.    if clone and clone.isWithin and ( self.consumeAll or clone.handled ) then\
  4400.        eventObj.handled = true\
  4401.    end\
  4402.    return true\
  4403. end\
  4404. \
  4405. --[[\
  4406.    @instance\
  4407.    @desc Selects the new page using the 'pageID'. If a function is given as argument #2 'animationOverride', it will be called instead of the customAnimation set (or the default animation method used).\
  4408.          Therefore the animationOverride is given full control of the transition, allowing for easy one-off transition effects.\
  4409. \
  4410.          If 'customAnimation' is set on the instance, it will be called if no 'animationOverride' is provided, providing a more long term override method.\
  4411. \
  4412.          If neither are provided, a normal animation will take place, using 'animationDuration' and 'animationEasing' set on the instance as parameters for the animation.\
  4413.    @param <string - pageID>, [function - animationOverride]\
  4414. ]]\
  4415. function PageContainer:selectPage( pageID, animationOverride )\
  4416.    local page = self:getPage( pageID )\
  4417. \
  4418.    self.selectedPage = page\
  4419.    if type( animationOverride ) == \"function\" then\
  4420.        return animationOverride( self.currentPage, page )\
  4421.    elseif self.customAnimation then\
  4422.        return self.customAnimation( self.currentPage, page )\
  4423.    end\
  4424. \
  4425.    self:animate( self.__ID .. \"_PAGE_CONTAINER_SELECTION\", \"scroll\", ( self:getPagePosition( pageID ) - 1 ) * self.width, self.animationDuration, self.animationEasing )\
  4426. end\
  4427. \
  4428. --[[\
  4429.    @instance\
  4430.    @desc Returns an integer representing the position of the page. These may change as pages are added and removed from the PageContainer - don't rely on them remaining constant\
  4431.    @param <string - pageID>\
  4432.    @return <number - position>\
  4433. ]]\
  4434. function PageContainer:getPagePosition( pageID )\
  4435.    local indexes = self.pageIndexes\
  4436.    for i = 1, #indexes do\
  4437.        if indexes[ i ] == pageID then\
  4438.            return i\
  4439.        end\
  4440.    end\
  4441. end\
  4442. \
  4443. --[[\
  4444.    @instance\
  4445.    @desc Ensures the node being added to the PageContainer is a 'Page' node because no other nodes should be added directly to this node\
  4446.    @param <Page Instance - node>\
  4447.    @return 'param1 (node)'\
  4448. ]]\
  4449. function PageContainer:addNode( node )\
  4450.    if Titanium.typeOf( node, \"Page\", true ) then\
  4451.        local pgInd = self.pageIndexes\
  4452.        if self:getPagePosition( node.id ) then\
  4453.            return error(\"Cannot add page '\"..tostring( node )..\"'. Another page with the same ID already exists inside this PageContainer\")\
  4454.        end\
  4455. \
  4456.        pgInd[ #pgInd + 1 ] = node.id\
  4457.        node.X = ( #pgInd - 1 ) * self.width + 1\
  4458. \
  4459.        return self.super:addNode( node )\
  4460.    end\
  4461. \
  4462.    return error(\"Only 'Page' nodes can be added as direct children of 'PageContainer' nodes, '\"..tostring( node )..\"' is invalid\")\
  4463. end\
  4464. \
  4465. --[[\
  4466.    @instance\
  4467.    @desc A alias \"for\" 'addNode', contextualized for the PageContainer\
  4468.    @param <Page Instance - page>\
  4469.    @return 'param1 (page)'\
  4470. ]]\
  4471. function PageContainer:addPage( page )\
  4472.    return self:addNode( page )\
  4473. end\
  4474. \
  4475. --[[\
  4476.    @instance\
  4477.    @desc A alias \"for\" 'getNode', contextualized for the PageContainer\
  4478.    @param <string - id>, [boolean - recursive]\
  4479.    @return [Node Instance - node]\
  4480. ]]\
  4481. function PageContainer:getPage( ... )\
  4482.    return self:getNode( ... )\
  4483. end\
  4484. \
  4485. --[[\
  4486.    @instance\
  4487.    @desc A alias \"for\" 'removeNode', contextualized for the PageContainer\
  4488.    @param <Node Instance | string - id>\
  4489.    @return <boolean - success>, [node - removedNode]\
  4490. ]]\
  4491. function PageContainer:removePage( ... )\
  4492.    return self:removeNode( ... )\
  4493. end\
  4494. \
  4495. --[[\
  4496.    @instance\
  4497.    @desc Shifts requests to clear the PageContainer area to the left, depending on the scroll position of the container\
  4498.    @param <number - x>, <number - y>, <number - width>, <number - height>\
  4499. ]]\
  4500. function PageContainer:redrawArea( x, y, width, height )\
  4501.    self.super:redrawArea( x, y, width, height, -self.scroll )\
  4502. end\
  4503. \
  4504. --[[\
  4505.    @instance\
  4506.    @desc Due to the contents of the PageContainer not actually moving (just the scroll), the content of the PageContainer must be manually cleared.\
  4507.          To fit this demand, the area of the PageContainer is cleared when the scroll parameter is changed.\
  4508. ]]\
  4509. function PageContainer:setScroll( scroll )\
  4510.    self.scroll = scroll\
  4511.    self.changed = true\
  4512.    self:redrawArea( 1, 1, self.width, self.height )\
  4513. end\
  4514. \
  4515. --[[\
  4516.    @instance\
  4517.    @desc Returns the absolute position of the node, with the 'scroll' used as a offset\
  4518.    @return <number - absoluteX>, <number - absoluteY>\
  4519. ]]\
  4520. function PageContainer:getAbsolutePosition()\
  4521.    local parent, application = self.parent, self.application\
  4522.    local X, Y = self.X - self.scroll, self.Y\
  4523.    if parent then\
  4524.        local pX, pY = self.parent:getAbsolutePosition()\
  4525.        return -1 + pX + X, -1 + pY + Y\
  4526.    else return X, Y end\
  4527. end\
  4528. ",
  4529. ["CharEvent.ti"]="--[[\
  4530.    @instance main - string (def. \"CHAR\") - The main type of the event, should remain unchanged\
  4531.    @instance char - string (def. false) - The character that has been pressed\
  4532. ]]\
  4533. \
  4534. class \"CharEvent\" extends \"Event\" {\
  4535.    main = \"CHAR\";\
  4536.    char = false;\
  4537. }\
  4538. \
  4539. --[[\
  4540.    @constructor\
  4541.    @desc Constructs the instance, adding the event name and the character to 'data'\
  4542.    @param <string - name>, <string - char>\
  4543. ]]\
  4544. function CharEvent:__init__( name, char )\
  4545.    self.name = name\
  4546.    self.char = char\
  4547. \
  4548.    self.data = { name, char }\
  4549. end\
  4550. ",
  4551. ["DynamicValue.ti"]="--[[\
  4552.    @instance propertyValues - table (def. {}) - The values of all watched properties, passed to the equation when solving the dynamic value equation\
  4553.    @instance properties - table (def. {}) - The properties being watched by this DynamicValue\
  4554.    @instance target - Instance (def. nil) - The Titanium instance that the DynamicValue belongs to. When re-solved, the 'property' is updated on this target to become the new value of the equation\
  4555.    @instance property - string (def. nil) - The property that the DynamicValue is responsible for updating on 'target'\
  4556.    @instance equation - string (def. nil) - The Lua equation that will be solved to find the value for 'property' on 'target'\
  4557. \
  4558.    A dynamic value object is used by MPropertyManager (primarily) to link properties to an equation.\
  4559. \
  4560.    When instantiated, the DynamicValue object automatically creates watch instructions on the properties provided via 'properties'.\
  4561.    Anytime one of the watched properties changes, the DynamicValue is updated and the equation provided is re-solved, with the new\
  4562.    property values provided.\
  4563. \
  4564.    This allows for equations dependent on other instances to become dynamic, changing with their target instances.\
  4565. ]]\
  4566. \
  4567. class \"DynamicValue\" {\
  4568.    propertyValues = {};\
  4569.    properties = {};\
  4570. }\
  4571. \
  4572. --[[\
  4573.    @constructor\
  4574.    @desc Creates watcher instructions towards 'target' for each property linked\
  4575.    @param <Instance - target>, <string - property>, <table - properties>, <string - equation>\
  4576. ]]\
  4577. function DynamicValue:__init__( ... )\
  4578.    self:resolve( ... )\
  4579.    self:attach()\
  4580. \
  4581.    local reg = Titanium.getClass( self.target.__type ):getRegistry().constructor\
  4582.    if reg and reg.argumentTypes then\
  4583.        self.type = reg.argumentTypes[ self.property ]\
  4584.    end\
  4585. \
  4586.    self:solve()\
  4587. end\
  4588. \
  4589. --[[\
  4590.    @instance\
  4591.    @desc Attach watch instructions to each required argument\
  4592. ]]\
  4593. function DynamicValue:attach()\
  4594.    local properties = self.properties\
  4595.    for i = 1, #properties do\
  4596.        local obj, prop = properties[ i ][ 2 ], properties[ i ][ 1 ]\
  4597. \
  4598.        obj:watchProperty( prop, function( _, __, val )\
  4599.            self.propertyValues[ i ] = val\
  4600.            self:solve()\
  4601.        end, \"DYNAMIC_LINK_\" .. self.__ID )\
  4602. \
  4603.        self.propertyValues[ i ] = obj[ prop ]\
  4604.    end\
  4605. end\
  4606. \
  4607. --[[\
  4608.    @instance\
  4609.    @desc Detaches the watcher instructions towards the targets of the dynamic value\
  4610. ]]\
  4611. function DynamicValue:detach()\
  4612.    local properties = self.properties\
  4613.    for i = 1, #properties do\
  4614.        local prop = properties[ i ]\
  4615.        prop[ 2 ]:unwatchProperty( prop[ 1 ], \"DYNAMIC_LINK_\" .. self.__ID )\
  4616.    end\
  4617. end\
  4618. \
  4619. --[[\
  4620.    @instance\
  4621.    @desc Solves the 'equation' by inserting the values fetched off of linked targets\
  4622. ]]\
  4623. function DynamicValue:solve()\
  4624.    local fn, err = loadstring( self.equation )\
  4625.    if not fn then return error(\"Failed to solve dynamic value equation (\"..tostring( eq )..\"). Parse exception: \" .. tostring( err )) end\
  4626. \
  4627.    local ok, val = pcall( fn, self.propertyValues )\
  4628.    if not ok then return error(\"Failed to solve dyamic value equation (\"..tostring( eq )..\"). Runtime exception: \" .. tostring( val )) end\
  4629. \
  4630.    self.target[ self.property ] = XMLParser.convertArgType( val, self.type )\
  4631. end\
  4632. \
  4633. configureConstructor {\
  4634.    orderedArguments = { \"target\", \"property\", \"properties\", \"equation\" },\
  4635.    argumentTypes = {\
  4636.        property = \"string\",\
  4637.        properties = \"table\",\
  4638.        equation = \"string\"\
  4639.    },\
  4640.    requiredArguments = true\
  4641. }\
  4642. ",
  4643. ["Node.ti"]="--[[\
  4644.    @static eventMatrix - table (def. {}) - Contains the event -> function name matrix. If an event name is found in the keys, the value is used as the function to call on the instance (otherwise, 'onEvent' is called)\
  4645.    @static anyMatrix - table (def. {}) - Only used when 'useAnyCallbacks' is true. If a key matching the 'main' key of the event instance is found, it's value is called (ie: MOUSE -> onMouse)\
  4646. \
  4647.    @instance enabled - boolean (def. true) - When 'true', node may receive events\
  4648.    @instance parentEnabled - boolean (def. true) - When 'true', the parent of this node is enabled\
  4649.    @instance visible - boolean (def. true) - When 'true', node is drawn to parent canvas\
  4650.    @instance allowMouse - boolean (def. false) - If 'false', mouse events shipped to this node are ignored\
  4651.    @instance allowKey - boolean (def. false) - If 'false', key events shipped to this node are ignored\
  4652.    @instance allowChar - boolean (def. false) - If 'false', key events that have a character (ie: 'a', 'b' and 'c', but not 'delete') shipped to this node are ignored\
  4653.    @instance useAnyCallbacks - boolean (def. false) - If 'true', events shipped to this node are handled through the static 'anyMatrix'\
  4654.    @instance disabledColour - colour (def. 128) - When the node is disabled (enabled 'false'), this colour should be used to draw the foreground\
  4655.    @instance disabledBackgroundColour - colour (def. 256) - When the node is disabled (enabled 'false'), this colour should be used to draw the background\
  4656.    @instance needsRedraw - boolean (def. true) - If true, the contents of the nodes canvas will be blit onto the parents canvas without redrawing the nodes canvas contents, unlike 'changed', which does both\
  4657.    @instance consumeWhenDisabled - boolean (def. true) - When true, mouse events that collide with this node while it is disabled will be consumed (handled = true). Non-mouse events are unaffected by this property\
  4658. \
  4659.    A Node is an object which makes up the applications graphical user interface (GUI).\
  4660. \
  4661.    Objects such as labels, buttons and text inputs are nodes.\
  4662. ]]\
  4663. \
  4664. class \"Node\" abstract() extends \"Component\" mixin \"MThemeable\" mixin \"MCallbackManager\" {\
  4665.    static = {\
  4666.        eventMatrix = {\
  4667.            mouse_click = \"onMouseClick\",\
  4668.            mouse_drag = \"onMouseDrag\",\
  4669.            mouse_up = \"onMouseUp\",\
  4670.            mouse_scroll = \"onMouseScroll\",\
  4671. \
  4672.            key = \"onKeyDown\",\
  4673.            key_up = \"onKeyUp\",\
  4674.            char = \"onChar\"\
  4675.        },\
  4676.        anyMatrix = {\
  4677.            MOUSE = \"onMouse\",\
  4678.            KEY = \"onKey\"\
  4679.        }\
  4680.    };\
  4681. \
  4682.    disabledColour = 128;\
  4683.    disabledBackgroundColour = 256;\
  4684. \
  4685.    allowMouse = false;\
  4686.    allowKey = false;\
  4687.    allowChar = false;\
  4688.    useAnyCallbacks = false;\
  4689. \
  4690.    enabled = true;\
  4691.    parentEnabled = true;\
  4692. \
  4693.    visible = true;\
  4694. \
  4695.    needsRedraw = true;\
  4696.    parent = false;\
  4697. \
  4698.    consumeWhenDisabled = true;\
  4699. }\
  4700. \
  4701. --[[\
  4702.    @constructor\
  4703.    @desc Creates a NodeCanvas (bound to self) and stores it inside of `self.canvas`. This canvas is drawn to the parents canvas at draw time.\
  4704. ]]\
  4705. function Node:__init__()\
  4706.    self:register( \"X\", \"Y\", \"colour\", \"backgroundColour\", \"enabled\", \"visible\", \"disabledColour\", \"disabledBackgroundColour\" )\
  4707. \
  4708.    if not self.canvas then self.raw.canvas = NodeCanvas( self ) end\
  4709. end\
  4710. \
  4711. --[[\
  4712.    @constructor\
  4713.    @desc Finishes construction by hooking the theme manager into the node.\
  4714. ]]\
  4715. function Node:__postInit__()\
  4716.    self:hook()\
  4717. end\
  4718. \
  4719. --[[\
  4720.    @setter\
  4721.    @desc Sets 'parentEnabled' and sets 'changed' to true\
  4722.    @param <boolean - parentEnabled>\
  4723. ]]\
  4724. function Node:setParentEnabled( parentEnabled )\
  4725.    self.parentEnabled = parentEnabled\
  4726.    self.changed = true\
  4727. end\
  4728. \
  4729. --[[\
  4730.    @setter\
  4731.    @desc Sets 'needsRedraw'. If the node now needs a redraw, it's parent (if any) will also have it's 'needsRedraw' property set to true\
  4732.    @param <boolean - needsRedraw>\
  4733. ]]\
  4734. function Node:setNeedsRedraw( needsRedraw )\
  4735.    self.needsRedraw = needsRedraw\
  4736. \
  4737.    if needsRedraw and self.parent then self.parent.needsRedraw = needsRedraw end\
  4738. end\
  4739. \
  4740. --[[\
  4741.    @setter\
  4742.    @desc Sets the enabled property of the node to 'enabled'. Sets node's 'changed' to true.\
  4743.    @param <boolean - enabled>\
  4744. ]]\
  4745. function Node:setEnabled( enabled )\
  4746.    self.enabled = enabled\
  4747.    self.changed = true\
  4748. end\
  4749. \
  4750. --[[\
  4751.    @getter\
  4752.    @desc Returns 'enabled', unless the parent is not enabled, in which case 'false' is returned\
  4753.    @return <boolean - enabled>\
  4754. ]]\
  4755. function Node:getEnabled()\
  4756.    if not self.parentEnabled then\
  4757.        return false\
  4758.    end\
  4759. \
  4760.    return self.enabled\
  4761. end\
  4762. \
  4763. --[[\
  4764.    @setter\
  4765.    @desc Sets 'parent' and sets the nodes 'changed' to true. If the node has a parent (ie: didn't set parent to false) the 'parentEnabled' property will be updated to match the parents 'enabled'\
  4766.    @param <MNodeContainer Instance - parent> - If a parent exists, this line is used\
  4767.    @param <boolean - parent> - If no parent exists, this line is used (false)\
  4768. ]]\
  4769. function Node:setParent( parent )\
  4770.    self.parent = parent\
  4771.    self.changed = true\
  4772. \
  4773.    if parent then\
  4774.        self.parentEnabled = Titanium.typeOf( parent, \"Application\" ) or parent.enabled\
  4775.    end\
  4776. end\
  4777. \
  4778. --[[\
  4779.    @setter\
  4780.    @desc Sets the node to visible/invisible depending on 'visible' paramater\
  4781.    @param <boolean - visible>\
  4782. ]]\
  4783. function Node:setVisible( visible )\
  4784.    self.visible = visible\
  4785.    self.changed = true\
  4786.    if not visible then\
  4787.        self:queueAreaReset()\
  4788.    end\
  4789. end\
  4790. \
  4791. --[[\
  4792.    @setter\
  4793.    @desc Sets the changed state of this node to 'changed'. If 'changed' then the parents of this node will also have changed set to true.\
  4794.    @param <boolean - changed>\
  4795. ]]\
  4796. function Node:setChanged( changed )\
  4797.    self.changed = changed\
  4798. \
  4799.    if changed then\
  4800.        local parent = self.parent\
  4801.        if parent and not parent.changed then\
  4802.            parent.changed = true\
  4803.        end\
  4804. \
  4805.        self.needsRedraw = true\
  4806.    end\
  4807. end\
  4808. \
  4809. --[[\
  4810.    @instance\
  4811.    @desc Handles events by triggering methods on the node depending on the event object passed\
  4812.    @param <Event Instance* - eventObj>\
  4813.    @return <boolean - propagate>\
  4814. \
  4815.    *Note: The event instance passed can be of variable type, ideally it extends 'Event' so that required methods are implemented on the eventObj.\
  4816. ]]\
  4817. function Node:handle( eventObj )\
  4818.    local main, sub, within = eventObj.main, eventObj.sub, false\
  4819.    local handled, enabled = eventObj.handled, self.enabled\
  4820. \
  4821.    if main == \"MOUSE\" then\
  4822.        if self.allowMouse then\
  4823.            within = eventObj.isWithin and eventObj:withinParent( self ) or false\
  4824. \
  4825.            if within and not enabled and self.consumeWhenDisabled then eventObj.handled = true end\
  4826.        else return end\
  4827.    elseif ( main == \"KEY\" and not self.allowKey ) or ( main == \"CHAR\" and not self.allowChar ) then\
  4828.        return\
  4829.    end\
  4830. \
  4831.    if not enabled then return end\
  4832. \
  4833.    local fn = Node.eventMatrix[ eventObj.name ] or \"onEvent\"\
  4834.    if self:can( fn ) then\
  4835.        self[ fn ]( self, eventObj, handled, within )\
  4836.    end\
  4837. \
  4838.    if self.useAnyCallbacks then\
  4839.        local anyFn = Node.anyMatrix[ main ]\
  4840.        if self:can( anyFn ) then\
  4841.            self[ anyFn ]( self, eventObj, handled, within )\
  4842.        end\
  4843.    end\
  4844. \
  4845.    return true\
  4846. end\
  4847. \
  4848. --[[\
  4849.    @instance\
  4850.    @desc Returns the absolute X, Y position of a node rather than its position relative to it's parent.\
  4851.    @return <number - X>, <number - Y>\
  4852. ]]\
  4853. function Node:getAbsolutePosition()\
  4854.    local parent, application = self.parent, self.application\
  4855.    if parent then\
  4856.        if parent == application then\
  4857.            return -1 + application.X + self.X, -1 + application.Y + self.Y\
  4858.        end\
  4859. \
  4860.        local pX, pY = self.parent:getAbsolutePosition()\
  4861.        return -1 + pX + self.X, -1 + pY + self.Y\
  4862.    else return self.X, self.Y end\
  4863. end\
  4864. \
  4865. --[[\
  4866.    @instance\
  4867.    @desc A shortcut method to quickly create a Tween instance and add it to the applications animations\
  4868.    @return <Tween Instance - animation> - If an application is set, the animation created is returned\
  4869.    @return <boolean - false> - If no application is set, false is returned\
  4870. ]]\
  4871. function Node:animate( ... )\
  4872.    if not self.application then return end\
  4873. \
  4874.    return self.application:addAnimation( Tween( self, ... ) )\
  4875. end\
  4876. \
  4877. configureConstructor {\
  4878.    argumentTypes = { enabled = \"boolean\", visible = \"boolean\", disabledColour = \"colour\", disabledBackgroundColour = \"colour\", consumeWhenDisabled = \"boolean\" }\
  4879. }\
  4880. ",
  4881. ["Canvas.ti"]="local function range( xBoundary, xDesired, width, canvasWidth )\
  4882.    local x1 = xBoundary > xDesired and 1 - xDesired or 1\
  4883.    local x2 = xDesired + width > canvasWidth and canvasWidth - xDesired or width\
  4884. \
  4885.    return x1, x2\
  4886. end\
  4887. \
  4888. --[[\
  4889.    @instance buffer - table (def. {}) - A one-dimensional table containing all the pixels inside the canvas. Pixel format: { char, fg, bg }\
  4890.    @instance last - table (def. {}) - A copy of buffer that is used to determine if the line has changed. If a pixel in the buffer doesn't match the same pixel in 'last', the line is redrawn\
  4891.    @instance width - number (def. 51) - The width of the canvas. Determines how many pixels pass before the next line starts\
  4892.    @instance height - number (def. 19) - The height of the canvas\
  4893.    @instance backgroundColour - colour, boolean, nil (def. 32768) - The background colour of the canvas. This is only used for pixels that do not have their own bg colour. If false/nil, the bg is left blank for the next parent to resolve. If '0', the background colour of the pixel under it is used (transparent)\
  4894.    @instance colour - colour, boolean, nil (def. 32768) - The foreground colour of the canvas. This is only used for pixels that do not have their own fg colour. If false/nil, the fg is left blank for the next parent to resolve. If '0', the colour of the pixel under it is used (transparent)\
  4895.    @instance backgroundTextColour - colour, nil (def. nil) - Only used when the pixels character is nil/false. If the pixel has no character, the character of the pixel under it is used (transparent). In this case, the foreground used to draw the pixel character is this property if set. If this property is not set, the foreground colour of the pixel under it is used instead\
  4896.    @instance backgroundChar - string, nil (def. nil) - The default character used for each pixel when the canvas is cleared and NOT transparent. If nil, no character is used (transparent)\
  4897.    @instance transparent - boolean, nil (def. nil) - When true, acts very similar to using backgroundChar 'nil' with the difference that the backgroundColour of cleared pixels is '0' (transparent)\
  4898. \
  4899.    The Canvas object is used by all components. It facilitates the drawing of pixels which are stored in its buffer.\
  4900. \
  4901.    The Canvas object is abstract. If you need a canvas for your object 'NodeCanvas' and 'TermCanvas' are provided with Titanium and may suite your needs.\
  4902. ]]\
  4903. \
  4904. class \"Canvas\" abstract() {\
  4905.    buffer = {};\
  4906.    last = {};\
  4907. \
  4908.    width = 51;\
  4909.    height = 19;\
  4910. \
  4911.    backgroundColour = 32768;\
  4912.    colour = 1;\
  4913. \
  4914.    transparent = false;\
  4915. }\
  4916. \
  4917. --[[\
  4918.    @constructor\
  4919.    @desc Constructs the canvas instance and binds it with the owner supplied.\
  4920.    @param <ClassInstance - owner>\
  4921. ]]\
  4922. function Canvas:__init__( owner )\
  4923.    self.raw.owner = Titanium.isInstance( owner ) and owner or error(\"Invalid argument for Canvas. Expected instance owner, got '\"..tostring( owner )..\"'\")\
  4924.    self.raw.width = owner.raw.width\
  4925.    self.raw.height = owner.raw.height\
  4926. \
  4927.    self.raw.colour = owner.raw.colour\
  4928.    self.raw.backgroundChar = owner.raw.backgroundChar\
  4929.    if self.raw.backgroundChar == \"nil\" then\
  4930.        self.raw.backgroundChar = nil\
  4931.    end\
  4932.    self.raw.backgroundTextColour = owner.raw.backgroundTextColour\
  4933.    self.raw.backgroundColour = owner.raw.backgroundColour\
  4934.    self.raw.transparent = owner.raw.transparent\
  4935. \
  4936.    self:clear()\
  4937. end\
  4938. \
  4939. --[[\
  4940.    @instance\
  4941.    @desc Replaces the canvas with a blank one\
  4942.    @param [number - colour]\
  4943. ]]\
  4944. function Canvas:clear( colour )\
  4945.    local pixel, buffer = { not self.transparent and self.backgroundChar, self.colour, self.transparent and 0 or colour or self.backgroundColour }, self.buffer\
  4946. \
  4947.    for index = 1, self.width * self.height do\
  4948.        buffer[ index ] = pixel\
  4949.    end\
  4950. end\
  4951. \
  4952. --[[\
  4953.    @instance\
  4954.    @desc Clears an area of the canvas defined by the arguments provided.\
  4955.    @param <number - areaX>, <number - areaY>, <number - areaWidth>, <number - areaHeight>, [number - colour]\
  4956. ]]\
  4957. function Canvas:clearArea( aX, aY, aWidth, aHeight, colour )\
  4958.    local aY, aX, cWidth = aY > 0 and aY - 1 or 0, aX > 0 and aX - 1 or 0, self.width\
  4959.    local pixel, buffer = { not self.transparent and self.backgroundChar, self.colour, self.transparent and 0 or colour or self.backgroundColour }, self.buffer\
  4960. \
  4961.    local xBoundary, yBoundary = cWidth - aX, self.height\
  4962.    local effectiveWidth = xBoundary < aWidth and xBoundary or aWidth\
  4963.    for y = 0, -1 + ( aHeight < yBoundary and aHeight or yBoundary ) do\
  4964.        local pos = aX + ( y + aY ) * cWidth\
  4965.        for x = 1, effectiveWidth do\
  4966.            buffer[ pos + x ] = pixel\
  4967.        end\
  4968.    end\
  4969. end\
  4970. \
  4971. --[[\
  4972.    @setter\
  4973.    @desc Updates the transparency setting of the canvas and then clears the canvas to apply this setting\
  4974.    @param <number - colour>\
  4975. ]]\
  4976. function Canvas:setTransparent( transparent )\
  4977.    self.transparent = transparent\
  4978.    self:clear()\
  4979. end\
  4980. \
  4981. --[[\
  4982.    @setter\
  4983.    @desc Updates the colour of the canvas and then clears the canvas\
  4984.    @param <number - colour>\
  4985. ]]\
  4986. function Canvas:setColour( colour )\
  4987.    self.colour = colour\
  4988.    self:clear()\
  4989. end\
  4990. \
  4991. --[[\
  4992.    @setter\
  4993.    @desc Updates the background colour of the canvas and then clears the canvas\
  4994.    @param <number - backgroundColour>\
  4995. ]]\
  4996. function Canvas:setBackgroundColour( backgroundColour )\
  4997.    self.backgroundColour = backgroundColour\
  4998.    self:clear()\
  4999. end\
  5000. \
  5001. --[[\
  5002.    @setter\
  5003.    @desc Updates the background character to be used when clearing the canvas. Clears the canvas to apply the change\
  5004.    @param <string/false/nil - char>\
  5005. ]]\
  5006. function Canvas:setBackgroundChar( char )\
  5007.    self.backgroundChar = char\
  5008.    self:clear()\
  5009. end\
  5010. \
  5011. --[[\
  5012.    @setter\
  5013.    @desc Updates the canvas width, and then clears the canvas to apply the change\
  5014. ]]\
  5015. function Canvas:setWidth( width )\
  5016. \9self.width = width\
  5017. \9self:clear()\
  5018. end\
  5019. \
  5020. --[[\
  5021.    @setter\
  5022.    @desc Updates the canvas height, and then clears the canvas to apply the change\
  5023. ]]\
  5024. function Canvas:setHeight( height )\
  5025. \9self.height = height\
  5026. \9self:clear()\
  5027. end\
  5028. \
  5029. --[[\
  5030.    @instance\
  5031.    @desc Draws the canvas to the target 'canvas' using the X and Y offsets. Pixel character, foreground and background colours are resolved according to their property values.\
  5032.    @param <Canvas - canvas>, [number - offsetX], [number - offsetY]\
  5033. ]]\
  5034. function Canvas:drawTo( canvas, offsetX, offsetY )\
  5035.    local offsetX = offsetX - 1 or 0\
  5036.    local offsetY = offsetY - 1 or 0\
  5037. \
  5038.    local sRaw, tRaw = self.raw, canvas.raw\
  5039.    local width, height, buffer = sRaw.width, sRaw.height, sRaw.buffer\
  5040.    local tWidth, tHeight, tBuffer = tRaw.width, tRaw.height, tRaw.buffer\
  5041. \
  5042.    local colour, backgroundColour, backgroundTextColour = sRaw.colour, sRaw.backgroundColour, sRaw.backgroundTextColour\
  5043.    local xStart, xEnd = range( 1, offsetX, width, tWidth )\
  5044. \
  5045.    local cache, tCache, top, tc, tf, tb, bot, bc, bf, bb, tPos = 0, offsetX + ( offsetY * tWidth )\
  5046.    for y = 1, height do\
  5047.        local cY = y + offsetY\
  5048.        if cY >= 1 and cY <= tHeight then\
  5049.            for x = xStart, xEnd do\
  5050.                top = buffer[ cache + x ]\
  5051.                tc, tf, tb, tPos = top[ 1 ], top[ 2 ], top[ 3 ], tCache + x\
  5052.                bot = tBuffer[ tPos ]\
  5053.                bc, bf, bb = bot[ 1 ], bot[ 2 ], bot[ 3 ]\
  5054. \
  5055.                if tc and ( tf and tf ~= 0 ) and ( tb and tb ~= 0 ) then\
  5056.                    tBuffer[ tPos ] = top\
  5057.                elseif not tc and tf == 0 and tb == 0 and bc and bf ~= 0 and bb ~= 0 then\
  5058.                    tBuffer[ tPos ] = bot\
  5059.                else\
  5060.                    local nc, nf, nb = tc or bc, tf or colour, tb or backgroundColour\
  5061. \
  5062.                    if not tc then\
  5063.                        nf = backgroundTextColour or bf\
  5064.                    end\
  5065. \
  5066.                    tBuffer[ tPos ] = { nc, nf == 0 and bf or nf, nb == 0 and bb or nb }\
  5067.                end\
  5068.            end\
  5069.        elseif cY > tHeight then\
  5070.            break\
  5071.        end\
  5072. \
  5073.        cache = cache + width\
  5074.        tCache = tCache + tWidth\
  5075.    end\
  5076. end\
  5077. ",
  5078. ["MouseEvent.ti"]="local string_sub, string_upper = string.sub, string.upper\
  5079. \
  5080. --[[\
  5081.    @instance main - string (def. \"MOUSE\") - The main type of the event, should remain unchanged\
  5082.    @instance sub - string (def. nil) - The sub type of the event (ie: mouse_up -> UP, mouse_scroll -> SCROLL, etc..)\
  5083.    @instance X - number (def. nil) - The X co-ordinate where the mouse event occurred\
  5084.    @instance Y - number (def. nil) - The Y co-ordinate where the mouse event occurred\
  5085.    @instance button - number (def. nil) - The code of the mouse button clicked (or, if mouse scroll the direction of scroll)\
  5086.    @instance isWithin - boolean (def. true) - If false, the mouse event has occurred outside of a parent.\
  5087. ]]\
  5088. \
  5089. class \"MouseEvent\" extends \"Event\" {\
  5090.    main = \"MOUSE\";\
  5091. \
  5092.    isWithin = true;\
  5093. }\
  5094. \
  5095. --[[\
  5096.    @constructor\
  5097.    @desc Sets the values given in their respective instance keys. Stores all values into a table 'data'.\
  5098.    @param <string - name>, <number - button>, <number - X>, <number - Y>, [string - sub]\
  5099. ]]\
  5100. function MouseEvent:__init__( name, button, X, Y, sub )\
  5101.    self.name = name\
  5102.    self.button = button\
  5103.    self.X = X\
  5104.    self.Y = Y\
  5105. \
  5106.    self.sub = sub or string_upper( string_sub( name, 7 ) )\
  5107. \
  5108.    self.data = { name, button, X, Y }\
  5109. end\
  5110. \
  5111. --[[\
  5112.    @instance\
  5113.    @desc Returns true if the mouse event was inside of the bounds provided.\
  5114.    @param <number - x>, <number - y>, <number - w>, <number - h>\
  5115.    @return <boolean - inBounds>\
  5116. ]]\
  5117. function MouseEvent:within( x, y, w, h )\
  5118.    local X, Y = self.X, self.Y\
  5119. \
  5120.    return X >= x and Y >= y and X <= -1 + x + w and Y <= -1 + y + h\
  5121. end\
  5122. \
  5123. --[[\
  5124.    @instance\
  5125.    @desc Returns true if the mouse event was inside the bounds of the parent provided (x, y, width and height)\
  5126.    @param <NodeContainer - parent>\
  5127. ]]\
  5128. function MouseEvent:withinParent( parent )\
  5129.    return self:within( parent.X, parent.Y, parent.width, parent.height )\
  5130. end\
  5131. \
  5132. --[[\
  5133.    @instance\
  5134.    @desc Clones 'self' and adjusts it's X & Y positions so that they're relative to 'parent'.\
  5135.    @param <NodeContainer - parent>\
  5136.    @return <MouseEvent Instance - clone>\
  5137. \
  5138.    Note: The clone's 'handle' method has been adjusted to also call 'handle' on the master event obj (self).\
  5139. ]]\
  5140. function MouseEvent:clone( parent )\
  5141.    local clone = MouseEvent( self.name, self.button, self.X - parent.X + 1, self.Y - parent.Y + 1, self.sub )\
  5142. \
  5143.    clone.handled = self.handled\
  5144.    clone.isWithin = self.isWithin\
  5145.    clone.setHandled = function( clone, handled )\
  5146.        clone.handled = handled\
  5147.        self.handled = handled\
  5148.    end\
  5149. \
  5150.    return clone\
  5151. end\
  5152. ",
  5153. ["EditableTextContainer.ti"]="local string_sub = string.sub\
  5154. \
  5155. --[[\
  5156.    The EditableTextContainer is a slighty more advanced version of TextContainer, allowing for changes to be made to the displayed text\
  5157. ]]\
  5158. \
  5159. class \"EditableTextContainer\" extends \"TextContainer\" {\
  5160.    allowKey = true,\
  5161.    allowChar = true\
  5162. }\
  5163. \
  5164. --[[\
  5165.    @instance\
  5166.    @desc Calls the super 'wrapText' with a reduced width (by one) so that space is left for the caret\
  5167.    @param <number - width>\
  5168. ]]\
  5169. function EditableTextContainer:wrapText( width )\
  5170.    self.super:wrapText( width - 1 )\
  5171. end\
  5172. \
  5173. --[[\
  5174.    @instance\
  5175.    @desc Inserts the content given, using the provided offsets where provided.\
  5176. \
  5177.          The 'offsetPost' (def. 1) will be added to the position when appending the remainder of the string\
  5178. \
  5179.          'offsetPre' (def. 0) is substracted from the position when creating the section to the prepended to the value. If this is >1, content\
  5180.          will be lost (ie: backspace).\
  5181. \
  5182.          If there is a selection when this method is called, the selected content will be removed.\
  5183. \
  5184.          The position will be increased by the length of the value provided.\
  5185.    @param <string - value>, [number - offsetPost], [number - offsetPre]\
  5186. ]]\
  5187. function EditableTextContainer:insertContent( value, offsetPost, offsetPre )\
  5188.    if self.selection then self:removeContent() end\
  5189. \
  5190.    local text = self.text\
  5191.    self.text = string_sub( text, 1, self.position - ( offsetPre or 0 ) ) .. value .. string_sub( text, self.position + ( offsetPost or 1 ) )\
  5192.    self.position = self.position + #value\
  5193. end\
  5194. \
  5195. --[[\
  5196.    @instance\
  5197.    @desc Removes the content at the position (or if a selection is made, the selected text). The preAmount (def. 1) specifies the content\
  5198.          to be kept BEFORE the selection/position. Hence, the higher the number the more content is lost.\
  5199. \
  5200.          Likewise, 'postAmount' (def. 1) is added to the remainder, the higher the number the more content AFTER the selection/position is lost\
  5201.    @param [number - preAmount], [number - postAmount]\
  5202. ]]\
  5203. function EditableTextContainer:removeContent( preAmount, postAmount )\
  5204.    preAmount = preAmount or 1\
  5205.    local text = self.text\
  5206.    if self.selection then\
  5207.        self.text = string_sub( text, 1, math.min( self.selection, self.position ) - preAmount ) .. string_sub( text, math.max( self.selection, self.position ) + ( postAmount or 1 ) )\
  5208.        self.position = math.min( self.position, self.selection ) - 1\
  5209. \
  5210.        self.selection = false\
  5211.    else\
  5212.        if self.position == 0 and preAmount > 0 then return end\
  5213. \
  5214.        self.text = string_sub( text, 1, self.position - preAmount ) .. string_sub( text, self.position + ( postAmount or 1 ) )\
  5215.        self.position = self.position - preAmount\
  5216.    end\
  5217. end\
  5218. \
  5219. --[[\
  5220.    @instance\
  5221.    @desc Handles a 'key' event by moving the cursor (arrow keys), or removing text (delete/backspace), amongst other things\
  5222.    @param <KeyEvent Instance - event>, <boolean - handled>\
  5223. ]]\
  5224. function EditableTextContainer:onKeyDown( event, handled )\
  5225.    if handled or not self.focused then return end\
  5226. \
  5227.    local key, lines, position, selection = event.keyName, self.lineConfig.lines, self.position, ( self.selection or self.position )\
  5228.    local isShift = self.application:isPressed( keys.leftShift ) or self.application:isPressed( keys.rightShift )\
  5229. \
  5230.    local old_tX\
  5231.    if key == \"up\" or key == \"down\" then\
  5232.        if not self.cache.tX then self.cache.tX = ( isShift and selection or position ) - lines[ isShift and self.cache.selY or self.cache.y ][ 2 ] end\
  5233. \
  5234.        old_tX = self.cache.tX\
  5235.    end\
  5236. \
  5237.    if key == \"up\" then\
  5238.        local previousLine = lines[ ( isShift and self.cache.selY or self.cache.y ) - 1]\
  5239.        if not previousLine then return end\
  5240. \
  5241.        self[ isShift and \"selection\" or \"position\" ] = math.min( previousLine[ 2 ] + self.cache.tX, previousLine[ 3 ] - 1 )\
  5242.    elseif key == \"down\" then\
  5243.        local nextLine = lines[ ( isShift and self.cache.selY or self.cache.y ) + 1]\
  5244.        if not nextLine then return end\
  5245. \
  5246.        self[ isShift and \"selection\" or \"position\" ] = math.min( nextLine[ 2 ] + self.cache.tX, nextLine[ 3 ] - 1 )\
  5247.    elseif key == \"left\" then\
  5248.        if isShift then\
  5249.            self.selection = selection - 1\
  5250.        else\
  5251.            self.position = math.min( position, selection ) - 1\
  5252.        end\
  5253.    elseif key == \"right\" then\
  5254.        if isShift then\
  5255.            self.selection = selection + 1\
  5256.        else\
  5257.            self.position = math.max( position, selection - 1 ) + 1\
  5258.        end\
  5259.    elseif key == \"backspace\" then\
  5260.        self:removeContent( ( isShift and self.position - lines[ self.cache.y ][ 2 ] or 0 ) + 1 )\
  5261.    elseif key == \"enter\" then\
  5262.        self:insertContent \"\\n\"\
  5263.    elseif key == \"home\" then\
  5264.        self[ isShift and \"selection\" or \"position\" ] = lines[ self.cache.y ][ 2 ] - 1\
  5265.    elseif key == \"end\" then\
  5266.        self[ isShift and \"selection\" or \"position\" ] = lines[ self.cache.y ][ 3 ] - ( lines[ self.cache.y + 1 ] and 1 or -1 )\
  5267.    end\
  5268. \
  5269.    self.cache.tX = old_tX or self.cache.tX\
  5270. end\
  5271. \
  5272. --[[\
  5273.    @instance\
  5274.    @desc Inserts the character pressed, replacing the selection if one is made\
  5275.    @param <CharEvent Instance - event>, <boolean - handled>\
  5276. ]]\
  5277. function EditableTextContainer:onChar( event, handled )\
  5278.    if handled or not self.focused then return end\
  5279.    self:insertContent( event.char )\
  5280. end\
  5281. \
  5282. --[[\
  5283.    @instance\
  5284.    @desc Invokes the setter for selection on the super before resetting 'cache.tX'\
  5285.    @param <number - selection>\
  5286. ]]\
  5287. function EditableTextContainer:setSelection( ... )\
  5288.    self.super:setSelection( ... )\
  5289.    self.cache.tX = false\
  5290. end\
  5291. \
  5292. --[[\
  5293.    @instance\
  5294.    @desc Invokes the setter for position on the super before resetting 'cache.tX'\
  5295.    @param <number - position>\
  5296. ]]\
  5297. function EditableTextContainer:setPosition( ... )\
  5298.    self.super:setPosition( ... )\
  5299.    self.cache.tX = false\
  5300. end\
  5301. \
  5302. --[[\
  5303.    @instance\
  5304.    @desc Returns the information for the caret position, using cache.x and cache.y (caret only displayed when no selection is made and the node is focused)\
  5305.    @return <boolean - visible>, <number - x>, <number - y>, <number - colour> - When the x and y position of the caret is NOT out of the bounds of the node\
  5306.    @return <boolean - false> - When the x or y position of the caret IS out of bounds\
  5307. ]]\
  5308. function EditableTextContainer:getCaretInfo()\
  5309.    if not ( self.cache.x and self.cache.y ) then return false end\
  5310.    local x, y = self.cache.x - self.xScroll, self.cache.y - self.yScroll\
  5311.    if x < 0 or x > self.width or y < 1 or y > self.height then return false end\
  5312. \
  5313.    local sX, sY = self:getAbsolutePosition()\
  5314.    return self.focused and not self.selection and true, x + sX, y + sY - 1, self.focusedColour or self.colour\
  5315. end\
  5316. ",
  5317. ["Event.ti"]="--[[\
  5318.    @static matrix - table (def. {}) - A table of eventName -> eventClass conversions (ie: mouse_click -> MouseEvent). Add custom events here to have them spawn a selected class.\
  5319. \
  5320.    @instance name - string (def. nil) - The name of the event\
  5321.    @instance data - table (def. nil) - A table containing all details of the event\
  5322.    @instance handled - boolean (def. false) - If true, the event has been used and should be ignored by other nodes unless they want to act on used events\
  5323. ]]\
  5324. \
  5325. class \"Event\" abstract() {\
  5326.    static = {\
  5327.        matrix = {}\
  5328.    }\
  5329. }\
  5330. \
  5331. --[[\
  5332.    @instance\
  5333.    @desc Returns true if the event name (index '1' of data) matches the parameter 'event' provided\
  5334.    @param <string - event>\
  5335.    @return <boolean - eq>\
  5336. ]]\
  5337. function Event:is( event )\
  5338.    return self.name == event\
  5339. end\
  5340. \
  5341. --[[\
  5342.    @setter\
  5343.    @desc Sets the 'handled' parameter to true. This indicates the event has been used and should not be used.\
  5344.    @param <boolean - handled>\
  5345. ]]\
  5346. function Event:setHandled( handled )\
  5347.    self.raw.handled = handled\
  5348. end\
  5349. \
  5350. --[[\
  5351.    @static\
  5352.    @desc Instantiates an event object if an entry for that event type is present inside the event matrix.\
  5353.    @param <string - eventName>, [... - eventData]\
  5354.    @return <Instance*>\
  5355. \
  5356.    *Note: The type of instance is variable. If an entry is present inside the matrix that class \"will\" be\
  5357.           instantiated, otherwise a 'GenericEvent' instance will be returned.\
  5358. ]]\
  5359. function Event.static.spawn( name, ... )\
  5360.    return ( Event.matrix[ name ] or GenericEvent )( name, ... )\
  5361. end\
  5362. \
  5363. --[[\
  5364.    @static\
  5365.    @desc Adds an entry to the event matrix. When an event named 'name' is caught, the class 'clasType' will be instantiated\
  5366.    @param <string - name>, <string - classType>\
  5367. ]]\
  5368. function Event.static.bindEvent( name, classType )\
  5369.    Event.matrix[ name ] = Titanium.getClass( classType ) or error( \"Class '\"..tostring( classType )..\"' cannot be found\" )\
  5370. end\
  5371. \
  5372. --[[\
  5373.    @static\
  5374.    @desc Removes an entry from the event matrix.\
  5375.    @param <string - name>\
  5376. ]]\
  5377. function Event.static.unbindEvent( name )\
  5378.    Event.matrix[ name ] = nil\
  5379. end\
  5380. ",
  5381. ["Parser.ti"]="--[[\
  5382.    @instance position - number (def. 0) - The current token index being parsed\
  5383.    @instance tokens - table (def. {}) - The tokens found via lexing, corresponds to 'position'\
  5384. \
  5385.    The parser class \"should\" be extended by classes that are used to parser lexer token output.\
  5386. ]]\
  5387. \
  5388. class \"Parser\" abstract() {\
  5389.    position = 0;\
  5390.    tokens = {};\
  5391. }\
  5392. \
  5393. --[[\
  5394.    @constructor\
  5395.    @desc Sets the tokens of the parser to those passed and begins parsing\
  5396.    @param <table - tokens>\
  5397. ]]\
  5398. function Parser:__init__( tokens )\
  5399.    if type( tokens ) ~= \"table\" then\
  5400.        return error \"Failed to parse. Invalid tokens\"\
  5401.    end\
  5402. \
  5403.    self.tokens = tokens\
  5404.    self:parse()\
  5405. end\
  5406. \
  5407. --[[\
  5408.    @instance\
  5409.    @desc Returns the token at 'position'\
  5410. ]]\
  5411. function Parser:getCurrentToken()\
  5412.    return self.tokens[ self.position ]\
  5413. end\
  5414. \
  5415. --[[\
  5416.    @instance\
  5417.    @desc Returns the token 'amount' ahead of the current position. Defaults to one position ahead\
  5418. ]]\
  5419. function Parser:peek( amount )\
  5420.    return self.tokens[ self.position + ( amount or 1 ) ]\
  5421. end\
  5422. \
  5423. --[[\
  5424.    @instance\
  5425.    @desc Tests the adjacent tokens to see if they are the correct type. Offsets can be provided and missing tokens can be configured to cause test failure\
  5426.    @param [string - before], [string - after], [boolean - optional], [number - beforeOffset], [number - afterOffset], [boolean - disallowMissing]\
  5427. \
  5428.    Note: If a token doesn't exist, it will NOT cause the test to fail unless 'disallowMissing' is set to true.\
  5429. ]]\
  5430. function Parser:testAdjacent( before, after, optional, beforeOffset, afterOffset, disallowMissing )\
  5431.    local leading, leadingPass, trailing, trailingPass = false, not before, false, not after\
  5432.    local function test( token, filter )\
  5433.        if not token then return not disallowMissing end\
  5434. \
  5435.        if type( filter ) == \"table\" then\
  5436.            for i = 1, #filter do\
  5437.                if token.type == filter[ i ] then return true end\
  5438.            end\
  5439.        else return token.type == filter end\
  5440.    end\
  5441. \
  5442.    if before then\
  5443.        leading = self:peek( -1 - ( beforeOffset or 0 ) )\
  5444.        leadingPass = test( leading, before )\
  5445.    end\
  5446. \
  5447. \
  5448.    if after then\
  5449.        trailing = self:peek( 1 + ( afterOffset or 0 ) )\
  5450.        trailingPass = test( trailing, after )\
  5451.    end\
  5452. \
  5453.    return ( optional and ( trailingPass or leadingPass ) or ( not optional and trailingPass and leadingPass ) ), leading, trailing\
  5454. end\
  5455. \
  5456. --[[\
  5457.    @instance\
  5458.    @desc Advances 'position' by one and returns the token at the new position\
  5459. ]]\
  5460. function Parser:stepForward( amount )\
  5461.    self.position = self.position + ( amount or 1 )\
  5462.    return self:getCurrentToken()\
  5463. end\
  5464. \
  5465. --[[\
  5466.    @instance\
  5467.    @desc Throws a error prefixed with information about the token being parsed at the time of error.\
  5468. ]]\
  5469. function Parser:throw( e, token )\
  5470.    local token = token or self:getCurrentToken()\
  5471.    if not token then\
  5472.        return error( \"Parser (\"..tostring( self.__type )..\") Error: \"..e, 2 )\
  5473.    end\
  5474. \
  5475.    return error( \"Parser (\"..tostring( self.__type )..\") Error. Line \"..token.line..\", char \"..token.char .. \": \"..e, 2 )\
  5476. end\
  5477. ",
  5478. ["GenericEvent.ti"]="--[[\
  5479.    @instance main - string (def. nil) - The uppercase version of the event name.\
  5480. \
  5481.    The GenericEvent class \"is\" spawned when an event that Titanium doesn't understand is caught in the Application event loop.\
  5482. \
  5483.    If you wish to spawn another sort of class \"when\" a certain event is caught, consider using `Event.static.bindEvent`.\
  5484. ]]\
  5485. \
  5486. class \"GenericEvent\" extends \"Event\"\
  5487. \
  5488. --[[\
  5489.    @constructor\
  5490.    @desc Constructs the GenericEvent instance by storing all passed arguments in 'data'. The first index (1) of data is stored inside 'name'\
  5491.    @param <string - name>, [var - arg1], ...\
  5492. ]]\
  5493. function GenericEvent:__init__( ... )\
  5494.    local args = { ... }\
  5495. \
  5496.    self.name = args[ 1 ]\
  5497.    self.main = self.name:upper()\
  5498. \
  5499.    self.data = args\
  5500. end\
  5501. ",
  5502. ["MActivatable.ti"]="--[[\
  5503.    @instance active - boolean (def. false) - When a node is active, it uses active colours. A node should be activated before being focused.\
  5504.    @instance activeColour - colour (def. 1) - The foreground colour of the node used while the node is active\
  5505.    @instance activeBackgroundColour - colour (def. 512) - The background colour of the node used while the node is active\
  5506. \
  5507.    A mixin \"to\" reuse code commonly written when developing nodes that can be (de)activated.\
  5508. ]]\
  5509. \
  5510. class \"MActivatable\" abstract() {\
  5511.    active = false;\
  5512. \
  5513.    activeColour = colours.white;\
  5514.    activeBackgroundColour = colours.cyan;\
  5515. }\
  5516. \
  5517. --[[\
  5518.    @constructor\
  5519.    @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\
  5520. ]]\
  5521. function MActivatable:MActivatable()\
  5522.    if Titanium.mixesIn( self, \"MThemeable\" ) then\
  5523.        self:register( \"active\", \"activeColour\", \"activeBackgroundColour\" )\
  5524.    end\
  5525. end\
  5526. \
  5527. --[[\
  5528.    @instance\
  5529.    @desc Sets the 'active' property to the 'active' argument passed. When the 'active' property changes the node will become 'changed'.\
  5530.    @param <boolean - active>\
  5531. ]]\
  5532. function MActivatable:setActive( active )\
  5533.    local raw = self.raw\
  5534.    if raw.active == active then return end\
  5535. \
  5536.    raw.active = active\
  5537.    self:queueAreaReset()\
  5538. end\
  5539. \
  5540. configureConstructor {\
  5541.    argumentTypes = { active = \"boolean\", activeColour = \"colour\", activeBackgroundColour = \"colour\" }\
  5542. } alias {\
  5543.    activeColor = \"activeColour\",\
  5544.    activeBackgroundColor = \"activeBackgroundColour\"\
  5545. }\
  5546. ",
  5547. ["Dropdown.ti"]="--[[\
  5548.    @instance maxHeight - number (def. false) - If set, the dropdown node may not exceed that height (meaning, the space for options to be displayed is maxHeight - 1)\
  5549.    @instance prompt - string (def. \"Please select\") - The default content of the dropdown toggle button. Will change to display selected option when an option is selected\
  5550.    @instance horizontalAlign - string (def. \"left\") - The horizontalAlign of the dropdown. The dropdown contents are linked (:linkProperties), so they will reflect the alignment property\
  5551.    @instance openIndicator - string (def. \" \\31\") - A string appended to the toggle button's text when the dropdown is open (options visible)\
  5552.    @instance closedIndicator - string (def. \" \\16\") - Identical to 'openIndicator', with the exception that this is visible when the dropdown is closed (options hidden)\
  5553.    @instance colour - colour (def. 1) - The colour of the dropdown options (not the toggle button), used when the buttons are not active\
  5554.    @instance backgroundColour - colour (def. 8) - The background colour of the dropdown options (not the toggle button), used when the buttons are not active\
  5555.    @instance activeColour - colour (def. nil) - The colour of the dropdown options (not the toggle button), used when the buttons are active\
  5556.    @instance activeBackgroundColour - colour (def. 512) - The background colour of the dropdown options (not the toggle button), when the buttons are active\
  5557.    @instance selectedColour - colour (def. 1) - The colour of the toggle button\
  5558.    @instance selectedBackgroundColour - colour (def. 256) - The background colour of the toggle button\
  5559.    @instance selectedOption - table (def. false) - The option (format: { displayName, value }) currently selected\
  5560.    @instance options - table (def. {}) - All options (format: { displayName, value }) the dropdown has registered - these can be selected (unless already selected)\
  5561. \
  5562.    The Dropdown node allows for easy multi-choice options inside of user forms. The toggle button will display the currently selected option, or, if none is selected the 'prompt' will be shown instead.\
  5563. \
  5564.    When one of the options are selected, the 'change' callback will be fired and the newly selected option is provided.\
  5565. \
  5566.    Upon instantiation, the dropdown will populate itself with buttons inside of it's 'optionContainer'. Each button representing a different option, that can be selected to select the option.\
  5567.    The button's \"colour\", \"activeColour\", \"disabledColour\", \"backgroundColour\", \"activeBackgroundColour\", \"disabledBackgroundColour\" and, \"horizontalAlign\" properties are dynamically linked to the Dropdown instance.\
  5568.    Thus, setting any of those properties on the dropdown itself will cause the setting to also be changed on all buttons. Avoid changing properties on the buttons directly, as the values will be overridden.\
  5569. \
  5570.    Similarily, the toggle button's \"horizontalAlign\", \"disabledColour\", \"disabledBackgroundColour\", \"activeColour\" and, \"activeBackgroundColour\" properties are linked to the dropdown instance. The colour, and backgroundColour\
  5571.    of the toggle button is controlled via 'selectedColour' and 'selectedBackgroundColour' respectively.\
  5572. ]]\
  5573. \
  5574. class \"Dropdown\" extends \"Container\" mixin \"MActivatable\" {\
  5575.    maxHeight = false;\
  5576. \
  5577.    prompt = \"Please select\";\
  5578.    horizontalAlign = \"left\";\
  5579. \
  5580.    openIndicator = \" \\31\";\
  5581.    closedIndicator = \" \\16\";\
  5582. \
  5583.    backgroundColour = colours.lightBlue;\
  5584.    colour = colours.white;\
  5585. \
  5586.    activeBackgroundColour = colours.cyan;\
  5587. \
  5588.    selectedColour = colours.white;\
  5589.    selectedBackgroundColour = colours.grey;\
  5590.    selectedOption = false;\
  5591.    options = {};\
  5592. \
  5593.    transparent = true;\
  5594. }\
  5595. \
  5596. --[[\
  5597.    @constructor\
  5598.    @desc Creates the dropdown instance and creates the option display (button) and container (scroll container). The selectable options live inside the optionContainer, and the selected option is displayed using the optionDisplay\
  5599.    @param [number - X], [number - Y], [number - width], [number - maxHeight], [string - prompt]\
  5600. ]]\
  5601. function Dropdown:__init__( ... )\
  5602.    self:super( ... )\
  5603. \
  5604.    self.optionDisplay = self:addNode( Button \"\":linkProperties( self, \"horizontalAlign\", \"disabledColour\", \"disabledBackgroundColour\", \"activeColour\", \"activeBackgroundColour\" ):on(\"trigger\", function() self:toggleOptionDisplay() end) )\
  5605.    self.optionContainer = self:addNode( ScrollContainer( 1, 2, self.width ):set{ xScrollAllowed = false } )\
  5606. \
  5607.    self:closeOptionDisplay()\
  5608.    self.consumeAll = false --TODO: Is this really needed - I think it would almost be better to have it as 'true' (default)\
  5609. end\
  5610. \
  5611. --[[\
  5612.    @instance\
  5613.    @desc Closes the dropdown by hiding (and disabling) the options container and updates the toggle button (in order to update the open/closed indicator)\
  5614. ]]\
  5615. function Dropdown:closeOptionDisplay()\
  5616.    local cont = self.optionContainer\
  5617.    cont.visible, cont.enabled = false, false\
  5618. \
  5619.    self:queueAreaReset()\
  5620.    self:updateDisplayButton()\
  5621. end\
  5622. \
  5623. --[[\
  5624.    @instance\
  5625.    @desc Opens the dropdown by showing (and enabled) the options container and updates the toggle button (in order to update the open/closed indicator)\
  5626. ]]\
  5627. function Dropdown:openOptionDisplay()\
  5628.    local cont = self.optionContainer\
  5629.    cont.visible, cont.enabled = true, true\
  5630. \
  5631.    self:queueAreaReset()\
  5632.    self:updateDisplayButton()\
  5633. end\
  5634. \
  5635. --[[\
  5636.    @instance\
  5637.    @desc If the option container is already visible, it is closed (:closeOptionDisplay), otherwise it is opened (:openOptionDisplay)\
  5638. ]]\
  5639. function Dropdown:toggleOptionDisplay()\
  5640.    if self.optionContainer.visible then\
  5641.        self:closeOptionDisplay()\
  5642.    else\
  5643.        self:openOptionDisplay()\
  5644.    end\
  5645. end\
  5646. \
  5647. --[[\
  5648.    @setter\
  5649.    @desc If the dropdown is disabled, the dropdowns option container is closed (:closeOptionDisplay)\
  5650.    @param <boolean - enabled>\
  5651. ]]\
  5652. function Dropdown:setEnabled( enabled )\
  5653.    self.super:setEnabled( enabled )\
  5654.    if not enabled then\
  5655.        self:closeOptionDisplay()\
  5656.    end\
  5657. end\
  5658. \
  5659. --[[\
  5660.    @instance\
  5661.    @desc Updates the toggle buttons text, width, colour and backgroundColour (the colour and backgroundColour are sourced from 'selectedColour' and 'selectedBackgroundColour' respectively)\
  5662. ]]\
  5663. function Dropdown:updateDisplayButton()\
  5664.    self.optionDisplay.text = ( type( self.selectedOption ) == \"table\" and self.selectedOption[ 1 ] or self.prompt ) .. ( self.optionContainer.visible and self.openIndicator or self.closedIndicator )\
  5665.    self.optionDisplay.width = #self.optionDisplay.text\
  5666. \
  5667.    self.optionDisplay:set {\
  5668.        colour = self.selectedColour,\
  5669.        backgroundColour = self.selectedBackgroundColour\
  5670.    }\
  5671. end\
  5672. \
  5673. --[[\
  5674.    @instance\
  5675.    @desc Updates the options by changing the text of each button, to match the order of the options.\
  5676. ]]\
  5677. function Dropdown:updateOptions()\
  5678.    local options, buttons = self.options, self.optionContainer.nodes\
  5679.    local selected = self.selectedOption\
  5680. \
  5681.    local buttonI = 1\
  5682.    for i = 1, #options do\
  5683.        if not selected or options[ i ] ~= selected then\
  5684.            local button = buttons[ buttonI ]\
  5685.            if button then\
  5686.                button.text = options[ i ][ 1 ]\
  5687.                button:off(\"trigger\", \"dropdownTrigger\"):on(\"trigger\", function()\
  5688.                    self.selectedOption = options[ i ]\
  5689.                end, \"dropdownTrigger\")\
  5690.            end\
  5691. \
  5692.            buttonI = buttonI + 1\
  5693.        end\
  5694.    end\
  5695. end\
  5696. \
  5697. --[[\
  5698.    @instance\
  5699.    @desc Creates/removes nodes depending on the amount of options to be displayed (ie: if there are too many nodes, excess are removed).\
  5700. \
  5701.          Invokes :updateOptions before adjusting the dropdown height with respect to 'maxHeight' (if set), and the 'yScroll'\
  5702. ]]\
  5703. function Dropdown:checkOptions()\
  5704.    local cont, options = self.optionContainer, self.options\
  5705.    local nodes = cont.nodes\
  5706.    local count = #nodes\
  5707.    self:updateDisplayButton()\
  5708. \
  5709.    local rOptionCount = #options - ( self.selectedOption and 1 or 0 )\
  5710.    if count > rOptionCount then\
  5711.        repeat\
  5712.            cont:removeNode( nodes[ #nodes ] )\
  5713.        until #nodes == rOptionCount\
  5714.    elseif count < rOptionCount then\
  5715.        repeat\
  5716.            cont:addNode(Button( \"ERR\", 1, #nodes + 1, self.width )\
  5717.                :linkProperties( self, \"colour\", \"activeColour\",\
  5718.                \"disabledColour\", \"backgroundColour\", \"activeBackgroundColour\",\
  5719.                \"disabledBackgroundColour\", \"horizontalAlign\" ))\
  5720.        until #nodes == rOptionCount\
  5721.    end\
  5722.    self:updateOptions()\
  5723. \
  5724.    count = #nodes\
  5725.    if self.maxHeight then\
  5726.        cont.height, self.height = math.min( count, self.maxHeight - 1 ), math.min( count + 1, self.maxHeight )\
  5727.    else\
  5728.        cont.height, self.height = count, count + 1\
  5729.    end\
  5730. \
  5731.    self.optionsChanged = false\
  5732.    if #options > 0 then cont.yScroll = math.min( cont.yScroll, count ) end\
  5733. end\
  5734. \
  5735. --[[\
  5736.    @instance\
  5737.    @desc Calls ':checkOptions' if 'optionsChanged', before calling the super 'draw' function\
  5738.    @param <... - args> - Arguments passed to the super 'draw' method\
  5739. ]]\
  5740. function Dropdown:draw( ... )\
  5741.    if self.optionsChanged then self:checkOptions() end\
  5742. \
  5743.    self.super:draw( ... )\
  5744. end\
  5745. \
  5746. --[[\
  5747.    @instance\
  5748.    @desc Returns the 'value' of the selected option, if an option is selected\
  5749.    @return <string - value>\
  5750. ]]\
  5751. function Dropdown:getSelectedValue()\
  5752.    if type( self.selectedOption ) ~= \"table\" then return end\
  5753. \
  5754.    return self.selectedOption[ 2 ]\
  5755. end\
  5756. \
  5757. --[[\
  5758.    @instance\
  5759.    @desc Adds the option provided, with the value given. This option is then selectable.\
  5760.    @param <string - option>, <string - value>\
  5761. ]]\
  5762. function Dropdown:addOption( option, value )\
  5763.    if type( option ) ~= \"string\" or value == nil then\
  5764.        return error \"Failed to add option to Dropdown node. Expected two arguments: string, val - where val is not nil\"\
  5765.    end\
  5766. \
  5767.    self:removeOption( option )\
  5768.    table.insert( self.options, { option, value } )\
  5769. \
  5770.    self.optionsChanged = true\
  5771. end\
  5772. \
  5773. --[[\
  5774.    @instance\
  5775.    @desc Removes the option given if present\
  5776. ]]\
  5777. function Dropdown:removeOption( option )\
  5778.    local options = self.options\
  5779.    for i = #options, 1, -1 do\
  5780.        if options[ i ] == option then\
  5781.            table.remove( options, i )\
  5782.        end\
  5783.    end\
  5784. \
  5785.    self.optionsChanged = true\
  5786. end\
  5787. \
  5788. --[[\
  5789.    @setter\
  5790.    @desc Updates the optionDisplay text to match the new prompt\
  5791.    @param <string - prompt>\
  5792. ]]\
  5793. function Dropdown:setPrompt( prompt )\
  5794.    self.prompt = prompt\
  5795.    self.optionDisplay.text = prompt\
  5796. end\
  5797. \
  5798. --[[\
  5799.    @setter\
  5800.    @desc Closes the option display and invokes the 'change' callback\
  5801.    @param <table - option>\
  5802. ]]\
  5803. function Dropdown:setSelectedOption( selected )\
  5804.    self.selectedOption = selected\
  5805.    self:closeOptionDisplay()\
  5806.    self.optionsChanged = true\
  5807. \
  5808.    self:executeCallbacks( \"change\", selected )\
  5809. end\
  5810. \
  5811. --[[\
  5812.    @instance\
  5813.    @desc Handles the eventObj given. If the event is a mouse click, and it missed the dropdown, the dropdown is closed (if open)\
  5814. ]]\
  5815. function Dropdown:handle( eventObj )\
  5816.    if not self.super:handle( eventObj ) then return end\
  5817. \
  5818.    if eventObj:is \"mouse_click\" and not self:isMouseColliding( eventObj ) and self.optionContainer.visible then\
  5819.        self:closeOptionDisplay()\
  5820.        eventObj.handled = true\
  5821.    end\
  5822. \
  5823.    return true\
  5824. end\
  5825. \
  5826. --[[\
  5827.    @instance\
  5828.    @desc Adds the TML object given. If the type is 'Option', the option is registered (using the tag content as the display, and it's 'value' argument as the value)\
  5829.    @param <table - TMLObj>\
  5830. ]]\
  5831. function Dropdown:addTMLObject( TMLObj )\
  5832.    if TMLObj.type == \"Option\" then\
  5833.        if TMLObj.content and TMLObj.arguments.value then\
  5834.            self:addOption( TMLObj.content, TMLObj.arguments.value )\
  5835.        else\
  5836.            error \"Failed to add TML object to Dropdown object. 'Option' tag must include content (not children) and a 'value' argument\"\
  5837.        end\
  5838.    else\
  5839.        error( \"Failed to add TML object to Dropdown object. Only 'Option' tags are accepted, '\" .. tostring( TMLObj.type ) .. \"' is invalid\" )\
  5840.    end\
  5841. end\
  5842. \
  5843. configureConstructor({\
  5844.    orderedArguments = { \"X\", \"Y\", \"width\", \"maxHeight\", \"prompt\" },\
  5845.    argumentTypes = {\
  5846.        maxHeight = \"number\",\
  5847.        prompt = \"string\",\
  5848. \
  5849.        selectedColour = \"colour\",\
  5850.        selectedBackgroundColour = \"colour\"\
  5851.    }\
  5852. }, true)\
  5853. \
  5854. alias {\
  5855.    selectedColor = \"selectedColour\",\
  5856.    selectedBackgroundColor = \"selectedBackgroundColour\"\
  5857. }\
  5858. ",
  5859. ["Tween.ti"]="--[[\
  5860.    @static easing - table (def. {}) - If a string is provided as the easing during instantiation, instead of a function, the easing function is searched for inside this table\
  5861. \
  5862.    @instance object - Instance (def. false) - The Titanium instance on which the target property will be animated\
  5863.    @instance property - string (def. false) - The target property that will be animated on the object\
  5864.    @instance initial - number (def. false) - The initial value of the property. Automatically set during instantiation\
  5865.    @instance final - number (def. false) - The target value for the property\
  5866.    @instance duration - number (def. 0) - The amount of time the animation will take to animate the property from 'initial' to 'final'\
  5867.    @instance clock - number (def. 0) - How far through the animation the instance is\
  5868.    @instance easing - function (def. nil) - The easing function to be used during the animation\
  5869. \
  5870.    The Tween class \"is\" Titaniums animation class (tween meaning inbetween values (from -> to)). However, Tween should not be used\
  5871.    to create animations, using :animate (on supporting instances, like any child class \"of\" 'Node') is preffered.\
  5872. \
  5873.    When animating, an easing can be set. If this easing is a string, it is searched for in 'Tween.static.easing'. All easing functions\
  5874.    shipped with Titanium are automatically imported via the Titanium starting script (src/scripts/Titanium.lua). Custom easing\
  5875.    functions can be created using the static function Tween.static.addEasing, or by manually adding them to the static 'easing' table.\
  5876. ]]\
  5877. \
  5878. class \"Tween\" {\
  5879.    static = {\
  5880.        easing = {}\
  5881.    };\
  5882. \
  5883.    object = false;\
  5884. \
  5885.    property = false;\
  5886.    initial = false;\
  5887.    final = false;\
  5888. \
  5889.    duration = 0;\
  5890.    clock = 0;\
  5891. }\
  5892. \
  5893. --[[\
  5894.    @constructor\
  5895.    @desc Constructs the tween instance, converting the 'easing' property into a function (if it's a string) and also stores the initial value of the property for later use.\
  5896.    @param <Object - object>, <string - name>, <string - property>, <number - final>, <number - duration>, [string/function - easing]\
  5897. ]]\
  5898. function Tween:__init__( ... )\
  5899.    self:resolve( ... )\
  5900.    if not Titanium.isInstance( self.object ) then\
  5901.        return error(\"Argument 'object' for tween must be a Titanium instance. '\"..tostring( self.object )..\"' is not a Titanium instance.\")\
  5902.    end\
  5903. \
  5904.    local easing = self.easing or \"linear\"\
  5905.    if type( easing ) == \"string\" then\
  5906.        self.easing = Tween.static.easing[ easing ] or error(\"Easing type '\"..tostring( easing )..\"' could not be found in 'Tween.static.easing'.\")\
  5907.    elseif type( easing ) == \"function\" then\
  5908.        self.easing = easing\
  5909.    else\
  5910.        return error \"Tween easing invalid. Must be a function to be invoked or name of easing type\"\
  5911.    end\
  5912. \
  5913.    self.initial = self.object[ self.property ]\
  5914.    self.clock = 0\
  5915. end\
  5916. \
  5917. --[[\
  5918.    @instance\
  5919.    @desc Sets the 'property' of 'object' to the rounded (down) result of the easing function selected. Passes the current clock time, the initial value, the difference between the initial and final values and the total Tween duration.\
  5920. ]]\
  5921. function Tween:performEasing()\
  5922.    self.object[ self.property ] = math.floor( self.easing( self.clock, self.initial, self.final - self.initial, self.duration ) + .5 )\
  5923. end\
  5924. \
  5925. --[[\
  5926.    @instance\
  5927.    @desc Updates the tween by increasing 'clock' by 'dt' via the setter 'setClock'\
  5928.    @param <number - dt>\
  5929.    @return <boolean - finished>\
  5930. ]]\
  5931. function Tween:update( dt )\
  5932.    return self:setClock( self.clock + dt )\
  5933. end\
  5934. \
  5935. --[[\
  5936.    @instance\
  5937.    @desc Sets the clock time to zero\
  5938.    @param <boolean - finished>\
  5939. ]]\
  5940. function Tween:reset()\
  5941.    return self:setClock( 0 )\
  5942. end\
  5943. \
  5944. --[[\
  5945.    @setter\
  5946.    @desc Sets the current 'clock'. If the clock is a boundary number, it is adjusted to match the boundary - otherwise it is set as is. Once set, 'performEasing' is called and the state of the Tween (finished or not) is returned\
  5947.    @param <number - clock>\
  5948.    @return <boolean - finished>\
  5949. ]]\
  5950. function Tween:setClock( clock )\
  5951.    if clock <= 0 then\
  5952.        self.clock = 0\
  5953.    elseif clock >= self.duration then\
  5954.        self.clock = self.duration\
  5955.    else\
  5956.        self.clock = clock\
  5957.    end\
  5958. \
  5959.    self:performEasing()\
  5960.    return self.clock >= self.duration\
  5961. end\
  5962. \
  5963. --[[\
  5964.    @static\
  5965.    @desc Binds the function 'easingFunction' to 'easingName'. Whenever an animation that uses easing of type 'easingName' is updated, this function will be called to calculate the value\
  5966.    @param <string - easingName>, <function - easingFunction>\
  5967.    @return <Class Base - Tween>\
  5968. ]]\
  5969. function Tween.static.addEasing( easingName, easingFunction )\
  5970.    if type( easingFunction ) ~= \"function\" then\
  5971.        return error \"Easing function must be of type 'function'\"\
  5972.    end\
  5973. \
  5974.    Tween.static.easing[ easingName ] = easingFunction\
  5975.    return Tween\
  5976. end\
  5977. \
  5978. configureConstructor {\
  5979.    orderedArguments = { \"object\", \"name\", \"property\", \"final\", \"duration\", \"easing\", \"promise\" },\
  5980.    requiredArguments = { \"object\", \"name\", \"property\", \"final\", \"duration\" },\
  5981.    argumentTypes = {\
  5982.        name = \"string\",\
  5983.        property = \"string\",\
  5984.        final = \"number\",\
  5985.        duration = \"number\",\
  5986.        promise = \"function\"\
  5987.    }\
  5988. }\
  5989. ",
  5990. ["MPropertyManager.ti"]="--[[\
  5991.    Tracks property changes and invokes custom callbacks when they change.\
  5992. \
  5993.    Note: Only supports watching of arguments that have had their types set via `configure`.\
  5994. ]]\
  5995. \
  5996. class \"MPropertyManager\" abstract() {\
  5997.    watching = {};\
  5998.    foreignWatchers = {};\
  5999.    links = {};\
  6000.    binds = {};\
  6001. }\
  6002. \
  6003. --[[\
  6004.    @constructor\
  6005.    @desc Hooks into all properties whose types have been defined. Un-hooked arguments cannot be watched.\
  6006. ]]\
  6007. function MPropertyManager:MPropertyManager()\
  6008.    local properties = Titanium.getClass( self.__type ):getRegistry().constructor\
  6009.    if not ( properties or properties.argumentTypes ) then return end\
  6010. \
  6011.    for property in pairs( properties.argumentTypes ) do\
  6012.        local setterName = Titanium.getSetterName( property )\
  6013.        local oldSetter = self.raw[ setterName ]\
  6014. \
  6015.        self[ setterName ] = function( instance, value )\
  6016.            value = self:updateWatchers( property, value )\
  6017. \
  6018.            if oldSetter then\
  6019.                oldSetter( self, instance, value )\
  6020.            else\
  6021.                self[ property ] = value\
  6022.            end\
  6023.        end\
  6024.    end\
  6025. \
  6026.    -- Destroys local and foreign watcher instructions\
  6027.    self:on(\"remove\", function( instance )\
  6028.        self:unwatchForeignProperty \"*\"\
  6029.        self:unwatchProperty( \"*\", false, true )\
  6030.    end)\
  6031. end\
  6032. \
  6033. --[[\
  6034.    @instance\
  6035.    @desc Invokes the callback function of any watching links, passing the instance and value.\
  6036.    @param <string - property>, [var - value]\
  6037.    @return [var - value]\
  6038. ]]\
  6039. function MPropertyManager:updateWatchers( property, value )\
  6040.    local function updateWatchers( prop )\
  6041.        local watchers = self.watching[ prop ]\
  6042.        if watchers then\
  6043.            for i = 1, #watchers do\
  6044.                local newVal = watchers[ i ][ 1 ]( self, prop, value )\
  6045. \
  6046.                if newVal ~= nil then\
  6047.                    value = newVal\
  6048.                end\
  6049.            end\
  6050.        end\
  6051.    end\
  6052. \
  6053.    if property == \"*\" then\
  6054.        for prop in pairs( self.watching ) do updateWatchers( prop ) end\
  6055.    else\
  6056.        updateWatchers( property )\
  6057.    end\
  6058. \
  6059.    return value\
  6060. end\
  6061. \
  6062. --[[\
  6063.    @instance\
  6064.    @desc Adds a watch instruction on 'object' for 'property'. The instruction is logged in 'foreignWatchers' for future modification (ie: destruction)\
  6065.    @param <string - property>, <Instance - object>, <function - callback>, [string - name]\
  6066. ]]\
  6067. function MPropertyManager:watchForeignProperty( property, object, callback, name )\
  6068.    if object == self then\
  6069.        return error \"Target object is not foreign. Select a foreign object or use :watchProperty\"\
  6070.    end\
  6071. \
  6072.    if not self.foreignWatchers[ property ] then self.foreignWatchers[ property ] = {} end\
  6073.    table.insert( self.foreignWatchers[ property ], object )\
  6074. \
  6075.    object:watchProperty( property, callback, name, self )\
  6076. end\
  6077. \
  6078. --[[\
  6079.    @instance\
  6080.    @desc Destroys the watch instruction for 'property'. If 'property' is '*', all property watchers are removed. If 'object' is given, only foreign links towards 'object' will be removed.\
  6081.    @param <string - property>, [Instance - object]\
  6082. ]]\
  6083. function MPropertyManager:unwatchForeignProperty( property, object, name )\
  6084.    local function unwatchProp( prop )\
  6085.        local foreignWatchers = self.foreignWatchers[ prop ]\
  6086. \
  6087.        if foreignWatchers then\
  6088.            for i = #foreignWatchers, 1, -1 do\
  6089.                if not object or foreignWatchers[ i ] == object then\
  6090.                    foreignWatchers[ i ]:unwatchProperty( prop, name, true )\
  6091.                    table.remove( foreignWatchers, i )\
  6092.                end\
  6093.            end\
  6094.        end\
  6095.    end\
  6096. \
  6097.    if property == \"*\" then\
  6098.        for prop in pairs( self.foreignWatchers ) do unwatchProp( prop ) end\
  6099.    else\
  6100.        unwatchProp( property )\
  6101.    end\
  6102. end\
  6103. \
  6104. --[[\
  6105.    @instance\
  6106.    @desc Removes headless references of 'property' to foreign links for 'object'. Used when the foreign target (object) has severed connection and traces must be removed from the creator (self).\
  6107.    @param <string - property>, <string - object>\
  6108. ]]\
  6109. function MPropertyManager:destroyForeignLink( property, object )\
  6110.    local watching = self.foreignWatchers[ property ]\
  6111.    if not watching then return end\
  6112. \
  6113.    for i = #watching, 1, -1 do\
  6114.        if watching[ i ] == object then\
  6115.            table.remove( watching, i )\
  6116.        end\
  6117.    end\
  6118. end\
  6119. \
  6120. --[[\
  6121.    @instance\
  6122.    @desc Instructs this object to call 'callback' when 'property' changes\
  6123.    @param <string - property>, <function - callback>, [string - name], [boolean - foreignOrigin]\
  6124. ]]\
  6125. function MPropertyManager:watchProperty( property, callback, name, foreignOrigin )\
  6126.    if name then\
  6127.        self:unwatchProperty( property, name )\
  6128.    end\
  6129. \
  6130.    if not self.watching[ property ] then self.watching[ property ] = {} end\
  6131.    table.insert( self.watching[ property ], { callback, name, foreignOrigin } )\
  6132. end\
  6133. \
  6134. --[[\
  6135.    @instance\
  6136.    @desc Removes watch instructions for 'property'. If 'name' is given, only watch instructions with that name will be removed.\
  6137.          If 'foreign' is true, watch instructions marked as originating from a foreign source will also be removed - else, only local instructions will be removed.\
  6138.          If 'preserveForeign' and 'foreign' are true, foreign links will be removed, however they will NOT be disconnected from their origin\
  6139.    @param <string - property>, [string - name], [boolean - foreign], [boolean - preserveForeign]\
  6140. ]]\
  6141. function MPropertyManager:unwatchProperty( property, name, foreign, preserveForeign )\
  6142.    local function unwatchProp( prop )\
  6143.        local watching = self.watching[ prop ]\
  6144. \
  6145.        if watching then\
  6146.            for i = #watching, 1, -1 do\
  6147.                if ( not name or watching[ i ][ 2 ] == name ) and ( foreign and watching[ i ][ 3 ] or ( not foreign and not watching[ i ][ 3 ] ) ) then\
  6148.                    if foreign and not preserveForeign then\
  6149.                        watching[ i ][ 3 ]:destroyForeignLink( prop, self )\
  6150.                    end\
  6151. \
  6152.                    table.remove( watching, i )\
  6153.                end\
  6154.            end\
  6155.        end\
  6156.    end\
  6157. \
  6158.    if property == \"*\" then\
  6159.        for prop in pairs( self.watching ) do unwatchProp( prop ) end\
  6160.    else\
  6161.        unwatchProp( property )\
  6162.    end\
  6163. end\
  6164. \
  6165. --[[\
  6166.    @instance\
  6167.    @desc Links properties given to 'target'. Properties can consist of tables or string values. If table, the first index represents the name of the foreign property to link to (belonging to 'target') and the second the local property to bind (belongs to 'self')\
  6168.          If the property is a string, the foreign property and local property match and a simple bind is produced\
  6169.    @param <Instance - target>, <var - properties>\
  6170.    @return <Instance - self>\
  6171. ]]\
  6172. function MPropertyManager:linkProperties( target, ... )\
  6173.    local links = self.links\
  6174.    local function createLink( foreignProperty, localProperty )\
  6175.        localProperty = localProperty or foreignProperty\
  6176. \
  6177.        if self.links[ localProperty ] then\
  6178.            return error(\"Failed to link foreign property '\"..tostring(foreignProperty)..\"' from '\"..tostring(target)..\"' to local property '\"..tostring(localProperty)..\"'. A link already exists for this local property, remove that link before linking\")\
  6179.        end\
  6180. \
  6181.        self:watchForeignProperty( foreignProperty, target, function( _, __, value )\
  6182.            self[ localProperty ] = value\
  6183.        end, \"PROPERTY_LINK_\" .. self.__ID )\
  6184. \
  6185.        links[ localProperty ], self[ localProperty ] = target, target[ foreignProperty ]\
  6186.    end\
  6187. \
  6188.    local properties = { ... }\
  6189.    for i = 1, #properties do\
  6190.        local prop = properties[ i ]\
  6191.        if type( prop ) == \"table\" then createLink( prop[ 1 ], prop[ 2 ] ) else createLink( prop ) end\
  6192.    end\
  6193. \
  6194.    return self\
  6195. end\
  6196. \
  6197. --[[\
  6198.    @instance\
  6199.    @desc Creates a dynamic property link to self and all provided arguments. Can be removed using 'unlinkProperties'\
  6200.    @param <string - property>, <table - arguments>, <string - equation>\
  6201. ]]\
  6202. function MPropertyManager:dynamicallyLinkProperty( property, arguments, equation )\
  6203.    if self.links[ property ] then\
  6204.        return error(\"Failed to create dynamic link for '\"..property..\"'. A link already exists for this property\")\
  6205.    elseif self.binds[ property ] then\
  6206.        return error(\"Lingering dynamic bind found for property '\"..property..\"'. Failed to bind property\")\
  6207.    end\
  6208. \
  6209.    self.links[ property ], self.binds[ property ] = true, DynamicValue( self, property, arguments, equation )\
  6210. end\
  6211. \
  6212. --[[\
  6213.    @instance\
  6214.    @desc Removes the property link for foreign properties ..., bound to 'target'. The properties provided represent the foreign property that is bound to, not the local property.\
  6215.    @param <Instance - target>, <... - foreignProperties>\
  6216.    @return <Instance - self>\
  6217. ]]\
  6218. function MPropertyManager:unlinkProperties( target, ... )\
  6219.    local properties, links, binds = { ... }, self.links, self.binds\
  6220.    for i = 1, #properties do\
  6221.        local prop = properties[ i ]\
  6222.        if binds[ prop ] then\
  6223.            binds[ prop ]:detach()\
  6224.            binds[ prop ], links[ prop ] = nil, nil\
  6225.        else\
  6226.            self:unwatchForeignProperty( prop, target, \"PROPERTY_LINK_\" .. self.__ID )\
  6227. \
  6228.            if links[ prop ] == target then\
  6229.                links[ prop ] = nil\
  6230.            end\
  6231.        end\
  6232.    end\
  6233. \
  6234.    return self\
  6235. end\
  6236. ",
  6237. ["DynamicEqParser.ti"]="local TERMS = { \"NAME\", \"STRING\", \"NUMBER\", \"PAREN\" }\
  6238. local BIN_AMBIG = { \"binary\", \"ambiguos\" }\
  6239. local UNA_AMBIG = { \"unary\", \"ambiguos\" }\
  6240. \
  6241. --[[\
  6242.    @instance stacks - table (def. {{}}) - A two dimensional table, containing the stacks used by the query (ie: self.value, self.parent.value). Can be resolved to find the values using :resolveStacks\
  6243.    @instance state - string (def. \"root\") - The current state of the parser, can be 'root' or 'name'. If 'root', :parseRootState will be called, else :parseNameState\
  6244.    @instance output - string (def. \"local args = ...; return\") - The Lua equation formed while parsing.\
  6245. \
  6246.    Parses the tokens from DynamicEqLexer into a Lua equation (string) and a set of stacks\
  6247. ]]\
  6248. \
  6249. class \"DynamicEqParser\" extends \"Parser\" {\
  6250.    state = \"root\";\
  6251.    stacks = {{}};\
  6252.    output = \"local args = ...; return \";\
  6253. }\
  6254. \
  6255. --[[\
  6256.    @constructor\
  6257.    @desc Invokes the Parser constructor, passing the tokens from the DynamicEqLexer (using the expression provided)\
  6258.    @param <string - expression>\
  6259. ]]\
  6260. function DynamicEqParser:__init__( expression )\
  6261.    self:super( DynamicEqLexer( expression ).tokens )\
  6262. end\
  6263. \
  6264. --[[\
  6265.    @instance\
  6266.    @desc Allows precise testing of adjacent operators so that a tokens position can be validated.\
  6267. \
  6268.          If the beforeType is specified, without an afterType the token before the current token must be an operator of the type specified using 'beforeType'.\
  6269.          If the afterType is specified, without a beforeType, the same as above applies for the token after the current token.\
  6270. \
  6271.          If both before and after type are specified, both the token before and after the current must match the type specified using 'beforeType' and 'afterType' respectively.\
  6272. \
  6273.          If 'optional', the test will no fail if no token exists before/after the current token (depending on which types are specified).\
  6274. \
  6275.          If the 'beforeOffset' or 'afterOffset' is specified, the token checked before or after the current token will be offset by the amount specified.\
  6276.    @param [string, table - beforeType], [string, table - afterType], [boolean - optional], [number - beforeOffset], [number - afterOffset]\
  6277.    @return <boolean - success>\
  6278. ]]\
  6279. function DynamicEqParser:testForOperator( beforeType, afterType, optional, beforeOffset, afterOffset )\
  6280.    local pass, before, after = self:testAdjacent( beforeType and \"OPERATOR\", afterType and \"OPERATOR\", false, beforeOffset, afterOffset, not optional )\
  6281.    if not pass then return false end\
  6282. \
  6283.    local function test( token, filter )\
  6284.        if not token then return true elseif not filter then return false end\
  6285.        if type( filter ) == \"table\" then\
  6286.            for i = 1, #filter do\
  6287.                if token[ filter[ i ] ] or filter[ i ] == \"*\" then return true end\
  6288.            end\
  6289.        else\
  6290.            if type == \"*\" then return true end\
  6291.            return token[ filter ]\
  6292.        end\
  6293.    end\
  6294. \
  6295.    local bT, aT = test( before, beforeType ), test( after, afterType )\
  6296.    if beforeType and afterType then return bT and aT else return ( beforeType and bT ) or ( afterType and aT ) end\
  6297. end\
  6298. \
  6299. --[[\
  6300.    @instance\
  6301.    @desc Tests for terms before the current token (if 'pre') and after the current token (if 'post'). If no token before/after current token and not 'optional', test will fail.\
  6302. \
  6303.          A term is a 'NAME', 'STRING', 'NUMBER', or 'PAREN' token from the lexer\
  6304.    @param [boolean - pre], [boolean - post], [boolean - optional]\
  6305.    @return <boolean - success>\
  6306. ]]\
  6307. function DynamicEqParser:testForTerms( pre, post, optional )\
  6308.    return self:testAdjacent( pre and TERMS, post and TERMS, false, false, false, not optional )\
  6309. end\
  6310. \
  6311. --[[\
  6312.    @instance\
  6313.    @desc Resolves the current stacks found using the parser by finding the Titanium instance attached to it. Stacks are passed to MPropertyManager:dynamicallyLinkProperty as 'arguments'\
  6314.    @param <Instance - target>\
  6315.    @return <table - instances>\
  6316. ]]\
  6317. function DynamicEqParser:resolveStacks( target )\
  6318.    local stacks, instances = self.stacks, {}\
  6319.    for i = 1, #stacks - ( #stacks[ #stacks ] == 0 and 1 or 0 ) do\
  6320.        local stack = stacks[ i ]\
  6321.        if #stack <= 1 then\
  6322.            self:throw(\"Invalid stack '\".. stack[ 1 ] ..\"'. At least 2 parts must exist to resolve\")\
  6323.        end\
  6324. \
  6325.        local stackStart, instancePoint = stack[ 1 ]\
  6326.        if stackStart == \"self\" then\
  6327.            instancePoint = target\
  6328.        elseif stackStart == \"parent\" then\
  6329.            instancePoint = target.parent\
  6330.        elseif stackStart == \"application\" then\
  6331.            instancePoint = target.application\
  6332.        else self:throw(\"Invalid stack start '\"..stackStart..\"'. Only self, parent and application allowed\") end\
  6333. \
  6334.        for p = 2, #stack - 1 do\
  6335.            if not instancePoint then self:throw(\"Failed to resolve stacks. Index '\"..stack[ p ]..\"' could not be accessed on '\"..tostring( instancePoint )..\"'\") end\
  6336.            instancePoint = instancePoint[ stack[ p ] ]\
  6337.        end\
  6338. \
  6339.        if not instancePoint then self:throw \"Invalid instance\" elseif not stack[ #stack ] then self:throw \"Invalid property\" end\
  6340.        instances[ #instances + 1 ] = { stack[ #stack ], instancePoint }\
  6341.    end\
  6342. \
  6343.    return instances\
  6344. end\
  6345. \
  6346. --[[\
  6347.    @instance\
  6348.    @desc Appends 'str' to the parser output. If no 'str' is given, the 'value' of the current token is appended instead\
  6349.    @param [string - str]\
  6350. ]]\
  6351. function DynamicEqParser:appendToOutput( str )\
  6352.    self.output = self.output .. ( str or self:getCurrentToken().value )\
  6353. end\
  6354. \
  6355. --[[\
  6356.    @instance\
  6357.    @desc Parses 'token' at the root state (ie: not resolving a name)\
  6358.    @param <table - token>\
  6359. ]]\
  6360. function DynamicEqParser:parseRootState( token )\
  6361.    token = token or self:getCurrentToken()\
  6362.    if token.type == \"NAME\" then\
  6363.        local filter = { \"OPERATOR\", \"DOT\", \"PAREN\" }\
  6364.        if not self:testAdjacent( filter, filter ) then self:throw(\"Unexpected name '\"..token.value..\"'\") end\
  6365. \
  6366.        self:appendToStack( token.value )\
  6367.        self:setState \"name\"\
  6368. \
  6369.        self:appendToOutput( \"args[\"..#self.stacks..\"]\" )\
  6370.    elseif token.type == \"PAREN\" then\
  6371.        if token.value == \"(\" then\
  6372.            if not ( self:testForOperator( BIN_AMBIG, false, true ) and ( self:testForTerms( false, true ) or self:testForOperator( false, UNA_AMBIG ) ) ) then\
  6373.                self:throw(\"Unexpected parentheses '\"..token.value..\"'\")\
  6374.            end\
  6375.        elseif token.value == \")\" then\
  6376.            if not ( self:testForTerms( true ) and self:testForOperator( false, BIN_AMBIG, true ) ) then\
  6377.                self:throw(\"Unexpected parentheses '\"..token.value..\"'\")\
  6378.            end\
  6379.        else self:throw(\"Invalid parentheses '\"..token.value..\"'\") end\
  6380. \
  6381.        self:appendToOutput()\
  6382.    elseif token.type == \"STRING\" then\
  6383.        local unaryOffset = self:testForOperator \"unary\" and 1 or 0\
  6384.        if not ( ( self:testForOperator( BIN_AMBIG, false, false, unaryOffset ) or self:testAdjacent( \"PAREN\", false, false, unaryOffset ) ) and ( self:testForOperator( false, BIN_AMBIG, true ) or self:testAdjacent( false, \"PAREN\" ) ) ) then\
  6385.            self:throw(\"Unexpected string '\"..token.value..\"'\")\
  6386.        end\
  6387. \
  6388.        self:appendToOutput( (\"%s%s%s\"):format( token.surroundedBy, token.value, token.surroundedBy ) )\
  6389.    elseif token.type == \"NUMBER\" then\
  6390.        if not self:testAdjacent( { \"OPERATOR\", \"PAREN\" }, { \"OPERATOR\", \"PAREN\" }, false, false, false ) then\
  6391.            self:throw(\"Unexpected number '\"..token.value..\"'\")\
  6392.        end\
  6393. \
  6394.        self:appendToOutput()\
  6395.    elseif token.type == \"OPERATOR\" then\
  6396.        if token.unary then\
  6397.            if not ( self:testForTerms( false, true ) and ( self:testForOperator( BIN_AMBIG ) or self:testAdjacent \"PAREN\" ) ) then\
  6398.                self:throw(\"Unexpected unary operator '\"..token.value..\"'. Operator must follow a binary operator and precede a term\")\
  6399.            end\
  6400.        elseif token.binary then\
  6401.            if not ( self:testForTerms( true ) and ( self:testForOperator( false, \"unary\" ) or self:testForTerms( false, true ) ) ) then\
  6402.                self:throw(\"Unexpected binary operator '\"..token.value..\"'. Expected terms before and after operator, or unary operator following\")\
  6403.            end\
  6404.        elseif token.ambiguos then\
  6405.            local trailing = self:testForTerms( false, true )\
  6406. \
  6407.            if not ( ( ( trailing or ( self:testForOperator( false, UNA_AMBIG ) and self:testForTerms( true ) ) ) and self:testForTerms( true, false, true ) ) or ( self:testForOperator( BIN_AMBIG ) and trailing ) ) then\
  6408.                self:throw(\"Unexpected ambiguos operator '\"..token.value..\"'\")\
  6409.            end\
  6410.        else self:throw(\"Unknown operator '\"..token.value..\"'\") end\
  6411. \
  6412.        self:appendToOutput( (\" %s \"):format( token.value ) )\
  6413.    else\
  6414.        self:throw(\"Unexpected block '\"..token.value..\"' of token type '\"..token.type..\"'.\")\
  6415.    end\
  6416. end\
  6417. \
  6418. --[[\
  6419.    @instance\
  6420.    @desc Resolves the name by using the token provided. If a 'DOT' is found and a 'NAME' follows, the name is appended to the parser stacks (otherwise, trailing DOT raises exception)\
  6421. \
  6422.          If no DOT is found, the parser state is reset to 'root'\
  6423.    @param <table - token>\
  6424. ]]\
  6425. function DynamicEqParser:parseNameState( token )\
  6426.    token = token or self:getCurrentToken()\
  6427.    if token.type == \"DOT\" then\
  6428.        local trailing = self:peek()\
  6429.        if trailing and trailing.type == \"NAME\" then\
  6430.            self:stepForward()\
  6431.            self:appendToStack( trailing.value )\
  6432.        else\
  6433.            local last = self:getStack()\
  6434.            self:throw(\"Failed to index '\" .. table.concat( last, \".\" ) .. \"'. No name following dot.\")\
  6435.        end\
  6436.    else\
  6437.        self:setState \"root\"\
  6438.        table.insert( self.stacks, {} )\
  6439. \
  6440.        self:parseRootState( token )\
  6441.    end\
  6442. end\
  6443. \
  6444. --[[\
  6445.    @instance\
  6446.    @desc Returns the current stack if no 'offset', otherwise returns the stack using the offset (ie: offset of -1 will return the last stack)\
  6447.    @param [number - offset]\
  6448.    @return [table - stack]\
  6449. ]]\
  6450. function DynamicEqParser:getStack( offset )\
  6451.    return self.stacks[ #self.stacks + ( offset or 0 ) ]\
  6452. end\
  6453. \
  6454. --[[\
  6455.    @instance\
  6456.    @desc Appends 'value' to the stack information (current stack is used if no 'stackOffset', otherwise the offset is used to find the stack)\
  6457.    @param <string - value>, [number - stackOffset]\
  6458. ]]\
  6459. function DynamicEqParser:appendToStack( value, stackOffset )\
  6460.    table.insert( self:getStack( stackOffset ), value )\
  6461. end\
  6462. \
  6463. --[[\
  6464.    @setter\
  6465.    @desc Sets the state of the parser\
  6466.    @param <string - state>\
  6467. ]]\
  6468. function DynamicEqParser:setState( state )\
  6469.    self.state = state\
  6470. end\
  6471. \
  6472. --[[\
  6473.    @instance\
  6474.    @desc Invokes the correct parser function (:parseRoot or Name state) depending on the parser 'state'\
  6475. \
  6476.          Token is automatically stepped forward after invoking the parser function.\
  6477. ]]\
  6478. function DynamicEqParser:parse()\
  6479.    local token = self:stepForward()\
  6480.    while token do\
  6481.        if self.state == \"root\" then\
  6482.            self:parseRootState()\
  6483.        elseif self.state == \"name\" then\
  6484.            self:parseNameState()\
  6485.        else\
  6486.            self:throw(\"Invalid parser state '\"..self.state..\"'\")\
  6487.        end\
  6488. \
  6489.        token = self:stepForward()\
  6490.    end\
  6491. end\
  6492. ",
  6493. ["QueryParser.ti"]="local function parseValue( val )\
  6494.    if val == \"true\" then return true\
  6495.    elseif val == \"false\" then return false end\
  6496. \
  6497.    return tonumber( val ) or error(\"Invalid value passed for parsing '\"..tostring( val )..\"'\")\
  6498. end\
  6499. \
  6500. --[[\
  6501.    @instance query - table (def. nil) - The parsed query - only holds the query once parsing is complete\
  6502. \
  6503.    Parses the tokens from QueryLexer into a table containing the query\
  6504. ]]\
  6505. \
  6506. class \"QueryParser\" extends \"Parser\"\
  6507. \
  6508. --[[\
  6509.    @constructor\
  6510.    @desc Invokes the Parser constructor, passing the tokens from QueryLexer\
  6511.    @param <string - queryString>\
  6512. ]]\
  6513. function QueryParser:__init__( queryString )\
  6514.    self:super( QueryLexer( queryString ).tokens )\
  6515. end\
  6516. \
  6517. --[[\
  6518.    @instance\
  6519.    @desc The main parser. Iterates over all tokens generating the table containing the query (stored in self.query)\
  6520. ]]\
  6521. function QueryParser:parse()\
  6522.    local allQueries, currentQuery, currentStep = {}, {}, {}\
  6523. \
  6524.    local nextStepDirect\
  6525.    local function advanceSection()\
  6526.        if next( currentStep ) then\
  6527.            table.insert( currentQuery, currentStep )\
  6528.            currentStep = { direct = nextStepDirect }\
  6529. \
  6530.            nextStepDirect = nil\
  6531.        end\
  6532.    end\
  6533. \
  6534.    local token = self:stepForward()\
  6535.    while token do\
  6536.        if token.type == \"QUERY_TYPE\" then\
  6537.            if currentStep.type then self:throw( \"Attempted to set query type to '\"..token.value..\"' when already set as '\"..currentStep.type..\"'\" ) end\
  6538. \
  6539.            currentStep.type = token.value\
  6540.        elseif token.type == \"QUERY_CLASS\" then\
  6541.            if not currentStep.classes then currentStep.classes = {} end\
  6542. \
  6543.            table.insert( currentStep.classes, token.value )\
  6544.        elseif token.type == \"QUERY_ID\" then\
  6545.            if currentStep.id then self:throw( \"Attempted to set query id to '\"..token.value..\"' when already set as '\"..currentStep.id..\"'\" ) end\
  6546. \
  6547.            currentStep.id = token.value\
  6548.        elseif token.type == \"QUERY_SEPERATOR\" then\
  6549.            if self.tokens[ self.position + 1 ].type ~= \"QUERY_DIRECT_PREFIX\" then\
  6550.                advanceSection()\
  6551.            end\
  6552.        elseif token.type == \"QUERY_END\" then\
  6553.            advanceSection()\
  6554. \
  6555.            if next( currentQuery ) then\
  6556.                table.insert( allQueries, currentQuery )\
  6557.                currentQuery = {}\
  6558.            else\
  6559.                self:throw( \"Unexpected '\"..token.value..\"' found, no left hand query\" )\
  6560.            end\
  6561.        elseif token.type == \"QUERY_COND_OPEN\" then\
  6562.            currentStep.condition = self:parseCondition()\
  6563.        elseif token.type == \"QUERY_DIRECT_PREFIX\" and not nextStepDirect then\
  6564.            nextStepDirect = true\
  6565.        else\
  6566.            self:throw( \"Unexpected '\"..token.value..\"' found while parsing query\" )\
  6567.        end\
  6568. \
  6569.        token = self:stepForward()\
  6570.    end\
  6571. \
  6572.    advanceSection()\
  6573.    if next( currentQuery ) then\
  6574.        table.insert( allQueries, currentQuery )\
  6575.    end\
  6576. \
  6577.    self.query = allQueries\
  6578. end\
  6579. \
  6580. --[[\
  6581.    @instance\
  6582.    @desc Used to parse conditions inside the query. Called from ':parse'\
  6583.    @return <table - conditions> - If a valid condition was found\
  6584. ]]\
  6585. function QueryParser:parseCondition()\
  6586.    local conditions, condition = {}, {}\
  6587. \
  6588.    local token = self:stepForward()\
  6589.    while true do\
  6590.        if token.type == \"QUERY_COND_ENTITY\" and ( condition.symbol or not condition.property ) then\
  6591.            condition[ condition.symbol and \"value\" or \"property\" ] = condition.symbol and parseValue( token.value ) or token.value\
  6592.        elseif token.type == \"QUERY_COND_STRING_ENTITY\" and condition.symbol then\
  6593.            condition.value = token.value\
  6594.        elseif token.type == \"QUERY_COND_SYMBOL\" and not condition.property and token.value == \"#\" then\
  6595.            condition.modifier = token.value\
  6596.        elseif token.type == \"QUERY_COND_SYMBOL\" and ( condition.property ) then\
  6597.            condition.symbol = token.value\
  6598.        elseif token.type == \"QUERY_COND_SEPERATOR\" and next( condition ) then\
  6599.            conditions[ #conditions + 1 ] = condition\
  6600.            condition = {}\
  6601.        elseif token.type == \"QUERY_COND_CLOSE\" and ( not condition.property or ( condition.property and condition.value ) ) then\
  6602.            break\
  6603.        else\
  6604.            self:throw( \"Unexpected '\"..token.value..\"' inside of condition block\" )\
  6605.        end\
  6606. \
  6607.        token = self:stepForward()\
  6608.    end\
  6609. \
  6610.    if next( condition ) then\
  6611.        conditions[ #conditions + 1 ] = condition\
  6612.    end\
  6613. \
  6614.    return #conditions > 0 and conditions or nil\
  6615. end\
  6616. ",
  6617. ["MTextDisplay.ti"]="local string_len, string_find, string_sub, string_gsub, string_match = string.len, string.find, string.sub, string.gsub, string.match\
  6618. \
  6619. --[[\
  6620.    This mixin \"is\" designed to be used by nodes that wish to display formatted text (e.g: Button, TextContainer).\
  6621.    The 'drawText' function should be called from the node during draw time.\
  6622. ]]\
  6623. \
  6624. class \"MTextDisplay\" abstract() {\
  6625.    lineConfig = {\
  6626.        lines = false;\
  6627.        alignedLines = false;\
  6628.        offsetY = 0;\
  6629.    };\
  6630. \
  6631.    verticalPadding = 0;\
  6632.    horizontalPadding = 0;\
  6633. \
  6634.    verticalAlign = \"top\";\
  6635.    horizontalAlign = \"left\";\
  6636. \
  6637.    includeNewlines = false;\
  6638. }\
  6639. \
  6640. --[[\
  6641.    @constructor\
  6642.    @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\
  6643. ]]\
  6644. function MTextDisplay:MTextDisplay()\
  6645.    if Titanium.mixesIn( self, \"MThemeable\" ) then\
  6646.        self:register( \"text\", \"verticalAlign\", \"horizontalAlign\", \"verticalPadding\", \"horizontalPadding\" )\
  6647.    end\
  6648. end\
  6649. \
  6650. --[[\
  6651.    @instance\
  6652.    @desc Generates a table of text lines by wrapping on newlines or when the line gets too long.\
  6653.    @param <number - width>\
  6654. ]]\
  6655. function MTextDisplay:wrapText( width )\
  6656.    local text, width, lines = self.text, width or self.width, {}\
  6657. \
  6658.    local current = 1\
  6659.    while text and string_len( text ) > 0 do\
  6660.        local section, pre, post = string_sub( text, 1, width )\
  6661.        local starting = current\
  6662.        local createTrail\
  6663. \
  6664.        if string_find( section, \"\\n\" ) then\
  6665.            pre, post = string_match( text, \"(.-\\n)(.*)$\" )\
  6666. \
  6667.            current = current + string_len( pre )\
  6668. \
  6669.            if post == \"\" then createTrail = true end\
  6670.        elseif string_len( text ) <= width then\
  6671.            pre = text\
  6672.            current = current + string_len( text )\
  6673.        else\
  6674.            local lastSpace, lastSpaceEnd = string_find( section, \"%s[%S]*$\" )\
  6675. \
  6676.            pre = lastSpace and string_gsub( string_sub( text, 1, lastSpace - 1 ), \"%s+$\", \"\" ) or section\
  6677.            post = lastSpace and string_sub( text, lastSpace + 1 ) or string_sub( text, width + 1 )\
  6678. \
  6679.            local match = lastSpace and string_match( string_sub( text, 1, lastSpace - 1 ), \"%s+$\" )\
  6680.            current = current + string_len( pre ) + ( match and #match or 1 )\
  6681.        end\
  6682. \
  6683.        lines[ #lines + 1 ], text = { pre, starting, current - 1, #lines + 1 }, post\
  6684. \
  6685.        if createTrail then lines[ #lines + 1 ] = { \"\", current, current, #lines + 1 } end\
  6686.    end\
  6687. \
  6688.    self.lineConfig.lines = lines\
  6689. end\
  6690. \
  6691. --[[\
  6692.    @instance\
  6693.    @desc Uses 'wrapText' to generate the information required to draw the text to the canvas correctly.\
  6694.    @param <colour - bg>, <colour - tc>\
  6695. ]]\
  6696. function MTextDisplay:drawText( bg, tc )\
  6697.    local lines = self.lineConfig.lines\
  6698.    if not lines then\
  6699.        self:wrapText()\
  6700.        lines = self.lineConfig.lines\
  6701.    end\
  6702. \
  6703.    local vPadding, hPadding = self.verticalPadding, self.horizontalPadding\
  6704. \
  6705.    local yOffset, xOffset = vPadding, hPadding\
  6706.    local vAlign, hAlign = self.verticalAlign, self.horizontalAlign\
  6707.    local width, height = self.width, self.height\
  6708. \
  6709.    if vAlign == \"centre\" then\
  6710.        yOffset = math.floor( ( height / 2 ) - ( #lines / 2 ) + .5 ) + vPadding\
  6711.    elseif vAlign == \"bottom\" then\
  6712.        yOffset = height - #lines - vPadding\
  6713.    end\
  6714. \
  6715.    local canvas, line = self.canvas\
  6716.    for i = 1, #lines do\
  6717.        local line, xOffset = lines[ i ], hPadding\
  6718.        local lineText = line[ 1 ]\
  6719.        if hAlign == \"centre\" then\
  6720.            xOffset = math.floor( width / 2 - ( #lineText / 2 ) + .5 )\
  6721.        elseif hAlign == \"right\" then\
  6722.            xOffset = width - #lineText - hPadding + 1\
  6723.        end\
  6724. \
  6725.        canvas:drawTextLine( xOffset + 1, i + yOffset, lineText, tc, bg )\
  6726.    end\
  6727. end\
  6728. \
  6729. configureConstructor {\
  6730.    argumentTypes = {\
  6731.        verticalPadding = \"number\",\
  6732.        horizontalPadding = \"number\",\
  6733. \
  6734.        verticalAlign = \"string\",\
  6735.        horizontalAlign = \"string\",\
  6736. \
  6737.        text = \"string\"\
  6738.    }\
  6739. }\
  6740. ",
  6741. ["ContextMenu.ti"]="--[[\
  6742.    The ContextMenu class \"allows\" developers to dynamically spawn context menus with content they can customize. This node takes the application bounds into account and ensures\
  6743.    the content doesn't spill out of view.\
  6744. ]]\
  6745. \
  6746. class \"ContextMenu\" extends \"Container\" {\
  6747.    static = {\
  6748.        allowedTypes = { \"Button\", \"Label\" }\
  6749.    };\
  6750. }\
  6751. \
  6752. --[[\
  6753.    @constructor\
  6754.    @desc Resolves constructor arguments and invokes super. The canvas of this node is also marked transparent, as the canvas of this node is a rectangular shape surrounding all subframes.\
  6755.    @param <table - structure>*\
  6756. \
  6757.    Note: Ordered arguments inherited from other classes not included\
  6758. ]]\
  6759. function ContextMenu:__init__( ... )\
  6760.    self:resolve( ... )\
  6761.    self:super()\
  6762. \
  6763.    self.transparent = true\
  6764. end\
  6765. \
  6766. --[[\
  6767.    @instance\
  6768.    @desc Population of the context menu requires a parent to be present. Therefore, when the parent is set on a node we will populate the\
  6769.          context menu, instead of at instantiation\
  6770.    @param <Node - parent>\
  6771. ]]\
  6772. function ContextMenu:setParent( parent )\
  6773.    self.parent = parent\
  6774. \
  6775.    if parent then\
  6776.        local frame = self:addNode( ScrollContainer() )\
  6777.        frame.frameID = 1\
  6778. \
  6779.        self:populate( frame, self.structure )\
  6780.        frame.visible = true\
  6781.    end\
  6782. end\
  6783. \
  6784. --[[\
  6785.    @instance\
  6786.    @desc Populates the context menu with the options specified in the 'structure' table.\
  6787.          Accounts for application edge by positioning the menu as to avoid the menu contents spilling out of view.\
  6788.    @param <MNodeContainer* - parent>, <table - structure>\
  6789. \
  6790.    Note: The 'parent' param must be a node that can contain other nodes.\
  6791. ]]\
  6792. function ContextMenu:populate( frame, structure )\
  6793.    local queue, q, totalWidth, totalHeight, negativeX = { { frame, structure } }, 1, 0, 0, 1\
  6794. \
  6795.    while q <= #queue do\
  6796.        local menu, structure, width = queue[ q ][ 1 ], queue[ q ][ 2 ], 0\
  6797.        local rules, Y = {}, 0\
  6798. \
  6799.        for i = 1, #structure do\
  6800.            Y = Y + 1\
  6801.            local part = structure[ i ]\
  6802.            local partType = part[ 1 ]:lower()\
  6803. \
  6804.            if partType == \"custom\" then\
  6805.                --TODO: Custom menu entries\
  6806.            else\
  6807.                if partType == \"menu\" then\
  6808.                    local subframe = self:addNode( ScrollContainer( nil, menu.Y + Y - 1 ) )\
  6809.                    if not menu.subframes then\
  6810.                        menu.subframes = { subframe }\
  6811.                    else\
  6812.                        table.insert( menu.subframes, subframe )\
  6813.                    end\
  6814. \
  6815.                    subframe.visible = false\
  6816. \
  6817.                    local id = #self.nodes\
  6818.                    subframe.frameID = id\
  6819.                    menu:addNode( Button( part[ 2 ], 1, Y ):on( \"trigger\", function()\
  6820.                        local subframes = menu.subframes\
  6821.                        for i = 1, #subframes do\
  6822.                            if subframes[ i ] ~= subframe and subframes[ i ].visible then\
  6823.                                self:closeFrame( subframes[ i ].frameID )\
  6824.                            end\
  6825.                        end\
  6826. \
  6827.                        if subframe.visible then\
  6828.                            self:closeFrame( id )\
  6829.                        else\
  6830.                            subframe.visible = true\
  6831.                        end\
  6832.                    end ) )\
  6833. \
  6834.                    table.insert( queue, { subframe, part[ 3 ], menu } )\
  6835.                elseif partType == \"rule\" then\
  6836.                    rules[ #rules + 1 ] = Y\
  6837.                elseif partType == \"button\" then\
  6838.                    menu:addNode( Button( part[ 2 ], 1, Y ):on( \"trigger\", part[ 3 ] ) )\
  6839.                elseif partType == \"label\" then\
  6840.                    menu:addNode( Label( part[ 2 ], 1, Y ) )\
  6841.                end\
  6842. \
  6843.                if partType ~= \"rule\" then\
  6844.                    width = math.max( width, #part[ 2 ] )\
  6845.                end\
  6846.            end\
  6847.        end\
  6848. \
  6849.        if width == 0 then error \"Failed to populate context menu. Content given has no detectable width (or zero). Cannot proceed without width greater than 0\" end\
  6850. \
  6851.        for n = 1, #menu.nodes do menu.nodes[ n ].width = width end\
  6852.        for r = 1, #rules do menu:addNode( Label( (\"-\"):rep( width ), 1, rules[ r ] ) ) end\
  6853. \
  6854.        local parentMenu, widthOffset, relX = queue[ q ][ 3 ], 0, 0\
  6855.        if parentMenu then\
  6856.            widthOffset, relX = parentMenu.width, parentMenu.X\
  6857.        end\
  6858. \
  6859.        local spill = ( relX + widthOffset + width + self.X - 1 ) - self.parent.width\
  6860.        if spill > 0 then\
  6861.            menu.X = relX - ( parentMenu and width or spill )\
  6862.        else\
  6863.            menu.X = relX + widthOffset\
  6864.        end\
  6865.        negativeX = math.min( negativeX, menu.X )\
  6866. \
  6867.        menu.width, menu.height = width, Y - math.max( menu.Y + Y - self.parent.height, 0 )\
  6868.        menu:cacheContent()\
  6869. \
  6870.        totalWidth, totalHeight = totalWidth + menu.width, totalHeight + math.max( menu.height - ( parentMenu and parentMenu.Y or 0 ), 1 )\
  6871.        q = q + 1\
  6872.    end\
  6873. \
  6874.    if negativeX < 1 then\
  6875.        local nodes = self.nodes\
  6876.        for i = 1, #nodes do\
  6877.            nodes[ i ].X = nodes[ i ].X - negativeX + 1\
  6878.        end\
  6879. \
  6880.        self.X = self.X + negativeX\
  6881.    end\
  6882. \
  6883.    self.width = totalWidth\
  6884.    self.height = totalHeight\
  6885. end\
  6886. \
  6887. --[[\
  6888.    @instance\
  6889.    @desc A modified Container.shipEvent to avoid shipping events to hidden submenus.\
  6890.    @param <Event - event>\
  6891. ]]\
  6892. function ContextMenu:shipEvent( event )\
  6893.    local nodes = self.nodes\
  6894.    for i = #nodes, 1, -1 do\
  6895.        if nodes[ i ].visible then\
  6896.            nodes[ i ]:handle( event )\
  6897.        end\
  6898.    end\
  6899. end\
  6900. \
  6901. --[[\
  6902.    @instance\
  6903.    @desc Invokes super (container) handle function. If event is a mouse event and it missed an open subframe the frames will be closed (if it was a CLICK) and the event will be unhandled\
  6904.          allowing further propagation and usage throughout the application.\
  6905.    @param <Event - eventObj>\
  6906.    @return <boolean - propagate>\
  6907. ]]\
  6908. function ContextMenu:handle( eventObj )\
  6909.    if not self.super:handle( eventObj ) then return end\
  6910. \
  6911.    if eventObj.main == \"MOUSE\" and not self:isMouseColliding( eventObj ) then\
  6912.        if eventObj.sub == \"CLICK\" then self:closeFrame( 1 ) end\
  6913.        eventObj.handled = false\
  6914.    end\
  6915. \
  6916.    return true\
  6917. end\
  6918. \
  6919. --[[\
  6920.    @instance\
  6921.    @desc Closes the frame using 'frameID', which represents the position of the frame in the 'nodes' table\
  6922.    @param <number - frameID>\
  6923. ]]\
  6924. function ContextMenu:closeFrame( frameID )\
  6925.    local framesToClose, i = { self.nodes[ frameID ] }, 1\
  6926.    while i <= #framesToClose do\
  6927.        local subframes = framesToClose[ i ].subframes or {}\
  6928.        for f = 1, #subframes do\
  6929.            if subframes[ f ].visible then\
  6930.                framesToClose[ #framesToClose + 1 ] = subframes[ f ]\
  6931.            end\
  6932.        end\
  6933. \
  6934.        framesToClose[ i ].visible = false\
  6935.        i = i + 1\
  6936.    end\
  6937. \
  6938.    self.changed = true\
  6939. end\
  6940. \
  6941. configureConstructor {\
  6942.    orderedArguments = { \"structure\" },\
  6943.    requiredArguments = { \"structure\" },\
  6944.    argumentTypes = {\
  6945.        structure = \"table\"\
  6946.    }\
  6947. }\
  6948. ",
  6949. ["MTogglable.ti"]="--[[\
  6950.    A small mixin \"to\" avoid rewriting code used by nodes that can be toggled on or off.\
  6951. ]]\
  6952. \
  6953. class \"MTogglable\" abstract() {\
  6954.    toggled = false;\
  6955. \
  6956.    toggledColour = colours.red;\
  6957.    toggledBackgroundColour = colours.white;\
  6958. }\
  6959. \
  6960. --[[\
  6961.    @constructor\
  6962.    @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\
  6963. ]]\
  6964. function MTogglable:MTogglable()\
  6965.    if Titanium.mixesIn( self, \"MThemeable\" ) then\
  6966.        self:register(\"toggled\", \"toggledColour\", \"toggledBackgroundColour\")\
  6967.    end\
  6968. end\
  6969. \
  6970. --[[\
  6971.    @instance\
  6972.    @desc 'toggled' to the opposite of what it currently is (toggles)\
  6973. ]]\
  6974. function MTogglable:toggle( ... )\
  6975.    self:setToggled( not self.toggled, ... )\
  6976. end\
  6977. \
  6978. --[[\
  6979.    @instance\
  6980.    @desc Sets toggled to 'toggled' and changed to 'true' when the 'toggled' param doesn't match the current value of toggled.\
  6981.    @param <boolean - toggled>, [vararg - onToggleArguments]\
  6982. ]]\
  6983. function MTogglable:setToggled( toggled, ... )\
  6984.    if self.toggled ~= toggled then\
  6985.        self.raw.toggled = toggled\
  6986.        self.changed = true\
  6987. \
  6988.        self:executeCallbacks( \"toggle\", ... )\
  6989.    end\
  6990. end\
  6991. \
  6992. configureConstructor {\
  6993.    argumentTypes = {\
  6994.        toggled = \"boolean\",\
  6995.        toggledColour = \"colour\",\
  6996.        toggledBackgroundColour = \"colour\"\
  6997.    }\
  6998. } alias {\
  6999.    toggledColor = \"toggledColour\",\
  7000.    toggledBackgroundColor = \"toggledBackgroundColour\"\
  7001. }\
  7002. ",
  7003. ["Image.ti"]="local function getFileExtension( path )\
  7004.    return path:match \".+%.(.-)$\" or \"\"\
  7005. end\
  7006. \
  7007. class \"Image\" extends \"Node\" {\
  7008.    static = { imageParsers = {} };\
  7009.    imagePath = false;\
  7010. }\
  7011. \
  7012. function Image:__init__( ... )\
  7013.    self:super()\
  7014.    self:resolve( ... )\
  7015. end\
  7016. \
  7017. --[[\
  7018.    @instance\
  7019.    @desc Depending on the file extension (self.path), an image parser will be called.\
  7020.          To add support for more extensions, simply add the function to the classes static ( Image.static.addParser( extension, function ) )\
  7021. ]]\
  7022. function Image:parseImage()\
  7023.    local path = self.path\
  7024.    if type( path ) ~= \"string\" then\
  7025.        return error(\"Failed to parse image, path '\"..tostring( path )..\"' is invalid\")\
  7026.    elseif not fs.exists( path ) or fs.isDir( path ) then\
  7027.        return error(\"Failed to parse image, path '\"..path..\"' is invalid and cannot be opened for parsing\")\
  7028.    end\
  7029. \
  7030.    local ext = getFileExtension( path )\
  7031.    if not Image.imageParsers[ ext ] then\
  7032.        return error(\"Failed to parse image, no image parser exists for \" .. ( ext == \"\" and \"'no ext'\" or \"'.\" .. ext .. \"'\" ) .. \" files for '\"..path..\"'\")\
  7033.    end\
  7034. \
  7035.    local f = fs.open( path, \"r\" )\
  7036.    local stream = f.readAll()\
  7037.    f.close()\
  7038. \
  7039.    local width, height, pixels = Image.imageParsers[ ext ]( stream )\
  7040.    for y = 1, height do\
  7041.        local pos = ( y - 1 ) * width\
  7042.        for x = 1, width do\
  7043.            local posX = pos + x\
  7044.            self.canvas.buffer[ posX ] = pixels[ posX ] or { \" \" }\
  7045.        end\
  7046.    end\
  7047. \
  7048.    self.width, self.height = width, height\
  7049.    self.changed = true\
  7050. end\
  7051. \
  7052. function Image:setPath( path )\
  7053.    self.path = path\
  7054.    self:parseImage()\
  7055. end\
  7056. \
  7057. function Image.static.setImageParser( extension, parserFunction )\
  7058.    if type( extension ) ~= \"string\" or type( parserFunction ) ~= \"function\" then\
  7059.        return error \"Failed to set image parser. Invalid arguments, expected string, function\"\
  7060.    end\
  7061. \
  7062.    Image.static.imageParsers[ extension ] = parserFunction\
  7063. \
  7064.    return Image\
  7065. end\
  7066. \
  7067. function Image:draw() end\
  7068. configureConstructor {\
  7069.    orderedArguments = { \"path\" },\
  7070.    requiredArguments = { \"path\" },\
  7071.    useProxy = { \"path\" },\
  7072.    argumentTypes = {\
  7073.        path = \"string\"\
  7074.    }\
  7075. }\
  7076. ",
  7077. ["QueryLexer.ti"]="--[[\
  7078.    @instance inCondition - boolean (def. false) - If true, the lexer is currently processing a condition\
  7079. \
  7080.    A lexer that processes node queries into tokens used by QueryParser\
  7081. ]]\
  7082. \
  7083. class \"QueryLexer\" extends \"Lexer\"\
  7084. \
  7085. --[[\
  7086.    @instance\
  7087.    @desc The main token creator\
  7088. ]]\
  7089. function QueryLexer:tokenize()\
  7090.    if self.stream:find \"^%s\" and not self.inCondition then\
  7091.        self:pushToken { type = \"QUERY_SEPERATOR\" }\
  7092.    end\
  7093. \
  7094.    local stream = self:trimStream()\
  7095. \
  7096.    if self.inCondition then\
  7097.        self:tokenizeCondition( stream )\
  7098.    elseif stream:find \"^%b[]\" then\
  7099.        self:pushToken { type = \"QUERY_COND_OPEN\" }\
  7100.        self:consume( 1 )\
  7101. \
  7102.        self.inCondition = true\
  7103.    elseif stream:find \"^%,\" then\
  7104.        self:pushToken { type = \"QUERY_END\", value = self:consumePattern \"^%,\" }\
  7105.    elseif stream:find \"^>\" then\
  7106.        self:pushToken { type = \"QUERY_DIRECT_PREFIX\", value = self:consumePattern \"^>\" }\
  7107.    elseif stream:find \"^#[^%s%.#%[%,]*\" then\
  7108.        self:pushToken { type = \"QUERY_ID\", value = self:consumePattern \"^#([^%s%.#%[]*)\" }\
  7109.    elseif stream:find \"^%.[^%s#%[%,]*\" then\
  7110.        self:pushToken { type = \"QUERY_CLASS\", value = self:consumePattern \"^%.([^%s%.#%[]*)\" }\
  7111.    elseif stream:find \"^[^,%s#%.%[]*\" then\
  7112.        self:pushToken { type = \"QUERY_TYPE\", value = self:consumePattern \"^[^,%s#%.%[]*\" }\
  7113.    else\
  7114.        self:throw(\"Unexpected block '\"..stream:match(\"(.-)%s\")..\"'\")\
  7115.    end\
  7116. end\
  7117. \
  7118. --[[\
  7119.    @instance\
  7120.    @desc When the lexer finds a condition (isCondition = true), this function is used to lex the condition\
  7121.    @param <string - stream>\
  7122. ]]\
  7123. function QueryLexer:tokenizeCondition( stream )\
  7124.    local first = stream:sub( 1, 1 )\
  7125.    if stream:find \"%b[]\" then\
  7126.        self:throw( \"Nested condition found '\"..tostring( stream:match \"%b[]\" )..\"'\" )\
  7127.    elseif stream:find \"^%b''\" or stream:find '^%b\"\"' then\
  7128.        local cnt = self:consumePattern( first == \"'\" and \"^%b''\" or '^%b\"\"' ):sub( 2, -2 )\
  7129.        if cnt:find \"%b''\" or cnt:find '%b\"\"' then\
  7130.            self:throw( \"Nested string found inside '\"..tostring( cnt )..\"'\" )\
  7131.        end\
  7132. \
  7133.        self:pushToken { type = \"QUERY_COND_STRING_ENTITY\", value = cnt }\
  7134.    elseif stream:find \"^%w+\" then\
  7135.        self:pushToken { type = \"QUERY_COND_ENTITY\", value = self:consumePattern \"^%w+\" }\
  7136.    elseif stream:find \"^%,\" then\
  7137.        self:pushToken { type = \"QUERY_COND_SEPERATOR\" }\
  7138.        self:consume( 1 )\
  7139.    elseif stream:find \"^[%p~]+\" then\
  7140.        self:pushToken { type = \"QUERY_COND_SYMBOL\", value = self:consumePattern \"^[%p~]+\" }\
  7141.    elseif stream:find \"^%]\" then\
  7142.        self:pushToken { type = \"QUERY_COND_CLOSE\" }\
  7143.        self:consume( 1 )\
  7144.        self.inCondition = false\
  7145.    else\
  7146.        self:throw(\"Invalid condition syntax. Expected property near '\"..tostring( stream:match \"%S*\" )..\"'\")\
  7147.    end\
  7148. end\
  7149. ",
  7150. ["Container.ti"]="--[[\
  7151.    @instance consumeAll - boolean (def. true) - If true ANY mouse event that collide with the container will be 'handled', regardless of whether or not the event collided with a contained node.\
  7152. \
  7153.    Container is a simple node that allows multiple nodes to be contained using relative positions.\
  7154. ]]\
  7155. \
  7156. class \"Container\" extends \"Node\" mixin \"MNodeContainer\" {\
  7157.    allowMouse = true;\
  7158.    allowKey = true;\
  7159.    allowChar = true;\
  7160. \
  7161.    consumeAll = true;\
  7162. }\
  7163. \
  7164. --[[\
  7165.    @instance\
  7166.    @desc Constructs the Container node with the value passed. If a nodes table is passed each entry inside of it will be added to the container as a node\
  7167.    @param [number - X], [number - Y], [number - width], [number - height], [table - nodes]\
  7168. ]]\
  7169. function Container:__init__( ... )\
  7170.    self:resolve( ... )\
  7171. \
  7172.    local toset = self.nodes\
  7173.    self.nodes = {}\
  7174. \
  7175.    if type( toset ) == \"table\" then\
  7176.        for i = 1, #toset do\
  7177.            self:addNode( toset[ i ] )\
  7178.        end\
  7179.    end\
  7180. \
  7181.    self:super()\
  7182. end\
  7183. \
  7184. --[[\
  7185.    @instance\
  7186.    @desc Returns true if the node given is visible inside of the container\
  7187.    @param <Node - node>, [number - width], [number - height]\
  7188.    @return <boolean - visible>\
  7189. ]]\
  7190. function Container:isNodeInBounds( node, width, height )\
  7191.    local left, top = node.X, node.Y\
  7192. \
  7193.    return not ( ( left + node.width ) < 1 or left > ( width or self.width ) or top > ( height or self.height ) or ( top + node.height ) < 1 )\
  7194. end\
  7195. \
  7196. --[[\
  7197.    @instance\
  7198.    @desc Draws contained nodes to container canvas. Nodes are only drawn if they are visible inside the container\
  7199.    @param [boolean - force], [number - offsetX], [number - offsetY]\
  7200. ]]\
  7201. function Container:draw( force, offsetX, offsetY )\
  7202.    if self.changed or force then\
  7203.        local canvas = self.canvas\
  7204. \
  7205.        local width, height = self.width, self.height\
  7206.        local nodes, node = self.nodes\
  7207.        local offsetX, offsetY = offsetX or 0, offsetY or 0\
  7208. \
  7209.        for i = 1, #nodes do\
  7210.            node = nodes[ i ]\
  7211. \
  7212.            if node.needsRedraw and node.visible then\
  7213.                node:draw( force )\
  7214. \
  7215.                node.canvas:drawTo( canvas, node.X + offsetX, node.Y + offsetY )\
  7216.                node.needsRedraw = false\
  7217.            end\
  7218.        end\
  7219. \
  7220.        self.changed = false\
  7221.    end\
  7222. end\
  7223. \
  7224. --[[\
  7225.    @instance\
  7226.    @desc Redirects all events to child nodes. Mouse events are adjusted to become relative to this container. Event handlers on Container are still fired if present\
  7227.    @param <Event - event>\
  7228.    @return <boolean - propagate>\
  7229. ]]\
  7230. function Container:handle( eventObj )\
  7231.    if not self.super:handle( eventObj ) then return end\
  7232. \
  7233.    local clone\
  7234.    if eventObj.main == \"MOUSE\" then\
  7235.        clone = eventObj:clone( self )\
  7236.        clone.isWithin = clone.isWithin and eventObj:withinParent( self ) or false\
  7237.    end\
  7238. \
  7239.    self:shipEvent( clone or eventObj )\
  7240.    if clone and clone.isWithin and ( self.consumeAll or clone.handled ) then\
  7241.        eventObj.handled = true\
  7242.    end\
  7243.    return true\
  7244. end\
  7245. \
  7246. --[[\
  7247.    @instance\
  7248.    @desc Ships the 'event' provided to every direct child\
  7249.    @param <Event Instance - event>\
  7250. ]]\
  7251. function Container:shipEvent( event )\
  7252.    local nodes = self.nodes\
  7253.    for i = #nodes, 1, -1 do\
  7254.        nodes[ i ]:handle( event )\
  7255.    end\
  7256. end\
  7257. \
  7258. --[[\
  7259.    @setter\
  7260.    @desc Sets the width property on 'self', and queues a redraw on each direct child node\
  7261.    @param <number - width>\
  7262. ]]\
  7263. function Container:setWidth( width )\
  7264. \9self.super:setWidth( width )\
  7265. \9local nodes = self.nodes\
  7266. \9for i = 1, #nodes do\
  7267. \9\9nodes[i].needsRedraw = true\
  7268. \9end\
  7269. end\
  7270. \
  7271. --[[\
  7272.    @setter\
  7273.    @desc Sets the height property on 'self', and queues a redraw on each direct child node\
  7274.    @param <number - height>\
  7275. ]]\
  7276. function Container:setHeight( height )\
  7277. \9self.super:setHeight( height )\
  7278. \9local nodes = self.nodes\
  7279. \9for i = 1, #nodes do\
  7280. \9\9nodes[i].needsRedraw = true\
  7281. \9end\
  7282. end\
  7283. \
  7284. \
  7285. configureConstructor({\
  7286.    orderedArguments = { \"X\", \"Y\", \"width\", \"height\", \"nodes\", \"backgroundColour\" },\
  7287.    argumentTypes = {\
  7288.        nodes = \"table\"\
  7289.    }\
  7290. }, true)\
  7291. ",
  7292. ["TermCanvas.ti"]="local tableConcat = table.concat\
  7293. local hex = {}\
  7294. for i = 0, 15 do\
  7295.    hex[2 ^ i] = (\"%x\"):format( i ) -- %x = lowercase hexadecimal\
  7296.    hex[(\"%x\"):format( i )] = 2 ^ i\
  7297. end\
  7298. \
  7299. --[[\
  7300.    The TermCanvas is an object that draws it's buffer directly to the ComputerCraft term object, unlike the NodeCanvas.\
  7301. \
  7302.    The TermCanvas should be used by high level objects, like 'Application'. Nodes should not be drawing directly to the term object.\
  7303.    If your object needs to draw to the canvas this class \"should\" be used.\
  7304. \
  7305.    Unlike NodeCanvas, TermCanvas has no drawing functions as it's purpose is not to generate the buffer, just draw it to the term object.\
  7306.    Nodes generate their content and store it in your buffer (and theirs aswell).\
  7307. ]]\
  7308. \
  7309. class \"TermCanvas\" extends \"Canvas\" {\
  7310.    static = { hex = hex };\
  7311. }\
  7312. \
  7313. --[[\
  7314.    @instance\
  7315.    @desc Draws the content of the canvas to the terminal object (term.blit). If 'force' is provided, even unchanged lines will be drawn, if not 'force' only changes lines will be blit.\
  7316. \
  7317.          The canvas contents are drawn using the X and Y position of the owner as the offset\
  7318. \
  7319.          If a pixel has a missing foreground or background colour, it will use the owner colour or background colour (respectively). If the owner has no colour set, defaults will be used\
  7320.          instead (foreground = 1, backgroundColour = 32768)\
  7321.    @param [boolean - force]\
  7322. ]]\
  7323. function TermCanvas:draw( force )\
  7324.    local owner = self.owner\
  7325.    local buffer, last = self.buffer, self.last\
  7326.    local X, Y, width, height = owner.X, owner.Y - 1, self.width, self.height\
  7327.    local colour, backgroundChar, backgroundTextColour, backgroundColour = self.colour, self.backgroundChar, self.backgroundTextColour, self.backgroundColour\
  7328. \
  7329.    local position, px, lpx = 1\
  7330.    for y = 1, height do\
  7331.        local changed\
  7332. \
  7333.        for x = 1, width do\
  7334.            px, lpx = buffer[ position ], last[ position ]\
  7335. \
  7336.            if force or not lpx or ( px[ 1 ] ~= lpx[ 1 ] or px[ 2 ] ~= lpx[ 2 ] or px[ 3 ] ~= lpx[ 3 ] ) then\
  7337.                changed = true\
  7338. \
  7339.                position = position - ( x - 1 )\
  7340.                break\
  7341.            end\
  7342. \
  7343.            position = position + 1\
  7344.        end\
  7345. \
  7346.        if changed then\
  7347.            local rowText, rowColour, rowBackground, pixel = {}, {}, {}\
  7348. \
  7349.            for x = 1, width do\
  7350.                pixel = buffer[ position ]\
  7351.                last[ position ] = pixel\
  7352. \
  7353.                local c, fg, bg = pixel[1], pixel[2], pixel[3]\
  7354. \
  7355.                rowColour[ x ] = hex[ type(fg) == \"number\" and fg ~= 0 and fg or colour or 1 ]\
  7356.                rowBackground[ x ] = hex[ type(bg) == \"number\" and bg ~= 0 and bg or backgroundColour or 32768 ]\
  7357.                if c then\
  7358.                    rowText[ x ] = c or backgroundChar or \" \"\
  7359.                else\
  7360.                    rowText[ x ] = backgroundChar or \" \"\
  7361.                    rowColour[ x ] = hex[ backgroundTextColour or 1 ]\
  7362.                end\
  7363. \
  7364.                position = position + 1\
  7365.            end\
  7366. \
  7367.            term.setCursorPos( X, y + Y )\
  7368.            term.blit( tableConcat( rowText ), tableConcat( rowColour ), tableConcat( rowBackground ) )\
  7369.        end\
  7370.    end\
  7371. end\
  7372. ",
  7373. ["RedirectCanvas.ti"]="local stringLen, stringSub = string.len, string.sub\
  7374. local isColour = term.isColour()\
  7375. \
  7376. local function testColour( col )\
  7377.    if not isColour and ( col ~= 1 or col ~= 32768 or col ~= 256 or col ~= 128 ) then\
  7378.        error \"Colour not supported\"\
  7379.    end\
  7380. \
  7381.    return true\
  7382. end\
  7383. \
  7384. --[[\
  7385.    @instance tX - number (def. 1) - The X position of the terminal redirect, controlled from inside the redirect itself\
  7386.    @instance tY - number (def. 1) - The Y position of the terminal redirect, controlled from inside the redirect itself\
  7387.    @instance tColour - number (def. 1) - The current colour of the terminal redirect, controlled from inside the redirect itself\
  7388.    @instance tBackgroundColour - number (def. 32768) - The current background colour of the terminal redirect, controlled from inside the redirect itself\
  7389.    @instance tCursor - boolean (def. false) - The current cursor state of the terminal redirect (true for blinking, false for hidden), controlled from inside the redirect itself\
  7390. \
  7391.    The RedirectCanvas is a class \"to\" be used by nodes that wish to redirect the term object. This canvas provides a terminal redirect and keeps track\
  7392.    of the terminals properties set inside the wrapped program (via the term methods).\
  7393. \
  7394.    This allows emulation of a shell program inside of Titanium without causing visual issues due to the shell program drawing directly to the terminal and not\
  7395.    through Titaniums canvas system.\
  7396. ]]\
  7397. \
  7398. class \"RedirectCanvas\" extends \"NodeCanvas\"\
  7399. \
  7400. --[[\
  7401.    @constructor\
  7402.    @desc Resets the terminal redirect, before running the super constructor\
  7403. ]]\
  7404. function RedirectCanvas:__init__( ... )\
  7405.    self:resetTerm()\
  7406.    self:super( ... )\
  7407. end\
  7408. \
  7409. --[[\
  7410.    @instance\
  7411.    @desc Resets the terminal redirect by setting tX, tY, tColour, tBackgroundColour, and tCursor back to default before clearing the canvas\
  7412. ]]\
  7413. function RedirectCanvas:resetTerm()\
  7414.    self.tX, self.tY, self.tColour, self.tBackgroundColour, self.tCursor = 1, 1, 1, 32768, false;\
  7415.    self:clear( 32768, true )\
  7416. end\
  7417. \
  7418. --[[\
  7419.    @instance\
  7420.    @desc Returns a table compatible with `term.redirect`\
  7421.    @return <table - redirect>\
  7422. ]]\
  7423. function RedirectCanvas:getTerminalRedirect()\
  7424.    local redirect = {}\
  7425. \
  7426.    function redirect.write( text )\
  7427.        text = tostring( text )\
  7428.        local tc, bg, tX, tY = self.tColour, self.tBackgroundColour, self.tX, self.tY\
  7429.        local buffer, position = self.buffer, self.width * ( tY - 1 ) + tX\
  7430. \
  7431.        for i = 1, math.min( stringLen( text ), self.width - tX + 1 ) do\
  7432.            buffer[ position ] = { stringSub( text, i, i ), tc, bg }\
  7433.            position = position + 1\
  7434.        end\
  7435. \
  7436.        self.tX = tX + stringLen( text )\
  7437.    end\
  7438. \
  7439.    function redirect.blit( text, colour, background )\
  7440.        if stringLen( text ) ~= stringLen( colour ) or stringLen( text ) ~= stringLen( background ) then\
  7441.            return error \"blit arguments must be the same length\"\
  7442.        end\
  7443. \
  7444.        local tX, hex = self.tX, TermCanvas.static.hex\
  7445.        local buffer, position = self.buffer, self.width * ( self.tY - 1 ) + tX\
  7446. \
  7447.        for i = 1, math.min( stringLen( text ), self.width - tX + 1 ) do\
  7448.            buffer[ position ] = { stringSub( text, i, i ), hex[ stringSub( colour, i, i ) ], hex[ stringSub( background, i, i ) ] }\
  7449.            position = position + 1\
  7450.        end\
  7451. \
  7452.        self.tX = tX + stringLen( text )\
  7453.    end\
  7454. \
  7455.    function redirect.clear()\
  7456.        self:clear( self.tBackgroundColour, true )\
  7457.    end\
  7458. \
  7459.    function redirect.clearLine()\
  7460.        local px = { \" \", self.tColour, self.tBackgroundColour }\
  7461.        local buffer, position = self.buffer, self.width * ( self.tY - 1 )\
  7462. \
  7463.        for i = 1, self.width do\
  7464.            buffer[ position ] = px\
  7465.            position = position + 1\
  7466.        end\
  7467.    end\
  7468. \
  7469.    function redirect.getCursorPos()\
  7470.        return self.tX, self.tY\
  7471.    end\
  7472. \
  7473.    function redirect.setCursorPos( x, y )\
  7474.        self.tX, self.tY = math.floor( x ), math.floor( y )\
  7475.    end\
  7476. \
  7477.    function redirect.getSize()\
  7478.        return self.width, self.height\
  7479.    end\
  7480. \
  7481.    function redirect.setCursorBlink( blink )\
  7482.        self.tCursor = blink\
  7483.    end\
  7484. \
  7485.    function redirect.setTextColour( tc )\
  7486.        if testColour( tc ) then\
  7487.            self.tColour = tc\
  7488.        end\
  7489.    end\
  7490. \
  7491.    function redirect.getTextColour()\
  7492.        return self.tColour\
  7493.    end\
  7494. \
  7495.    function redirect.setBackgroundColour( bg )\
  7496.        if testColour( bg ) then\
  7497.            self.tBackgroundColour = bg\
  7498.        end\
  7499.    end\
  7500. \
  7501.    function redirect.getBackgroundColour()\
  7502.        return self.tBackgroundColour\
  7503.    end\
  7504. \
  7505.    function redirect.scroll( n )\
  7506.        local offset, buffer, nL = self.width * n, self.buffer, n < 0\
  7507.        local pixelCount, blank = self.width * self.height, { \" \", self.tColour, self.tBackgroundColour }\
  7508. \
  7509.        for i = nL and pixelCount or 1, nL and 1 or pixelCount, nL and -1 or 1 do\
  7510.            buffer[ i ] = buffer[ i + offset ] or blank\
  7511.        end\
  7512.    end\
  7513. \
  7514.    function redirect.isColour()\
  7515.        return isColour\
  7516.    end\
  7517. \
  7518.    -- American spelling compatibility layer\
  7519.    redirect.isColor = redirect.isColour\
  7520. \9redirect.setBackgroundColor = redirect.setBackgroundColour\
  7521. \9redirect.setTextColor = redirect.setTextColour\
  7522. \9redirect.getBackgroundColor = redirect.getBackgroundColour\
  7523. \9redirect.getTextColor = redirect.getTextColour\
  7524. \
  7525.    return redirect\
  7526. end\
  7527. \
  7528. --[[\
  7529.    @instance\
  7530.    @desc Modified Canvas.clear. Only sets pixels that do not exist (doesn't really clear the canvas, just ensures it is the correct size).\
  7531.          This is to prevent the program running via the term redirect isn't cleared away. Call this function with 'force' and all pixels will be\
  7532.          replaced (the terminal redirect uses this method).\
  7533. \
  7534.          Alternatively, self:getTerminalRedirect().clear() will also clear the canvas entirely\
  7535.    @param [number - col], [boolean - force]\
  7536. ]]\
  7537. function RedirectCanvas:clear( col, force )\
  7538.    local col = col or self.tBackgroundColour\
  7539.    local pixel, buffer = { \" \", col, col }, self.buffer\
  7540. \
  7541.    for index = 1, self.width * self.height do\
  7542.        if not buffer[ index ] or force then\
  7543.            buffer[ index ] = pixel\
  7544.        end\
  7545.    end\
  7546. end\
  7547. ",
  7548. ["Button.ti"]="--[[\
  7549.    @instance buttonLock - number (def. 1) - If 1 or 2, only mouse events with that button code will be handled. If 0, any mouse events will be handled\
  7550. \
  7551.    A Button is a node that can be clicked to trigger a callback.\
  7552.    The button can contain text which can span multiple lines, however if too much text is entered it will be truncated to fit the button dimensions.\
  7553. \
  7554.    When the Button is clicked, the 'trigger' callback will be executed.\
  7555. ]]\
  7556. \
  7557. class \"Button\" extends \"Node\" mixin \"MTextDisplay\" mixin \"MActivatable\" {\
  7558.    allowMouse = true;\
  7559.    buttonLock = 1;\
  7560. }\
  7561. \
  7562. --[[\
  7563.    @constructor\
  7564.    @desc Accepts button arguments and resolves them.\
  7565.    @param <string - text>, [number - X], [number - Y], [number - width], [number - height]\
  7566. ]]\
  7567. function Button:__init__( ... )\
  7568.    self:resolve( ... )\
  7569.    self:super()\
  7570. \
  7571.    self:register(\"width\", \"height\", \"buttonLock\")\
  7572. end\
  7573. \
  7574. --[[\
  7575.    @instance\
  7576.    @desc Sets the button to 'active' when the button is clicked with the valid mouse button (self.buttonLock)\
  7577.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  7578. ]]\
  7579. function Button:onMouseClick( event, handled, within )\
  7580.    if not handled and within and ( self.buttonLock == 0 or event.button == self.buttonLock ) then\
  7581.        self.active, event.handled = true, true\
  7582.    end\
  7583. end\
  7584. \
  7585. --[[\
  7586.    @instance\
  7587.    @desc Sets the button to inactive when the mouse button is released. If released on button while active 'onTrigger' callback is fired.\
  7588.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  7589. ]]\
  7590. function Button:onMouseUp( event, handled, within )\
  7591.    if within and not handled and self.active then\
  7592.        event.handled = true\
  7593.        self:executeCallbacks \"trigger\"\
  7594.    end\
  7595. \
  7596.    self.active = false\
  7597. end\
  7598. \
  7599. --[[\
  7600.    @instance\
  7601.    @desc Draws the text to the node canvas\
  7602.    @param [boolean - force]\
  7603. ]]\
  7604. function Button:draw( force )\
  7605.    local raw = self.raw\
  7606.    if raw.changed or force then\
  7607.        local tc, bg\
  7608.        if not self.enabled then\
  7609.            bg, tc = raw.disabledBackgroundColour, raw.disabledColour\
  7610.        elseif self.active then\
  7611.            bg, tc = raw.activeBackgroundColour, raw.activeColour\
  7612.        end\
  7613. \
  7614.        raw.canvas:clear( bg )\
  7615.        self:drawText( bg, tc )\
  7616. \
  7617.        raw.changed = false\
  7618.    end\
  7619. end\
  7620. \
  7621. --[[\
  7622.    @setter\
  7623.    @desc Sets the text of the button and then wraps the new text for display.\
  7624.    @param <string - text>\
  7625. ]]\
  7626. function Button:setText( text )\
  7627.    if self.text == text then return end\
  7628. \
  7629.    self.text = text\
  7630.    self.changed = true\
  7631.    self:wrapText()\
  7632. end\
  7633. \
  7634. --[[\
  7635.    @setter\
  7636.    @desc Sets the width of the button and then re-wraps the text to fit in the dimensions.\
  7637.    @param <number - width>\
  7638. ]]\
  7639. function Button:setWidth( width )\
  7640.    self.super:setWidth( width )\
  7641.    self:wrapText()\
  7642. end\
  7643. \
  7644. configureConstructor {\
  7645.    orderedArguments = { \"text\" },\
  7646.    requiredArguments = { \"text\" },\
  7647.    argumentTypes = {\
  7648.        buttonLock = \"number\"\
  7649.    }\
  7650. }\
  7651. ",
  7652. ["MFocusable.ti"]="--[[\
  7653.    @instance focused - boolean (def. false) - If true, the node is focused. Certain events will be rejected by nodes when not focused (ie: text input events)\
  7654. \
  7655.    A focusable object is an object that after a mouse_click and a mouse_up event occur on the object is 'focused'.\
  7656. \
  7657.    An 'input' is a good example of a focusable node, it is activatable (while being clicked) but it also focusable (allows you to type after being focused).\
  7658. ]]\
  7659. \
  7660. class \"MFocusable\" abstract() {\
  7661.    focused = false;\
  7662. }\
  7663. \
  7664. --[[\
  7665.    @constructor\
  7666.    @desc If the instance mixes in MThemeable, the \"focused\", \"focusedColour\", and \"focusedBackgroundColour\" are all registered as theme properties\
  7667. ]]\
  7668. function MFocusable:MFocusable()\
  7669.    if Titanium.mixesIn( self, \"MThemeable\" ) then\
  7670.        self:register(\"focused\", \"focusedColour\", \"focusedBackgroundColour\")\
  7671.    end\
  7672. end\
  7673. \
  7674. --[[\
  7675.    @setter\
  7676.    @desc Invokes the super setter, and unfocuses the node if it is disabled\
  7677.    @param <boolean - enabled>\
  7678. ]]\
  7679. function MFocusable:setEnabled( enabled )\
  7680.    self.super:setEnabled( enabled )\
  7681. \
  7682.    if not enabled and self.focused then\
  7683.        self:unfocus()\
  7684.    end\
  7685. end\
  7686. \
  7687. --[[\
  7688.    @setter\
  7689.    @desc If the node's focused property is changed, the nodes 'changed' property is set and the focused property is updated\
  7690.    @param <boolean - focused>\
  7691. ]]\
  7692. function MFocusable:setFocused( focused )\
  7693.    local raw = self.raw\
  7694.    if raw.focused == focused then return end\
  7695. \
  7696.    self.changed = true\
  7697.    self.focused = focused\
  7698. end\
  7699. \
  7700. --[[\
  7701.    @instance\
  7702.    @desc The preferred way of focusing a node. Sets the 'focused' property to true and focuses the node application wide\
  7703. ]]\
  7704. function MFocusable:focus()\
  7705.    if not self.enabled then return end\
  7706. \
  7707.    if self.application then self.application:focusNode( self ) end\
  7708.    self.focused = true\
  7709. end\
  7710. \
  7711. --[[\
  7712.    @instance\
  7713.    @desc The preferred way of un-focusing a node. Sets the 'focused' property to false and un-focuses the node application wide\
  7714. ]]\
  7715. function MFocusable:unfocus()\
  7716.    if self.application then self.application:unfocusNode( self ) end\
  7717.    self.focused = false\
  7718. end\
  7719. \
  7720. configureConstructor {\
  7721.    argumentTypes = {\
  7722.        focusedBackgroundColour = \"colour\",\
  7723.        focusedColour = \"colour\",\
  7724.        focused = \"boolean\"\
  7725.    }\
  7726. } alias {\
  7727.    focusedColor = \"focusedColour\",\
  7728.    focusedBackgroundColor = \"focusedBackgroundColour\"\
  7729. }\
  7730. ",
  7731. ["ScrollContainer.ti"]="--[[\
  7732.    @instance cache - table (def. {}) - Contains information cached via the caching methods\
  7733.    @instance mouse - table (def. { ... }) - Contains information regarding the currently selected scrollbar, and the origin of the mouse event\
  7734.    @instance xScroll - number (def. 0) - The horizontal scroll offset\
  7735.    @instance yScroll - number (def. 0) - The vertical scroll offset\
  7736.    @instance xScrollAllowed - boolean (def. true) - If false, horizontal scrolling is not allowed (scrollbar will not appear, and mouse events will be ignored)\
  7737.    @instance yScrollAllowed - boolean (def. true) - If false, vertical scrolling is not allowed (scrollbar will not appear, and mouse events will be ignored)\
  7738.    @instance propagateMouse - boolean (def. false) - If false, all incoming mouse events will be handled\
  7739.    @instance trayColour - colour (def. 256) - The colour of the scrollbar tray (where the scrollbar is not occupying)\
  7740.    @instance scrollbarColour - colour (def. 128) - The colour of the scrollbar itself\
  7741.    @instance activeScrollbarColour - colour (def. 512) - The colour of the scrollbar while being held (mouse)\
  7742. \
  7743.    The ScrollContainer node is a more complex version of Container, allowing for horizontal and vertical scrolling.\
  7744. ]]\
  7745. \
  7746. class \"ScrollContainer\" extends \"Container\" {\
  7747.    cache = {};\
  7748. \
  7749.    xScroll = 0;\
  7750.    yScroll = 0;\
  7751. \
  7752.    xScrollAllowed = true;\
  7753.    yScrollAllowed = true;\
  7754.    propagateMouse = true;\
  7755. \
  7756.    trayColour = 256;\
  7757.    scrollbarColour = 128;\
  7758.    activeScrollbarColour = colours.cyan;\
  7759. \
  7760.    mouse = {\
  7761.        selected = false;\
  7762.        origin = false;\
  7763.    };\
  7764. }\
  7765. \
  7766. --[[\
  7767.    @constructor\
  7768.    @desc Registers 'scrollbarColour', 'activeScrollbarColour', 'trayColour' as theme properties and invokes the Container constructor with ALL properties passed to this constructor\
  7769.    @param <... - args>\
  7770. ]]\
  7771. function ScrollContainer:__init__( ... )\
  7772.    self:register( \"scrollbarColour\", \"activeScrollbarColour\", \"trayColour\" )\
  7773.    self:super( ... )\
  7774. end\
  7775. \
  7776. --[[\
  7777.    @instance\
  7778.    @desc Handles a mouse click by moving the scrollbar (if click occurred on tray) or activating a certain scrollbar (allows mouse_drag manipulation) if the click was on the scrollbar.\
  7779. \
  7780.          If mouse click occurred off of the scroll bar, event is not handled and children nodes can make use of it.\
  7781.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  7782. ]]\
  7783. function ScrollContainer:onMouseClick( event, handled, within )\
  7784.    if handled or not within then return end\
  7785. \
  7786.    local cache, mouse, key = self.cache, self.mouse\
  7787.    local X, Y = event.X - self.X + 1, event.Y - self.Y + 1\
  7788. \
  7789.    if cache.yScrollActive and X == self.width and Y <= cache.displayHeight then\
  7790.        key = \"y\"\
  7791.    elseif cache.xScrollActive and Y == self.height and X <= cache.displayWidth then\
  7792.        key = \"x\"\
  7793.    else return end\
  7794. \
  7795.    local scrollFn = self[ \"set\"..key:upper()..\"Scroll\" ]\
  7796.    local edge, size = cache[ key .. \"ScrollPosition\" ], cache[ key .. \"ScrollSize\" ]\
  7797.    local cScale, dScale = cache[ \"content\" .. ( key == \"x\" and \"Width\" or \"Height\" ) ], cache[ \"display\" .. ( key == \"x\" and \"Width\" or \"Height\" ) ]\
  7798. \
  7799.    local rel = key == \"x\" and X or Y\
  7800.    if rel < edge then\
  7801.        event.handled = scrollFn( self, math.floor( cScale * ( rel / dScale ) - .5 ) )\
  7802.    elseif rel >= edge and rel <= edge + size - 1 then\
  7803.        mouse.selected, mouse.origin = key == \"x\" and \"h\" or \"v\", rel - edge + 1\
  7804.    elseif rel > edge + size - 1 then\
  7805.        event.handled = scrollFn( self, math.floor( cScale * ( ( rel - size + 1 ) / dScale ) - .5 ) )\
  7806.    end\
  7807. \
  7808.    self:cacheScrollbarPosition()\
  7809.    self.changed = true\
  7810. end\
  7811. \
  7812. --[[\
  7813.    @instance\
  7814.    @desc Moves the scroll of the ScrollContainer depending on the scroll direction and whether or not shift is held.\
  7815. \
  7816.          Scrolling that occurs while the shift key is held (or if there is ONLY a horizontal scrollbar) will adjust the horizontal scroll. Otherwise, the vertical scroll will be affected\
  7817.          if present.\
  7818.    @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\
  7819. ]]\
  7820. function ScrollContainer:onMouseScroll( event, handled, within )\
  7821.    local cache, app = self.cache, self.application\
  7822.    if handled or not within or not ( cache.xScrollActive or cache.yScrollActive ) then return end\
  7823. \
  7824.    local isXScroll = ( cache.xScrollActive and ( not cache.yScrollActive or ( app:isPressed( keys.leftShift ) or app:isPressed( keys.rightShift ) ) ) )\
  7825. \
  7826.    event.handled = self[\"set\".. ( isXScroll and \"X\" or \"Y\" ) ..\"Scroll\"]( self, self[ ( isXScroll and \"x\" or \"y\" ) .. \"Scroll\" ] + event.button )\
  7827.    self:cacheScrollbarPosition()\
  7828. end\
  7829. \
  7830. --[[\
  7831.    @instance\
  7832.    @desc If a scrollbar was selected, it is deselected preventing mouse_drag events from further manipulating it.\
  7833.    @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\
  7834. ]]\
  7835. function ScrollContainer:onMouseUp( event, handled, within )\
  7836.    if self.mouse.selected then\
  7837.        self.mouse.selected = false\
  7838.        self.changed = true\
  7839.    end\
  7840. end\
  7841. \
  7842. --[[\
  7843.    @instance\
  7844.    @desc If a scrollbar is selected it's value is manipulated when dragged\
  7845.    @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\
  7846. ]]\
  7847. function ScrollContainer:onMouseDrag( event, handled, within )\
  7848.    local mouse, cache = self.mouse, self.cache\
  7849.    if handled or not mouse.selected then return end\
  7850. \
  7851.    local isV = mouse.selected == \"v\"\
  7852.    local key = isV and \"Y\" or \"X\"\
  7853.    local scaleKey = isV and \"Height\" or \"Width\"\
  7854. \
  7855.    event.handled = self[ \"set\" .. key .. \"Scroll\" ]( self, math.floor( cache[\"content\" .. scaleKey ] * ( ( ( event[ key ] - self[ key ] + 1 ) - mouse.origin ) / cache[\"display\" .. scaleKey ] ) - .5 ) )\
  7856. end\
  7857. \
  7858. --[[\
  7859.    @instance\
  7860.    @desc Calls the super :addNode with all arguments passed to the function, re-caches the content and returns the node (arg #1)\
  7861.    @param <Node Instance - node>, <... - args>\
  7862.    @return <Node Instande - node>\
  7863. ]]\
  7864. function ScrollContainer:addNode( node, ... )\
  7865.    self.super:addNode( node, ... )\
  7866.    self:cacheContent()\
  7867. \
  7868.    return node\
  7869. end\
  7870. \
  7871. --[[\
  7872.    @instance\
  7873.    @desc A custom handle function that adjusts the values of incoming mouse events to work correctly with scroll offsets\
  7874.    @param <Event Instance - eventObj>\
  7875.    @return <boolean - propagate>\
  7876. ]]\
  7877. function ScrollContainer:handle( eventObj )\
  7878.    local cache, isWithin = self.cache, eventObj.isWithin\
  7879.    local cloneEv\
  7880. \
  7881.    if eventObj.main == \"MOUSE\" then\
  7882.        eventObj.isWithin = eventObj:withinParent( self )\
  7883.        if ( not cache.yScrollActive or ( eventObj.X - self.X + 1 ) ~= self.width ) and ( not cache.xScrollActive or ( eventObj.Y - self.Y + 1 ) ~= self.height ) then\
  7884.            cloneEv = eventObj:clone( self )\
  7885.            cloneEv.Y = cloneEv.Y + self.yScroll\
  7886.            cloneEv.X = cloneEv.X + self.xScroll\
  7887.        end\
  7888.    else cloneEv = eventObj end\
  7889. \
  7890.    if cloneEv then self:shipEvent( cloneEv ) end\
  7891.    local r = self.super.super:handle( eventObj )\
  7892. \
  7893.    eventObj.isWithin = isWithin\
  7894.    return r == nil and true or r\
  7895. end\
  7896. \
  7897. --[[\
  7898.    @instance\
  7899.    @desc Returns true if the node, with respect to the horizontal and vertical scroll is within the bounds of the container\
  7900.    @param <Node Instance - node>, [number - width], [number - height]\
  7901.    @return <boolean - inBounds>\
  7902. ]]\
  7903. function ScrollContainer:isNodeInBounds( node, width, height )\
  7904.    local left, top = node.X - self.xScroll, node.Y - self.yScroll\
  7905. \
  7906.    return not ( ( left + node.width ) < 1 or left > ( width or self.width ) or top > ( height or self.height ) or ( top + node.height ) < 1 )\
  7907. end\
  7908. \
  7909. --[[\
  7910.    @instance\
  7911.    @desc Invokes the Container draw function, offsetting the draw with the horizontal/vertical scroll.\
  7912. \
  7913.          After draw, the ScrollContainers scrollbars are drawn (:drawScrollbars)\
  7914.    @param [boolean - force]\
  7915. ]]\
  7916. function ScrollContainer:draw( force )\
  7917.    if self.changed or force then\
  7918.        self.super:draw( force, -self.xScroll, -self.yScroll )\
  7919.        self:drawScrollbars()\
  7920.    end\
  7921. end\
  7922. \
  7923. --[[\
  7924.    @instance\
  7925.    @desc Draws the enabled scrollbars. If both horizontal and vertical scrollbars are enabled, the bottom-right corner is filled in to prevent a single line of transparent space\
  7926. ]]\
  7927. function ScrollContainer:drawScrollbars()\
  7928.    local cache, canvas = self.cache, self.canvas\
  7929.    local xEnabled, yEnabled = cache.xScrollActive, cache.yScrollActive\
  7930. \
  7931.    if xEnabled then\
  7932.        canvas:drawBox( 1, self.height, cache.displayWidth, 1, self.trayColour )\
  7933.        canvas:drawBox( cache.xScrollPosition, self.height, cache.xScrollSize, 1, self.mouse.selected == \"h\" and self.activeScrollbarColour or self.scrollbarColour )\
  7934.    end\
  7935. \
  7936.    if yEnabled then\
  7937.        canvas:drawBox( self.width, 1, 1, cache.displayHeight, self.trayColour )\
  7938.        canvas:drawBox( self.width, cache.yScrollPosition, 1, cache.yScrollSize, self.mouse.selected == \"v\" and self.activeScrollbarColour or self.scrollbarColour )\
  7939.    end\
  7940. \
  7941.    if yEnabled and xEnabled then\
  7942.        canvas:drawPoint( self.width, self.height, \" \", 1, self.trayColour )\
  7943.    end\
  7944. end\
  7945. \
  7946. --[[\
  7947.    @instance\
  7948.    @desc Invokes the super :redrawArea, offset by the scroll containers horizontal and vertical scroll\
  7949.    @param <number - x>, <number - y>, <number - width>, <number - height>\
  7950. ]]\
  7951. function ScrollContainer:redrawArea( x, y, width, height )\
  7952.    self.super:redrawArea( x, y, width, height, -self.xScroll, -self.yScroll )\
  7953. end\
  7954. \
  7955. --[[\
  7956.    @setter\
  7957.    @desc Sets 'yScroll', ensuring it doesn't go out of range. The position of the scrollbars are re-cached to reflect the new scroll position.\
  7958. \
  7959.          If the new scroll value is not the same as the old value, OR 'propagateMouse' is false, 'true' will be returned\
  7960.    @param <number - yScroll>\
  7961.    @return <boolean - consume>\
  7962. ]]\
  7963. function ScrollContainer:setYScroll( yScroll )\
  7964.    local oY, cache = self.yScroll, self.cache\
  7965.    self.yScroll = math.max( 0, math.min( cache.contentHeight - cache.displayHeight, yScroll ) )\
  7966. \
  7967.    self:cacheScrollbarPosition()\
  7968.    if ( not self.propagateMouse ) or oY ~= self.yScroll then\
  7969.        return true\
  7970.    end\
  7971. end\
  7972. \
  7973. --[[\
  7974.    @setter\
  7975.    @desc Sets 'xScroll', ensuring it doesn't go out of range. The position of the scrollbars are re-cached to reflect the new scroll position.\
  7976. \
  7977.          If the new scroll value is not the same as the old value, OR 'propagateMouse' is false, 'true' will be returned\
  7978.    @param <number - xScroll>\
  7979.    @return <boolean - consume>\
  7980. ]]\
  7981. function ScrollContainer:setXScroll( xScroll )\
  7982.    local oX, cache = self.xScroll, self.cache\
  7983.    self.xScroll = math.max( 0, math.min( cache.contentWidth - cache.displayWidth, xScroll ) )\
  7984. \
  7985.    self:cacheScrollbarPosition()\
  7986.    if ( not self.propagateMouse ) or oX ~= self.xScroll then\
  7987.        return true\
  7988.    end\
  7989. end\
  7990. \
  7991. --[[\
  7992.    @instance\
  7993.    @desc Invokes the super setter for 'height', and caches the content information (:cacheContent)\
  7994.    @param <number - height>\
  7995. ]]\
  7996. function ScrollContainer:setHeight( height )\
  7997.    self.super:setHeight( height )\
  7998.    self:cacheContent()\
  7999. end\
  8000. \
  8001. --[[\
  8002.    @instance\
  8003.    @desc Invokes the super setter for 'width', and caches the content information (:cacheContent)\
  8004.    @param <number - width>\
  8005. ]]\
  8006. function ScrollContainer:setWidth( width )\
  8007.    self.super:setWidth( width )\
  8008.    self:cacheContent()\
  8009. end\
  8010. \
  8011. --[[ Caching Functions ]]--\
  8012. function ScrollContainer:cacheContent()\
  8013.    self:cacheContentSize()\
  8014.    self:cacheActiveScrollbars()\
  8015. end\
  8016. \
  8017. --[[\
  8018.    @instance\
  8019.    @desc Finds the width and height bounds of the content and caches it inside 'contentWidth' and 'contentHeight' respectively\
  8020. ]]\
  8021. function ScrollContainer:cacheContentSize()\
  8022.    local w, h = 0, 0\
  8023. \
  8024.    local nodes, node = self.nodes\
  8025.    for i = 1, #nodes do\
  8026.        node = nodes[ i ]\
  8027. \
  8028.        w = math.max( node.X + node.width - 1, w )\
  8029.        h = math.max( node.Y + node.height - 1, h )\
  8030.    end\
  8031. \
  8032.    self.cache.contentWidth, self.cache.contentHeight = w, h\
  8033. end\
  8034. \
  8035. --[[\
  8036.    @instance\
  8037.    @desc Caches the display size of the container, with space made for the scrollbars (width - 1 if vertical scroll active, height - 1 if horizontal scroll active).\
  8038. \
  8039.          If 'single', the next cache function will not be called, allowing for other nodes to insert custom logic\
  8040.    @param [boolean - single]\
  8041. ]]\
  8042. function ScrollContainer:cacheDisplaySize( single )\
  8043.    local cache = self.cache\
  8044.    cache.displayWidth, cache.displayHeight = self.width - ( cache.yScrollActive and 1 or 0 ), self.height - ( cache.xScrollActive and 1 or 0 )\
  8045. \
  8046.    if not single then self:cacheScrollbarSize() end\
  8047. end\
  8048. \
  8049. --[[\
  8050.    @instance\
  8051.    @desc Caches the active scrollbars. If the contentWidth > displayWidth then the horizontal scrollbar is active. If the contentHeight > displayHeight then the vertical scrollbar is active.\
  8052. ]]\
  8053. function ScrollContainer:cacheActiveScrollbars()\
  8054.    local cache = self.cache\
  8055.    local cWidth, cHeight, sWidth, sHeight = cache.contentWidth, cache.contentHeight, self.width, self.height\
  8056.    local xAllowed, yAllowed = self.xScrollAllowed, self.yScrollAllowed\
  8057. \
  8058.    local horizontal, vertical\
  8059.    if ( cWidth > sWidth and xAllowed ) or ( cHeight > sHeight and yAllowed ) then\
  8060.        cache.xScrollActive, cache.yScrollActive = cWidth > sWidth - 1 and xAllowed, cHeight > sHeight - 1 and yAllowed\
  8061.    else\
  8062.        cache.xScrollActive, cache.yScrollActive = false, false\
  8063.    end\
  8064. \
  8065.    self:cacheDisplaySize()\
  8066. end\
  8067. \
  8068. --[[\
  8069.    @instance\
  8070.    @desc Calculates the width/height of the active scrollbar(s) using the content size, and display size\
  8071. ]]\
  8072. function ScrollContainer:cacheScrollbarSize()\
  8073.    local cache = self.cache\
  8074.    cache.xScrollSize, cache.yScrollSize = math.floor( cache.displayWidth * ( cache.displayWidth / cache.contentWidth ) + .5 ), math.floor( cache.displayHeight * ( cache.displayHeight / cache.contentHeight ) + .5 )\
  8075. \
  8076.    self:cacheScrollbarPosition()\
  8077. end\
  8078. \
  8079. --[[\
  8080.    @instance\
  8081.    @desc Uses the xScroll and yScroll properties to calculate the visible position of the active scrollbar(s)\
  8082. ]]\
  8083. function ScrollContainer:cacheScrollbarPosition()\
  8084.    local cache = self.cache\
  8085.    cache.xScrollPosition, cache.yScrollPosition = math.ceil( self.xScroll / cache.contentWidth * cache.displayWidth + .5 ), math.ceil( self.yScroll / cache.contentHeight * cache.displayHeight + .5 )\
  8086. \
  8087.    self.changed = true\
  8088.    self:redrawArea( 1, 1, self.width, self.height )\
  8089. end\
  8090. \
  8091. configureConstructor {\
  8092.    argumentTypes = {\
  8093.        scrollbarColour = \"colour\",\
  8094.        activeScrollbarColour = \"colour\",\
  8095.        xScrollAllowed = \"boolean\",\
  8096.        yScrollAllowed = \"boolean\"\
  8097.    }\
  8098. }\
  8099. ",
  8100. ["Lexer.ti"]="--[[\
  8101.    @static escapeChars - table (def. { ... }) - A table containing a special character -> escape character matrix (ie: n -> \"\\n\")\
  8102. \
  8103.    @instance stream - string (def. false) - The current stream being lexed\
  8104.    @instance tokens - table (def. {}) - The tokens currently found by the lexer\
  8105.    @instance line - number (def. 1) - A number representing the line of the string currently being lexed\
  8106.    @instance char - number (def. 1) - A number representing the character of the current line being lexed\
  8107. ]]\
  8108. \
  8109. class \"Lexer\" abstract() {\
  8110.    static = {\
  8111.        escapeChars = {\
  8112.            a = \"\\a\",\
  8113.            b = \"\\b\",\
  8114.            f = \"\\f\",\
  8115.            n = \"\\n\",\
  8116.            r = \"\\r\",\
  8117.            t = \"\\t\",\
  8118.            v = \"\\v\"\
  8119.        }\
  8120.    };\
  8121. \
  8122.    stream = false;\
  8123. \
  8124.    tokens = {};\
  8125. \
  8126.    line = 1;\
  8127.    char = 1;\
  8128. }\
  8129. \
  8130. --[[\
  8131.    @constructor\
  8132.    @desc Constructs the Lexer instance by providing the instance with a 'stream'.\
  8133.    @param <string - stream>, [boolean - manual]\
  8134. ]]\
  8135. function Lexer:__init__( stream, manual )\
  8136.    if type( stream ) ~= \"string\" then\
  8137.        return error \"Failed to initialise Lexer instance. Invalid stream paramater passed (expected string)\"\
  8138.    end\
  8139.    self.stream = stream\
  8140. \
  8141.    if not manual then\
  8142.        self:formTokens()\
  8143.    end\
  8144. end\
  8145. \
  8146. --[[\
  8147.    @instance\
  8148.    @desc This function is used to repeatedly call 'tokenize' until the stream has been completely consumed.\
  8149. ]]\
  8150. function Lexer:formTokens()\
  8151.    while self.stream and self.stream:find \"%S\" do\
  8152.        self:tokenize()\
  8153.    end\
  8154. end\
  8155. \
  8156. --[[\
  8157.    @instance\
  8158.    @desc A simple function that is used to add a token to the instances 'tokens' table.\
  8159.    @param <table - token>\
  8160. ]]\
  8161. function Lexer:pushToken( token )\
  8162.    local tokens = self.tokens\
  8163. \
  8164.    token.char = self.char\
  8165.    token.line = self.line\
  8166.    tokens[ #tokens + 1 ] = token\
  8167. end\
  8168. \
  8169. --[[\
  8170.    @instance\
  8171.    @desc Consumes the stream by 'amount'.\
  8172. ]]\
  8173. function Lexer:consume( amount )\
  8174.    local stream = self.stream\
  8175.    self.stream = stream:sub( amount + 1 )\
  8176. \
  8177.    self.char = self.char + amount\
  8178.    return content\
  8179. end\
  8180. \
  8181. --[[\
  8182.    @instance\
  8183.    @desc Uses the Lua pattern provided to select text from the stream that matches the pattern. The text is then consumed from the stream (entire pattern, not just selected text)\
  8184.    @param <string - pattern>, [number - offset]\
  8185. ]]\
  8186. function Lexer:consumePattern( pattern, offset )\
  8187.    local cnt = self.stream:match( pattern )\
  8188. \
  8189.    self:consume( select( 2, self.stream:find( pattern ) ) + ( offset or 0 ) )\
  8190.    return cnt\
  8191. end\
  8192. \
  8193. --[[\
  8194.    @instance\
  8195.    @desc Searches for the next occurence of 'opener'. Once found all text between the first two occurences is selected and consumed resulting in a XML_STRING token.\
  8196.    @param <char - opener>\
  8197.    @return <string - consumedString>\
  8198. ]]\
  8199. function Lexer:consumeString( opener )\
  8200.    local stream, closingIndex = self.stream\
  8201. \
  8202.    if stream:find( opener, 2 ) then\
  8203.        local str, c, escaped = {}\
  8204.        for i = 2, #stream do\
  8205.            c = stream:sub( i, i )\
  8206. \
  8207.            if escaped then\
  8208.                str[ #str + 1 ] = Lexer.escapeChars[ c ] or c\
  8209.                escaped = false\
  8210.            elseif c == \"\\\\\" then\
  8211.                escaped = true\
  8212.            elseif c == opener then\
  8213.                self:consume( i )\
  8214.                return table.concat( str )\
  8215.            else\
  8216.                str[ #str + 1 ] = c\
  8217.            end\
  8218.        end\
  8219.    end\
  8220. \
  8221.    self:throw( \"Failed to lex stream. Expected string end (\"..opener..\")\" )\
  8222. end\
  8223. \
  8224. --[[\
  8225.    @instance\
  8226.    @desc Removes all trailing spaces from\
  8227. ]]\
  8228. function Lexer:trimStream()\
  8229.    local stream = self.stream\
  8230. \
  8231.    local newLn = stream:match(\"^(\\n+)\")\
  8232.    if newLn then self:newline( #newLn ) end\
  8233. \
  8234.    local spaces = select( 2, stream:find \"^%s*%S\" )\
  8235. \
  8236.    self.stream = stream:sub( spaces )\
  8237.    self.char = self.char + spaces - 1\
  8238. \
  8239.    return self.stream\
  8240. end\
  8241. \
  8242. --[[\
  8243.    @instance\
  8244.    @desc Advanced 'line' by 'amount' (or 1) and sets 'char' back to zero\
  8245. ]]\
  8246. function Lexer:newline( amount )\
  8247.    self.line = self.line + ( amount or 1 )\
  8248.    self.char = 0\
  8249. end\
  8250. \
  8251. --[[\
  8252.    @instance\
  8253.    @desc Throws error 'e' prefixed with information regarding current position and stores the error in 'exception' for later reference\
  8254. ]]\
  8255. function Lexer:throw( e )\
  8256.    self.exception = \"Lexer (\" .. tostring( self.__type ) .. \") Exception at line '\"..self.line..\"', char '\"..self.char..\"': \"..e\
  8257.    return error( self.exception )\
  8258. end\
  8259. ",
  8260. ["NodeCanvas.ti"]="local string_sub = string.sub\
  8261. \
  8262. --[[\
  8263.    The NodeCanvas is an object that allows classes to draw to their canvas using functions that are useful when drawing 'nodes', hence the name.\
  8264. \
  8265.    The NodeCanvas should only be used by high-level objects (Nodes). Low level objects, such as 'Application' that require the ability to draw to the CraftOS terminal object\
  8266.    should be using TermCanvas instead.\
  8267. ]]\
  8268. \
  8269. class \"NodeCanvas\" extends \"Canvas\"\
  8270. \
  8271. --[[\
  8272.    @instance\
  8273.    @desc Draws a single pixel using the arguments given. Char must only be one character long (hence the name).\
  8274. \
  8275.          Foreground and background colours will fallback to the canvas colour and backgroundColour (respectively) if not provided.\
  8276.    @param <number - x>, <number - y>, <string - char>, [number - tc], [number - bg]\
  8277. ]]\
  8278. function NodeCanvas:drawPoint( x, y, char, tc, bg )\
  8279.    if #char > 1 then return error \"drawPoint can only draw one character\" end\
  8280. \
  8281.    self.buffer[ ( self.width * ( y - 1 ) ) + x ] = { char, tc or self.colour, bg or self.backgroundColour }\
  8282. end\
  8283. \
  8284. --[[\
  8285.    @instance\
  8286.    @desc Draws a line of text starting at the position given.\
  8287. \
  8288.          Foreground and background colours will fallback to the canvas colour and backgroundColour (respectively) if not provided.\
  8289.    @param <number - x>, <number - y>, <stringh - text>, [number - tc], [number - bg]\
  8290. ]]\
  8291. function NodeCanvas:drawTextLine( x, y, text, tc, bg )\
  8292.    local tc, bg = tc or self.colour, bg or self.backgroundColour\
  8293. \
  8294.    local buffer, start = self.buffer, ( self.width * ( y - 1 ) ) + x\
  8295.    for i = 1, #text do\
  8296.        buffer[ -1 + start + i ] = { string_sub( text, i, i ), tc, bg }\
  8297.    end\
  8298. end\
  8299. \
  8300. --[[\
  8301.    @instance\
  8302.    @desc Draws a rectangle, with it's upper left corner being dictated by the x and y positions given\
  8303. \
  8304.          If not provided, 'col' will fallback to the backgroundColour of the canvas.\
  8305.    @param <number - x>, <number - y>, <number - width>, <number - height>, [number - col]\
  8306. ]]\
  8307. function NodeCanvas:drawBox( x, y, width, height, col )\
  8308.    local tc, bg = self.colour, col or self.backgroundColour\
  8309.    local buffer = self.buffer\
  8310. \
  8311.    local px = { \" \", tc, bg }\
  8312.    for y = math.max( 0, y ), y + height - 1 do\
  8313.        for x = math.max( 1, x ), x + width - 1 do\
  8314.            buffer[ ( self.width * ( y - 1 ) ) + x ] = px\
  8315.        end\
  8316.    end\
  8317. end\
  8318. ",
  8319. ["Label.ti"]="--[[\
  8320.    A Label is a node which displays a single line of text. The text cannot be changed by the user directly, however the text can be changed by the program.\
  8321. ]]\
  8322. \
  8323. class \"Label\" extends \"Node\" {\
  8324.    labelFor = false;\
  8325. \
  8326.    allowMouse = true;\
  8327.    active = false;\
  8328. }\
  8329. \
  8330. --[[\
  8331.    @constructor\
  8332.    @param <string - text>, [number - X], [number - Y]\
  8333. ]]\
  8334. function Label:__init__( ... )\
  8335.    self:resolve( ... )\
  8336.    self.raw.width = #self.text\
  8337. \
  8338.    self:super()\
  8339.    self:register \"text\"\
  8340. end\
  8341. \
  8342. --[[\
  8343.    @instance\
  8344.    @desc Mouse click event handler. On click the label will wait for a mouse up, if found labelFor is notified\
  8345.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  8346. ]]\
  8347. function Label:onMouseClick( event, handled, within )\
  8348.    self.active = self.labelFor and within and not handled\
  8349. end\
  8350. \
  8351. --[[\
  8352.    @instance\
  8353.    @desc If the mouse click handler has set the label to active, trigger the onLabelClicked callback\
  8354.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  8355. ]]\
  8356. function Label:onMouseUp( event, handled, within )\
  8357.    if not self.labelFor then return end\
  8358. \
  8359.    local labelFor = self.application:getNode( self.labelFor, true )\
  8360.    if self.active and not handled and within and labelFor:can \"onLabelClicked\" then\
  8361.        labelFor:onLabelClicked( self, event, handled, within )\
  8362.    end\
  8363. \
  8364.    self.active = false\
  8365. end\
  8366. \
  8367. --[[\
  8368.    @instance\
  8369.    @desc Clears the Label's canvas and draws a line of text if the label has changed.\
  8370.    @param [boolean - force]\
  8371. ]]\
  8372. function Label:draw( force )\
  8373.    local raw = self.raw\
  8374.    if raw.changed or force then\
  8375.        raw.canvas:drawTextLine( 1, 1, raw.text )\
  8376. \
  8377.        raw.changed = false\
  8378.    end\
  8379. end\
  8380. \
  8381. --[[\
  8382.    @instance\
  8383.    @desc Sets the text of a node. Once set, the nodes 'changed' status is set to true along with its parent(s)\
  8384.    @param <string - text>\
  8385. ]]\
  8386. function Label:setText( text )\
  8387.    if self.text == text then return end\
  8388. \
  8389.    self.text = text\
  8390.    self.width = #text\
  8391. end\
  8392. \
  8393. configureConstructor({\
  8394.    orderedArguments = { \"text\", \"X\", \"Y\" },\
  8395.    requiredArguments = { \"text\" },\
  8396.    argumentTypes = { text = \"string\" }\
  8397. }, true)\
  8398. ",
  8399. ["TextContainer.ti"]="local string_sub = string.sub\
  8400. local function resolvePosition( self, lines, X, Y )\
  8401.    local posY = math.min( #lines, Y )\
  8402.    if posY == 0 then return 0 end\
  8403. \
  8404.    local selectedLine = lines[ posY ]\
  8405.    return math.min( selectedLine[ 3 ] - ( posY == #lines and 0 or 1 ), selectedLine[ 2 ] + X - 1 )\
  8406. end\
  8407. \
  8408. --[[\
  8409.    The TextContainer object is a very helpful node when it comes time to display a lot of text.\
  8410. \
  8411.    The text is automatically wrapped to fit the containers width, and a vertical scrollbar will appear when the content becomes too tall.\
  8412. \
  8413.    The text can also be selected, using click and drag, and retrieved using :getSelectedText\
  8414. ]]\
  8415. \
  8416. class \"TextContainer\" extends \"ScrollContainer\" mixin \"MTextDisplay\" mixin \"MFocusable\" {\
  8417.    position = 1,\
  8418.    selection = false,\
  8419. \
  8420.    text = \"\",\
  8421. \
  8422.    selectedColour = colours.blue,\
  8423.    selectedBackgroundColour = colours.lightBlue,\
  8424. \
  8425.    allowMouse = true\
  8426. }\
  8427. \
  8428. --[[\
  8429.    @instance\
  8430.    @desc Constructs the instance, and disables horizontal scrolling\
  8431.    @param [string - text], [number - x], [number - y], [number - width], [number - height]\
  8432. ]]\
  8433. function TextContainer:__init__( ... )\
  8434.    self:resolve( ... )\
  8435. \
  8436.    self:super()\
  8437.    self.xScrollAllowed = false\
  8438. end\
  8439. \
  8440. --[[\
  8441.    @instance\
  8442.    @desc An overwrite of 'ScrollContainer:cacheContentSize' that sets the content height to the amount of lines, instead of performing a node check.\
  8443. ]]\
  8444. function TextContainer:cacheContentSize()\
  8445.    self.cache.contentWidth, self.cache.contentHeight = self.width, self.lineConfig.lines and #self.lineConfig.lines or 0\
  8446. end\
  8447. \
  8448. --[[\
  8449.    @instance\
  8450.    @desc Calls ScrollContainer:cacheDisplaySize with 'true', allowing the TextContainer to use it's own display calculations, and re-wrap the text\
  8451.          to fit correctly (scrollbar)\
  8452. ]]\
  8453. function TextContainer:cacheDisplaySize()\
  8454.    self.super:cacheDisplaySize( true )\
  8455. \
  8456.    self:wrapText( self.cache.displayWidth )\
  8457.    self:cacheContentSize()\
  8458.    self:cacheScrollbarSize()\
  8459. end\
  8460. \
  8461. --[[\
  8462.    @instance\
  8463.    @desc Draws the text lines created by 'wrapText' using the selection where apropriate\
  8464. ]]\
  8465. function TextContainer:draw()\
  8466.    if self.changed then\
  8467.        local selection = self.selection\
  8468.        if selection then\
  8469.            local position = self.position\
  8470. \
  8471.            self:drawLines(\
  8472.                self.lineConfig.lines,\
  8473.                selection < position and selection or position,\
  8474.                selection < position and position or selection\
  8475.            )\
  8476.        else self:drawLines( self.lineConfig.lines ) end\
  8477. \
  8478.        self:drawScrollbars()\
  8479. \
  8480.        self.changed = false\
  8481.    end\
  8482. end\
  8483. \
  8484. --[[\
  8485.    @instance\
  8486.    @desc Draws the lines (created by wrapText) with respect to the text containers selection and the alignment options (horizontalAlign and verticalAlign)\
  8487.    @param <table - lines>, [number - selectionStart], [number - selectionStop]\
  8488. ]]\
  8489. function TextContainer:drawLines( lines, selectionStart, selectionStop )\
  8490.    local vAlign, hAlign = self.verticalAlign, self.horizontalAlign\
  8491.    local width, height = self.width, self.height\
  8492. \
  8493.    local yOffset = 0\
  8494.    if vAlign == \"centre\" then\
  8495.        yOffset = math.floor( ( height / 2 ) - ( #lines / 2 ) + .5 )\
  8496.    elseif vAlign == \"bottom\" then\
  8497.        yOffset = height - #lines\
  8498.    end\
  8499. \
  8500.    local tc, bg, sTc, sBg\
  8501.    if not self.enabled then\
  8502.        tc, bg = self.disabledColour, self.disabledBackgroundColour\
  8503.    elseif self.focused then\
  8504.        tc, bg = self.focusedColour, self.focusedBackgroundColour\
  8505.        sTc, sBg = self.selectedColour, self.selectedBackgroundColour\
  8506.    end\
  8507. \
  8508.    tc, bg = tc or self.colour, bg or self.backgroundColour\
  8509.    sTc, sBg = sTc or tc, sBg or bg\
  8510. \
  8511.    local pos, sel, canvas = self.position, self.selection, self.canvas\
  8512.    local isSelection = selectionStart and selectionStop\
  8513. \
  8514.    canvas:clear( bg )\
  8515.    local cacheX, cacheY, cacheSelX, cacheSelY = 0, 1, false, false\
  8516.    for i = self.yScroll + 1, #lines do\
  8517.        local Y, line, xOffset = yOffset + i - self.yScroll, lines[ i ], 1\
  8518.        local lineContent, lineStart, lineEnd = line[ 1 ], line[ 2 ], line[ 3 ]\
  8519.        if hAlign == \"centre\" then\
  8520.            xOffset = math.floor( width / 2 - ( #line / 2 ) + .5 )\
  8521.        elseif hAlign == \"right\" then\
  8522.            xOffset = width - #line + 1\
  8523.        end\
  8524. \
  8525.        if isSelection then\
  8526.            local pre, current, post\
  8527.            local lineSelectionStart, lineSelectionStop = selectionStart - lineStart + 1, lineEnd - ( lineEnd - selectionStop ) - lineStart + 1\
  8528.            if selectionStart >= lineStart and selectionStop <= lineEnd then\
  8529.                -- The selection start and end are within this line. Single line selection\
  8530.                -- This line has three segments - unselected (1), selected (2), unselected (3)\
  8531. \
  8532.                pre = string_sub( lineContent, 1, lineSelectionStart - 1 )\
  8533.                current = string_sub( lineContent, lineSelectionStart, lineSelectionStop )\
  8534.                post = string_sub( lineContent, lineSelectionStop + 1 )\
  8535.            elseif selectionStart >= lineStart and selectionStart <= lineEnd then\
  8536.                -- The selectionStart is here, but not the end. The selection is multiline.\
  8537.                -- This line has two segments - unselected (1) and selected (2)\
  8538. \
  8539.                pre = string_sub( lineContent, 1, lineSelectionStart - 1 )\
  8540.                current = string_sub( lineContent, lineSelectionStart )\
  8541.            elseif selectionStop >= lineStart and selectionStop <= lineEnd then\
  8542.                -- The selectionStop is here, but not the start. The selection is multiline\
  8543.                -- This line has two segments - selected(1) and unselected (2)\
  8544. \
  8545.                pre = \"\"\
  8546.                current = string_sub( lineContent, 1, lineSelectionStop )\
  8547.                post = string_sub( lineContent, lineSelectionStop + 1 )\
  8548.            elseif selectionStart <= lineStart and selectionStop >= lineEnd then\
  8549.                -- The selection neither starts, nor ends here - however it IS selected.\
  8550.                -- This line has one segment - selected(1)\
  8551. \
  8552.                pre = \"\"\
  8553.                current = lineContent\
  8554.            else\
  8555.                -- The selection is out of the bounds of this line - it is unselected\
  8556.                -- This line has one segment - unselected(1)\
  8557. \
  8558.                pre = lineContent\
  8559.            end\
  8560. \
  8561.            if pre then canvas:drawTextLine( xOffset, Y, pre, tc, bg ) end\
  8562.            if current then canvas:drawTextLine( xOffset + #pre, Y, current, sTc, sBg ) end\
  8563.            if post then canvas:drawTextLine( xOffset + #pre + #current, Y, post, tc, bg ) end\
  8564.        else canvas:drawTextLine( xOffset, Y, lineContent, tc, bg ) end\
  8565. \
  8566.        if pos >= lineStart and pos <= lineEnd then\
  8567.            if pos == lineEnd and self.lineConfig.lines[ i + 1 ] then\
  8568.                cacheY = i + 1\
  8569.            else\
  8570.                cacheX, cacheY = pos - lineStart + 1, i\
  8571.            end\
  8572.        end\
  8573.        if sel and sel >= lineStart and sel <= lineEnd then\
  8574.            if sel == lineEnd and self.lineConfig.lines[ i + 1 ] then\
  8575.                cacheSelY = i + 1\
  8576.            else\
  8577.                cacheSelX, cacheSelY = sel - lineStart + 1, i\
  8578.            end\
  8579.        end\
  8580.    end\
  8581. \
  8582.    self.cache.x, self.cache.y = cacheX, cacheY\
  8583.    self.cache.selX, self.cache.selY = cachcSelX, cacheSelY\
  8584. end\
  8585. \
  8586. --[[\
  8587.    @instance\
  8588.    @desc Returns position and selection, ordered for use in 'string.sub'\
  8589.    @return <number - selectionStart>, <number - selectionStop> - When a selection exists, the bounds are returned\
  8590.    @return <boolean - false> - When no selection is found, false is returned\
  8591. ]]\
  8592. function TextContainer:getSelectionRange()\
  8593.    local position, selection = self.position, self.selection\
  8594.    return position < selection and position or selection, position < selection and selection or position\
  8595. end\
  8596. \
  8597. --[[\
  8598.    @instance\
  8599.    @desc Uses :getSelectionRange to find the selected text\
  8600.    @return <string - selection> - When a selection exists, it is returned\
  8601.    @return <boolean - false> - If no selection is found, false is returned\
  8602. ]]\
  8603. function TextContainer:getSelectedText()\
  8604.    if not self.selection then return false end\
  8605.    return self.text:sub( self:getSelectionRange() )\
  8606. end\
  8607. \
  8608. --[[\
  8609.    @instance\
  8610.    @desc Handles a mouse click. If the mouse occured on the vertical scroll bar, the click is sent to the ScrollContainer handle function.\
  8611.          Otherwise the selection is removed and the current position is changed.\
  8612.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  8613. ]]\
  8614. function TextContainer:onMouseClick( event, handled, within )\
  8615.    if not handled and within then\
  8616.        local X = event.X - self.X + 1\
  8617.        if X == self.width and self.cache.yScrollActive then\
  8618.            self.super:onMouseClick( event, handled, within )\
  8619.            return\
  8620.        end\
  8621. \
  8622.        local isShift = self.application:isPressed( keys.leftShift ) or self.application:isPressed( keys.rightShift )\
  8623. \
  8624.        if not isShift then self.selection = false end\
  8625.        self[ isShift and \"selection\" or \"position\" ] = resolvePosition( self, self.lineConfig.lines, X + self.xScroll, event.Y - self.Y + 1 + self.yScroll )\
  8626. \
  8627.        self.changed = true\
  8628.        self:focus()\
  8629.    else\
  8630.        self:unfocus()\
  8631.    end\
  8632. end\
  8633. \
  8634. --[[\
  8635.    @instance\
  8636.    @desc Handles a mouse draw. If the vertical scrollbar is currently selected, the mouse draw is passed to the ScrollContainer and ignored by further calculations\
  8637.          Otherwise, the selection is expanded depending on the new selection positions.\
  8638.    @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\
  8639. ]]\
  8640. function TextContainer:onMouseDrag( event, handled, within )\
  8641.    if handled or not within then return end\
  8642.    local X = event.X - self.X + 1\
  8643.    if X == self.width and self.cache.yScrollActive then self.super:onMouseDrag( event, handled, within ) end\
  8644.    if self.mouse.selected == \"v\" or not self.focused then return end\
  8645. \
  8646.    self.selection = resolvePosition( self, self.lineConfig.lines, X + self.xScroll, event.Y - self.Y + 1 + self.yScroll )\
  8647. \
  8648.    self.changed = true\
  8649. end\
  8650. \
  8651. --[[\
  8652.    @setter\
  8653.    @desc Sets the node to 'changed' when the selection is updated\
  8654.    @param <number - selection>\
  8655. ]]\
  8656. function TextContainer:setSelection( selection )\
  8657.    self.selection = selection and math.max( math.min( #self.text, selection ), 1 ) or false\
  8658.    self.changed = true\
  8659. end\
  8660. \
  8661. --[[\
  8662.    @setter\
  8663.    @desc Sets the node to 'changed' when the position is updated\
  8664.    @param <number - position>\
  8665. ]]\
  8666. function TextContainer:setPosition( position )\
  8667.    self.position = position and math.max( math.min( #self.text, position ), 0 ) or false\
  8668.    self.selection = false\
  8669.    self.changed = true\
  8670. end\
  8671. \
  8672. --[[\
  8673.    @setter\
  8674.    @desc Updates the TextContainer by re-wrapping the text, and re-aligning the scroll bars when new text is set\
  8675. ]]\
  8676. function TextContainer:setText( text )\
  8677.    self.text = text\
  8678.    self:wrapText( self.cache.displayWidth )\
  8679.    self:cacheContent()\
  8680. \
  8681.    self.yScroll = math.min( self.yScroll, self.cache.contentHeight - self.cache.displayHeight )\
  8682. end\
  8683. \
  8684. \
  8685. configureConstructor({\
  8686.    orderedArguments = { \"text\", \"X\", \"Y\", \"width\", \"height\" },\
  8687.    argumentTypes = { text = \"string\" }\
  8688. }, true)\
  8689. ",
  8690. }
  8691. local scriptFiles = {
  8692. ["Class.lua"]=true,
  8693. ["Titanium.lua"]=true,
  8694. }
  8695. local preLoad = {
  8696. }
  8697. local loaded = {}
  8698. local function loadFile( name, verify )
  8699.     if loaded[ name ] then return end
  8700.  
  8701.     local content = files[ name ]
  8702.     if content then
  8703.         local output, err = loadstring( content, name )
  8704.         if not output or err then return error( "Failed to load Lua chunk. File '"..name.."' has a syntax error: "..tostring( err ), 0 ) end
  8705.  
  8706.         local ok, err = pcall( output )
  8707.         if not ok or err then return error( "Failed to execute Lua chunk. File '"..name.."' crashed: "..tostring( err ), 0 ) end
  8708.  
  8709.         if verify then
  8710.             local className = name:gsub( "%..*", "" )
  8711.             local class = Titanium.getClass( className )
  8712.  
  8713.             if class then
  8714.                 if not class:isCompiled() then class:compile() end
  8715.             else return error( "File '"..name.."' failed to create class '"..className.."'" ) end
  8716.         end
  8717.  
  8718.         loaded[ name ] = true
  8719.     else return error("Failed to load Titanium. File '"..tostring( name ).."' cannot be found.") end
  8720. end
  8721.  
  8722. -- Load our class file
  8723. loadFile( "Class.lua" )
  8724.  
  8725. Titanium.setClassLoader(function( name )
  8726.     local fName = name..".ti"
  8727.  
  8728.     if not files[ fName ] then
  8729.         return error("Failed to find file '"..fName..", to load missing class '"..name.."'.", 3)
  8730.     else
  8731.         loadFile( fName, true )
  8732.     end
  8733. end)
  8734.  
  8735. -- Load any files specified by our config file
  8736. for i = 1, #preLoad do loadFile( preLoad[ i ], not scriptFiles[ preLoad[ i ] ] ) end
  8737.  
  8738. -- Load all class files
  8739. for name in pairs( files ) do if not scriptFiles[ name ] then
  8740.     loadFile( name, true )
  8741. end end
  8742.  
  8743. -- Load all script files
  8744. for name in pairs( scriptFiles ) do loadFile( name, false ) end
Add Comment
Please, Sign In to add comment