· 4 years ago · Jun 02, 2021, 04:34 PM
1local files = {
2["Canvas.ti"]="--[[\13\
3 The Canvas object is used by all components. It facilitates the drawing of pixels which are stored in its buffer.\13\
4\13\
5 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.\13\
6--]]\13\
7\13\
8local function range( xBoundary, xDesired, width, canvasWidth )\13\
9 local x1 = xBoundary > xDesired and 1 - xDesired or 1\13\
10 local x2 = xDesired + width > canvasWidth and canvasWidth - xDesired or width\13\
11\13\
12 return x1, x2\13\
13end\13\
14\13\
15class \"Canvas\" abstract() {\13\
16 buffer = {};\13\
17 last = {};\13\
18\13\
19 width = 51;\13\
20 height = 19;\13\
21\13\
22 backgroundColour = 32768;\13\
23 colour = 1;\13\
24}\13\
25\13\
26--[[\13\
27 @constructor\13\
28 @desc Constructs the canvas instance and binds it with the owner supplied.\13\
29 @param <ClassInstance - owner>\13\
30]]\13\
31function Canvas:__init__( owner )\13\
32 self.raw.owner = Titanium.isInstance( owner ) and owner or error(\"Invalid argument for Canvas. Expected instance owner, got '\"..tostring( owner )..\"'\")\13\
33 self.raw.width = owner.raw.width\13\
34 self.raw.height = owner.raw.height\13\
35\13\
36 self.raw.backgroundColour = owner.raw.backgroundColour\13\
37 self.raw.colour = owner.raw.colour\13\
38\13\
39 self:clear()\13\
40end\13\
41\13\
42--[[\13\
43 @instance\13\
44 @desc Replaces the canvas with a blank one\13\
45 @param [number - colour]\13\
46]]\13\
47function Canvas:clear( colour )\13\
48 local pixel, buffer = { \" \", self.colour, colour or self.backgroundColour }, self.buffer\13\
49\13\
50 for index = 1, self.width * self.height do\13\
51 buffer[ index ] = pixel\13\
52 end\13\
53end\13\
54\13\
55--[[\13\
56 @instance\13\
57 @desc Updates the colour of the canvas and then clears the canvas\13\
58 @param <number - colour>\13\
59]]\13\
60function Canvas:setColour( colour )\13\
61 self.colour = colour\13\
62 self:clear()\13\
63end\13\
64\13\
65--[[\13\
66 @instance\13\
67 @desc Updates the background colour of the canvas and then clears the canvas\13\
68 @param <number - backgroundColour>\13\
69]]\13\
70function Canvas:setBackgroundColour( backgroundColour )\13\
71 self.backgroundColour = backgroundColour\13\
72 self:clear()\13\
73end\13\
74\13\
75--[[\13\
76 @instance\13\
77 @desc Draws the canvas to the target 'canvas' using the X and Y offsets\13\
78 @param <Canvas - canvas>, [number - offsetX], [number - offsetY]\13\
79]]\13\
80function Canvas:drawTo( canvas, offsetX, offsetY )\13\
81 local offsetX = offsetX - 1 or 0\13\
82 local offsetY = offsetY - 1 or 0\13\
83\13\
84 local sRaw, tRaw = self.raw, canvas.raw\13\
85 local width, height, buffer = sRaw.width, sRaw.height, sRaw.buffer\13\
86 local tWidth, tHeight, tBuffer = tRaw.width, tRaw.height, tRaw.buffer\13\
87\13\
88 local xStart, xEnd = range( 1, offsetX, width, tWidth )\13\
89\13\
90 local cache, tCache, char, tc, bg, pixel, cPixel, tPos = 0, offsetX + ( offsetY * tWidth )\13\
91 for y = 1, height do\13\
92 local cY = y + offsetY\13\
93 if cY >= 1 and cY <= tHeight then\13\
94 for x = xStart, xEnd do\13\
95 pixel = buffer[ cache + x ]\13\
96 tc, bg, tPos = pixel[ 2 ], pixel[ 3 ], tCache + x\13\
97\13\
98 if not tc or not bg then\13\
99 cPixel = tBuffer[ tPos ]\13\
100 tBuffer[ tPos ] = { pixel[ 1 ], tc or cPixel[ 2 ], bg or cPixel[ 3 ] }\13\
101 else\13\
102 tBuffer[ tPos ] = pixel\13\
103 end\13\
104 end\13\
105 elseif cY > tHeight then\13\
106 break\13\
107 end\13\
108\13\
109 cache = cache + width\13\
110 tCache = tCache + tWidth\13\
111 end\13\
112end\13\
113",
114["MCallbackManager.ti"]="--[[\13\
115 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.\13\
116\13\
117 These conditions may include node specific callbacks, like a button click or input submission.\13\
118]]\13\
119\13\
120class \"MCallbackManager\" abstract() {\13\
121 callbacks = {}\13\
122}\13\
123\13\
124--[[\13\
125 @instance\13\
126 @desc Assigns a function 'fn' to 'callbackName'.\13\
127 @param <string - name>, <function - fn>, [string - id]\13\
128]]\13\
129function MCallbackManager:on( callbackName, fn, id )\13\
130 if not ( type( callbackName ) == \"string\" and type( fn ) == \"function\" ) or ( id and type( id ) ~= \"string\" ) then\13\
131 return error \"Expected string, function, [string]\"\13\
132 end\13\
133\13\
134 local callbacks = self.callbacks\13\
135 if not callbacks[ callbackName ] then callbacks[ callbackName ] = {} end\13\
136\13\
137 table.insert( callbacks[ callbackName ], { fn, id } )\13\
138\13\
139 return self\13\
140end\13\
141\13\
142--[[\13\
143 @instance\13\
144 @desc Removes all callbacks for a certain condition. If an id is provided only callbacks matching that id will be executed.\13\
145 @param <string - callbackName>, [string - id]\13\
146]]\13\
147function MCallbackManager:off( callbackName, id )\13\
148 if id then\13\
149 local callbacks = self.callbacks[ callbackName ]\13\
150\13\
151 if callbacks then\13\
152 for i = #callbacks, 1, -1 do\13\
153 if callbacks[ i ][ 2 ] == id then\13\
154 table.remove( callbacks, i )\13\
155 end\13\
156 end\13\
157 end\13\
158 else self.callbacks[ callbackName ] = nil end\13\
159\13\
160 return self\13\
161end\13\
162\13\
163--[[\13\
164 @instance\13\
165 @desc Executes all assigned functions for 'callbackName' with 'self' and the arguments passed to this function.\13\
166 @param <string - callbackName>, [vararg - ...]\13\
167]]\13\
168function MCallbackManager:executeCallbacks( callbackName, ... )\13\
169 local callbacks = self.callbacks[ callbackName ]\13\
170\13\
171 if callbacks then\13\
172 for i = 1, #callbacks do callbacks[ i ][ 1 ]( self, ... ) end\13\
173 end\13\
174end\13\
175\13\
176function MCallbackManager:canCallback( name )\13\
177 return #self.callbacks[ name ] > 0\13\
178end\13\
179",
180["RedirectCanvas.ti"]="--[[\13\
181 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\13\
182 of the terminals properties set inside the wrapped program (via the term methods).\13\
183\13\
184 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\13\
185 through Titaniums canvas system.\13\
186--]]\13\
187\13\
188local stringLen, stringSub = string.len, string.sub\13\
189local isColour = term.isColour()\13\
190\13\
191local function testColour( col )\13\
192 if not isColour and ( col ~= 1 or col ~= 32768 or col ~= 256 or col ~= 128 ) then\13\
193 error \"Colour not supported\"\13\
194 end\13\
195\13\
196 return true\13\
197end\13\
198\13\
199class \"RedirectCanvas\" extends \"NodeCanvas\"\13\
200\13\
201function RedirectCanvas:__init__( ... )\13\
202 self:resetTerm()\13\
203 self:super( ... )\13\
204end\13\
205\13\
206function RedirectCanvas:resetTerm()\13\
207 self.tX, self.tY, self.tColour, self.tBackgroundColour, self.tCursor = 1, 1, 1, 32768, false;\13\
208 self:clear( 32768, true )\13\
209end\13\
210\13\
211--[[\13\
212 @instance\13\
213 @desc Returns a table compatible with `term.redirect`\13\
214 @return <table - redirect>\13\
215]]\13\
216function RedirectCanvas:getTerminalRedirect()\13\
217 local redirect = {}\13\
218\13\
219 function redirect.write( text )\13\
220 text = tostring( text )\13\
221 local tc, bg, tX, tY = self.tColour, self.tBackgroundColour, self.tX, self.tY\13\
222 local buffer, position = self.buffer, self.width * ( tY - 1 ) + tX\13\
223\13\
224 for i = 1, math.min( stringLen( text ), self.width - tX + 1 ) do\13\
225 buffer[ position ] = { stringSub( text, i, i ), tc, bg }\13\
226 position = position + 1\13\
227 end\13\
228\13\
229 self.tX = tX + stringLen( text )\13\
230 end\13\
231\13\
232 function redirect.blit( text, colour, background )\13\
233 if stringLen( text ) ~= stringLen( colour ) or stringLen( text ) ~= stringLen( background ) then\13\
234 return error \"blit arguments must be the same length\"\13\
235 end\13\
236\13\
237 local tX, hex = self.tX, TermCanvas.static.hex\13\
238 local buffer, position = self.buffer, self.width * ( self.tY - 1 ) + tX\13\
239\13\
240 for i = 1, math.min( stringLen( text ), self.width - tX + 1 ) do\13\
241 buffer[ position ] = { stringSub( text, i, i ), hex[ stringSub( colour, i, i ) ], hex[ stringSub( background, i, i ) ] }\13\
242 position = position + 1\13\
243 end\13\
244\13\
245 self.tX = tX + stringLen( text )\13\
246 end\13\
247\13\
248 function redirect.clear()\13\
249 self:clear( self.tBackgroundColour, true )\13\
250 end\13\
251\13\
252 function redirect.clearLine()\13\
253 local px = { \" \", self.tColour, self.tBackgroundColour }\13\
254 local buffer, position = self.buffer, self.width * ( self.tY - 1 )\13\
255\13\
256 for i = 1, self.width do\13\
257 buffer[ position ] = px\13\
258 position = position + 1\13\
259 end\13\
260 end\13\
261\13\
262 function redirect.getCursorPos()\13\
263 return self.tX, self.tY\13\
264 end\13\
265\13\
266 function redirect.setCursorPos( x, y )\13\
267 self.tX, self.tY = math.floor( x ), math.floor( y )\13\
268 end\13\
269\13\
270 function redirect.getSize()\13\
271 return self.width, self.height\13\
272 end\13\
273\13\
274 function redirect.setCursorBlink( blink )\13\
275 self.tCursor = blink\13\
276 end\13\
277\13\
278 function redirect.setTextColour( tc )\13\
279 if testColour( tc ) then\13\
280 self.tColour = tc\13\
281 end\13\
282 end\13\
283\13\
284 function redirect.getTextColour()\13\
285 return self.tColour\13\
286 end\13\
287\13\
288 function redirect.setBackgroundColour( bg )\13\
289 if testColour( bg ) then\13\
290 self.tBackgroundColour = bg\13\
291 end\13\
292 end\13\
293\13\
294 function redirect.getBackgroundColour()\13\
295 return self.tBackgroundColour\13\
296 end\13\
297\13\
298 function redirect.scroll( n )\13\
299 local offset, buffer, nL = self.width * n, self.buffer, n < 0\13\
300 local pixelCount, blank = self.width * self.height, { \" \", self.tColour, self.tBackgroundColour }\13\
301\13\
302 for i = nL and pixelCount or 1, nL and 1 or pixelCount, nL and -1 or 1 do\13\
303 buffer[ i ] = buffer[ i + offset ] or blank\13\
304 end\13\
305 end\13\
306\13\
307 function redirect.isColour()\13\
308 return isColour\13\
309 end\13\
310\13\
311 -- American spelling compatibility layer\13\
312 redirect.isColor = redirect.isColour\13\
313\9redirect.setBackgroundColor = redirect.setBackgroundColour\13\
314\9redirect.setTextColor = redirect.setTextColour\13\
315\9redirect.getBackgroundColor = redirect.getBackgroundColour\13\
316\9redirect.getTextColor = redirect.getTextColour\13\
317\13\
318 return redirect\13\
319end\13\
320\13\
321--[[\13\
322 @instance\13\
323 @desc Modified Canvas.clear. Only sets pixels that do not exist (doesn't really clear the canvas, just ensures it is the correct size).\13\
324 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\13\
325 replaced (the terminal redirect uses this method).\13\
326\13\
327 Alternatively, self.getTerminalRedirect.clear() will also clear the canvas entirely\13\
328 @param [number - col], [boolean - force]\13\
329]]\13\
330function RedirectCanvas:clear( col, force )\13\
331 local col = col or self.tBackgroundColour\13\
332 local pixel, buffer = { \" \", col, col }, self.buffer\13\
333\13\
334 for index = 1, self.width * self.height do\13\
335 if not buffer[ index ] or force then\13\
336 buffer[ index ] = pixel\13\
337 end\13\
338 end\13\
339end\13\
340",
341["Terminal.ti"]="local function isThreadRunning( obj )\13\
342 if not obj.thread then return false end\13\
343\13\
344 return obj.thread.running\13\
345end\13\
346\13\
347class \"Terminal\" extends \"Node\" mixin \"MFocusable\" {\13\
348 static = {\13\
349 focusedEvents = {\13\
350 MOUSE = true,\13\
351 KEY = true,\13\
352 CHAR = true\13\
353 }\13\
354 };\13\
355\13\
356 canvas = true;\13\
357 displayThreadStatus = true;\13\
358}\13\
359\13\
360function Terminal:__init__( ... )\13\
361 self:resolve( ... )\13\
362 self:super()\13\
363\13\
364 self.canvas = RedirectCanvas( self )\13\
365 self.redirect = self.canvas:getTerminalRedirect()\13\
366end\13\
367\13\
368function Terminal:wrapChunk()\13\
369 if type( self.chunk ) ~= \"function\" then\13\
370 return error \"Cannot wrap chunk. No chunk function set.\"\13\
371 end\13\
372\13\
373 self.canvas:resetTerm()\13\
374\13\
375 self.thread = Thread( self.chunk )\13\
376 self:resume( GenericEvent \"titanium_terminal_start\" )\13\
377end\13\
378\13\
379function Terminal:resume( event )\13\
380 if not isThreadRunning( self ) then return end\13\
381\13\
382 if not Titanium.typeOf( event, \"Event\", true ) then\13\
383 return error \"Invalid event object passed to resume terminal thread\"\13\
384 end\13\
385\13\
386 local thread, old = self.thread, term.redirect( self.redirect )\13\
387 thread:filterHandle( event )\13\
388 term.redirect( old )\13\
389\13\
390 if not thread.running then\13\
391 if type( thread.exception ) == \"string\" then\13\
392 if self.displayThreadStatus then\13\
393 self:emulate(function() printError( \"Thread Crashed: \" .. tostring( thread.exception ) ) end)\13\
394 end\13\
395\13\
396 self:executeCallbacks(\"exception\", thread)\13\
397 else\13\
398 if self.displayThreadStatus then\13\
399 self:emulate(function() print \"Finished\" end)\13\
400 end\13\
401\13\
402 self:executeCallbacks(\"graceful_finish\", thread)\13\
403 end\13\
404\13\
405 self:executeCallbacks(\"finish\", thread)\13\
406 end\13\
407\13\
408 self.changed = true\13\
409end\13\
410\13\
411function Terminal:emulate( fn )\13\
412 if type( fn ) ~= \"function\" then\13\
413 return error(\"Failed to emulate function. '\"..tostring( fn )..\" is not valid'\")\13\
414 end\13\
415\13\
416 local old = term.redirect( self.redirect )\13\
417 local ok, err = pcall( fn )\13\
418 term.redirect( old )\13\
419\13\
420 if not ok then\13\
421 return error(\"Failed to emulate function. Error: \"..tostring( err ), 3)\13\
422 end\13\
423end\13\
424\13\
425function Terminal:setChunk( chunk )\13\
426 self.chunk = chunk\13\
427 self:wrapChunk()\13\
428end\13\
429\13\
430function Terminal:getCaretInfo()\13\
431 local c = self.canvas\13\
432 return isThreadRunning( self ) and c.tCursor, c.tX + self.X - 1, c.tY + self.Y - 1, c.tColour\13\
433end\13\
434\13\
435function Terminal:handle( eventObj )\13\
436 if eventObj.handled or not isThreadRunning( self ) then return end\13\
437\13\
438 if eventObj.main == \"MOUSE\" then\13\
439 if eventObj:withinParent( self ) then self:focus() else self:unfocus() end\13\
440 eventObj = eventObj:clone( self )\13\
441 end\13\
442\13\
443 if Terminal.focusedEvents[ eventObj.main ] and not self.focused then return end\13\
444 self:resume( eventObj )\13\
445end\13\
446\13\
447function Terminal:draw( force ) end\13\
448\13\
449configureConstructor({\13\
450 orderedArguments = { \"X\", \"Y\", \"width\", \"height\", \"chunk\" },\13\
451 argumentTypes = { chunk = \"function\" },\13\
452 useProxy = { \"chunk\" }\13\
453}, true)\13\
454",
455["MTogglable.ti"]="--[[\13\
456 A small mixin \"to\" avoid rewriting code used by nodes that can be toggled on or off.\13\
457]]\13\
458\13\
459class \"MTogglable\" abstract() {\13\
460 toggled = false;\13\
461\13\
462 toggledColour = colours.red;\13\
463 toggledBackgroundColour = colours.white;\13\
464}\13\
465\13\
466--[[\13\
467 @constructor\13\
468 @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\13\
469]]\13\
470function MTogglable:MTogglable()\13\
471 if Titanium.mixesIn( self, \"MThemeable\" ) then\13\
472 self:register(\"toggled\", \"toggledColour\", \"toggledBackgroundColour\")\13\
473 end\13\
474end\13\
475\13\
476--[[\13\
477 @instance\13\
478 @desc 'toggled' to the opposite of what it currently is (toggles)\13\
479]]\13\
480function MTogglable:toggle( ... )\13\
481 self:setToggled( not self.toggled, ... )\13\
482end\13\
483\13\
484--[[\13\
485 @instance\13\
486 @desc Sets toggled to 'toggled' and changed to 'true' when the 'toggled' param doesn't match the current value of toggled.\13\
487 @param <boolean - toggled>, [vararg - onToggleArguments]\13\
488]]\13\
489function MTogglable:setToggled( toggled, ... )\13\
490 if self.toggled ~= toggled then\13\
491 self.raw.toggled = toggled\13\
492 self.changed = true\13\
493\13\
494 self:executeCallbacks( \"toggle\", ... )\13\
495 end\13\
496end\13\
497\13\
498configureConstructor {\13\
499 argumentTypes = {\13\
500 toggled = \"boolean\",\13\
501 toggledColour = \"colour\",\13\
502 toggledBackgroundColour = \"colour\"\13\
503 }\13\
504} alias {\13\
505 toggledColor = \"toggledColour\",\13\
506 toggledBackgroundColor = \"toggledBackgroundColour\"\13\
507}\13\
508",
509["TermCanvas.ti"]="--[[\13\
510 The TermCanvas is an object that draws it's buffer directly to the ComputerCraft term object, unlike the NodeCanvas.\13\
511\13\
512 The TermCanvas should be used by high level objects, like 'Application'. Nodes should not be drawing directly to the term object.\13\
513 If your object needs to draw to the canvas this class \"should\" be used.\13\
514\13\
515 Unlike NodeCanvas, TermCanvas has no drawing functions as it's purpose is not to generate the buffer, just draw it to the term object.\13\
516 Nodes generate their content and store it in your buffer (and theirs aswell).\13\
517--]]\13\
518\13\
519local hex = {}\13\
520for i = 0, 15 do\13\
521 hex[2 ^ i] = (\"%x\"):format( i ) -- %x = lowercase hexadecimal\13\
522end\13\
523\13\
524local tableConcat = table.concat\13\
525\13\
526class \"TermCanvas\" extends \"Canvas\" {\13\
527 static = { hex = hex }\13\
528}\13\
529\13\
530function TermCanvas:__init__( owner )\13\
531 self:super( owner )\13\
532\13\
533 self.raw.X = owner.raw.X\13\
534 self.raw.Y = owner.raw.Y\13\
535end\13\
536\13\
537function TermCanvas:draw( force )\13\
538 local owner = self.owner\13\
539 local buffer, last = self.buffer, self.last\13\
540 local X, Y, width, height = owner.X, owner.Y - 1, self.width, self.height\13\
541 local colour, backgroundColour = self.colour, self.backgroundColour\13\
542\13\
543 local position, px, lpx = 1\13\
544 for y = 1, height do\13\
545 local changed\13\
546\13\
547 for x = 1, width do\13\
548 px, lpx = buffer[ position ], last[ position ]\13\
549\13\
550 if force or not lpx or ( px[ 1 ] ~= lpx[ 1 ] or px[ 2 ] ~= lpx[ 2 ] or px[ 3 ] ~= lpx[ 3 ] ) then\13\
551 changed = true\13\
552\13\
553 position = position - ( x - 1 )\13\
554 break\13\
555 end\13\
556\13\
557 position = position + 1\13\
558 end\13\
559\13\
560 if changed then\13\
561 local rowText, rowColour, rowBackground, pixel = {}, {}, {}\13\
562\13\
563 for x = 1, width do\13\
564 pixel = buffer[ position ]\13\
565 last[ position ] = pixel\13\
566\13\
567 rowText[ x ] = pixel[ 1 ]\13\
568 rowColour[ x ] = hex[ pixel[ 2 ] or 1 ]\13\
569 rowBackground[ x ] = hex[ pixel[ 3 ] or 32768 ]\13\
570\13\
571 position = position + 1\13\
572 end\13\
573\13\
574 term.setCursorPos( X, y + Y )\13\
575 term.blit( tableConcat( rowText ), tableConcat( rowColour ), tableConcat( rowBackground ) )\13\
576 end\13\
577 end\13\
578end\13\
579",
580["NodeQuery.ti"]="local function format( original, symbol, final )\13\
581 local wrapper = type( original ) == \"string\" and '\"' or \"\"\13\
582 local finalWrapper = type( final ) == \"string\" and '\"' or \"\"\13\
583\13\
584 return (\"return %s%s%s %s %s%s%s\"):format( wrapper, tostring( original ), wrapper, symbol, finalWrapper, tostring( final ), finalWrapper )\13\
585end\13\
586\13\
587local function testCondition( node, condition )\13\
588 local fn, err = loadstring( format( node[ condition.property ], condition.symbol, condition.value ) )\13\
589 if fn then return fn() end\13\
590\13\
591 return fn()\13\
592end\13\
593\13\
594local function queryScope( scope, section, results )\13\
595 local last = {}\13\
596\13\
597 local node\13\
598 for i = 1, #scope do\13\
599 node = scope[ i ]\13\
600\13\
601 if ( not section.id or node.id == section.id ) and\13\
602 ( not section.type or node.__type == section.type ) and\13\
603 ( not section.classes or node:hasClass( section.classes ) ) then\13\
604 local condition, failed = section.condition\13\
605 if condition then\13\
606 local conditionPart\13\
607 for c = 1, #condition do\13\
608 if not testCondition( node, condition[ c ] ) then\13\
609 failed = true\13\
610 break\13\
611 end\13\
612 end\13\
613 end\13\
614\13\
615 if not failed then\13\
616 last[ #last + 1 ] = node\13\
617 end\13\
618 end\13\
619 end\13\
620\13\
621 return last\13\
622end\13\
623\13\
624local function createScope( results, direct )\13\
625 local scope = {}\13\
626 for i = 1, #results do\13\
627 local innerScope = direct and results[ i ].nodes or results[ i ].collatedNodes\13\
628\13\
629 for r = 1, #innerScope do\13\
630 scope[ #scope + 1 ] = innerScope[ r ]\13\
631 end\13\
632 end\13\
633\13\
634 return scope\13\
635end\13\
636\13\
637local function performQuery( query, base )\13\
638 local lastResults, section = base\13\
639\13\
640 for i = 1, #query do\13\
641 section = query[ i ]\13\
642 lastResults = queryScope( createScope( lastResults, section.direct ), section )\13\
643 end\13\
644\13\
645 return lastResults\13\
646end\13\
647\13\
648class \"NodeQuery\" {\13\
649 static = { supportedMethods = { \"addClass\", \"removeClass\", \"setClass\", \"set\", \"animate\", \"on\", \"off\" } };\13\
650 result = false;\13\
651\13\
652 parent = false;\13\
653}\13\
654\13\
655function NodeQuery:__init__( parent, queryString )\13\
656 if not ( Titanium.isInstance( parent ) and type( queryString ) == \"string\" ) then\13\
657 return error \"Node query requires Titanium instance and string query\"\13\
658 end\13\
659 self.parent = parent\13\
660\13\
661 self.parsedQuery = QueryParser( queryString ).query\13\
662 self.result = self:query()\13\
663\13\
664 local sup = NodeQuery.supportedMethods\13\
665 for i = 1, #sup do\13\
666 self[ sup[ i ] ] = function( self, ... ) self:executeOnNodes( sup[ i ], ... ) end\13\
667 end\13\
668end\13\
669\13\
670--[[\13\
671 @static\13\
672 @desc Returns a table containing the nodes matching the conditions set in 'query'\13\
673 @return <table - results>\13\
674]]\13\
675function NodeQuery:query()\13\
676 local query, results = self.parsedQuery, {}\13\
677 if type( query ) ~= \"table\" then return error( \"Cannot perform query. Invalid query object passed\" ) end\13\
678\13\
679 local parent = { self.parent }\13\
680 for i = 1, #query do\13\
681 local res = performQuery( query[ i ], parent )\13\
682\13\
683 for r = 1, #res do\13\
684 results[ #results + 1 ] = res[ r ]\13\
685 end\13\
686 end\13\
687\13\
688 return results\13\
689end\13\
690\13\
691--[[\13\
692 @instance\13\
693 @desc Returns true if the class 'class' exists on all nodes in the result set, false otherwise\13\
694 @param <table|string - class>\13\
695 @return <boolean - hasClass>\13\
696]]\13\
697function NodeQuery:hasClass( class )\13\
698 local nodes = self.result\13\
699 for i = 1, #nodes do\13\
700 if not nodes[ i ]:hasClass( class ) then\13\
701 return false\13\
702 end\13\
703 end\13\
704\13\
705 return true\13\
706end\13\
707\13\
708function NodeQuery:each( fn )\13\
709 local nodes = self.result\13\
710 for i = 1, #nodes do\13\
711 fn( nodes[ i ] )\13\
712 end\13\
713end\13\
714\13\
715--[[\13\
716 @instance\13\
717 @desc Iterates over each node in the result set, calling 'fnName' with arguments '...' on each\13\
718 @param <string - fnName>, [vararg - ...]\13\
719]]\13\
720function NodeQuery:executeOnNodes( fnName, ... )\13\
721 local nodes, node = self.result\13\
722 for i = 1, #nodes do\13\
723 node = nodes[ i ]\13\
724\13\
725 if node:can( fnName ) then\13\
726 node[ fnName ]( node, ... )\13\
727 end\13\
728 end\13\
729end\13\
730",
731["MKeyHandler.ti"]="--[[\13\
732 The key handler mixin \"facilitates\" common features of objects that utilize key events. The mixin \"can\" manage hotkeys and will check them for validity\13\
733 when a key event is caught.\13\
734]]\13\
735\13\
736class \"MKeyHandler\" abstract() {\13\
737 static = {\13\
738 keyAlias = {}\13\
739 };\13\
740\13\
741 keys = {};\13\
742 hotkeys = {};\13\
743 cooldown = false;\13\
744}\13\
745\13\
746--[[\13\
747 @instance\13\
748 @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.\13\
749 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).\13\
750 The registered hotkeys will be updated everytime this function is called.\13\
751 @param <KeyEvent - event>\13\
752]]\13\
753function MKeyHandler:handleKey( event )\13\
754 local keyCode = event.keyCode\13\
755 if event.sub == \"DOWN\" then\13\
756 self.keys[ keyCode ] = event.held\13\
757 self:checkHotkeys( keyCode )\13\
758 else\13\
759 self.keys[ keyCode ] = nil\13\
760 self:checkHotkeys()\13\
761 end\13\
762end\13\
763\13\
764--[[\13\
765 @instance\13\
766 @desc Returns true if a key is pressed (regardless of held state) and false otherwise\13\
767 @param <number - keyCode>\13\
768 @return <boolean - isPressed>\13\
769]]\13\
770function MKeyHandler:isPressed( keyCode )\13\
771 return self.keys[ keyCode ] ~= nil\13\
772end\13\
773\13\
774--[[\13\
775 @instance\13\
776 @desc Returns true if the key is pressed and held, or false otherwise\13\
777 @param <number - keyCode>\13\
778 @return <boolean - isHeld>\13\
779]]\13\
780function MKeyHandler:isHeld( keyCode )\13\
781 return self.keys[ keyCode ]\13\
782end\13\
783\13\
784--[[\13\
785 @instance\13\
786 @desc Breaks 'hotkey' into key names and check their status. The last element of the hotkey must be pressed last (be the active key)\13\
787 Hotkey format \"leftCtrl-leftShift-t\" (keyName-keyName-keyName)\13\
788 @param <string - hotkey>, [number - key]\13\
789 @return <boolean - hotkeyMatch>\13\
790]]\13\
791function MKeyHandler:matchesHotkey( hotkey, key )\13\
792 for segment in hotkey:gmatch \"(%w-)%-\" do\13\
793\9\9if self.keys[ keys[ segment ] ] == nil then\13\
794\9\9\9return false\13\
795 end\13\
796\9end\13\
797\13\
798\9return key == keys[ hotkey:gsub( \".+%-\", \"\" ) ]\13\
799end\13\
800\13\
801--[[\13\
802 @instance\13\
803 @desc Registers a hotkey by adding it's callback and hotkey string to the handlers 'hotkeys'.\13\
804 @param <string - name>, <string - hotkey>, <function - callback>\13\
805]]\13\
806function MKeyHandler:registerHotkey( name, hotkey, callback )\13\
807 if not ( type( name ) == \"string\" and type( hotkey ) == \"string\" and type( callback ) == \"function\" ) then\13\
808 return error \"Expected string, string, function\"\13\
809 end\13\
810\13\
811 self.hotkeys[ name ] = { hotkey, callback }\13\
812end\13\
813\13\
814--[[\13\
815 @instance\13\
816 @desc Iterates through the registered hotkeys and checks for matches using 'matchesHotkey'. If a hotkey matches it's registered callback is invoked\13\
817 @param [number - key]\13\
818]]\13\
819function MKeyHandler:checkHotkeys( key )\13\
820 for _, hotkey in pairs( self.hotkeys ) do\13\
821 if self:matchesHotkey( hotkey[ 1 ], key ) then\13\
822 hotkey[ 2 ]( self, key )\13\
823 end\13\
824 end\13\
825end\13\
826",
827["Parser.ti"]="--[[\13\
828 The parser class \"should\" be extended by classes that are used to parser lexer token output.\13\
829]]\13\
830\13\
831class \"Parser\" abstract() {\13\
832 position = 0;\13\
833 tokens = {};\13\
834}\13\
835\13\
836--[[\13\
837 @constructor\13\
838 @desc Sets the tokens of the parser to those passed and begins parsing\13\
839 @param <table - tokens>\13\
840]]\13\
841function Parser:__init__( tokens )\13\
842 if type( tokens ) ~= \"table\" then\13\
843 return error \"Failed to parse. Invalid tokens\"\13\
844 end\13\
845\13\
846 self.tokens = tokens\13\
847 self:parse()\13\
848end\13\
849\13\
850--[[\13\
851 @instance\13\
852 @desc Returns the token at 'position'\13\
853]]\13\
854function Parser:getCurrentToken()\13\
855 return self.tokens[ self.position ]\13\
856end\13\
857\13\
858--[[\13\
859 @instance\13\
860 @desc Advances 'position' by one and returns the token at the new position\13\
861]]\13\
862function Parser:stepForward( amount )\13\
863 self.position = self.position + ( amount or 1 )\13\
864 return self:getCurrentToken()\13\
865end\13\
866\13\
867--[[\13\
868 @instance\13\
869 @desc Throws a error prefixed with information about the token being parsed at the time of error.\13\
870]]\13\
871function Parser:throw( e, token )\13\
872 local token = token or self:getCurrentToken()\13\
873 return error( \"Parser (\"..tostring( self.__type )..\") Error. Line \"..token.line..\", char \"..token.char .. \": \"..e, 2 )\13\
874end\13\
875",
876["TML.ti"]="--[[\13\
877 @local\13\
878 @desc Creates a table of arguments using the classes constructor configuration. This table is then unpacked (and the result returned)\13\
879 @param <Class Base - class>, <table - target>\13\
880 @return [var - args...]\13\
881]]\13\
882local function formArguments( class, target )\13\
883 local reg = class:getRegistry()\13\
884 local constructor, alias, args = reg.constructor, reg.alias, target.arguments\13\
885 local returnArguments, trailingTable = {}, {}\13\
886\13\
887 if not constructor then return nil end\13\
888 local argumentTypes = constructor.argumentTypes\13\
889\13\
890 local ordered, set, target = constructor.orderedArguments, {}\13\
891 for i = 1, #ordered do\13\
892 target = ordered[ i ]\13\
893 returnArguments[ i ] = XMLParser.convertArgType( args[ target ], argumentTypes[ alias[ target ] or target ] )\13\
894 set[ ordered[ i ] ] = true\13\
895 end\13\
896\13\
897 for argName, argValue in pairs( args ) do\13\
898 if not set[ argName ] then\13\
899 trailingTable[ argName ] = XMLParser.convertArgType( argValue, argumentTypes[ alias[ argName ] or argName ] )\13\
900 end\13\
901 end\13\
902\13\
903 if next( trailingTable ) then\13\
904 returnArguments[ #ordered + 1 ] = trailingTable\13\
905 end\13\
906\13\
907 return unpack( returnArguments, 1, next(trailingTable) and #ordered + 1 or #ordered )\13\
908end\13\
909\13\
910--[[\13\
911 The TML class \"is\" used to parse an XML tree into Titanium nodes.\13\
912]]\13\
913\13\
914class \"TML\" {\13\
915 tree = false;\13\
916 parent = false;\13\
917}\13\
918\13\
919--[[\13\
920 @constructor\13\
921 @desc Constructs the TML instance by storing the parent and tree on 'self' and then parsing the tree.\13\
922 @param <Class Instance - parent>, <table - tree>\13\
923]]\13\
924function TML:__init__( parent, source )\13\
925 self.parent = parent\13\
926 self.tree = XMLParser( source ).tree\13\
927\13\
928 self:parseTree()\13\
929end\13\
930\13\
931--[[\13\
932 @instance\13\
933 @desc Parses 'self.tree' by creating and adding node instances to their parents.\13\
934]]\13\
935function TML:parseTree()\13\
936 local queue = { { self.parent, self.tree } }\13\
937\13\
938 local i, parent, tree = 1\13\
939 while i <= #queue do\13\
940 parent, tree = queue[ i ][ 1 ], queue[ i ][ 2 ]\13\
941\13\
942 local target\13\
943 for t = 1, #tree do\13\
944 target = tree[ t ]\13\
945\13\
946 local classArg = target.arguments[\"class\"]\13\
947 if classArg then target.arguments[\"class\"] = nil end\13\
948\13\
949 local itemClass = Titanium.getClass( target.type ) or error( \"Failed to spawn XML tree. Failed to find class '\"..target.type..\"'\" )\13\
950 local itemInstance = itemClass( formArguments( itemClass, target ) )\13\
951\13\
952 if classArg then\13\
953 itemInstance.classes = type( itemInstance.classes ) == \"table\" and itemInstance.classes or {}\13\
954 for className in classArg:gmatch \"%S+\" do\13\
955 itemInstance:addClass( className )\13\
956 end\13\
957 end\13\
958\13\
959 if target.children then\13\
960 table.insert( queue, { itemInstance, target.children } )\13\
961 end\13\
962\13\
963 if parent:can \"addNode\" then\13\
964 parent:addNode( itemInstance )\13\
965 else\13\
966 return error(\"Failed to spawn XML tree. \"..tostring( parent )..\" cannot contain nodes.\")\13\
967 end\13\
968 end\13\
969\13\
970 i = i + 1\13\
971 end\13\
972end\13\
973\13\
974--[[\13\
975 @static\13\
976 @desc Reads the data from 'path' and creates a TML instance with the contents as the source (arg #2)\13\
977 @param <Class Instance - parent>, <string - path>\13\
978 @return <TML Instance - instance>\13\
979]]\13\
980function TML.static.fromFile( parent, path )\13\
981 if not fs.exists( path ) then return error( \"Path \"..tostring( path )..\" cannot be found\" ) end\13\
982\13\
983 local h = fs.open( path, \"r\" )\13\
984 local content = h.readAll()\13\
985 h.close()\13\
986\13\
987 return TML( parent, content )\13\
988end\13\
989",
990["QueryParser.ti"]="local function parseValue( val )\
991 if val == \"true\" then return true\
992 elseif val == \"false\" then return false end\
993\
994 return tonumber( val ) or error(\"Invalid value passed for parsing '\"..tostring( val )..\"'\")\
995end\
996\
997class \"QueryParser\" extends \"Parser\"\
998\
999function QueryParser:__init__( queryString )\
1000 self:super( QueryLexer( queryString ).tokens )\
1001end\
1002\
1003function QueryParser:parse()\
1004 local allQueries, currentQuery, currentStep = {}, {}, {}\
1005\
1006 local nextStepDirect\
1007 local function advanceSection()\
1008 if next( currentStep ) then\
1009 table.insert( currentQuery, currentStep )\
1010 currentStep = { direct = nextStepDirect }\
1011\
1012 nextStepDirect = nil\
1013 end\
1014 end\
1015\
1016 local token = self:stepForward()\
1017 while token do\
1018 if token.type == \"QUERY_TYPE\" then\
1019 if currentStep.type then self:throw( \"Attempted to set query type to '\"..token.value..\"' when already set as '\"..currentStep.type..\"'\" ) end\
1020\
1021 currentStep.type = token.value\
1022 elseif token.type == \"QUERY_CLASS\" then\
1023 if not currentStep.classes then currentStep.classes = {} end\
1024\
1025 table.insert( currentStep.classes, token.value )\
1026 elseif token.type == \"QUERY_ID\" then\
1027 if currentStep.id then self:throw( \"Attempted to set query id to '\"..token.value..\"' when already set as '\"..currentStep.id..\"'\" ) end\
1028\
1029 currentStep.id = token.value\
1030 elseif token.type == \"QUERY_SEPERATOR\" then\
1031 if self.tokens[ self.position + 1 ].type ~= \"QUERY_DIRECT_PREFIX\" then\
1032 advanceSection()\
1033 end\
1034 elseif token.type == \"QUERY_END\" then\
1035 advanceSection()\
1036\
1037 if next( currentQuery ) then\
1038 table.insert( allQueries, currentQuery )\
1039 currentQuery = {}\
1040 else\
1041 self:throw( \"Unexpected '\"..token.value..\"' found, no left hand query\" )\
1042 end\
1043 elseif token.type == \"QUERY_COND_OPEN\" then\
1044 currentStep.condition = self:parseCondition()\
1045 elseif token.type == \"QUERY_DIRECT_PREFIX\" and not nextStepDirect then\
1046 nextStepDirect = true\
1047 else\
1048 self:throw( \"Unexpected '\"..token.value..\"' found while parsing query\" )\
1049 end\
1050\
1051 token = self:stepForward()\
1052 end\
1053\
1054 advanceSection()\
1055 if next( currentQuery ) then\
1056 table.insert( allQueries, currentQuery )\
1057 end\
1058\
1059 self.query = allQueries\
1060end\
1061\
1062function QueryParser:parseCondition()\
1063 local conditions, condition = {}, {}\
1064\
1065 local token = self:stepForward()\
1066 while true do\
1067 if token.type == \"QUERY_COND_ENTITY\" and ( condition.symbol or not condition.property ) then\
1068 condition[ condition.symbol and \"value\" or \"property\" ] = condition.symbol and parseValue( token.value ) or token.value\
1069 elseif token.type == \"QUERY_COND_STRING_ENTITY\" and condition.symbol then\
1070 condition.value = token.value\
1071 elseif token.type == \"QUERY_COND_SYMBOL\" and not condition.property and token.value == \"#\" then\
1072 condition.modifier = token.value\
1073 elseif token.type == \"QUERY_COND_SYMBOL\" and ( condition.property ) then\
1074 condition.symbol = token.value\
1075 elseif token.type == \"QUERY_COND_SEPERATOR\" and next( condition ) then\
1076 conditions[ #conditions + 1 ] = condition\
1077 condition = {}\
1078 elseif token.type == \"QUERY_COND_CLOSE\" and ( not condition.property or ( condition.property and condition.value ) ) then\
1079 break\
1080 else\
1081 self:throw( \"Unexpected '\"..token.value..\"' inside of condition block\" )\
1082 end\
1083\
1084 token = self:stepForward()\
1085 end\
1086\
1087 if next( condition ) then\
1088 conditions[ #conditions + 1 ] = condition\
1089 end\
1090\
1091 return #conditions > 0 and conditions or nil\
1092end\
1093",
1094["MAnimationManager.ti"]="class \"MAnimationManager\" abstract() {\13\
1095 animations = {};\13\
1096 animationTimer = false;\13\
1097\13\
1098 time = false;\13\
1099}\13\
1100\13\
1101--[[\13\
1102 @desc When the animation timer ticks, update animations attached to this application and requeue the timer if more animations must occur.\13\
1103]]\13\
1104function MAnimationManager:updateAnimations()\13\
1105 local dt = os.clock() - self.time\13\
1106\13\
1107 local anims, anim = self.animations\13\
1108 for i = #anims, 1, -1 do\13\
1109 anim = anims[ i ]\13\
1110\13\
1111 if anim:update( dt ) then\13\
1112 if type( anim.promise ) == \"function\" then\13\
1113 anim:promise( self )\13\
1114 end\13\
1115\13\
1116 self:removeAnimation( anim )\13\
1117 end\13\
1118 end\13\
1119\13\
1120 self.timer = false\13\
1121 if #anims > 0 then self:restartAnimationTimer() end\13\
1122end\13\
1123\13\
1124--[[\13\
1125 @instance\13\
1126 @desc Adds an animation to this object, on update this animation will be updated\13\
1127 @param <Tween - animation>\13\
1128]]\13\
1129function MAnimationManager:addAnimation( animation )\13\
1130 if not Titanium.typeOf( animation, \"Tween\", true ) then\13\
1131 return error(\"Failed to add animation to manager. '\"..tostring( animation )..\"' is invalid, Tween instance expected\")\13\
1132 end\13\
1133\13\
1134 self:removeAnimation( animation.name )\13\
1135 table.insert( self.animations, animation )\13\
1136\13\
1137 if not self.timer then\13\
1138 self:restartAnimationTimer()\13\
1139 end\13\
1140\13\
1141 return animation\13\
1142end\13\
1143\13\
1144--[[\13\
1145 @instance\13\
1146 @desc Removes an animation from this object, it will stop receiving updates from this object\13\
1147]]\13\
1148function MAnimationManager:removeAnimation( animation )\13\
1149 local searchName\13\
1150 if type( animation ) == \"string\" then\13\
1151 searchName = true\13\
1152 elseif not Titanium.typeOf( animation, \"Tween\", true ) then\13\
1153 return error(\"Failed to remove animation from manager. '\"..tostring( animation )..\"' is invalid, Tween instance expected\")\13\
1154 end\13\
1155\13\
1156 local anims = self.animations\13\
1157 for i = 1, #anims do\13\
1158 if ( searchName and anims[ i ].name == animation ) or ( not searchName and anims[ i ] == animation ) then\13\
1159 return table.remove( anims, i )\13\
1160 end\13\
1161 end\13\
1162end\13\
1163\13\
1164--[[\13\
1165 @instance\13\
1166 @desc When an animation is queued the timer is created for 'time' (0.05). This replaces the currently running timer (if any).\13\
1167 The objects 'time' is then updated to the current time (os.clock)\13\
1168 @param [number - time]\13\
1169]]\13\
1170function MAnimationManager:restartAnimationTimer( time )\13\
1171 if self.timer then\13\
1172 os.cancelTimer( self.timer )\13\
1173 end\13\
1174\13\
1175 self.time = os.clock()\13\
1176 self.timer = os.startTimer( type( time ) == \"number\" and time or .05 )\13\
1177end\13\
1178",
1179["Component.ti"]="--[[\13\
1180 A Component is an object that can be respresented visually.\13\
1181--]]\13\
1182\13\
1183class \"Component\" abstract() {\13\
1184 width = 1;\13\
1185 height = 1;\13\
1186 X = 1;\13\
1187 Y = 1;\13\
1188}\13\
1189\13\
1190function Component:set( tbl )\13\
1191 if type( tbl ) ~= \"table\" then\13\
1192 return error \"Table expected\"\13\
1193 end\13\
1194\13\
1195 for property, value in pairs( tbl ) do\13\
1196 self[ property ] = value\13\
1197 end\13\
1198\13\
1199 return self\13\
1200end\13\
1201\13\
1202function Component:setX( X )\13\
1203 self.X = X\13\
1204 self.changed = true\13\
1205end\13\
1206\13\
1207function Component:setY( Y )\13\
1208 self.Y = Y\13\
1209 self.changed = true\13\
1210end\13\
1211\13\
1212function Component:setWidth( width )\13\
1213 self.width = width\13\
1214 self.canvas.width = width\13\
1215\13\
1216 self.changed = true\13\
1217end\13\
1218\13\
1219function Component:setHeight( height )\13\
1220 self.height = height\13\
1221 self.canvas.height = height\13\
1222\13\
1223 self.changed = true\13\
1224end\13\
1225\13\
1226function Component:setColour( colour )\13\
1227 self.colour = colour\13\
1228 self.canvas.colour = colour\13\
1229\13\
1230 self.changed = true\13\
1231end\13\
1232\13\
1233function Component:setBackgroundColour( backgroundColour )\13\
1234 self.backgroundColour = backgroundColour\13\
1235 self.canvas.backgroundColour = backgroundColour\13\
1236\13\
1237 self.changed = true\13\
1238end\13\
1239\13\
1240configureConstructor {\13\
1241 orderedArguments = { \"X\", \"Y\", \"width\", \"height\" },\13\
1242 argumentTypes = { X = \"number\", Y = \"number\", width = \"number\", height = \"number\", colour = \"colour\", backgroundColour = \"colour\" }\13\
1243} alias {\13\
1244 color = \"colour\",\13\
1245 backgroundColor = \"backgroundColour\"\13\
1246}\13\
1247",
1248["NodeCanvas.ti"]="--[[\13\
1249 The NodeCanvas is an object that allows classes to draw to their canvas using functions that are useful when drawing 'nodes', hence the name.\13\
1250\13\
1251 The NodeCanvas is most suited to these use cases:\13\
1252 - You would like to utilise helpful functions that making drawing nodes easier.\13\
1253 - Your object is not high level. High level objects use to TermCanvas which draws to the terminal, the NodeCanvas draws to a parent canvas\13\
1254--]]\13\
1255\13\
1256local string_sub = string.sub\13\
1257\13\
1258class \"NodeCanvas\" extends \"Canvas\"\13\
1259\13\
1260function NodeCanvas:drawPoint( x, y, char, tc, bg )\13\
1261 if #char > 1 then return error \"drawPoint can only draw one character\" end\13\
1262\13\
1263 self.buffer[ ( self.width * ( y - 1 ) ) + x ] = { char, tc or self.colour, bg or self.backgroundColour }\13\
1264end\13\
1265\13\
1266function NodeCanvas:drawTextLine( x, y, text, tc, bg )\13\
1267 local tc, bg = tc or self.colour, bg or self.backgroundColour\13\
1268\13\
1269 local buffer, start = self.buffer, ( self.width * ( y - 1 ) ) + x\13\
1270 for i = 1, #text do\13\
1271 buffer[ -1 + start + i ] = { string_sub( text, i, i ), tc, bg }\13\
1272 end\13\
1273end\13\
1274\13\
1275function NodeCanvas:drawBox( x, y, width, height, col )\13\
1276 local tc, bg = self.colour, col or self.backgroundColour\13\
1277 local buffer = self.buffer\13\
1278\13\
1279 local px = { \" \", tc, bg }\13\
1280 for y = math.max( 0, y ), y + height - 1 do\13\
1281 for x = math.max( 1, x ), x + width - 1 do\13\
1282 buffer[ ( self.width * ( y - 1 ) ) + x ] = px\13\
1283 end\13\
1284 end\13\
1285end\13\
1286",
1287["PageContainer.ti"]="class \"PageContainer\" extends \"ScrollContainer\" {\13\
1288 pages = {};\13\
1289 selectedPage = false;\13\
1290}\13\
1291\13\
1292--[[\13\
1293 @instance\13\
1294 @desc Adds the page instance provided to the PageContainers 'pages'\13\
1295 @param <Page Instance - page>\13\
1296]]\13\
1297function PageContainer:addPage( page )\13\
1298 if not Titanium.typeOf( page, \"Page\", true ) then return error(\"Invalid page '\"..tostring( page )..\"'\") end\13\
1299\13\
1300 self:removePage( page.name )\13\
1301\13\
1302 if self.application then\13\
1303 page.application = self.application\13\
1304 end\13\
1305 page.parent = self\13\
1306\13\
1307 table.insert( self.pages, page )\13\
1308end\13\
1309\13\
1310--[[\13\
1311 @instance\13\
1312 @desc Removes the page (or page with name provided) from the PageContainers pages.\13\
1313 @param <Page Instance - page OR string - pageName>\13\
1314 @return [Page Instance - removedPage]\13\
1315]]\13\
1316function PageContainer:removePage( page )\13\
1317 local searchName = type( page ) == \"string\" and true or ( Titanium.typeOf( page, \"Page\", true ) and page or error(\"Invalid target '\"..tostring( page )..\"' to remove\", 2) )\13\
1318 local pages = self.pages\13\
1319 for i = 1, #pages do\13\
1320 if ( searchName and pages[ i ].name == page ) or ( not searchName and pages[ i ] == page ) then\13\
1321 if self.selectedPage == page then\13\
1322 self.selectedPage = false\13\
1323 end\13\
1324\13\
1325 page.parent = nil\13\
1326 page.application = nil\13\
1327 return table.remove( pages, i )\13\
1328 end\13\
1329 end\13\
1330end\13\
1331\13\
1332--[[\13\
1333 @instance\13\
1334 @desc Selects the page with name 'name'. This pages is then used when drawing to the PageContainer canvas.\13\
1335 @param <string - name>\13\
1336 @return <boolean - success>\13\
1337]]\13\
1338function PageContainer:selectPage( name )\13\
1339 local pages = self.pages\13\
1340 for i = 1, #pages do\13\
1341 if pages[ i ].name == name then\13\
1342 self.selectedPage = pages[ i ]\13\
1343\13\
1344 return true\13\
1345 end\13\
1346 end\13\
1347\13\
1348 self.selectedPage = false\13\
1349 return false\13\
1350end\13\
1351\13\
1352function PageContainer:setSelectedPage( page )\13\
1353 self.selectedPage = page\13\
1354 self.changed = true\13\
1355\13\
1356 self:cacheContent()\13\
1357 self.yScroll = 0\13\
1358 self.xScroll = 0\13\
1359end\13\
1360\13\
1361function PageContainer:addNode( node )\13\
1362 if not Titanium.typeOf( node, \"Page\", true ) then\13\
1363 return error(\"PageContainer only supports 'Page' as direct child nodes, '\"..tostring( node )..\"' is not valid. To add content, add your nodes to a direct child page - not directly to the PageContainer\")\13\
1364 end\13\
1365\13\
1366 self:addPage( node )\13\
1367end\13\
1368\13\
1369--[[\13\
1370 @instance\13\
1371 @desc Allows the ScrollContainer.draw to fetch the nodes for the selected page, rather than using the 'nodes' table\13\
1372 @return <table - nodes>\13\
1373]]\13\
1374function PageContainer:getNodes()\13\
1375 if self.selectedPage then\13\
1376 return self.selectedPage.nodes\13\
1377 end\13\
1378\13\
1379 return {}\13\
1380end\13\
1381\13\
1382function PageContainer:setApplication( app )\13\
1383 if Titanium.typeOf( app, \"Application\", true ) then\13\
1384 self.application = app\13\
1385\13\
1386 local pages = self.pages\13\
1387 for i = 1, #pages do\13\
1388 pages[ i ].application = app\13\
1389 end\13\
1390 else\13\
1391 return error(\"Failed to set Application. Invalid value passed '\"..tostring( app )..\"'\")\13\
1392 end\13\
1393end\13\
1394\13\
1395--[[\13\
1396 @instance\13\
1397 @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.\13\
1398 @param <boolean - changed>\13\
1399]]\13\
1400function PageContainer:setChanged( changed )\13\
1401 self.changed = changed\13\
1402\13\
1403 if changed then\13\
1404 local parent = self.parent\13\
1405 if parent and not parent.changed then\13\
1406 parent.changed = true\13\
1407 end\13\
1408 end\13\
1409end\13\
1410\13\
1411--[[\13\
1412 @instance\13\
1413 @desc Caches all nodes under this container (and child containers) in 'collatedNodes'.\13\
1414 This list maybe out of date if 'collate' isn't called before usage. Caching is not automatic.\13\
1415 @param [table - collated]\13\
1416]]\13\
1417function PageContainer:collate( collated )\13\
1418 local collated = collated or {}\13\
1419\13\
1420 local pages, page = self.pages\13\
1421 for p = 1, #pages do\13\
1422 collated[ #collated + 1 ] = pages[ p ]\13\
1423 local nodes, node = pages[ p ].nodes\13\
1424 for i = 1, #nodes do\13\
1425 node = nodes[ i ]\13\
1426 collated[ #collated + 1 ] = node\13\
1427\13\
1428 local collatedNode = node.collatedNodes\13\
1429 if collatedNode then\13\
1430 for i = 1, #collatedNode do\13\
1431 collated[ #collated + 1 ] = collatedNode[ i ]\13\
1432 end\13\
1433 end\13\
1434 end\
1435 end\13\
1436\13\
1437 self.collatedNodes = collated\13\
1438end\13\
1439\13\
1440configureConstructor({\13\
1441 orderedArguments = { \"X\", \"Y\", \"width\", \"height\", \"pages\", \"backgroundColour\" },\13\
1442 argumentTypes = { pages = \"table\" }\13\
1443}, true)\13\
1444",
1445["CharEvent.ti"]="class \"CharEvent\" extends \"Event\" {\13\
1446 main = \"CHAR\";\13\
1447 char = false;\13\
1448}\13\
1449\13\
1450function CharEvent:__init__( name, char )\13\
1451 self.name = name\13\
1452 self.char = char\13\
1453\13\
1454 self.data = { name, char }\13\
1455end\13\
1456",
1457["Titanium.lua"]="--[[\
1458 Event declaration\
1459 =================\
1460\
1461 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\
1462 starts blank, so we define basic events here. (on event type 'key', spawn instance of type 'value')\
1463]]\
1464Event.static.matrix = {\
1465 mouse_click = MouseEvent,\
1466 mouse_drag = MouseEvent,\
1467 mouse_up = MouseEvent,\
1468 mouse_scroll = MouseEvent,\
1469\
1470 key = KeyEvent,\
1471 key_up = KeyEvent,\
1472\
1473 char = CharEvent\
1474}\
1475\
1476--[[\
1477 Tween setup\
1478 ===========\
1479\
1480 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\
1481 are adjusted versions (the algorithm has remained the same, however code formatting and variable names are largely changed to match Titanium) of\
1482 the easing functions published by kikito on GitHub. Refer to 'LICENSE' in this project root for more information (and Enrique's license).\
1483\
1484 The functions are passed 4 arguments, these arguments are listed below:\
1485 - 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)\
1486 - initial: The value of the property being animated at the instantiation of the tween. This is usually added as a Y-Axis transformation.\
1487 - 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.\
1488 - duration: The total duration of the running Tween.\
1489\
1490 Certain functions are passed extra arguments. The Tween class doesn't pass these in, however custom animation engines could invoke these easing functions\
1491 through `Tween.static.easing.<easingType>`.\
1492]]\
1493\
1494local abs, pow, asin, sin, sqrt, pi = math.abs, math.pow, math.asin, math.sin, math.sqrt, math.pi\
1495local easing = Tween.static.easing\
1496-- Linear easing function\
1497Tween.addEasing(\"linear\", function( clock, initial, change, duration )\
1498 return change * clock / duration + initial\
1499end)\
1500\
1501-- Quad easing functions\
1502Tween.addEasing(\"inQuad\", function( clock, initial, change, duration )\
1503 return change * pow( clock / duration, 2 ) + initial\
1504end).addEasing(\"outQuad\", function( clock, initial, change, duration )\
1505 local clock = clock / duration\
1506 return -change * clock * ( clock - 2 ) + initial\
1507end).addEasing(\"inOutQuad\", function( clock, initial, change, duration )\
1508 local clock = clock / duration * 2\
1509 if clock < 1 then\
1510 return change / 2 * pow( clock, 2 ) + initial\
1511 end\
1512\
1513 return -change / 2 * ( ( clock - 1 ) * ( clock - 3 ) - 1 ) + initial\
1514end).addEasing(\"outInQuad\", function( clock, initial, change, duration )\
1515 if clock < duration / 2 then\
1516 return easing.outQuad( clock * 2, initial, change / 2, duration )\
1517 end\
1518\
1519 return easing.inQuad( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration)\
1520end)\
1521\
1522-- Cubic easing functions\
1523Tween.addEasing(\"inCubic\", function( clock, initial, change, duration )\
1524 return change * pow( clock / duration, 3 ) + initial\
1525end).addEasing(\"outCubic\", function( clock, initial, change, duration )\
1526 return change * ( pow( clock / duration - 1, 3 ) + 1 ) + initial\
1527end).addEasing(\"inOutCubic\", function( clock, initial, change, duration )\
1528 local clock = clock / duration * 2\
1529 if clock < 1 then\
1530 return change / 2 * clock * clock * clock + initial\
1531 end\
1532\
1533 clock = clock - 2\
1534 return change / 2 * (clock * clock * clock + 2) + initial\
1535end).addEasing(\"outInCubic\", function( clock, initial, change, duration )\
1536 if clock < duration / 2 then\
1537 return easing.outCubic( clock * 2, initial, change / 2, duration )\
1538 end\
1539\
1540 return easing.inCubic( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1541end)\
1542\
1543-- Quart easing functions\
1544Tween.addEasing(\"inQuart\", function( clock, initial, change, duration )\
1545 return change * pow( clock / duration, 4 ) + initial\
1546end).addEasing(\"outQuart\", function( clock, initial, change, duration )\
1547 return -change * ( pow( clock / duration - 1, 4 ) - 1 ) + initial\
1548end).addEasing(\"inOutQuart\", function( clock, initial, change, duration )\
1549 local clock = clock / duration * 2\13\
1550 if clock < 1 then\13\
1551 return change / 2 * pow(clock, 4) + initial\13\
1552 end\13\
1553\13\
1554 return -change / 2 * ( pow( clock - 2, 4 ) - 2 ) + initial\
1555end).addEasing(\"outInQuart\", function( clock, initial, change, duration )\
1556 if clock < duration / 2 then\13\
1557 return easing.outQuart( clock * 2, initial, change / 2, duration )\13\
1558 end\13\
1559\13\
1560 return easing.inQuart( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1561end)\
1562\
1563-- Quint easing functions\
1564Tween.addEasing(\"inQuint\", function( clock, initial, change, duration )\
1565 return change * pow( clock / duration, 5 ) + initial\
1566end).addEasing(\"outQuint\", function( clock, initial, change, duration )\
1567 return change * ( pow( clock / duration - 1, 5 ) + 1 ) + initial\
1568end).addEasing(\"inOutQuint\", function( clock, initial, change, duration )\
1569 local clock = clock / duration * 2\
1570 if clock < 1 then\
1571 return change / 2 * pow( clock, 5 ) + initial\
1572 end\
1573\
1574 return change / 2 * (pow( clock - 2, 5 ) + 2 ) + initial\
1575end).addEasing(\"outInQuint\", function( clock, initial, change, duration )\
1576 if clock < duration / 2 then\
1577 return easing.outQuint( clock * 2, initial, change / 2, duration )\
1578 end\
1579\
1580 return easing.inQuint( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1581end)\
1582\
1583-- Sine easing functions\
1584Tween.addEasing(\"inSine\", function( clock, initial, change, duration )\
1585 return -change * cos( clock / duration * ( pi / 2 ) ) + change + initial\
1586end).addEasing(\"outSine\", function( clock, initial, change, duration )\
1587 return change * sin( clock / duration * ( pi / 2 ) ) + initial\
1588end).addEasing(\"inOutSine\", function( clock, initial, change, duration )\
1589 return -change / 2 * ( cos( pi * clock / duration ) - 1 ) + initial\
1590end).addEasing(\"outInSine\", function( clock, initial, change, duration )\
1591 if clock < duration / 2 then\
1592 return easing.outSine( clock * 2, initial, change / 2, duration )\
1593 end\
1594\
1595 return easing.inSine( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1596end)\
1597\
1598-- Expo easing functions\
1599Tween.addEasing(\"inExpo\", function( clock, initial, change, duration )\
1600 if clock == 0 then\
1601 return initial\
1602 end\
1603 return change * pow( 2, 10 * ( clock / duration - 1 ) ) + initial - change * 0.001\
1604end).addEasing(\"outExpo\", function( clock, initial, change, duration )\
1605 if clock == duration then\
1606 return initial + change\
1607 end\
1608\
1609 return change * 1.001 * ( -pow( 2, -10 * clock / duration ) + 1 ) + initial\
1610end).addEasing(\"inOutExpo\", function( clock, initial, change, duration )\
1611 if clock == 0 then\
1612 return initial\
1613 elseif clock == duration then\
1614 return initial + change\
1615 end\
1616\
1617 local clock = clock / duration * 2\
1618 if clock < 1 then\
1619 return change / 2 * pow( 2, 10 * ( clock - 1 ) ) + initial - change * 0.0005\
1620 end\
1621\
1622 return change / 2 * 1.0005 * ( -pow( 2, -10 * ( clock - 1 ) ) + 2 ) + initial\
1623end).addEasing(\"outInExpo\", function( clock, initial, change, duration )\
1624 if clock < duration / 2 then\
1625 return easing.outExpo( clock * 2, initial, change / 2, duration )\
1626 end\
1627\
1628 return easing.inExpo( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1629end)\
1630\
1631-- Circ easing functions\
1632Tween.addEasing(\"inCirc\", function( clock, initial, change, duration )\
1633 return -change * ( sqrt( 1 - pow( clock / duration, 2 ) ) - 1 ) + initial\
1634end).addEasing(\"outCirc\", function( clock, initial, change, duration )\
1635 return change * sqrt( 1 - pow( clock / duration - 1, 2 ) ) + initial\
1636end).addEasing(\"inOutCirc\", function( clock, initial, change, duration )\
1637 local clock = clock / duration * 2\
1638 if clock < 1 then\
1639 return -change / 2 * ( sqrt( 1 - clock * clock ) - 1 ) + initial\
1640 end\
1641\
1642 clock = clock - 2\
1643 return change / 2 * ( sqrt( 1 - clock * clock ) + 1 ) + initial\
1644end).addEasing(\"outInCirc\", function( clock, initial, change, duration )\
1645 if clock < duration / 2 then\
1646 return easing.outCirc( clock * 2, initial, change / 2, duration )\
1647 end\
1648\
1649 return easing.inCirc( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1650end)\
1651\
1652-- Elastic easing functions\
1653local function calculatePAS(p,a,change,duration)\
1654 local p, a = p or duration * 0.3, a or 0\
1655 if a < abs( change ) then\
1656 return p, change, p / 4 -- p, a, s\
1657 end\
1658\
1659 return p, a, p / ( 2 * pi ) * asin( change / a ) -- p,a,s\
1660end\
1661\
1662Tween.addEasing(\"inElastic\", function( clock, initial, change, duration, amplitude, period )\
1663 if clock == 0 then return initial end\
1664\
1665 local clock, s = clock / duration\
1666 if clock == 1 then\
1667 return initial + change\
1668 end\
1669\
1670 clock, p, a, s = clock - 1, calculatePAS( p, a, change, duration )\
1671 return -( a * pow( 2, 10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) ) + initial\
1672end).addEasing(\"outElastic\", function( clock, initial, change, duration, amplitude, period )\
1673 if clock == 0 then\
1674 return initial\
1675 end\
1676 local clock, s = clock / duration\
1677\
1678 if clock == 1 then\
1679 return initial + change\
1680 end\
1681\
1682 local p,a,s = calculatePAS( period, amplitude, change, duration )\
1683 return a * pow( 2, -10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) + change + initial\
1684end).addEasing(\"inOutElastic\", function( clock, initial, change, duration, amplitude, period )\
1685 if clock == 0 then return initial end\
1686\
1687 local clock = clock / duration * 2\
1688 if clock == 2 then return initial + change end\
1689\
1690 local clock, p, a, s = clock - 1, calculatePAS( period, amplitude, change, duration )\
1691 if clock < 0 then\
1692 return -0.5 * ( a * pow( 2, 10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) ) + initial\
1693 end\
1694\
1695 return a * pow( 2, -10 * clock ) * sin( ( clock * duration - s ) * ( 2 * pi ) / p ) * 0.5 + change + initial\
1696end).addEasing(\"outInElastic\", function( clock, initial, change, duration, amplitude, period )\
1697 if clock < duration / 2 then\
1698 return easing.outElastic( clock * 2, initial, change / 2, duration, amplitude, period )\
1699 end\
1700\
1701 return easing.inElastic( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration, amplitude, period )\
1702end)\
1703\
1704-- Back easing functions\
1705Tween.addEasing(\"inBack\", function( clock, initial, change, duration, s )\
1706 local s, clock = s or 1.70158, clock / duration\
1707\
1708 return change * clock * clock * ( ( s + 1 ) * clock - s ) + initial\
1709end).addEasing(\"outBack\", function( clock, initial, change, duration, s )\
1710 local s, clock = s or 1.70158, clock / duration - 1\
1711\
1712 return change * ( clock * clock * ( ( s + 1 ) * clock + s ) + 1 ) + initial\
1713end).addEasing(\"inOutBack\", function( clock, initial, change, duration, s )\
1714 local s, clock = ( s or 1.70158 ) * 1.525, clock / duration * 2\
1715 if clock < 1 then\
1716 return change / 2 * ( clock * clock * ( ( s + 1 ) * clock - s ) ) + initial\
1717 end\
1718\
1719 clock = clock - 2\
1720 return change / 2 * ( clock * clock * ( ( s + 1 ) * clock + s ) + 2 ) + initial\
1721end).addEasing(\"outInBack\", function( clock, initial, change, duration, s )\
1722 if clock < duration / 2 then\
1723 return easing.outBack( clock * 2, initial, change / 2, duration, s )\
1724 end\
1725\
1726 return easing.inBack( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration, s )\
1727end)\
1728\
1729-- Bounce easing functions\
1730Tween.addEasing(\"inBounce\", function( clock, initial, change, duration )\
1731 return change - easing.outBounce( duration - clock, 0, change, duration ) + initial\
1732end).addEasing(\"outBounce\", function( clock, initial, change, duration )\
1733 local clock = clock / duration\
1734 if clock < 1 / 2.75 then\
1735 return change * ( 7.5625 * clock * clock ) + initial\
1736 elseif clock < 2 / 2.75 then\
1737 clock = clock - ( 1.5 / 2.75 )\
1738 return change * ( 7.5625 * clock * clock + 0.75 ) + initial\
1739 elseif clock < 2.5 / 2.75 then\
1740 clock = clock - ( 2.25 / 2.75 )\
1741 return change * ( 7.5625 * clock * clock + 0.9375 ) + initial\
1742 end\
1743\
1744 clock = clock - (2.625 / 2.75)\
1745 return change * (7.5625 * clock * clock + 0.984375) + initial\
1746end).addEasing(\"inOutBounce\", function( clock, initial, change, duration )\
1747 if clock < duration / 2 then\
1748 return easing.inBounce( clock * 2, 0, change, duration ) * 0.5 + initial\
1749 end\
1750\
1751 return easing.outBounce( clock * 2 - duration, 0, change, duration ) * 0.5 + change * .5 + initial\
1752end).addEasing(\"outInBounce\", function( clock, initial, change, duration )\
1753 if clock < duration / 2 then\
1754 return easing.outBounce( clock * 2, initial, change / 2, duration )\
1755 end\
1756\
1757 return easing.inBounce( ( clock * 2 ) - duration, initial + change / 2, change / 2, duration )\
1758end)\
1759",
1760["Class.lua"]="--[[\13\
1761 Titanium Class System - Version 1.1\13\
1762\13\
1763 Copyright (c) Harry Felton 2016\13\
1764]]\13\
1765\13\
1766local classes, classRegistry, currentClass, currentRegistry = {}, {}\13\
1767local reserved = {\13\
1768 static = true,\13\
1769 super = true,\13\
1770 __type = true,\13\
1771 isCompiled = true,\13\
1772 compile = true\13\
1773}\13\
1774\13\
1775local missingClassLoader\13\
1776\13\
1777local getters = setmetatable( {}, { __index = function( self, name )\13\
1778 self[ name ] = \"get\" .. name:sub( 1, 1 ):upper() .. name:sub( 2 )\13\
1779\13\
1780 return self[ name ]\13\
1781end })\13\
1782\13\
1783local setters = setmetatable( {}, { __index = function( self, name )\13\
1784 self[ name ] = \"set\"..name:sub(1, 1):upper()..name:sub(2)\13\
1785\13\
1786 return self[ name ]\13\
1787end })\13\
1788\13\
1789local isNumber = {}\13\
1790for i = 0, 15 do isNumber[2 ^ i] = true end\13\
1791\13\
1792--[[ Constants ]]--\13\
1793local ERROR_BUG = \"\\nPlease report this via GitHub @ hbomb79/Titanium\"\13\
1794local ERROR_GLOBAL = \"Failed to %s to %s\\n\"\13\
1795local ERROR_NOT_BUILDING = \"No class is currently being built. Declare a class before invoking '%s'\"\13\
1796\13\
1797--[[ Helper functions ]]--\13\
1798local function throw( ... )\13\
1799 return error( table.concat( { ... }, \"\\n\" ) , 2 )\13\
1800end\13\
1801\13\
1802local function verifyClassEntry( target )\13\
1803 return type( target ) == \"string\" and type( classes[ target ] ) == \"table\" and type( classRegistry[ target ] ) == \"table\"\13\
1804end\13\
1805\13\
1806local function verifyClassObject( target, autoCompile )\13\
1807 if not Titanium.isClass( target ) then\13\
1808 return false\13\
1809 end\13\
1810\13\
1811 if autoCompile and not target:isCompiled() then\13\
1812 target:compile()\13\
1813 end\13\
1814\13\
1815 return true\13\
1816end\13\
1817\13\
1818local function isBuilding( ... )\13\
1819 if type( currentRegistry ) == \"table\" or type( currentClass ) == \"table\" then\13\
1820 if not ( currentRegistry and currentClass ) then\13\
1821 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)\13\
1822 end\13\
1823 return true\13\
1824 end\13\
1825\13\
1826 if #({ ... }) > 0 then\13\
1827 return throw( ... )\13\
1828 else\13\
1829 return false\13\
1830 end\13\
1831end\13\
1832\13\
1833local function getClass( target )\13\
1834 if verifyClassEntry( target ) then\13\
1835 return classes[ target ]\13\
1836 elseif missingClassLoader then\13\
1837 local oC, oCReg = currentClass, currentRegistry\13\
1838 currentClass, currentRegistry = nil, nil\13\
1839\13\
1840 missingClassLoader( target )\13\
1841 local c = classes[ target ]\13\
1842 if not verifyClassObject( c, true ) then\13\
1843 throw(\"Failed to load missing class '\"..target..\"'.\\n\", \"The missing class loader failed to load class '\"..target..\"'.\\n\")\13\
1844 end\13\
1845\13\
1846 currentClass, currentRegistry = oC, oCReg\13\
1847\13\
1848 return c\13\
1849 else throw(\"Class '\"..target..\"' not found\") end\13\
1850end\13\
1851\13\
1852local function deepCopy( source )\13\
1853 if type( source ) == \"table\" then\13\
1854 local copy = {}\13\
1855 for key, value in next, source, nil do\13\
1856 copy[ deepCopy( key ) ] = deepCopy( value )\13\
1857 end\13\
1858 return copy\13\
1859 else\13\
1860 return source\13\
1861 end\13\
1862end\13\
1863\13\
1864local function propertyCatch( tbl )\13\
1865 if type( tbl ) == \"table\" then\13\
1866 if tbl.static then\13\
1867 if type( tbl.static ) ~= \"table\" then\13\
1868 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.\")\13\
1869 end\13\
1870\13\
1871\13\
1872 local cStatic, cOwnedStatics = currentRegistry.static, currentRegistry.ownedStatics\13\
1873 for key, value in pairs( tbl.static ) do\13\
1874 if reserved[ key ] then\13\
1875 throw(\13\
1876 \"Failed to set static key '\"..key..\"' on building class '\"..currentRegistry.type..\"'\",\13\
1877 \"'\"..key..\"' is reserved by Titanium for internal processes.\"\13\
1878 )\13\
1879 end\13\
1880\13\
1881 cStatic[ key ] = value\13\
1882 cOwnedStatics[ key ] = type( value ) == \"nil\" and nil or true\13\
1883 end\13\
1884\13\
1885 tbl.static = nil\13\
1886 end\13\
1887\13\
1888 local cKeys, cOwned = currentRegistry.keys, currentRegistry.ownedKeys\13\
1889 for key, value in pairs( tbl ) do\13\
1890 cKeys[ key ] = value\13\
1891 cOwned[ key ] = type( value ) == \"nil\" and nil or true\13\
1892 end\13\
1893 elseif type( tbl ) ~= \"nil\" then\13\
1894 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]\")\13\
1895 end\13\
1896end\13\
1897\13\
1898local function createFunctionWrapper( fn, superLevel )\13\
1899 return function( instance, ... )\13\
1900 local oldSuper = instance:setSuper( superLevel )\13\
1901\13\
1902 local v = { fn( ... ) }\13\
1903\13\
1904 instance.super = oldSuper\13\
1905\13\
1906 return unpack( v )\13\
1907 end\13\
1908end\13\
1909\13\
1910\13\
1911--[[ Local Functions ]]--\13\
1912local function compileSupers( targets )\13\
1913 local inheritedKeys, superMatrix = {}, {}, {}\13\
1914 local function compileSuper( target, id )\13\
1915 local factories = {}\13\
1916 local targetType = target.__type\13\
1917 local targetReg = classRegistry[ targetType ]\13\
1918\13\
1919 for key, value in pairs( targetReg.keys ) do\13\
1920 if not reserved[ key ] then\13\
1921 local toInsert = value\13\
1922 if type( value ) == \"function\" then\13\
1923 factories[ key ] = function( instance, ... )\13\
1924 --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 )( { ... } ))\13\
1925 local oldSuper = instance:setSuper( id + 1 )\13\
1926 local v = { value( instance, ... ) }\13\
1927\13\
1928 instance.super = oldSuper\13\
1929 return unpack( v )\13\
1930 end\13\
1931\13\
1932 toInsert = factories[ key ]\13\
1933 end\13\
1934\13\
1935 inheritedKeys[ key ] = toInsert\13\
1936 end\13\
1937 end\13\
1938\13\
1939 -- Handle inheritance\13\
1940 for key, value in pairs( inheritedKeys ) do\13\
1941 if type( value ) == \"function\" and not factories[ key ] then\13\
1942 factories[ key ] = value\13\
1943 end\13\
1944 end\13\
1945\13\
1946 superMatrix[ id ] = { factories, targetReg }\13\
1947 end\13\
1948\13\
1949 for id = #targets, 1, -1 do compileSuper( targets[ id ], id ) end\13\
1950\13\
1951 return inheritedKeys, function( instance )\13\
1952 local matrix, matrixReady = {}\13\
1953 local function generateMatrix( target, id )\13\
1954 local superTarget, matrixTbl, matrixMt = superMatrix[ id ], {}, {}\13\
1955 local factories, reg = superTarget[ 1 ], superTarget[ 2 ]\13\
1956\13\
1957 matrixTbl.__type = reg.type\13\
1958\13\
1959 local raw, owned, wrapCache, factory, upSuper = reg.raw, reg.ownedKeys, {}\13\
1960\13\
1961 function matrixMt:__tostring()\13\
1962 return \"[\"..reg.type..\"] Super #\"..id..\" of '\"..instance.__type..\"' instance\"\13\
1963 end\13\
1964 function matrixMt:__newindex( k, v )\13\
1965 if not matrixReady and k == \"super\" then\13\
1966 upSuper = v\13\
1967 return\13\
1968 end\13\
1969\13\
1970 throw(\"Cannot set keys on super. Illegal action.\")\13\
1971 end\13\
1972 function matrixMt:__index( k )\13\
1973 factory = factories[ k ]\13\
1974 if factory then\13\
1975 if not wrapCache[ k ] then\13\
1976 wrapCache[ k ] = (function( _, ... )\13\
1977 return factory( instance, ... )\13\
1978 end)\13\
1979 end\13\
1980\13\
1981 return wrapCache[ k ]\13\
1982 else\13\
1983 if k == \"super\" then\13\
1984 return upSuper\13\
1985 else\13\
1986 return throw(\"Cannot lookup value for key '\"..k..\"' on super\", \"Only functions can be accessed from supers.\")\13\
1987 end\13\
1988 end\13\
1989 end\13\
1990 function matrixMt:__call( instance, ... )\13\
1991 local init = self.__init__\13\
1992 if type( init ) == \"function\" then\13\
1993 return init( self, ... )\13\
1994 else\13\
1995 throw(\"Failed to execute super constructor. __init__ method not found\")\13\
1996 end\13\
1997 end\13\
1998\13\
1999 setmetatable( matrixTbl, matrixMt )\13\
2000 return matrixTbl\13\
2001 end\13\
2002\13\
2003 local last = matrix\13\
2004 for id = 1, #targets do\13\
2005 last.super = generateMatrix( targets[ id ], id )\13\
2006 last = last.super\13\
2007 end\13\
2008\13\
2009 martixReady = true\13\
2010 return matrix\13\
2011 end\13\
2012end\13\
2013local function mergeValues( a, b )\13\
2014 if type( a ) == \"table\" and type( b ) == \"table\" then\13\
2015 local merged = deepCopy( a ) or throw( \"Invalid base table for merging.\" )\13\
2016\13\
2017 if #b == 0 and next( b ) then\13\
2018 for key, value in pairs( b ) do merged[ key ] = value end\13\
2019 elseif #b > 0 then\13\
2020 for i = 1, #b do table.insert( merged, i, b[ i ] ) end\13\
2021 end\13\
2022\13\
2023 return merged\13\
2024 end\13\
2025\13\
2026 return b == nil and a or b\13\
2027end\13\
2028local constructorTargets = { \"orderedArguments\", \"requiredArguments\", \"argumentTypes\", \"useProxy\" }\13\
2029local function compileConstructor( superReg )\13\
2030 local constructorConfiguration = {}\13\
2031\13\
2032 local superConfig, currentConfig = superReg.constructor, currentRegistry.constructor\13\
2033 if not currentConfig and superConfig then\13\
2034 currentRegistry.constructor = superConfig\13\
2035 return\13\
2036 elseif currentConfig and not superConfig then\13\
2037 superConfig = {}\13\
2038 elseif not currentConfig and not superConfig then\13\
2039 return\13\
2040 end\13\
2041\13\
2042 local constructorKey\13\
2043 for i = 1, #constructorTargets do\13\
2044 constructorKey = constructorTargets[ i ]\13\
2045 if not ( ( constructorKey == \"orderedArguments\" and currentConfig.clearOrdered ) or ( constructorKey == \"requiredArguments\" and currentConfig.clearRequired ) ) then\13\
2046 currentConfig[ constructorKey ] = mergeValues( superConfig[ constructorKey ], currentConfig[ constructorKey ] )\13\
2047 end\13\
2048 end\13\
2049end\13\
2050local function compileCurrent()\13\
2051 isBuilding(\13\
2052 \"Cannot compile current class.\",\13\
2053 \"No class is being built at time of call. Declare a class be invoking 'compileCurrent'\"\13\
2054 )\13\
2055 local ownedKeys, ownedStatics, allMixins = currentRegistry.ownedKeys, currentRegistry.ownedStatics, currentRegistry.allMixins\13\
2056\13\
2057 -- Mixins\13\
2058 local cConstructor = currentRegistry.constructor\13\
2059 for target in pairs( currentRegistry.mixins ) do\13\
2060 allMixins[ target ] = true\13\
2061 local reg = classRegistry[ target ]\13\
2062\13\
2063 local t = { { reg.keys, currentRegistry.keys, ownedKeys }, { reg.static, currentRegistry.static, ownedStatics }, { reg.alias, currentRegistry.alias, currentRegistry.alias } }\13\
2064 for i = 1, #t do\13\
2065 local source, target, owned = t[ i ][ 1 ], t[ i ][ 2 ], t[ i ][ 3 ]\13\
2066 for key, value in pairs( source ) do\13\
2067 if not owned[ key ] then\13\
2068 target[ key ] = value\13\
2069 end\13\
2070 end\13\
2071 end\13\
2072\13\
2073 local constructor = reg.constructor\13\
2074 if constructor then\13\
2075 if constructor.clearOrdered then cConstructor.orderedArguments = nil end\13\
2076 if constructor.clearRequired then cConstructor.requiredArguments = nil end\13\
2077\13\
2078 local target\13\
2079 for i = 1, #constructorTargets do\13\
2080 target = constructorTargets[ i ]\13\
2081 cConstructor[ target ] = mergeValues( cConstructor[ target ], constructor[ target ] )\13\
2082 end\13\
2083 end\13\
2084 end\13\
2085\13\
2086 -- Supers\13\
2087 local superKeys\13\
2088 if currentRegistry.super then\13\
2089 local supers = {}\13\
2090\13\
2091 local last, c, newC = currentRegistry.super.target\13\
2092 while last do\13\
2093 c = getClass( last, true )\13\
2094\13\
2095 supers[ #supers + 1 ] = c\13\
2096 newC = classRegistry[ last ].super\13\
2097 last = newC and newC.target or false\13\
2098 end\13\
2099\13\
2100 superKeys, currentRegistry.super.matrix = compileSupers( supers )\13\
2101\13\
2102 -- Inherit alias from previous super\13\
2103 local currentAlias = currentRegistry.alias\13\
2104 for alias, redirect in pairs( classRegistry[ supers[ 1 ].__type ].alias ) do\13\
2105 if currentAlias[ alias ] == nil then\13\
2106 currentAlias[ alias ] = redirect\13\
2107 end\13\
2108 end\13\
2109\13\
2110 for mName in pairs( classRegistry[ supers[ 1 ].__type ].allMixins ) do\13\
2111 allMixins[ mName ] = true\13\
2112 end\13\
2113\13\
2114 compileConstructor( classRegistry[ supers[ 1 ].__type ] )\13\
2115 end\13\
2116\13\
2117 -- Generate instance function wrappers\13\
2118 local instanceWrappers, instanceVariables = {}, {}\13\
2119 for key, value in pairs( currentRegistry.keys ) do\13\
2120 if type( value ) == \"function\" then\13\
2121 instanceWrappers[ key ] = true\13\
2122 instanceVariables[ key ] = createFunctionWrapper( value, 1 )\13\
2123 else\13\
2124 instanceVariables[ key ] = value\13\
2125 end\13\
2126 end\13\
2127 if superKeys then\13\
2128 for key, value in pairs( superKeys ) do\13\
2129 if not instanceVariables[ key ] then\13\
2130 if type( value ) == \"function\" then\13\
2131 instanceWrappers[ key ] = true\13\
2132 instanceVariables[ key ] = function( _, ... ) return value( ... ) end\13\
2133 else\13\
2134 instanceVariables[ key ] = value\13\
2135 end\13\
2136 end\13\
2137 end\13\
2138 end\13\
2139\13\
2140 -- Finish compilation\13\
2141 currentRegistry.initialWrappers = instanceWrappers\13\
2142 currentRegistry.initialKeys = instanceVariables\13\
2143 currentRegistry.compiled = true\13\
2144\13\
2145 currentRegistry = nil\13\
2146 currentClass = nil\13\
2147\13\
2148end\13\
2149local function spawn( target, ... )\13\
2150 if not verifyClassEntry( target ) then\13\
2151 throw(\13\
2152 \"Failed to spawn class instance of '\"..tostring( target )..\"'\",\13\
2153 \"A class entity named '\"..tostring( target )..\"' doesn't exist.\"\13\
2154 )\13\
2155 end\13\
2156\13\
2157 local classEntry, classReg = classes[ target ], classRegistry[ target ]\13\
2158 if classReg.abstract or not classReg.compiled then\13\
2159 throw(\13\
2160 \"Failed to instantiate class '\"..classReg.type..\"'\",\13\
2161 \"Class '\"..classReg.type..\"' \"..(classReg.abstract and \"is abstract. Cannot instantiate abstract class.\" or \"has not been compiled. Cannot instantiate.\")\13\
2162 )\13\
2163 end\13\
2164\13\
2165 local wrappers, wrapperCache = deepCopy( classReg.initialWrappers ), {}\13\
2166 local raw = deepCopy( classReg.initialKeys )\13\
2167 local alias = classReg.alias\13\
2168\13\
2169 local instanceID = string.sub( tostring( raw ), 8 )\13\
2170\13\
2171 local supers = {}\13\
2172 local function indexSupers( last, ID )\13\
2173 while last.super do\13\
2174 supers[ ID ] = last.super\13\
2175 last = last.super\13\
2176 ID = ID + 1\13\
2177 end\13\
2178 end\13\
2179\13\
2180 local instanceObj, instanceMt = { raw = raw, __type = target, __instance = true }, { __metatable = {} }\13\
2181 local getting, useGetters, setting, useSetters = {}, true, {}, true\13\
2182 function instanceMt:__index( k )\13\
2183 local k = alias[ k ] or k\13\
2184\13\
2185 local getFn = getters[ k ]\13\
2186 if useGetters and not getting[ k ] and wrappers[ getFn ] then\13\
2187 getting[ k ] = true\13\
2188 local v = self[ getFn ]( self )\13\
2189 getting[ k ] = nil\13\
2190\13\
2191 return v\13\
2192 elseif wrappers[ k ] then\13\
2193 if not wrapperCache[ k ] then\13\
2194 wrapperCache[ k ] = function( ... )\13\
2195 --print(\"Wrapper for \"..k..\". Arguments: \"..( function( args ) local s = \"\"; for i = 1, #args do s = s .. \" - \" .. tostring( args[ i ] ) .. \"\\n\" end return s end )( { ... } ) )\13\
2196 return raw[ k ]( self, ... )\13\
2197 end\13\
2198 end\13\
2199\13\
2200 return wrapperCache[ k ]\13\
2201 else return raw[ k ] end\13\
2202 end\13\
2203\13\
2204 function instanceMt:__newindex( k, v )\13\
2205 local k = alias[ k ] or k\13\
2206\13\
2207 local setFn = setters[ k ]\13\
2208 if useSetters and not setting[ k ] and wrappers[ setFn ] then\13\
2209 setting[ k ] = true\13\
2210 self[ setFn ]( self, v )\13\
2211 setting[ k ] = nil\13\
2212 elseif type( v ) == \"function\" and useSetters then\13\
2213 wrappers[ k ] = true\13\
2214 raw[ k ] = createFunctionWrapper( v, 1 )\13\
2215 else\13\
2216 wrappers[ k ] = nil\13\
2217 raw[ k ] = v\13\
2218 end\13\
2219 end\13\
2220\13\
2221 function instanceMt:__tostring()\13\
2222 return \"[Instance] \"..target..\" (\"..instanceID..\")\"\13\
2223 end\13\
2224\13\
2225 if classReg.super then\13\
2226 instanceObj.super = classReg.super.matrix( instanceObj ).super\13\
2227 indexSupers( instanceObj, 1 )\13\
2228 end\13\
2229\13\
2230 local old\13\
2231 function instanceObj:setSuper( target )\13\
2232 old, instanceObj.super = instanceObj.super, supers[ target ]\13\
2233 return old\13\
2234 end\13\
2235\13\
2236 local function setSymKey( key, value )\13\
2237 useSetters = false\13\
2238 instanceObj[ key ] = value\13\
2239 useSetters = true\13\
2240 end\13\
2241\13\
2242 local resolved\13\
2243 local resolvedArguments = {}\13\
2244 function instanceObj:resolve( ... )\13\
2245 if resolved then return false end\13\
2246\13\
2247 local args, config = { ... }, classReg.constructor\13\
2248 if not config then\13\
2249 throw(\"Failed to resolve \"..tostring( instance )..\" constructor arguments. No configuration has been set via 'configureConstructor'.\")\13\
2250 end\13\
2251\13\
2252 local configRequired, configOrdered, configTypes, configProxy = config.requiredArguments, config.orderedArguments, config.argumentTypes or {}, config.useProxy or {}\13\
2253\13\
2254 local argumentsRequired = {}\13\
2255 if configRequired then\13\
2256 local target = type( configRequired ) == \"table\" and configRequired or configOrdered\13\
2257\13\
2258 for i = 1, #target do argumentsRequired[ target[ i ] ] = true end\13\
2259 end\13\
2260\13\
2261 local orderedMatrix = {}\13\
2262 for i = 1, #configOrdered do orderedMatrix[ configOrdered[ i ] ] = i end\13\
2263\13\
2264 local proxyAll, proxyMatrix = type( configProxy ) == \"boolean\" and configProxy, {}\13\
2265 if not proxyAll then\13\
2266 for i = 1, #configProxy do proxyMatrix[ configProxy[ i ] ] = true end\13\
2267 end\13\
2268\13\
2269 local function handleArgument( position, name, value )\13\
2270 local desiredType = configTypes[ name ]\13\
2271 desiredType = (desiredType and desiredType == \"colour\" or desiredType == \"color\") and \"number\" or desiredType --TODO: Check if number is valid (maybe?)\13\
2272\13\
2273 if desiredType and type( value ) ~= desiredType then\13\
2274 return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. Invalid type for argument '\"..name..\"'. Type \"..configTypes[ name ]..\" expected, \"..type( value )..\" was received.\")\13\
2275 end\13\
2276\13\
2277 resolvedArguments[ name ], argumentsRequired[ name ] = true, nil\13\
2278 if proxyAll or proxyMatrix[ name ] then\13\
2279 self[ name ] = value\13\
2280 else\13\
2281 setSymKey( name, value )\13\
2282 end\13\
2283 end\13\
2284\13\
2285 for iter, value in pairs( args ) do\13\
2286 if configOrdered[ iter ] then\13\
2287 handleArgument( iter, configOrdered[ iter ], value )\13\
2288 elseif type( value ) == \"table\" then\13\
2289 for key, v in pairs( value ) do\13\
2290 handleArgument( orderedMatrix[ key ], key, v )\13\
2291 end\13\
2292 else\13\
2293 return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. Invalid argument found at ordered position \"..iter..\".\")\13\
2294 end\13\
2295 end\13\
2296\13\
2297 if next( argumentsRequired ) then\13\
2298 local str, name = \"\"\13\
2299 local function append( cnt )\13\
2300 str = str ..\"- \"..cnt..\"\\n\"\13\
2301 end\13\
2302\13\
2303 return throw(\"Failed to resolve '\"..tostring( target )..\"' constructor arguments. The following required arguments were not provided:\\n\\n\"..(function()\13\
2304 str = \"Ordered:\\n\"\13\
2305 for i = 1, #configOrdered do\13\
2306 name = configOrdered[ i ]\13\
2307 if argumentsRequired[ name ] then\13\
2308 append( name .. \" [#\"..i..\"]\" )\13\
2309 argumentsRequired[ name ] = nil\13\
2310 end\13\
2311 end\13\
2312\13\
2313 if next( argumentsRequired ) then\13\
2314 str = str .. \"\\nTrailing:\\n\"\13\
2315 for name, _ in pairs( argumentsRequired ) do append( name ) end\13\
2316 end\13\
2317\13\
2318 return str\13\
2319 end)())\13\
2320 end\13\
2321\13\
2322 resolved = true\13\
2323 return true\13\
2324 end\13\
2325 instanceObj.__resolved = resolvedArguments\13\
2326\13\
2327 function instanceObj:can( method )\13\
2328 return wrappers[ method ] or false\13\
2329 end\13\
2330\13\
2331 local locked = { __index = true, __newindex = true }\13\
2332 function instanceObj:setMetaMethod( method, fn )\13\
2333 if type( method ) ~= \"string\" then\13\
2334 throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Expected string for argument #1, got '\"..tostring( method )..\"' of type \"..type( method ) )\13\
2335 elseif type( fn ) ~= \"function\" then\13\
2336 throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Expected function for argument #2, got '\"..tostring( fn )..\"' of type \"..type( fn ) )\13\
2337 end\13\
2338\13\
2339 method = \"__\"..method\13\
2340 if locked[ method ] then\13\
2341 throw( \"Failed to set metamethod '\"..tostring( method )..\"'\", \"Metamethod locked\" )\13\
2342 end\13\
2343\13\
2344 instanceMt[ method ] = fn\13\
2345 end\13\
2346\13\
2347 function instanceObj:lockMetaMethod( method )\13\
2348 if type( method ) ~= \"string\" then\13\
2349 throw( \"Failed to lock metamethod '\"..tostring( method )..\"'\", \"Expected string, got '\"..tostring( method )..\"' of type \"..type( method ) )\13\
2350 end\13\
2351\13\
2352 locked[ \"__\"..method ] = true\13\
2353 end\13\
2354\13\
2355 setmetatable( instanceObj, instanceMt )\13\
2356 if type( instanceObj.__init__ ) == \"function\" then instanceObj:__init__( ... ) end\13\
2357\13\
2358 for mName in pairs( classReg.allMixins ) do\13\
2359 if type( instanceObj[ mName ] ) == \"function\" then instanceObj[ mName ]( instanceObj ) end\13\
2360 end\13\
2361\13\
2362 if type( instanceObj.__postInit__ ) == \"function\" then instanceObj:__postInit__( ... ) end\13\
2363\13\
2364 return instanceObj\13\
2365end\13\
2366\13\
2367\13\
2368--[[ Global functions ]]--\13\
2369\13\
2370function class( name )\13\
2371 if isBuilding() then\13\
2372 throw(\13\
2373 \"Failed to declare class '\"..tostring( name )..\"'\",\13\
2374 \"A new class cannot be declared until the currently building class has been compiled.\",\13\
2375 \"\\nCompile '\"..tostring( currentRegistry.type )..\"' before declaring '\"..tostring( name )..\"'\"\13\
2376 )\13\
2377 end\13\
2378\13\
2379 local function nameErr( reason )\13\
2380 throw( \"Failed to declare class '\"..tostring( name )..\"'\\n\", string.format( \"Class name %s is not valid. %s\", tostring( name ), reason ) )\13\
2381 end\13\
2382\13\
2383 if type( name ) ~= \"string\" then\13\
2384 nameErr \"Class names must be a string\"\13\
2385 elseif not name:find \"%a\" then\13\
2386 nameErr \"No alphabetic characters could be found\"\13\
2387 elseif name:find \"%d\" then\13\
2388 nameErr \"Class names cannot contain digits\"\13\
2389 elseif classes[ name ] then\13\
2390 nameErr \"A class with that name already exists\"\13\
2391 elseif reserved[ name ] then\13\
2392 nameErr (\"'\"..name..\"' is reserved for Titanium processes\")\13\
2393 else\13\
2394 local char = name:sub( 1, 1 )\13\
2395 if char ~= char:upper() then\13\
2396 nameErr \"Class names must begin with an uppercase character\"\13\
2397 end\13\
2398 end\13\
2399\13\
2400 local classReg = {\13\
2401 type = name,\13\
2402\13\
2403 static = {},\13\
2404 keys = {},\13\
2405 ownedStatics = {},\13\
2406 ownedKeys = {},\13\
2407\13\
2408 initialWrappers = {},\13\
2409 initialKeys = {},\13\
2410\13\
2411 mixins = {},\13\
2412 allMixins = {},\13\
2413 alias = {},\13\
2414\13\
2415 constructor = false,\13\
2416 super = false,\13\
2417\13\
2418 compiled = false,\13\
2419 abstract = false\13\
2420 }\13\
2421\13\
2422 -- Class metatable\13\
2423 local classMt = { __metatable = {} }\13\
2424 function classMt:__tostring()\13\
2425 return (classReg.compiled and \"[Compiled] \" or \"\") .. \"Class '\"..name..\"'\"\13\
2426 end\13\
2427\13\
2428 local keys, owned = classReg.keys, classReg.ownedKeys\13\
2429 local staticKeys, staticOwned = classReg.static, classReg.ownedStatics\13\
2430 function classMt:__newindex( k, v )\13\
2431 if classReg.compiled then\13\
2432 throw(\13\
2433 \"Failed to set key on class base.\", \"\",\13\
2434 \"This class base is compiled, once a class base is compiled new keys cannot be added to it\",\13\
2435 \"\\nPerhaps you meant to set the static key '\"..name..\".static.\"..k..\"' instead.\"\13\
2436 )\13\
2437 end\13\
2438\13\
2439 keys[ k ] = v\13\
2440 owned[ k ] = type( v ) == \"nil\" and nil or true\13\
2441 end\13\
2442 function classMt:__index( k )\13\
2443 if owned[ k ] then\13\
2444 throw (\13\
2445 \"Access to key '\"..k..\"' denied.\",\13\
2446 \"Instance keys cannot be accessed from a class base, regardless of compiled state\",\13\
2447 classReg.ownedStatics[ k ] and \"\\nPerhaps you meant to access the static variable '\" .. name .. \".static.\".. k .. \"' instead\" or nil\13\
2448 )\13\
2449 elseif staticOwned[ k ] then\13\
2450 return staticKeys[ k ]\13\
2451 end\13\
2452 end\13\
2453 function classMt:__call( ... )\13\
2454 return spawn( name, ... )\13\
2455 end\13\
2456\13\
2457 -- Static metatable\13\
2458 local staticMt = { __index = staticKeys }\13\
2459 function staticMt:__newindex( k, v )\13\
2460 staticKeys[ k ] = v\13\
2461 staticOwned[ k ] = type( v ) == \"nil\" and nil or true\13\
2462 end\13\
2463\13\
2464 -- Class object\13\
2465 local classObj = { __type = name }\13\
2466 classObj.static = setmetatable( {}, staticMt )\13\
2467 classObj.compile = compileCurrent\13\
2468\13\
2469 function classObj:isCompiled() return classReg.compiled end\13\
2470\13\
2471 function classObj:getRegistry() return classReg end\13\
2472\13\
2473 setmetatable( classObj, classMt )\13\
2474\13\
2475 -- Export\13\
2476 currentRegistry = classReg\13\
2477 classRegistry[ name ] = classReg\13\
2478\13\
2479 currentClass = classObj\13\
2480 classes[ name ] = classObj\13\
2481\13\
2482 _G[ name ] = classObj\13\
2483\13\
2484 return propertyCatch\13\
2485end\13\
2486\13\
2487function extends( name )\13\
2488 isBuilding(\13\
2489 string.format( ERROR_GLOBAL, \"extend\", \"target class '\"..tostring( name )..\"'\" ), \"\",\13\
2490 string.format( ERROR_NOT_BUILDING, \"extends\" )\13\
2491 )\13\
2492\13\
2493 currentRegistry.super = {\13\
2494 target = name\13\
2495 }\13\
2496 return propertyCatch\13\
2497end\13\
2498\13\
2499function mixin( name )\13\
2500 if type( name ) ~= \"string\" then\13\
2501 throw(\"Invalid mixin target '\"..tostring( name )..\"'\")\13\
2502 end\13\
2503\13\
2504 isBuilding(\13\
2505 string.format( ERROR_GLOBAL, \"mixin\", \"target class '\".. name ..\"'\" ),\13\
2506 string.format( ERROR_NOT_BUILDING, \"mixin\" )\13\
2507 )\13\
2508\13\
2509 local mixins = currentRegistry.mixins\13\
2510 if mixins[ name ] then\13\
2511 throw(\13\
2512 string.format( ERROR_GLOBAL, \"mixin class '\".. name ..\"'\", \"class '\"..currentRegistry.type)\13\
2513 \"'\".. name ..\"' has already been mixed in to this target class.\"\13\
2514 )\13\
2515 end\13\
2516\13\
2517 if not getClass( name, true ) then\13\
2518 throw(\13\
2519 string.format( ERROR_GLOBAL, \"mixin class '\".. name ..\"'\", \"class '\"..currentRegistry.type ),\13\
2520 \"The mixin class '\".. name ..\"' failed to load\"\13\
2521 )\13\
2522 end\13\
2523\13\
2524 mixins[ name ] = true\13\
2525 return propertyCatch\13\
2526end\13\
2527\13\
2528function abstract()\13\
2529 isBuilding(\13\
2530 \"Failed to enforce abstract class policy\\n\",\13\
2531 string.format( ERROR_NOT_BUILDING, \"abstract\" )\13\
2532 )\13\
2533\13\
2534 currentRegistry.abstract = true\13\
2535 return propertyCatch\13\
2536end\13\
2537\13\
2538function alias( target )\13\
2539 local FAIL_MSG = \"Failed to implement alias targets\\n\"\13\
2540 isBuilding( FAIL_MSG, string.format( ERROR_NOT_BUILDING, \"alias\" ) )\13\
2541\13\
2542 local tbl = type( target ) == \"table\" and target or (\13\
2543 type( target ) == \"string\" and (\13\
2544 type( _G[ target ] ) == \"table\" and _G[ target ] or throw( FAIL_MSG, \"Failed to find '\"..tostring( target )..\"' table in global environment.\" )\13\
2545 ) or throw( FAIL_MSG, \"Expected type table as target, got '\"..tostring( target )..\"' of type \"..type( target ) )\13\
2546 )\13\
2547\13\
2548 local cAlias = currentRegistry.alias\13\
2549 for alias, redirect in pairs( tbl ) do\13\
2550 cAlias[ alias ] = redirect\13\
2551 end\13\
2552\13\
2553 return propertyCatch\13\
2554end\13\
2555\13\
2556function configureConstructor( config, clearOrdered, clearRequired )\13\
2557 isBuilding(\13\
2558 \"Failed to configure class constructor\\n\",\13\
2559 string.format( ERROR_NOT_BUILDING, \"configureConstructor\" )\13\
2560 )\13\
2561\13\
2562 if type( config ) ~= \"table\" then\13\
2563 throw (\13\
2564 \"Failed to configure class constructor\\n\",\13\
2565 \"Expected type 'table' as first argument\"\13\
2566 )\13\
2567 end\13\
2568\13\
2569 local constructor = {\13\
2570 clearOrdered = clearOrdered or nil,\13\
2571 clearRequired = clearRequired or nil\13\
2572 }\13\
2573 for key, value in pairs( config ) do constructor[ key ] = value end\13\
2574\13\
2575 currentRegistry.constructor = constructor\13\
2576 return propertyCatch\13\
2577end\13\
2578\13\
2579--[[ Class Library ]]--\13\
2580Titanium = {}\13\
2581\13\
2582function Titanium.getGetterName( property ) return getters[ property ] end\13\
2583\13\
2584function Titanium.getSetterName( property ) return setters[ property ] end\13\
2585\13\
2586function Titanium.getClass( name )\13\
2587 return classes[ name ]\13\
2588end\13\
2589\13\
2590function Titanium.getClasses()\13\
2591 return classes\13\
2592end\13\
2593\13\
2594function Titanium.isClass( target )\13\
2595 return type( target ) == \"table\" and type( target.__type ) == \"string\" and verifyClassEntry( target.__type )\13\
2596end\13\
2597\13\
2598function Titanium.isInstance( target )\13\
2599 return Titanium.isClass( target ) and target.__instance\13\
2600end\13\
2601\13\
2602function Titanium.typeOf( target, classType, instance )\13\
2603 if not Titanium.isClass( target ) or ( instance and not Titanium.isInstance( target ) ) then\13\
2604 return false\13\
2605 end\13\
2606\13\
2607 local targetReg = classRegistry[ target.__type ]\13\
2608\13\
2609 return targetReg.type == classType or ( targetReg.super and Titanium.typeOf( classes[ targetReg.super.target ], classType ) ) or false\13\
2610end\13\
2611\13\
2612function Titanium.mixesIn( target, mixinName )\13\
2613 if not Titanium.isClass( target ) then return false end\13\
2614\13\
2615 return classRegistry[ target.__type ].allMixins[ mixinName ]\13\
2616end\13\
2617\13\
2618function Titanium.setClassLoader( fn )\13\
2619 if type( fn ) ~= \"function\" then\13\
2620 throw( \"Failed to set class loader\", \"Value '\"..tostring( fn )..\"' is invalid, expected function\" )\13\
2621 end\13\
2622\13\
2623 missingClassLoader = fn\13\
2624end\13\
2625\13\
2626local preprocessTargets = {\"class\", \"extends\", \"alias\", \"mixin\"}\13\
2627function Titanium.preprocess( text )\13\
2628 local keyword\13\
2629 for i = 1, #preprocessTargets do\13\
2630 keyword = preprocessTargets[ i ]\13\
2631\13\
2632 for value in text:gmatch( keyword .. \" ([_%a][_%w]*)%s\" ) do\13\
2633 text = text:gsub( keyword .. \" \" .. value, keyword..\" \\\"\"..value..\"\\\"\" )\13\
2634 end\13\
2635 end\13\
2636\13\
2637 for name in text:gmatch( \"abstract class (\\\".[^%s]+\\\")\" ) do\13\
2638 text = text:gsub( \"abstract class \"..name, \"class \"..name..\" abstract()\" )\13\
2639 end\13\
2640\13\
2641 return text\13\
2642end\13\
2643",
2644["RadioButton.ti"]="class \"RadioButton\" extends \"Checkbox\" {\13\
2645 static = {\13\
2646 groups = {}\13\
2647 };\13\
2648\13\
2649 group = false;\13\
2650}\13\
2651\13\
2652function RadioButton:__init__( ... )\13\
2653 self:super( ... )\13\
2654\13\
2655 if self.toggled then\13\
2656 RadioButton.deselectInGroup( self.group, self )\13\
2657 end\13\
2658end\13\
2659\13\
2660function RadioButton:select( ... )\13\
2661 RadioButton.deselectInGroup( self.group )\13\
2662\13\
2663 self.toggled = true\13\
2664 self:executeCallbacks \"select\"\13\
2665end\13\
2666\13\
2667function RadioButton:onMouseUp( event, handled, within )\13\
2668 if not handled and within and self.active then\13\
2669 self:select( event, handled, within )\13\
2670\13\
2671 event.handled = true\13\
2672 end\13\
2673\13\
2674 self.active = false\13\
2675end\13\
2676\13\
2677function RadioButton:onLabelClicked( label, event, handled, within )\13\
2678 self:select( event, handled, within, label )\13\
2679 event.handled = true\13\
2680end\13\
2681\13\
2682function RadioButton:setGroup( group )\13\
2683 if self.group then\13\
2684 RadioButton.removeFromGroup( self, self.group )\13\
2685 end\13\
2686 self.group = group\13\
2687\13\
2688 RadioButton.addToGroup( self, group )\13\
2689end\13\
2690\13\
2691function RadioButton.static.addToGroup( node, group )\13\
2692 local g = RadioButton.groups[ group ]\13\
2693 if type( g ) == \"table\" then\13\
2694 RadioButton.removeFromGroup( node, group )\13\
2695\13\
2696 table.insert( g, node )\13\
2697 else\13\
2698 RadioButton.groups[ group ] = { node }\13\
2699 end\13\
2700end\13\
2701\13\
2702function RadioButton.static.removeFromGroup( node, group )\13\
2703 local index = RadioButton.isInGroup( node, group )\13\
2704 if index then\13\
2705 table.remove( RadioButton.groups[ group ], index )\13\
2706\13\
2707 if #RadioButton.groups[ group ] == 0 then\13\
2708 RadioButton.groups[ group ] = nil\13\
2709 end\13\
2710 end\13\
2711end\13\
2712\13\
2713function RadioButton.static.isInGroup( node, group )\13\
2714 local g = RadioButton.groups[ group ]\13\
2715 for i = 1, #g do\13\
2716 if g[ i ] == node then return i end\13\
2717 end\13\
2718\13\
2719 return false\13\
2720end\13\
2721\13\
2722function RadioButton.static.deselectInGroup( group, target )\13\
2723 local g = RadioButton.groups[ group ]\13\
2724\13\
2725 for i = 1, #g do if ( not target or ( target and g[ i ] ~= target ) ) then g[ i ].toggled = false end end\13\
2726end\13\
2727\13\
2728function RadioButton.static.getValue( group )\13\
2729 local g = RadioButton.groups[ group ]\13\
2730 if g then\13\
2731 local radio\13\
2732 for i = 1, #g do\13\
2733 radio = g[ i ]\13\
2734 if radio.toggled then return radio.value end\13\
2735 end\13\
2736 end\13\
2737end\13\
2738\13\
2739configureConstructor({\13\
2740 orderedArguments = { \"X\", \"Y\", \"group\" },\13\
2741 requiredArguments = { \"group\" },\13\
2742 argumentTypes = { group = \"string\" },\13\
2743 useProxy = { \"group\" }\13\
2744}, true, true )\13\
2745",
2746["Application.ti"]="--[[\13\
2747 An Application object is the entry point to a Titanium Application.\13\
2748 The program loop, nodes, threads and animations are handled by the Application object.\13\
2749--]]\13\
2750\13\
2751class \"Application\" extends \"Component\" mixin \"MThemeManager\" mixin \"MKeyHandler\" mixin \"MCallbackManager\" mixin \"MAnimationManager\" mixin \"MNodeContainer\" {\13\
2752 width = 51;\13\
2753 height = 19;\13\
2754\13\
2755 threads = {};\13\
2756 nodes = {};\13\
2757\13\
2758 running = false;\13\
2759 terminatable = false;\13\
2760}\13\
2761\13\
2762--[[\13\
2763 @constructor\13\
2764 @desc Constructs an instance of the Application by setting all necessary unique properties on it\13\
2765 @param [number - width], [number - height]\13\
2766 @return <nil>\13\
2767]]\13\
2768function Application:__init__( ... )\13\
2769 self:resolve( ... )\13\
2770 self.canvas = TermCanvas( self )\13\
2771\13\
2772 self:setMetaMethod(\"add\", function( a, b )\13\
2773 local t = a ~= self and a or b\13\
2774\13\
2775 if Titanium.typeOf( t, \"Node\", true ) then\13\
2776 return self:addNode( t )\13\
2777 elseif Titanium.typeOf( t, \"Thread\", true ) then\13\
2778 return self:addThread( t )\13\
2779 end\13\
2780\13\
2781 error \"Invalid targets for application '__add'. Expected node or thread.\"\13\
2782 end)\13\
2783end\13\
2784\13\
2785function Application:focusNode( node )\13\
2786 if not Titanium.typeOf( node, \"Node\", true ) then\13\
2787 return error \"Failed to update application focused node. Invalid node object passed.\"\13\
2788 end\13\
2789\13\
2790 self:unfocusNode()\13\
2791 self.focusedNode = node\13\
2792 node.changed = true\13\
2793\13\
2794\13\
2795 node:executeCallbacks( \"focus\", self )\13\
2796end\13\
2797\13\
2798function Application:unfocusNode()\13\
2799 local node = self.focusedNode\13\
2800 if not node then return end\13\
2801\13\
2802 self.focusedNode = nil\13\
2803\13\
2804 node.raw.focused = false\13\
2805 node.changed = true\13\
2806\13\
2807 node:executeCallbacks( \"unfocus\", self )\13\
2808end\13\
2809\13\
2810--[[\13\
2811 @instance\13\
2812 @desc Adds a new thread named 'name' running 'func'. This thread will receive events caught by the Application engine\13\
2813 @param <threadObj - Thread Instance>\13\
2814 @return [threadObj | error]\13\
2815]]\13\
2816function Application:addThread( threadObj )\13\
2817 if not Titanium.typeOf( threadObj, \"Thread\", true ) then\13\
2818 error( \"Failed to add thread, object '\"..tostring( threadObj )..\"' is invalid. Thread Instance required\")\13\
2819 end\13\
2820\13\
2821 table.insert( self.threads, threadObj )\13\
2822\13\
2823 return threadObj\13\
2824end\13\
2825\13\
2826--[[\13\
2827 @instance\13\
2828 @desc Removes the thread named 'name'*\13\
2829 @param <Instance 'Thread'/string name - target>\13\
2830 @return <boolean - success>, [node - removedThread**]\13\
2831\13\
2832 *Note: In order for the thread to be removed its 'id' field must match the 'id' parameter.\13\
2833 **Note: Removed thread will only be returned if a thread was removed (and thus success 'true')\13\
2834]]\13\
2835function Application:removeThread( target )\13\
2836 if not Titanium.typeOf( target, \"Thread\", true ) then\13\
2837 return error( \"Cannot perform search for thread using target '\"..tostring( target )..\"'.\" )\13\
2838 end\13\
2839\13\
2840 local searchID = type( target ) == \"string\"\13\
2841 local threads, thread, threadID = self.threads\13\
2842 for i = 1, #threads do\13\
2843 thread = threads[ i ]\13\
2844\13\
2845 if ( searchID and thread.id == target ) or ( not searchID and thread == target ) then\13\
2846 thread:stop()\13\
2847\13\
2848 table.remove( threads, i )\13\
2849 return true, thread\13\
2850 end\13\
2851 end\13\
2852\13\
2853 return false\13\
2854end\13\
2855\13\
2856--[[\13\
2857 @instance\13\
2858 @desc Ships events to threads, if the thread requires a Titanium event, that will be passed instead.\13\
2859 @param <AnyEvent - eventObj>, <vararg - eData>\13\
2860]]\13\
2861function Application:handleThreads( eventObj, ... )\13\
2862 local threads = self.threads\13\
2863\13\
2864 local thread\13\
2865 for i = 1, #threads do\13\
2866 thread = threads[ i ]\13\
2867\13\
2868 if thread.titaniumEvents then\13\
2869 thread:handle( eventObj )\13\
2870 else\13\
2871 thread:handle( ... )\13\
2872 end\13\
2873 end\13\
2874end\13\
2875\13\
2876--[[\13\
2877 @instance\13\
2878 @desc Appends nodes loaded via TML to the Applications nodes.\13\
2879 @param <string - path>\13\
2880]]\13\
2881function Application:importFromTML( path )\13\
2882 TML.fromFile( self, path )\13\
2883 self.changed = true\13\
2884end\13\
2885\13\
2886--[[\13\
2887 @instance\13\
2888 @desc Removes all nodes from the Application and inserts those loaded via TML\13\
2889 @param <string - path>\13\
2890]]\13\
2891function Application:replaceWithTML( path )\13\
2892 local nodes, node = self.nodes\13\
2893 for i = #nodes, 1, -1 do\13\
2894 node = nodes[ i ]\13\
2895 node.parent = nil\13\
2896 node.application = nil\13\
2897\13\
2898 table.remove( nodes, i )\13\
2899 end\13\
2900\13\
2901 self:importFromTML( path )\13\
2902end\13\
2903\13\
2904--[[\13\
2905 @instance\13\
2906 @desc Begins the program loop\13\
2907]]\13\
2908function Application:start()\13\
2909 -- local animationTimer = timer.new( 0 )\13\
2910\13\
2911 self.running = true\13\
2912 while self.running do\13\
2913 self:draw()\13\
2914 local event = { coroutine.yield() }\13\
2915 local eName = event[ 1 ]\13\
2916\13\
2917 if eName == \"timer\" and event[ 2 ] == self.timer then\13\
2918 -- animationTimer = timer.new( .5 )\13\
2919 self:updateAnimations()\13\
2920 elseif eName == \"terminate\" and self.terminatable then\13\
2921 printError \"Application Terminated\"\13\
2922 self:stop()\13\
2923 end\13\
2924\13\
2925 self:handle( unpack( event ) )\13\
2926 end\13\
2927end\13\
2928\13\
2929--[[\13\
2930 @instance\13\
2931 @desc Draws changed nodes (or all nodes if 'force' is true)\13\
2932 @param [boolean - force]\13\
2933]]\13\
2934function Application:draw( force )\13\
2935 if not self.changed then return end\13\
2936\13\
2937 local canvas = self.canvas\13\
2938 local nodes, node = self.nodes\13\
2939\13\
2940 canvas:clear()\13\
2941 for i = 1, #nodes do\13\
2942 node = nodes[ i ]\13\
2943 node:draw( force )\13\
2944\13\
2945 node.canvas:drawTo( canvas, node.X, node.Y )\13\
2946 end\13\
2947 self.changed = false\13\
2948\13\
2949 local focusedNode, caretEnabled, caretX, caretY, caretColour = self.focusedNode\13\
2950 if focusedNode and focusedNode:can \"getCaretInfo\" then\13\
2951 caretEnabled, caretX, caretY, caretColour = focusedNode:getCaretInfo()\13\
2952 end\13\
2953\13\
2954 term.setCursorBlink( caretEnabled or false )\13\
2955 canvas:draw()\13\
2956\13\
2957 if caretEnabled then\13\
2958 term.setTextColour( caretColour or self.colour or 32768 )\13\
2959 term.setCursorPos( caretX or 1, caretY or 1 )\13\
2960 end\13\
2961end\13\
2962\13\
2963--[[\13\
2964 @instance\13\
2965 @desc Spawns a Titanium event instance and ships it to nodes and threads.\13\
2966 @param <table - event>\13\
2967]]\13\
2968function Application:handle( eName, ... )\13\
2969 local eventObject = Event.spawn( eName, ... )\13\
2970 if eventObject.main == \"KEY\" then self:handleKey( eventObject ) end\13\
2971\13\
2972 local nodes, node = self.nodes\13\
2973 for i = #nodes, 1, -1 do\13\
2974 node = nodes[ i ]\13\
2975 -- The node will update itself depending on the event. Once all are updated they are drawn if changed.\13\
2976 if node then node:handle( eventObject ) end\13\
2977 end\13\
2978\13\
2979 self:executeCallbacks( eName, ... )\13\
2980 self:handleThreads( eventObject, eName, ... )\13\
2981end\13\
2982\13\
2983--[[\13\
2984 @instance\13\
2985 @desc Stops the program loop\13\
2986]]\13\
2987function Application:stop()\13\
2988 if self.running then\13\
2989 self.running = false\13\
2990 os.queueEvent( \"ti_app_close\" )\13\
2991 else\13\
2992 return error \"Application already stopped\"\13\
2993 end\13\
2994end\13\
2995",
2996["Event.ti"]="class \"Event\" abstract() {\13\
2997 static = {\13\
2998 matrix = {}\13\
2999 }\13\
3000}\13\
3001\13\
3002--[[\13\
3003 @instance\13\
3004 @desc Returns true if the event name (index '1' of data) matches the parameter 'event' provided\13\
3005 @param <string - event>\13\
3006 @return <boolean - eq>\13\
3007]]\13\
3008function Event:is( event )\13\
3009 return self.name == event\13\
3010end\13\
3011\13\
3012--[[\13\
3013 @instance\13\
3014 @desc Sets the 'handled' paramater to true. This indicates the event has been used and should not be used.\13\
3015]]\13\
3016function Event:setHandled( handled )\13\
3017 self.raw.handled = handled\13\
3018end\13\
3019\13\
3020--[[\13\
3021 @static\13\
3022 @desc Instantiates an event object if an entry for that event type is present inside the event matrix.\13\
3023 @param <string - eventName>, [vararg - eventData]\13\
3024 @return <Instance*>\13\
3025\13\
3026 *Note: The type of instance is variable. If an entry is present inside the matrix that class \"will\" be\13\
3027 instantiated, otherwise a 'GenericEvent' instance will be returned.\13\
3028]]\13\
3029function Event.static.spawn( name, ... )\13\
3030 return ( Event.matrix[ name ] or GenericEvent )( name, ... )\13\
3031end\13\
3032\13\
3033--[[\13\
3034 @static\13\
3035 @desc Adds an entry to the event matrix. When an event named 'name' is caught, the class 'clasType' will be instantiated\13\
3036 @param <string - name>, <string - classType>\13\
3037]]\13\
3038function Event.static.bindEvent( name, classType )\13\
3039 Event.matrix[ name ] = Titanium.getClass( classType ) or error( \"Class '\"..tostring( classType )..\"' cannot be found\" )\13\
3040end\13\
3041\13\
3042--[[\13\
3043 @static\13\
3044 @desc Removes an entry from the event matrix.\13\
3045 @param <string - name>\13\
3046]]\13\
3047function Event.static.unbindEvent( name )\13\
3048 Event.matrix[ name ] = nil\13\
3049end\13\
3050",
3051["MThemeManager.ti"]="--[[\13\
3052 The MThemeManager mixin \"should\" be used by classes that want to manage objects which are themeable, the main example being the 'Application' class.\13\
3053]]\13\
3054\13\
3055class \"MThemeManager\" abstract() {\13\
3056 themes = {}\13\
3057}\13\
3058\13\
3059--[[\13\
3060 @instance\13\
3061 @desc Adds the given theme into this objects `themes` table\13\
3062 @param <Theme Instance - theme>\13\
3063]]\13\
3064function MThemeManager:addTheme( theme )\13\
3065 self:removeTheme( theme )\13\
3066 table.insert( self.themes, theme )\13\
3067\13\
3068 self:update()\13\
3069end\13\
3070\13\
3071--[[\13\
3072 @instance\13\
3073 @desc Removes the given theme from this objects `themes` table. Returns true if a theme was removed, false otherwise.\13\
3074 @param <Instance 'Theme'/string name - target>\13\
3075 @return <boolean - success>\13\
3076]]\13\
3077function MThemeManager:removeTheme( target )\13\
3078 local searchName = ( type( target ) == \"string\" and true ) or ( not Titanium.typeOf( target, \"Theme\", true ) and error \"Invalid target to remove\" )\13\
3079 local themes = self.themes\13\
3080 for i = 1, #themes do\13\
3081 if ( searchName and themes[ i ].name == target ) or ( not searchName and themes[ i ] == target ) then\13\
3082 table.remove( themes, i )\13\
3083 self:update()\13\
3084\13\
3085 return true\13\
3086 end\13\
3087 end\13\
3088end\13\
3089\13\
3090--[[\13\
3091 @instance\13\
3092 @desc Adds a theme instance named 'name' and imports the file contents from 'location' to this object\13\
3093 @param <string - name>, <string - location>\13\
3094]]\13\
3095function MThemeManager:importTheme( name, location )\13\
3096 self:addTheme( Theme.fromFile( name, location ) )\13\
3097end\13\
3098\13\
3099--[[\13\
3100 @instance\13\
3101 @desc Updates the theme information bound to this object. All nodes under this parent are reset and the rules are reapplied to each.\13\
3102 'onThemeUpdate' is called on each child node allowing for each node to update themselves after their new theme information has been applied.\13\
3103 @param [table - target]\13\
3104]]\13\
3105function MThemeManager:update( target )\13\
3106 self:cacheAllApplicableRules()\13\
3107\13\
3108 local target = target or self.collatedNodes\13\
3109 for i = 1, #target do target[ i ]:onThemeUpdate() end\13\
3110end\13\
3111\13\
3112--[[\13\
3113 @instance\13\
3114 @desc Resets the applicableRules table on all nodes of type 'type' that are under this node (collated/query)\13\
3115 @param <string - type>\13\
3116]]\13\
3117function MThemeManager:resetApplicableRules( type )\13\
3118 local nodes = type == \"*\" and self.collatedNodes or self:query( type ).result\13\
3119 for i = 1, #nodes do\13\
3120 nodes[ i ].applicableRules = {}\13\
3121 end\13\
3122end\13\
3123\13\
3124--[[\13\
3125 @instance\13\
3126 @desc Iterates over the rules given and applies them as 'applicable rules' to each eligible node (collated/query).\13\
3127 These 'applicable rules' are then used by the node to determine theme properties.\13\
3128 @param <table - rules>\13\
3129]]\13\
3130function MThemeManager:cacheApplicableRules( rules )\13\
3131 local rule\13\
3132\13\
3133 for i = 1, #rules do\13\
3134 rule = rules[ i ]\13\
3135\13\
3136 self:query( rule.query ):each(function( item )\13\
3137 local applicable = item.applicableRules\13\
3138 applicable[ #applicable + 1 ] = rule\13\
3139 end)\13\
3140 end\13\
3141end\13\
3142\13\
3143--[[\13\
3144 @instance\13\
3145 @desc Iterates over each theme bound to this object and caches the rules for type 'type' by passing them to 'MThemeManager.cacheApplicableRules'\13\
3146 @param <string - type>, [boolean - manualReset]\13\
3147]]\13\
3148function MThemeManager:cacheApplicableRulesForType( type, manualReset )\13\
3149 local themes = self.themes\13\
3150 if not manualReset then self:resetApplicableRules( type ) end\13\
3151\13\
3152 for i = 1, #themes do\13\
3153 self:cacheApplicableRules( themes[ i ].rules[ type ] or {} )\13\
3154 end\13\
3155end\13\
3156\13\
3157--[[\13\
3158 @instance\13\
3159 @desc Caches all rules from all themes bound to this object by passing them to 'MThemeManager.cacheApplicableRules'\13\
3160 @param [boolean - manualReset]\13\
3161]]\13\
3162function MThemeManager:cacheAllApplicableRules( manualReset )\13\
3163 local themes, rules = self.themes\13\
3164 if not manualReset then self:resetApplicableRules \"*\" end\13\
3165\13\
3166 for i = 1, #themes do\13\
3167 rules = themes[ i ].rules\13\
3168 for type in pairs( rules ) do\13\
3169 self:cacheApplicableRules( rules[ type ] )\13\
3170 end\13\
3171 end\13\
3172end\13\
3173",
3174["Container.ti"]="class \"Container\" extends \"Node\" mixin \"MNodeContainer\" {\13\
3175 allowMouse = true;\13\
3176 allowKey = true;\13\
3177 allowChar = true;\13\
3178}\13\
3179\13\
3180--[[\13\
3181 @instance\13\
3182 @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\13\
3183 @param [number - X], [number - Y], [number - width], [number - height], [table - nodes]\13\
3184]]\13\
3185function Container:__init__( ... )\13\
3186 self:resolve( ... )\13\
3187\13\
3188 local toset = self.nodes\13\
3189 self.nodes = {}\13\
3190\13\
3191 if type( toset ) == \"table\" then\13\
3192 for i = 1, #toset do\13\
3193 self:addNode( toset[ i ] )\13\
3194 end\13\
3195 end\13\
3196\13\
3197 self:super()\13\
3198end\13\
3199\13\
3200--[[\13\
3201 @instance\13\
3202 @desc Returns true if the node given is visible inside of the container\13\
3203 @param <Node - node>, [number - width], [number - height]\13\
3204 @return <boolean - visible>\13\
3205]]\13\
3206function Container:isNodeInBounds( node, width, height )\13\
3207 local left, top = node.X, node.Y\13\
3208\13\
3209 return not ( ( left + node.width ) < 1 or left > ( width or self.width ) or top > ( height or self.height ) or ( top + node.height ) < 1 )\13\
3210end\13\
3211\13\
3212--[[\13\
3213 @instance\13\
3214 @desc Draws contained nodes to container canvas. Nodes are only drawn if they are visible inside the container\13\
3215 @param [boolean - force]\13\
3216]]\13\
3217function Container:draw( force )\13\
3218 if self.changed or force then\13\
3219 local raw = self.raw\13\
3220 local canvas = raw.canvas\13\
3221\13\
3222 local width, height = self.width, self.height\13\
3223 local nodes, node = raw.nodes\13\
3224\13\
3225 canvas:clear()\13\
3226 for i = 1, #nodes do\13\
3227 node = nodes[ i ]\13\
3228\13\
3229 if self:isNodeInBounds( node, width, height ) then\13\
3230 node:draw( force )\13\
3231\13\
3232 node.canvas:drawTo( canvas, node.X, node.Y )\13\
3233 end\13\
3234 end\13\
3235\13\
3236 raw.changed = false\13\
3237 end\13\
3238end\13\
3239\13\
3240--[[\13\
3241 @instance\13\
3242 @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\13\
3243 @param <Event - event>\13\
3244 @return <boolean - propagate>\13\
3245]]\13\
3246function Container:handle( eventObj )\13\
3247 if not self.super:handle( eventObj ) then return end\13\
3248\13\
3249 local clone\13\
3250 if eventObj.main == \"MOUSE\" then\13\
3251 clone = eventObj:clone( self )\13\
3252 clone.isWithin = clone.isWithin and eventObj:withinParent( self ) or false\13\
3253 end\13\
3254\13\
3255 self:shipEvent( clone or eventObj )\13\
3256 if clone and clone.isWithin and not clone.handled then\13\
3257 clone.handled = true\13\
3258 end\13\
3259 return true\13\
3260end\13\
3261\13\
3262function Container:shipEvent( event )\13\
3263 local nodes = self.nodes\13\
3264 for i = 1, #nodes do\13\
3265 nodes[ i ]:handle( event )\13\
3266 end\13\
3267end\13\
3268\13\
3269\13\
3270configureConstructor({\13\
3271 orderedArguments = { \"X\", \"Y\", \"width\", \"height\", \"nodes\", \"backgroundColour\" },\13\
3272 argumentTypes = {\13\
3273 nodes = \"table\"\13\
3274 }\13\
3275}, true)\13\
3276",
3277["Lexer.ti"]="class \"Lexer\" abstract() {\13\
3278 stream = false;\13\
3279\13\
3280 tokens = {};\13\
3281\13\
3282 line = 1;\13\
3283 char = 1;\13\
3284}\13\
3285\13\
3286--[[\13\
3287 @constructor\13\
3288 @desc Constructs the Lexer instance by providing the instance with a 'stream'.\13\
3289 @param <string - stream>, [boolean - manual]\13\
3290]]\13\
3291function Lexer:__init__( stream, manual )\13\
3292 if type( stream ) ~= \"string\" then\13\
3293 return error \"Failed to initialise Lexer instance. Invalid stream paramater passed (expected string)\"\13\
3294 end\13\
3295 self.stream = stream\13\
3296\13\
3297 if not manual then\13\
3298 self:formTokens()\13\
3299 end\13\
3300end\13\
3301\13\
3302--[[\13\
3303 @instance\13\
3304 @desc This function is used to repeatedly call 'tokenize' until the stream has been completely consumed.\13\
3305]]\13\
3306function Lexer:formTokens()\13\
3307 while self.stream and self.stream:find \"%S\" do\13\
3308 self:tokenize()\13\
3309 end\13\
3310end\13\
3311\13\
3312--[[\13\
3313 @instance\13\
3314 @desc A simple function that is used to add a token to the instances 'tokens' table.\13\
3315 @param <table - token>\13\
3316]]\13\
3317function Lexer:pushToken( token )\13\
3318 local tokens = self.tokens\13\
3319\13\
3320 token.char = self.char\13\
3321 token.line = self.line\13\
3322 tokens[ #tokens + 1 ] = token\13\
3323end\13\
3324\13\
3325--[[\13\
3326 @instance\13\
3327 @desc Consumes the stream by 'amount'.\13\
3328]]\13\
3329function Lexer:consume( amount )\13\
3330 local stream = self.stream\13\
3331 self.stream = stream:sub( amount + 1 )\13\
3332\13\
3333 self.char = self.char + amount\13\
3334 return content\13\
3335end\13\
3336\13\
3337--[[\13\
3338 @instance\13\
3339 @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)\13\
3340 @param <string - pattern>, [number - offset]\13\
3341]]\13\
3342function Lexer:consumePattern( pattern, offset )\13\
3343 local cnt = self.stream:match( pattern )\13\
3344\13\
3345 self:consume( select( 2, self.stream:find( pattern ) ) + ( offset or 0 ) )\13\
3346 return cnt\13\
3347end\13\
3348\13\
3349--[[\13\
3350 @instance\13\
3351 @desc Removes all trailing spaces from\13\
3352]]\13\
3353function Lexer:trimStream()\13\
3354 -- Search for newlines\13\
3355 local stream = self.stream\13\
3356\13\
3357 local newLn = stream:match(\"^(\\n+)\")\13\
3358 if newLn then self:newline( #newLn ) end\13\
3359\13\
3360 local spaces = select( 2, stream:find \"^%s*%S\" )\13\
3361\13\
3362 self.stream = stream:sub( spaces )\13\
3363 self.char = self.char + spaces - 1\13\
3364end\13\
3365\13\
3366--[[\13\
3367 @instance\13\
3368 @desc Advanced 'line' by 'amount' (or 1) and sets 'char' back to zero\13\
3369]]\13\
3370function Lexer:newline( amount )\13\
3371 self.line = self.line + ( amount or 1 )\13\
3372 self.char = 0\13\
3373end\13\
3374\13\
3375--[[\13\
3376 @instance\13\
3377 @desc Throws error 'e' prefixed with information regarding current position and stores the error in 'exception' for later reference\13\
3378]]\13\
3379function Lexer:throw( e )\13\
3380 self.exception = \"Lexer (\" .. tostring( self.__type ) .. \") Exception at line '\"..self.line..\"', char '\"..self.char..\"': \"..e\13\
3381 return error( self.exception )\13\
3382end\13\
3383",
3384["Thread.ti"]="class \"Thread\" {\13\
3385 running = false;\13\
3386\13\
3387 func = false;\13\
3388 co = false;\13\
3389\13\
3390 filter = false;\13\
3391 exception = false;\13\
3392\13\
3393 titaniumEvents = false;\13\
3394}\13\
3395\13\
3396function Thread:__init__( ... )\13\
3397 self:resolve( ... )\13\
3398 self:start()\13\
3399end\13\
3400\13\
3401function Thread:start()\13\
3402 self.co = coroutine.create( self.func )\13\
3403 self.running = true\13\
3404 self.filter = false\13\
3405end\13\
3406\13\
3407function Thread:stop()\13\
3408 self.running = false\13\
3409end\13\
3410\13\
3411function Thread:filterHandle( eventObj )\13\
3412 if self.titaniumEvents then\13\
3413 self:handle( eventObj )\13\
3414 else\13\
3415 self:handle( unpack( eventObj.data ) )\13\
3416 end\13\
3417end\13\
3418\13\
3419function Thread:handle( ... )\13\
3420 if not self.running then return false end\13\
3421\13\
3422 local tEvents, cFilter, eMain, co, ok, filter = self.titaniumEvents, self.filter, select( 1, ... ), self.co\13\
3423 if tEvents then\13\
3424 if not cFilter or ( eMain:is( cFilter ) or eMain:is( \"terminate\" ) ) then\13\
3425 ok, filter = coroutine.resume( co, eMain )\13\
3426 else return end\13\
3427 else\13\
3428 if not cFilter or ( eMain == cFilter or eMain == \"terminate\" ) then\13\
3429 ok, filter = coroutine.resume( co, ... )\13\
3430 else return end\13\
3431 end\13\
3432\13\
3433 if ok then\13\
3434 if coroutine.status( co ) == \"dead\" then\13\
3435 self.running = false\13\
3436 end\13\
3437\13\
3438 self.filter = filter\13\
3439 else\13\
3440 self.exception = filter\13\
3441 self.running = false\13\
3442 end\13\
3443end\13\
3444\13\
3445configureConstructor {\13\
3446 orderedArguments = { \"func\", \"titaniumEvents\", \"id\" },\13\
3447 requiredArguments = { \"func\" }\13\
3448}\13\
3449",
3450["GenericEvent.ti"]="--[[\13\
3451 The GenericEvent class \"is\" spawned when an event that Titanium doesn't understand is caught in the Application event loop.\13\
3452\13\
3453 If you wish to spawn another sort of class \"when\" a certain event is caught, consider using `Event.static.bindEvent`.\13\
3454]]\13\
3455\13\
3456class \"GenericEvent\" extends \"Event\"\13\
3457\13\
3458--[[\13\
3459 @constructor\13\
3460 @desc Constructs the GenericEvent instance by storing all passed arguments in 'data'. The first index (1) of data is stored inside 'name'\13\
3461 @param <string - name>, [var - arg1], ...\13\
3462]]\13\
3463function GenericEvent:__init__( ... )\13\
3464 local args = { ... }\13\
3465\13\
3466 self.name = args[ 1 ]\13\
3467 self.main = self.name:upper()\13\
3468\13\
3469 self.data = args\13\
3470end\13\
3471",
3472["MThemeable.ti"]="--[[\13\
3473 The MThemeable mixin \"facilitates\" the use of themes on objects.\13\
3474 It allows properties to be registered allowing the object to monitor property changes and apply them correctly.\13\
3475\13\
3476 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'.\13\
3477]]\13\
3478\13\
3479class \"MThemeable\" abstract() {\13\
3480 static = { isUpdating = false };\13\
3481 hooked = false;\13\
3482\13\
3483 properties = {};\13\
3484 classes = {};\13\
3485 applicableRules = {};\13\
3486\13\
3487 mainValues = {};\13\
3488 defaultValues = {};\13\
3489 setterBackup = {};\13\
3490}\13\
3491\13\
3492--[[\13\
3493 @instance\13\
3494 @desc Registers the properties provided. These properties are monitored for changes.\13\
3495 @param <string - property>, ...\13\
3496]]\13\
3497function MThemeable:register( ... )\13\
3498 if self.hooked then return error \"Cannot register new properties while hooked. Unhook the theme handler before registering new properties\" end\13\
3499\13\
3500 local args = { ... }\13\
3501 for i = 1, #args do\13\
3502 self.properties[ args[ i ] ] = true\13\
3503 end\13\
3504end\13\
3505\13\
3506--[[\13\
3507 @instance\13\
3508 @desc Unregisters properties provided\13\
3509 @param <string - property>, ...\13\
3510]]\13\
3511function MThemeable:unregister( ... )\13\
3512 if self.hooked then return error \"Cannot unregister properties while hooked. Unhook the theme handler before unregistering properties\" end\13\
3513\13\
3514 local args = { ... }\13\
3515 for i = 1, #args do\13\
3516 self.properties[ args[ i ] ] = nil\13\
3517 end\13\
3518end\13\
3519\13\
3520--[[\13\
3521 @instance\13\
3522 @desc Hooks into the instance by changing setters of registered properties so that the theme manager is notified of the change.\13\
3523]]\13\
3524function MThemeable:hook()\13\
3525 if self.hooked then return error \"Failed to hook theme handler. Already hooked\" end\13\
3526\13\
3527 for property in pairs( self.properties ) do\13\
3528 local setterName, setterFn = Titanium.getSetterName( property )\13\
3529 if self:can( setterName ) then\13\
3530 self.setterBackup[ property ] = self.raw[ setterName ]\13\
3531 setterFn = self.raw[ setterName ]\13\
3532 end\13\
3533\13\
3534 self[ setterName ] = function( instance, value )\13\
3535 local newValue = value\13\
3536 if not MThemeable.isUpdating then\13\
3537 self.mainValues[ property ] = value\13\
3538\13\
3539 newValue = self:fetchPropertyValue( property )\13\
3540 end\13\
3541\13\
3542 if setterFn then\13\
3543 setterFn( self, instance, newValue )\13\
3544 else self[ property ] = newValue end\13\
3545 end\13\
3546\13\
3547 self[ self.__resolved[ property ] and \"mainValues\" or \"defaultValues\" ][ property ] = self[ property ]\13\
3548 end\13\
3549 self.hooked = true\13\
3550end\13\
3551\13\
3552--[[\13\
3553 @instance\13\
3554 @desc Reverses the hooking process by restoring the old setters (stored on hook)\13\
3555]]\13\
3556function MThemeable:unhook()\13\
3557 if not self.hooked then return error \"Failed to unhook theme handler. Already unhooked\" end\13\
3558\13\
3559 local setters, setter = self.setterBackup\13\
3560 for property in pairs( self.properties ) do\13\
3561 setter = setters[ property ]\13\
3562 if setter then self.raw[ Titanium.getSetterName( property ) ] = setter end\13\
3563 end\13\
3564\13\
3565 self.setterBackup, self.hooked = {}, false\13\
3566end\13\
3567\13\
3568function MThemeable:fetchPropertyValue( property )\13\
3569 local requireImportant, newValue\13\
3570 if self.mainValues[ property ] then\13\
3571 requireImportant = true\13\
3572 newValue = self.mainValues[ property ]\13\
3573 end\13\
3574\13\
3575 local rules, r = self.applicableRules\13\
3576 for i = 1, #rules do\13\
3577 r = rules[ i ]\13\
3578 if r.property == property and ( not requireImportant or ( requireImportant and r.important ) ) then\13\
3579 newValue = r.value\13\
3580\13\
3581 if r.important then requireImportant = true end\13\
3582 end\13\
3583 end\13\
3584\13\
3585 return newValue\13\
3586end\13\
3587\13\
3588--[[\13\
3589 @instance\13\
3590 @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)\13\
3591 @param <string - property>\13\
3592]]\13\
3593function MThemeable:updateProperty( property )\13\
3594 if not self.properties[ property ] then\13\
3595 return error( \"Failed to update property '\"..tostring( property )..\"'. Property not registered\" )\13\
3596 end\13\
3597\13\
3598\13\
3599 local new = self:fetchPropertyValue( property )\13\
3600 MThemeable.static.isUpdating = true\13\
3601 if new then\13\
3602 self[ property ] = new\13\
3603 elseif ( not val and ( self[ property ] ~= self.mainValues[ property ] ) ) then\13\
3604 self[ property ] = self.mainValues[ property ] or self.defaultValues[ property ]\13\
3605 end\13\
3606\13\
3607 MThemeable.static.isUpdating = false\13\
3608end\13\
3609\13\
3610function MThemeable:queueUpdate()\13\
3611 self.needsRuleUpdate = true\13\
3612end\13\
3613\13\
3614--[[\13\
3615 @instance\13\
3616 @desc Updates each registered property\13\
3617]]\13\
3618function MThemeable:updateProperties()\13\
3619 if self.needsRuleUpdate and self.application then\13\
3620 self.needsRuleUpdate = false\13\
3621\13\
3622 self.applicableRules = {}\13\
3623 self.application:cacheApplicableRulesForType( self.__type, true )\13\
3624 end\13\
3625\13\
3626 for property in pairs( self.properties ) do self:updateProperty( property ) end\13\
3627end\13\
3628\13\
3629--[[\13\
3630 @instance\13\
3631 @desc Adds class 'class' and updated TML properties\13\
3632 @param <string - class>\13\
3633]]\13\
3634function MThemeable:addClass( class )\13\
3635 self.classes[ class ] = true\13\
3636 self:queueUpdate()\13\
3637 self:updateProperties()\13\
3638end\13\
3639\13\
3640--[[\13\
3641 @instance\13\
3642 @desc Removes class 'class' and updated TML properties\13\
3643 @param <string - class>\13\
3644]]\13\
3645function MThemeable:removeClass( class )\13\
3646 self.classes[ class ] = nil\13\
3647 self:queueUpdate()\13\
3648 self:updateProperties()\13\
3649end\13\
3650\13\
3651--[[\13\
3652 @instance\13\
3653 @desc Shortcut method to set class \"if\" 'has' is truthy or remove it otherwise (updates properties too)\13\
3654 @param <string - class>, [var - has]\13\
3655]]\13\
3656function MThemeable:setClass( class, has )\13\
3657 self.classes[ class ] = has and true or nil\13\
3658 self:queueUpdate()\13\
3659 self:updateProperties()\13\
3660end\13\
3661\13\
3662--[[\13\
3663 @instance\13\
3664 @desc Returns true if:\13\
3665 - Param passed is a table and all values inside the table are set as classes on this object\13\
3666 - Param is string and this object has that class\13\
3667 @param <string|table - class>\13\
3668 @return <boolean - has>\13\
3669]]\13\
3670function MThemeable:hasClass( t )\13\
3671 if type( t ) == \"string\" then\13\
3672 return self.classes[ t ]\13\
3673 elseif type( t ) == \"table\" then\13\
3674 for i = 1, #t do\13\
3675 if not self.classes[ t[ i ] ] then\13\
3676 return false\13\
3677 end\13\
3678 end\13\
3679\13\
3680 return true\13\
3681 else\13\
3682 return error(\"Invalid target '\"..tostring( t )..\"' for class check\")\13\
3683 end\13\
3684end\13\
3685\13\
3686function MThemeable:onThemeUpdate()\13\
3687 self:queueUpdate()\13\
3688 self:updateProperties()\13\
3689end\13\
3690",
3691["MTextDisplay.ti"]="--[[\13\
3692 This mixin \"is\" designed to be used by nodes that wish to display formatted text (e.g: Button, TextContainer).\13\
3693 The 'drawText' function should be called from the node during draw time.\13\
3694]]\13\
3695\13\
3696local string_len, string_find, string_sub, string_gsub, string_match = string.len, string.find, string.sub, string.gsub, string.match\13\
3697\13\
3698class \"MTextDisplay\" abstract() {\13\
3699 lineConfig = {\13\
3700 lines = false;\13\
3701 alignedLines = false;\13\
3702 offsetY = 0;\13\
3703 };\13\
3704\13\
3705 verticalPadding = 0;\13\
3706 horizontalPadding = 0;\13\
3707\13\
3708 verticalAlign = \"top\";\13\
3709 horizontalAlign = \"left\";\13\
3710}\13\
3711\13\
3712--[[\13\
3713 @constructor\13\
3714 @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\13\
3715]]\13\
3716function MTextDisplay:MTextDisplay()\13\
3717 if Titanium.mixesIn( self, \"MThemeable\" ) then\13\
3718 self:register( \"text\", \"verticalAlign\", \"horizontalAlign\", \"verticalPadding\", \"horizontalPadding\" )\13\
3719 end\13\
3720end\13\
3721\13\
3722--[[\13\
3723 @instance\13\
3724 @desc Generates a table of text lines by wrapping on newlines or when the line gets too long.\13\
3725]]\13\
3726function MTextDisplay:wrapText()\13\
3727 local text, width, lines = self.text, self.width, {}\13\
3728\13\
3729 while text and string_len( text ) > 0 do\13\
3730 local section, pre, post = string_sub( text, 1, width )\13\
3731\13\
3732 if string_find( section, \"\\n\" ) then\13\
3733 pre, post = string_match( text, \"(.-)\\n%s*(.*)$\" )\13\
3734 elseif string_len( text ) <= width then\13\
3735 pre = text\13\
3736 else\13\
3737 local lastSpace = string_find( section, \"%s[%S]*$\" )\13\
3738\13\
3739 pre = lastSpace and string_gsub( string_sub( text, 1, lastSpace - 1 ), \"%s+$\", \"\" ) or section\13\
3740 post = lastSpace and string_sub( text, lastSpace + 1 ) or string_sub( text, width + 1 )\13\
3741 end\13\
3742\13\
3743 lines[ #lines + 1 ], text = pre, post\13\
3744 end\13\
3745\13\
3746 self.lineConfig.lines = lines\13\
3747end\13\
3748\13\
3749--[[\13\
3750 @instance\13\
3751 @desc Uses 'wrapText' to generate the information required to draw the text to the canvas correctly.\13\
3752]]\13\
3753function MTextDisplay:drawText( bg, tc )\13\
3754 local lines = self.lineConfig.lines\13\
3755 if not lines then\13\
3756 self:wrapText()\13\
3757 lines = self.lineConfig.lines\13\
3758 end\13\
3759\13\
3760 local vPadding, hPadding = self.verticalPadding, self.horizontalPadding\13\
3761\13\
3762 local yOffset, xOffset = vPadding, hPadding\13\
3763 local vAlign, hAlign = self.verticalAlign, self.horizontalAlign\13\
3764 local width, height = self.width, self.height\13\
3765\13\
3766 if vAlign == \"centre\" then\13\
3767 yOffset = math.floor( ( height / 2 ) - ( #lines / 2 ) + .5 ) + vPadding\13\
3768 elseif vAlign == \"bottom\" then\13\
3769 yOffset = height - #lines - vPadding\13\
3770 end\13\
3771\13\
3772 local canvas, line = self.canvas\13\
3773 for i = 1, #lines do\13\
3774 local line, xOffset = lines[ i ], hPadding\13\
3775 if hAlign == \"centre\" then\13\
3776 xOffset = math.floor( width / 2 - ( #line / 2 ) + .5 )\13\
3777 elseif hAlign == \"right\" then\13\
3778 xOffset = width - #line - hPadding + 1\13\
3779 end\13\
3780\13\
3781 canvas:drawTextLine( xOffset + 1, i + yOffset, line, tc, bg )\13\
3782 end\13\
3783end\13\
3784\13\
3785configureConstructor {\13\
3786 argumentTypes = {\13\
3787 verticalPadding = \"number\",\13\
3788 horizontalPadding = \"number\",\13\
3789\13\
3790 verticalAlign = \"string\",\13\
3791 horizontalAlign = \"string\",\13\
3792\13\
3793 text = \"string\"\13\
3794 }\13\
3795}\13\
3796",
3797["Node.ti"]="--[[\13\
3798 A Node is an object which makes up the applications graphical user interface (GUI).\13\
3799\13\
3800 Objects such as labels, buttons and text inputs are nodes.\13\
3801--]]\13\
3802\13\
3803class \"Node\" abstract() extends \"Component\" mixin \"MThemeable\" mixin \"MCallbackManager\" {\13\
3804 static = {\13\
3805 eventMatrix = {\13\
3806 mouse_click = \"onMouseClick\",\13\
3807 mouse_drag = \"onMouseDrag\",\13\
3808 mouse_up = \"onMouseUp\",\13\
3809 mouse_scroll = \"onMouseScroll\",\13\
3810\13\
3811 key = \"onKeyDown\",\13\
3812 key_up = \"onKeyUp\",\13\
3813 char = \"onChar\"\13\
3814 },\13\
3815 anyMatrix = {\13\
3816 MOUSE = \"onMouse\",\13\
3817 KEY = \"onKey\"\13\
3818 }\13\
3819 };\13\
3820\13\
3821 allowMouse = false;\13\
3822 allowKey = false;\13\
3823 allowChar = false;\13\
3824 useAnyCallbacks = false;\13\
3825\13\
3826 enabled = true;\13\
3827 visible = true;\13\
3828\13\
3829 changed = true;\13\
3830 parent = false;\13\
3831}\13\
3832\13\
3833--[[\13\
3834 @constructor\13\
3835 @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.\13\
3836]]\13\
3837function Node:__init__()\13\
3838 self:register( \"X\", \"Y\", \"colour\", \"backgroundColour\", \"enabled\", \"visible\" )\13\
3839\13\
3840 if not self.canvas then self.raw.canvas = NodeCanvas( self ) end\13\
3841end\13\
3842\13\
3843--[[\13\
3844 @constructor\13\
3845 @desc Finishes construction by hooking the theme manager into the node.\13\
3846]]\13\
3847function Node:__postInit__()\13\
3848 self:hook()\13\
3849end\13\
3850\13\
3851--[[\13\
3852 @instance\13\
3853 @desc Sets the enabled property of the node to 'enabled'. Sets node's 'changed' to true.\13\
3854 @param <boolean - enabled>\13\
3855]]\13\
3856function Node:setEnabled( enabled )\13\
3857 self.enabled = enabled\13\
3858 self.changed = true\13\
3859end\13\
3860\13\
3861--[[\13\
3862 @instance\13\
3863 @desc Sets the node to visible/invisible depending on 'visible' paramater\13\
3864 @param <boolean - visible>\13\
3865]]\13\
3866function Node:setVisible( visible )\13\
3867 self.visible = visible\13\
3868 self.changed = true\13\
3869end\13\
3870\13\
3871--[[\13\
3872 @instance\13\
3873 @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.\13\
3874 @param <boolean - changed>\13\
3875]]\13\
3876function Node:setChanged( changed )\13\
3877 self.changed = changed\13\
3878\13\
3879 if changed then\13\
3880 local parent = self.parent\13\
3881 if parent and not parent.changed then\13\
3882 parent.changed = true\13\
3883 end\13\
3884 end\13\
3885end\13\
3886\13\
3887--[[\13\
3888 @instance\13\
3889 @desc Handles events by triggering methods on the node depending on the event object passed\13\
3890 @param <Event Instance* - eventObj>\13\
3891 @return <boolean - propagate>\13\
3892\13\
3893 *Note: The event instance passed can be of variable type, ideally is extends 'Event' so that required methods are implemented on the eventObj.\13\
3894]]\13\
3895function Node:handle( eventObj )\13\
3896 local main, sub, within = eventObj.main, eventObj.sub, false\13\
3897 local handled = eventObj.handled\13\
3898\13\
3899 if main == \"MOUSE\" then\13\
3900 if self.allowMouse then\13\
3901 within = eventObj.isWithin and eventObj:withinParent( self ) or false\13\
3902 else return end\13\
3903 elseif ( main == \"KEY\" and not self.allowKey ) or ( main == \"CHAR\" and not self.allowChar ) then\13\
3904 return\13\
3905 end\13\
3906\13\
3907 local fn = Node.eventMatrix[ eventObj.name ] or \"onEvent\"\13\
3908 if self:can( fn ) then\13\
3909 self[ fn ]( self, eventObj, handled, within )\13\
3910 end\13\
3911\13\
3912 if self.useAnyCallbacks then\13\
3913 local anyFn = Node.anyMatrix[ main ]\13\
3914 if self:can( anyFn ) then\13\
3915 self[ anyFn ]( self, eventObj, handled, within )\13\
3916 end\13\
3917 end\13\
3918\13\
3919 return true\13\
3920end\13\
3921\13\
3922--[[\13\
3923 @instance\13\
3924 @desc Returns the absolute X, Y position of a node rather than its position relative to it's parent.\13\
3925 @return <number - X>, <number - Y>\13\
3926]]\13\
3927function Node:getAbsolutePosition()\13\
3928 local parent, application = self.parent, self.application\13\
3929 if parent then\13\
3930 if parent == application then\13\
3931 return -1 + application.X + self.X, -1 + application.Y + self.Y\13\
3932 end\13\
3933\13\
3934 local pX, pY = self.parent:getAbsolutePosition()\13\
3935 return -1 + pX + self.X, -1 + pY + self.Y\13\
3936 else return self.X, self.Y end\13\
3937end\13\
3938\13\
3939function Node:animate( ... )\13\
3940 if not self.application then return end\13\
3941\13\
3942 return self.application:addAnimation( Tween( self, ... ) )\13\
3943end\13\
3944\13\
3945configureConstructor {\13\
3946 argumentTypes = { enabled = \"boolean\", visible = \"boolean\" }\13\
3947}\13\
3948",
3949["QueryLexer.ti"]="class \"QueryLexer\" extends \"Lexer\"\
3950\
3951function QueryLexer:tokenize()\
3952 if self.stream:find \"^%s\" and not self.inCondition then\
3953 self:pushToken { type = \"QUERY_SEPERATOR\" }\
3954 end\
3955\
3956 self:trimStream()\
3957 local stream = self.stream\
3958\
3959 if self.inCondition then\
3960 self:tokenizeCondition( stream )\
3961 elseif stream:find \"^%b[]\" then\
3962 self:pushToken { type = \"QUERY_COND_OPEN\" }\
3963 self:consume( 1 )\
3964\
3965 self.inCondition = true\
3966 elseif stream:find \"^%,\" then\
3967 self:pushToken { type = \"QUERY_END\", value = self:consumePattern \"^%,\" }\
3968 elseif stream:find \"^>\" then\
3969 self:pushToken { type = \"QUERY_DIRECT_PREFIX\", value = self:consumePattern \"^>\" }\
3970 elseif stream:find \"^#[^%s%.#%[%,]*\" then\
3971 self:pushToken { type = \"QUERY_ID\", value = self:consumePattern \"^#([^%s%.#%[]*)\" }\
3972 elseif stream:find \"^%.[^%s#%[%,]*\" then\
3973 self:pushToken { type = \"QUERY_CLASS\", value = self:consumePattern \"^%.([^%s%.#%[]*)\" }\
3974 elseif stream:find \"^[^,%s#%.%[]*\" then\
3975 self:pushToken { type = \"QUERY_TYPE\", value = self:consumePattern \"^[^,%s#%.%[]*\" }\
3976 else\
3977 self:throw(\"Unexpected block '\"..stream:match(\"(.-)%s\")..\"'\")\
3978 end\
3979end\
3980\
3981function QueryLexer:tokenizeCondition( stream )\
3982 local first = stream:sub( 1, 1 )\
3983 if stream:find \"%b[]\" then\
3984 self:throw( \"Nested condition found '\"..tostring( stream:match \"%b[]\" )..\"'\" )\
3985 elseif stream:find \"^%b''\" or stream:find '^%b\"\"' then\
3986 local cnt = self:consumePattern( first == \"'\" and \"^%b''\" or '^%b\"\"' ):sub( 2, -2 )\
3987 if cnt:find \"%b''\" or cnt:find '%b\"\"' then\
3988 self:throw( \"Nested string found inside '\"..tostring( cnt )..\"'\" )\
3989 end\
3990\
3991 self:pushToken { type = \"QUERY_COND_STRING_ENTITY\", value = cnt }\
3992 elseif stream:find \"^%w+\" then\
3993 self:pushToken { type = \"QUERY_COND_ENTITY\", value = self:consumePattern \"^%w+\" }\
3994 elseif stream:find \"^%,\" then\
3995 self:pushToken { type = \"QUERY_COND_SEPERATOR\" }\
3996 self:consume( 1 )\
3997 elseif stream:find \"^[%p~]+\" then\
3998 self:pushToken { type = \"QUERY_COND_SYMBOL\", value = self:consumePattern \"^[%p~]+\" }\
3999 elseif stream:find \"^%]\" then\
4000 self:pushToken { type = \"QUERY_COND_CLOSE\" }\
4001 self:consume( 1 )\
4002 self.inCondition = false\
4003 else\
4004 self:throw(\"Invalid condition syntax. Expected property near '\"..tostring( stream:match \"%S*\" )..\"'\")\
4005 end\
4006end\
4007",
4008["MFocusable.ti"]="--[[\13\
4009 A focusable object is an object that after a mouse_click and a mouse_up event occur on the object is 'focused'.\13\
4010\13\
4011 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).\13\
4012]]\13\
4013\13\
4014class \"MFocusable\" abstract() {\13\
4015 focused = false;\13\
4016}\13\
4017\13\
4018function MFocusable:MFocusable()\13\
4019 if Titanium.mixesIn( self, \"MThemeable\" ) then\13\
4020 self:register(\"focused\", \"focusedColour\", \"focusedBackgroundColour\")\13\
4021 end\13\
4022end\13\
4023\13\
4024function MFocusable:setFocused( focused )\13\
4025 local raw = self.raw\13\
4026 if raw.focused == focused then return end\13\
4027\13\
4028 self.changed = true\13\
4029 self.focused = focused\13\
4030end\13\
4031\13\
4032function MFocusable:focus()\13\
4033 if self.application then self.application:focusNode( self ) end\13\
4034 self.focused = true\13\
4035end\13\
4036\13\
4037function MFocusable:unfocus()\13\
4038 if self.application then self.application:unfocusNode( self ) end\13\
4039 self.focused = false\13\
4040end\13\
4041\13\
4042configureConstructor {\13\
4043 argumentTypes = {\13\
4044 focusedBackgroundColour = \"colour\",\13\
4045 focusedColour = \"colour\",\13\
4046 focused = \"boolean\"\13\
4047 }\13\
4048} alias {\13\
4049 focusedColor = \"focusedColour\",\13\
4050 focusedBackgroundColor = \"focusedBackgroundColour\"\13\
4051}\13\
4052",
4053["Tween.ti"]="class \"Tween\" {\13\
4054 static = {\13\
4055 easing = {}\13\
4056 };\13\
4057\13\
4058 object = false;\13\
4059\13\
4060 property = false;\13\
4061 initial = false;\13\
4062 final = false;\13\
4063\13\
4064 duration = 0;\13\
4065 clock = 0;\13\
4066}\13\
4067\13\
4068--[[\13\
4069 @constructor\13\
4070 @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.\13\
4071 @param <Object - object>, <string - name>, <string - property>, <number - final>, <number - duration>, [string/function - easing]\13\
4072]]\13\
4073function Tween:__init__( ... )\13\
4074 self:resolve( ... )\13\
4075 if not Titanium.isInstance( self.object ) then\13\
4076 return error(\"Argument 'object' for tween must be a Titanium instance. '\"..tostring( self.object )..\"' is not a Titanium instance.\")\13\
4077 end\13\
4078\13\
4079 local easing = self.easing or \"linear\"\13\
4080 if type( easing ) == \"string\" then\13\
4081 self.easing = Tween.static.easing[ easing ] or error(\"Easing type '\"..tostring( easing )..\"' could not be found in 'Tween.static.easing'.\")\13\
4082 elseif type( easing ) == \"function\" then\13\
4083 self.easing = easing\13\
4084 else\13\
4085 return error \"Tween easing invalid. Must be a function to be invoked or name of easing type\"\13\
4086 end\13\
4087\13\
4088 self.initial = self.object[ self.property ]\13\
4089 self.clock = 0\13\
4090end\13\
4091\13\
4092--[[\13\
4093 @instance\13\
4094 @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.\13\
4095]]\13\
4096function Tween:performEasing()\13\
4097 self.object[ self.property ] = math.floor( self.easing( self.clock, self.initial, self.final - self.initial, self.duration ) + .5 )\13\
4098end\13\
4099\13\
4100--[[\13\
4101 @instance\13\
4102 @desc Updates the tween by increasing 'clock' by 'dt' via the setter 'setClock'\13\
4103 @param <number - dt>\13\
4104 @return <boolean - finished>\13\
4105]]\13\
4106function Tween:update( dt )\13\
4107 return self:setClock( self.clock + dt )\13\
4108end\13\
4109\13\
4110--[[\13\
4111 @instance\13\
4112 @desc Sets the clock time to zero\13\
4113 @param <boolean - finished>\13\
4114]]\13\
4115function Tween:reset()\13\
4116 return self:setClock( 0 )\13\
4117end\13\
4118\13\
4119--[[\13\
4120 @instance\13\
4121 @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\13\
4122 @param <number - clock>\13\
4123 @return <boolean - finished>\13\
4124]]\13\
4125function Tween:setClock( clock )\13\
4126 if clock <= 0 then\13\
4127 self.clock = 0\13\
4128 elseif clock >= self.duration then\13\
4129 self.clock = self.duration\13\
4130 else\13\
4131 self.clock = clock\13\
4132 end\13\
4133\13\
4134 self:performEasing()\13\
4135 return self.clock >= self.duration\13\
4136end\13\
4137\13\
4138--[[\13\
4139 @static\13\
4140 @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\13\
4141 @param <string - easingName>, <function - easingFunction>\13\
4142 @return <Class Base - Tween>\13\
4143]]\13\
4144function Tween.static.addEasing( easingName, easingFunction )\13\
4145 if type( easingFunction ) ~= \"function\" then\13\
4146 return error \"Easing function must be of type 'function'\"\13\
4147 end\13\
4148\13\
4149 Tween.static.easing[ easingName ] = easingFunction\13\
4150 return Tween\13\
4151end\13\
4152\13\
4153configureConstructor {\13\
4154 orderedArguments = { \"object\", \"name\", \"property\", \"final\", \"duration\", \"easing\", \"promise\" },\13\
4155 requiredArguments = { \"object\", \"name\", \"property\", \"final\", \"duration\" },\13\
4156 argumentTypes = {\13\
4157 name = \"string\",\13\
4158 property = \"string\",\13\
4159 final = \"number\",\13\
4160 duration = \"number\",\13\
4161 promise = \"function\"\13\
4162 }\13\
4163}\13\
4164",
4165["MActivatable.ti"]="--[[\13\
4166 A mixin \"to\" reuse code commonly written when developing nodes that can be (de)activated.\13\
4167]]\13\
4168\13\
4169class \"MActivatable\" abstract() {\13\
4170 active = false;\13\
4171\13\
4172 activeColour = colours.white;\13\
4173 activeBackgroundColour = colours.cyan;\13\
4174}\13\
4175\13\
4176--[[\13\
4177 @constructor\13\
4178 @desc Registers properties used by this class \"with\" the theme handler if the object mixes in 'MThemeable'\13\
4179]]\13\
4180function MActivatable:MActivatable()\13\
4181 if Titanium.mixesIn( self, \"MThemeable\" ) then\13\
4182 self:register( \"active\", \"activeColour\", \"activeBackgroundColour\" )\13\
4183 end\13\
4184end\13\
4185\13\
4186--[[\13\
4187 @instance\13\
4188 @desc Sets the 'active' property to the 'active' argument passed. When the 'active' property changes the node will become 'changed'.\13\
4189 @param <boolean - active>\13\
4190]]\13\
4191function MActivatable:setActive( active )\13\
4192 local raw = self.raw\13\
4193 if raw.active == active then return end\13\
4194\13\
4195 raw.active = active\13\
4196 self.changed = true\13\
4197end\13\
4198\13\
4199configureConstructor {\13\
4200 argumentTypes = { active = \"boolean\", activeColour = \"colour\", activeBackgroundColour = \"colour\" }\13\
4201} alias {\13\
4202 activeColor = \"activeColour\",\13\
4203 activeBackgroundColor = \"activeBackgroundColour\"\13\
4204}\13\
4205",
4206["XMLParser.ti"]="--[[\13\
4207 The XMLParser class \"is\" used to handle the lexing and parsing of XMLParser source into a parse tree.\13\
4208]]\13\
4209\13\
4210class \"XMLParser\" extends \"Parser\" {\13\
4211 tokens = false;\13\
4212 tree = false;\13\
4213}\13\
4214\13\
4215--[[\13\
4216 @constructor\13\
4217 @desc Creates a 'Lexer' instance with the source and stores the tokens provided. Invokes 'parse' once lexical analysis complete.\13\
4218]]\13\
4219function XMLParser:__init__( source )\13\
4220 local lex = XMLLexer( source )\13\
4221 self:super( lex.tokens )\13\
4222end\13\
4223\13\
4224--[[\13\
4225 @instance\13\
4226 @desc Iterates through every token and constructs a tree of XML layers\13\
4227]]\13\
4228function XMLParser:parse()\13\
4229 local stack, top, token = {{}}, false, self:stepForward()\13\
4230 local isTagOpen, settingAttribute\13\
4231\13\
4232 while token do\13\
4233 if settingAttribute then\13\
4234 if token.type == \"XML_ATTRIBUTE_VALUE\" or token.type == \"XML_STRING_ATTRIBUTE_VALUE\" then\13\
4235 top.arguments[ settingAttribute ] = token.value\13\
4236 settingAttribute = false\13\
4237 else\13\
4238 self:throw( \"Unexpected \"..token.type..\". Expected attribute value following XML_ASSIGNMENT token.\" )\13\
4239 end\13\
4240 else\13\
4241 if token.type == \"XML_OPEN\" then\13\
4242 if isTagOpen then\13\
4243 self:throw \"Unexpected XML_OPEN token. Expected XML attributes or end of tag.\"\13\
4244 end\13\
4245 isTagOpen = true\13\
4246\13\
4247 top = { type = token.value, arguments = {} }\13\
4248 table.insert( stack, top )\13\
4249 elseif token.type == \"XML_END\" then\13\
4250 local toClose = table.remove( stack )\13\
4251 top = stack[ #stack ]\13\
4252\13\
4253 if not top then\13\
4254 self:throw(\"Nothing to close with XML_END of type '\"..token.value..\"'\")\13\
4255 elseif toClose.type ~= token.value then\13\
4256 self:throw(\"Tried to close \"..toClose.type..\" with XML_END of type '\"..token.value..\"'\")\13\
4257 end\13\
4258\13\
4259 if not top.children then top.children = {} end\13\
4260 table.insert( top.children, toClose )\13\
4261 elseif token.type == \"XML_END_CLOSE\" then\13\
4262 top = stack[ #stack - 1 ]\13\
4263\13\
4264 if not top then\13\
4265 self:throw(\"Unexpected XML_END_CLOSE tag (/>)\")\13\
4266 end\13\
4267\13\
4268 if not top.children then top.children = {} end\13\
4269 table.insert( top.children, table.remove( stack ) )\13\
4270 elseif token.type == \"XML_CLOSE\" then\13\
4271 isTagOpen = false\13\
4272 elseif token.type == \"XML_ATTRIBUTE\" then\13\
4273 local next = self:stepForward()\13\
4274\13\
4275 if next.type == \"XML_ASSIGNMENT\" then\13\
4276 settingAttribute = token.value\13\
4277 else\13\
4278 top.arguments[ token.value ] = true\13\
4279 self.position = self.position - 1\13\
4280 end\13\
4281 elseif token.type == \"XML_CONTENT\" then\13\
4282 if not top.type then\13\
4283 self:throw(\"Unexpected XML_CONTENT. Invalid content: \"..token.value)\13\
4284 end\13\
4285\13\
4286 top.content = token.value\13\
4287 else\13\
4288 self:throw(\"Unexpected \"..token.type)\13\
4289 end\13\
4290 end\13\
4291\13\
4292 if token.type == \"XML_END\" or token.type == \"XML_END_CLOSE\" then\13\
4293 isTagOpen = false\13\
4294 end\13\
4295\13\
4296 if top.content and top.children then\13\
4297 self:throw \"XML layers cannot contain child nodes and XML_CONTENT at the same time\"\13\
4298 end\13\
4299\13\
4300 token = self:stepForward()\13\
4301 end\13\
4302 self.tree = stack[ 1 ].children\13\
4303end\13\
4304\13\
4305--[[\13\
4306 @static\13\
4307 @desc When lexing the XML arguments they are all stored as strings as a result of the string operations to find tokens.\13\
4308 This function converts a value to the type given (#2)\13\
4309 @param <var - argumentValue>, <string - desiredType>\13\
4310 @return <desiredType* - value>\13\
4311\13\
4312 *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.\13\
4313]]\13\
4314function XMLParser.static.convertArgType( argumentValue, desiredType )\13\
4315 local vType = type( argumentValue )\13\
4316\13\
4317 if not desiredType or not argumentValue or vType == desiredType then\13\
4318 return argumentValue\13\
4319 end\13\
4320\13\
4321 if desiredType == \"string\" then\13\
4322 return tostring( argumentValue )\13\
4323 elseif desiredType == \"number\" then\13\
4324 return tonumber( argumentValue ) or error( \"Failed to cast argument to number. Value: \"..tostring( argumentValue )..\" is not a valid number\" )\13\
4325 elseif desiredType == \"boolean\" then\13\
4326 if argumentValue == \"true\" then return true\13\
4327 elseif argumentValue == \"false\" then return false\13\
4328 else\13\
4329 return error( \"Failed to cast argument to boolean. Value: \"..tostring( argumentValue )..\" is not a valid boolean (true or false)\" )\13\
4330 end\13\
4331 elseif desiredType == \"colour\" or desiredType == \"color\" then\13\
4332 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\" )\13\
4333 else\13\
4334 return error( \"Failed to cast argument. Unknown target type '\"..tostring( desiredType )..\"'\" )\13\
4335 end\13\
4336end\13\
4337",
4338["Pane.ti"]="--[[\13\
4339 A pane is a very simple node that simply draws a box at 'X', 'Y' with dimensions 'width', 'height'.\13\
4340]]\13\
4341\13\
4342class \"Pane\" extends \"Node\" {\13\
4343 backgroundColour = colours.black;\13\
4344\13\
4345 allowMouse = true;\13\
4346 useAnyCallbacks = true;\13\
4347}\13\
4348\13\
4349--[[\13\
4350 @instance\13\
4351 @desc Resolves arguments and calls super constructor.\13\
4352]]\13\
4353function Pane:__init__( ... )\13\
4354 self:resolve( ... )\13\
4355 self:super()\13\
4356end\13\
4357\13\
4358--[[\13\
4359 @instance\13\
4360 @desc Clears the canvas, the canvas background colour becomes 'backgroundColour' during the clear.\13\
4361 @param [boolean - force]\13\
4362]]\13\
4363function Pane:draw( force )\13\
4364 local raw = self.raw\13\
4365 if raw.changed or force then\13\
4366 raw.canvas:clear()\13\
4367 raw.changed = false\13\
4368 end\13\
4369end\13\
4370\13\
4371--[[\13\
4372 @instance\13\
4373 @desc Handles any mouse events cast onto this node to prevent nodes under it being affected by them.\13\
4374 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
4375]]\13\
4376function Pane:onMouse( event, handled, within )\13\
4377 if not within or handled then return end\13\
4378\13\
4379 event.handled = true\13\
4380end\13\
4381\13\
4382configureConstructor({\13\
4383 orderedArguments = {\"X\", \"Y\", \"width\", \"height\", \"backgroundColour\"}\13\
4384}, true)\13\
4385",
4386["Page.ti"]="class \"Page\" extends \"MNodeContainer\"\13\
4387\13\
4388function Page:__init__( name, ... )\13\
4389 self.name = type( name ) == \"string\" and name or error(\"Invalid name '\"..tostring( name )..\"'\", 2)\13\
4390\13\
4391 local args, nodes = { ... }, self.nodes\13\
4392 for i = 1, #args do\13\
4393 if Titanium.typeOf( args[ i ], \"Node\", true ) then\13\
4394 nodes[ #nodes + 1 ] = args[ i ]\13\
4395 end\13\
4396 end\13\
4397end\13\
4398\13\
4399function Page:setParent( parent )\13\
4400 if Titanium.typeOf( parent, \"PageContainer\", true ) then\13\
4401 self.parent = parent\13\
4402 local nodes = self.nodes\13\
4403\13\
4404 for i = 1, #nodes do\13\
4405 nodes[ i ].parent = parent\13\
4406 end\13\
4407 else\13\
4408 return error(\"Failed to set parent of page to '\"..tostring( parent )..\"'. Parent MUST be a PageContainer, this is the only place 'Page' instances should reside\")\13\
4409 end\13\
4410end\13\
4411\13\
4412function Page:addNode( node )\13\
4413 self.super:addNode( node )\13\
4414 node.parent = self.parent\13\
4415end\13\
4416\13\
4417function Page:onThemeUpdate() end\13\
4418configureConstructor {\13\
4419 orderedArguments = { \"name\" },\13\
4420 argumentTypes = {\13\
4421 name = \"string\"\13\
4422 }\13\
4423}\13\
4424",
4425["MNodeContainer.ti"]="class \"MNodeContainer\" abstract() {\13\
4426 nodes = {}\13\
4427}\13\
4428\13\
4429--[[\13\
4430 @instance\13\
4431 @desc Adds a node to the object. This node will have its object and parent (this) set\13\
4432 @param <Instance 'Node' - node>\13\
4433 @return 'param1 (node)'\13\
4434]]\13\
4435function MNodeContainer:addNode( node )\13\
4436 if not Titanium.typeOf( node, \"Node\", true ) then\13\
4437 return error( \"Cannot add '\"..tostring( node )..\" as Node on '\"..tostring( self )..\"'\" )\13\
4438 end\13\
4439\13\
4440 node.parent = self\13\
4441 if Titanium.typeOf( self, \"Application\", true ) then\13\
4442 node.application = self\13\
4443 else\13\
4444 if Titanium.typeOf( self.application, \"Application\", true ) then\13\
4445 node.application = self.application\13\
4446 end\13\
4447 end\13\
4448\13\
4449 self.changed = true\13\
4450 self.collatedNodes = false\13\
4451\13\
4452 table.insert( self.nodes, node )\13\
4453 node:updateProperties()\13\
4454\13\
4455 if node.focused then node:focus() end\13\
4456 return node\13\
4457end\13\
4458\13\
4459--[[\13\
4460 @instance\13\
4461 @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)\13\
4462 @param <Instance 'Node'/string name - target>\13\
4463 @return <boolean - success>, [node - removedNode**]\13\
4464\13\
4465 *Note: In order for the node to be removed its 'name' field must match the 'name' parameter.\13\
4466 **Note: Removed node will only be returned if a node was removed (and thus success 'true')\13\
4467]]\13\
4468function MNodeContainer:removeNode( target )\13\
4469 local searchName = type( target ) == \"string\"\13\
4470\13\
4471 if not searchName and not Titanium.typeOf( target, \"Node\", true ) then\13\
4472 return error( \"Cannot perform search for node using target '\"..tostring( target )..\"' to remove.\" )\13\
4473 end\13\
4474\13\
4475 local nodes, node, nodeName = self.nodes, nil\13\
4476 for i = 1, #nodes do\13\
4477 node = nodes[ i ]\13\
4478\13\
4479 if ( searchName and node.id == target ) or ( not searchName and node == target ) then\13\
4480 node.parent = nil\13\
4481 node.application = nil\13\
4482\13\
4483 if self.focusedNode == node then\13\
4484 node.focused = false\13\
4485 end\13\
4486\13\
4487 self.changed = true\13\
4488 self.collatedNodes = false\13\
4489\13\
4490 table.remove( nodes, i )\13\
4491 return true, node\13\
4492 end\13\
4493 end\13\
4494\13\
4495 return false\13\
4496end\13\
4497\13\
4498--[[\13\
4499 @instance\13\
4500 @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.\13\
4501 @param <string - id>, [boolean - recursive]\13\
4502 @return [Node Instance - node]\13\
4503]]\13\
4504function MNodeContainer:getNode( id, recursive )\13\
4505 local nodes, node = recursive and self.collatedNodes or self.nodes\13\
4506\13\
4507 for i = 1, #nodes do\13\
4508 node = nodes[ i ]\13\
4509 if node.id == id then\13\
4510 return node\13\
4511 end\13\
4512 end\13\
4513end\13\
4514\13\
4515--[[\13\
4516 @instance\13\
4517 @desc Returns a 'NodeQuery' instance containing the nodes that matched the query and methods to manipulate\13\
4518 @param <string - query>\13\
4519 @return <NodeQuery Instance - Query Result>\13\
4520]]\13\
4521function MNodeContainer:query( query )\13\
4522 return NodeQuery( self, query )\13\
4523end\13\
4524\13\
4525function MNodeContainer:getCollatedNodes()\13\
4526 if not self.collatedNodes or #self.collatedNodes == 0 then\13\
4527 self:collate()\13\
4528 end\13\
4529\13\
4530 return self.collatedNodes\13\
4531end\13\
4532\13\
4533--[[\13\
4534 @instance\13\
4535 @desc Caches all nodes under this container (and child containers) in 'collatedNodes'.\13\
4536 This list maybe out of date if 'collate' isn't called before usage. Caching is not automatic.\13\
4537 @param [table - collated]\13\
4538]]\13\
4539function MNodeContainer:collate( collated )\13\
4540 local collated = collated or {}\13\
4541\13\
4542 local nodes, node = self.nodes\13\
4543 for i = 1, #nodes do\13\
4544 node = nodes[ i ]\13\
4545 collated[ #collated + 1 ] = node\13\
4546\13\
4547 local collatedNode = node.collatedNodes\13\
4548 if collatedNode then\13\
4549 for i = 1, #collatedNode do\13\
4550 collated[ #collated + 1 ] = collatedNode[ i ]\13\
4551 end\13\
4552 end\13\
4553 end\13\
4554\13\
4555 self.collatedNodes = collated\13\
4556end\13\
4557\13\
4558--[[\13\
4559 @instance\13\
4560 @desc Iterates over child nodes to ensure that nodes added to this container prior to Application set are updated (with the new Application)\13\
4561 @param <Application - app>\13\
4562]]\13\
4563function MNodeContainer:setApplication( app )\13\
4564 if Titanium.typeOf( app, \"Application\", true ) then\13\
4565 self.application = app\13\
4566\13\
4567 local nodes = self.nodes\13\
4568 for i = 1, #nodes do\13\
4569 nodes[ i ].application = app\13\
4570 end\13\
4571 else\13\
4572 return error(\"Failed to set Application. Invalid value passed '\"..tostring( app )..\"'\")\13\
4573 end\13\
4574end\13\
4575",
4576["KeyEvent.ti"]="class \"KeyEvent\" extends \"Event\" {\13\
4577 main = \"KEY\";\13\
4578\13\
4579 sub = false;\13\
4580\13\
4581 keyCode = false;\13\
4582 keyName = false;\13\
4583}\13\
4584\13\
4585function KeyEvent:__init__( name, key, held, sub )\13\
4586 self.name = name\13\
4587 self.sub = sub or name == \"key_up\" and \"UP\" or \"DOWN\"\13\
4588 self.held = held\13\
4589\13\
4590 self.keyCode = key\13\
4591 self.keyName = keys.getName( key )\13\
4592\13\
4593 self.data = { name, key, held }\13\
4594end\13\
4595",
4596["XMLLexer.ti"]="class \"XMLLexer\" extends \"Lexer\" {\13\
4597 static = {\13\
4598 escapeChars = {\13\
4599 a = \"\\a\",\13\
4600 b = \"\\b\",\13\
4601 f = \"\\f\",\13\
4602 n = \"\\n\",\13\
4603 r = \"\\r\",\13\
4604 t = \"\\t\",\13\
4605 v = \"\\v\"\13\
4606 }\13\
4607 };\13\
4608\13\
4609 openTag = false;\13\
4610 definingAttribute = false;\13\
4611 currentAttribute = false;\13\
4612}\13\
4613\13\
4614--[[\13\
4615 @instance\13\
4616 @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.\13\
4617 @param <char - opener>\13\
4618 @return <string - consumedString>\13\
4619]]\13\
4620function XMLLexer:consumeString( opener )\13\
4621 local stream, closingIndex = self.stream\13\
4622\13\
4623 if stream:find( opener, 2 ) then\13\
4624 local str, c, escaped = {}\13\
4625 for i = 2, #stream do\13\
4626 c = stream:sub( i, i )\13\
4627\13\
4628 if escaped then\13\
4629 str[ #str + 1 ] = XMLLexer.escapeChars[ c ] or c\13\
4630 escaped = false\13\
4631 elseif c == \"\\\\\" then\13\
4632 escaped = true\13\
4633 elseif c == opener then\13\
4634 self:consume( i )\13\
4635 return table.concat( str )\13\
4636 else\13\
4637 str[ #str + 1 ] = c\13\
4638 end\13\
4639 end\13\
4640 end\13\
4641\13\
4642 self:throw( \"Failed to lex stream. Expected string end (\"..opener..\")\" )\13\
4643end\13\
4644\13\
4645--[[\13\
4646 @instance\13\
4647 @desc Converts the stream into tokens by way of pattern matching\13\
4648]]\13\
4649function XMLLexer:tokenize()\13\
4650 self:trimStream()\13\
4651 local stream, openTag, currentAttribute, definingAttribute = self.stream, self.openTag, self.currentAttribute, self.definingAttribute\13\
4652 local first = stream:sub( 1, 1 )\13\
4653\13\
4654 if stream:find \"^<(%w+)\" then\13\
4655 self:pushToken({type = \"XML_OPEN\", value = self:consumePattern \"^<(%w+)\"})\13\
4656 self.openTag = true\13\
4657 elseif stream:find \"^</(%w+)>\" then\13\
4658 self:pushToken({type = \"XML_END\", value = self:consumePattern \"^</(%w+)>\"})\13\
4659 self.openTag = false\13\
4660 elseif stream:find \"^/>\" then\13\
4661 self:pushToken({type = \"XML_END_CLOSE\"})\13\
4662 self:consume( 2 )\13\
4663 self.openTag = false\13\
4664 elseif openTag and stream:find \"^%w+\" then\13\
4665 self:pushToken({type = definingAttribute and \"XML_ATTRIBUTE_VALUE\" or \"XML_ATTRIBUTE\", value = self:consumePattern \"^%w+\"})\13\
4666\13\
4667 if not definingAttribute then\13\
4668 self.currentAttribute = true\13\
4669 return\13\
4670 end\13\
4671 elseif not openTag and stream:find \"^([^<]+)\" then\13\
4672 local content = self:consumePattern \"^([^<]+)\"\13\
4673\13\
4674 local newlines = select( 2, content:gsub(\"\\n\", \"\") )\13\
4675 if newlines then self:newline( newlines ) end\13\
4676\13\
4677 self:pushToken({type = \"XML_CONTENT\", value = content })\13\
4678 elseif first == \"=\" then\13\
4679 self:pushToken({type = \"XML_ASSIGNMENT\", value = \"=\"})\13\
4680 self:consume( 1 )\13\
4681\13\
4682 if currentAttribute then\13\
4683 self.definingAttribute = true\13\
4684 end\13\
4685\13\
4686 return\13\
4687 elseif first == \"'\" or first == \"\\\"\" then\13\
4688 self:pushToken({type = definingAttribute and \"XML_STRING_ATTRIBUTE_VALUE\" or \"XML_STRING\", value = self:consumeString( first )})\13\
4689 elseif first == \">\" then\13\
4690 self:pushToken({type = \"XML_CLOSE\"})\13\
4691 self.openTag = false\13\
4692 self:consume( 1 )\13\
4693 else\13\
4694 self:throw(\"Unexpected block '\"..stream:match(\"(.-)%s\")..\"'\")\13\
4695 end\13\
4696\13\
4697 if self.currentAttribute then self.currentAttribute = false end\13\
4698 if self.definingAttribute then self.definingAttribute = false end\13\
4699end\13\
4700",
4701["Button.ti"]="--[[\13\
4702 A Button is a node that can be clicked to trigger a callback.\13\
4703 The button can containtext which can span multiple lines, however if too much text is entered it will be truncated to fit the button dimensions.\13\
4704]]\13\
4705\13\
4706class \"Button\" extends \"Node\" mixin \"MTextDisplay\" mixin \"MActivatable\" {\13\
4707 allowMouse = true;\13\
4708}\13\
4709\13\
4710--[[\13\
4711 @constructor\13\
4712 @desc Accepts button arguments and resolves them.\13\
4713 @param <string - text>, [number - X], [number - Y], [number - width], [number - height]\13\
4714]]\13\
4715function Button:__init__( ... )\13\
4716 self:resolve( ... )\13\
4717 self:super()\13\
4718\13\
4719 self:register(\"width\", \"height\")\13\
4720end\13\
4721\13\
4722--[[\13\
4723 @instance\13\
4724 @desc Sets the button to 'active' when the button is clicked\13\
4725 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
4726]]\13\
4727function Button:onMouseClick( event, handled, within )\13\
4728 if not handled then\13\
4729 self.active = within\13\
4730\13\
4731 if within then\13\
4732 event.handled = true\13\
4733 end\13\
4734 end\13\
4735end\13\
4736\13\
4737--[[\13\
4738 @instance\13\
4739 @desc Sets the button to inactive when the mouse button is released. If released on button while active 'onTrigger' callback is fired.\13\
4740 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
4741]]\13\
4742function Button:onMouseUp( event, handled, within )\13\
4743 if within and not handled and self.active then\13\
4744 self:executeCallbacks \"trigger\"\13\
4745 end\13\
4746\13\
4747 self.active = false\13\
4748end\13\
4749\13\
4750--[[\13\
4751 @instance\13\
4752 @desc Draws the text to the node canvas\13\
4753 @param [boolean - force]\13\
4754]]\13\
4755function Button:draw( force )\13\
4756 local raw = self.raw\13\
4757 if raw.changed or force then\13\
4758 local active = raw.active\13\
4759 raw.canvas:clear( active and raw.activeBackgroundColour )\13\
4760 self:drawText( active and raw.activeBackgroundColour, active and raw.activeColour )\13\
4761\13\
4762 raw.changed = false\13\
4763 end\13\
4764end\13\
4765\13\
4766--[[\13\
4767 @instance\13\
4768 @desc Sets the text of the button and then wraps the new text for display.\13\
4769 @param <string - text>\13\
4770]]\13\
4771function Button:setText( text )\13\
4772 if self.text == text then return end\13\
4773\13\
4774 self.text = text\13\
4775 self:wrapText()\13\
4776end\13\
4777\13\
4778--[[\13\
4779 @instance\13\
4780 @desc Sets the width of the button and then re-wraps the text to fit in the dimensions.\13\
4781 @param <number - width>\13\
4782]]\13\
4783function Button:setWidth( width )\13\
4784 self.super:setWidth( width )\13\
4785 self:wrapText()\13\
4786end\13\
4787\13\
4788configureConstructor {\13\
4789 orderedArguments = { \"text\" },\13\
4790 requiredArguments = { \"text\" }\13\
4791}\13\
4792",
4793["Input.ti"]="--[[\13\
4794 The Input class \"provides\" the user with the ability to insert a single line of text.\13\
4795]]\13\
4796\13\
4797class \"Input\" extends \"Node\" mixin \"MActivatable\" mixin \"MFocusable\" {\13\
4798 position = 0;\13\
4799 scroll = 0;\13\
4800 value = \"\";\13\
4801\13\
4802 selection = false;\13\
4803 selectedColour = false;\13\
4804 selectedBackgroundColour = colours.lightBlue;\13\
4805\13\
4806 placeholder = false;\13\
4807 placeholderColour = 256;\13\
4808\13\
4809 allowMouse = true;\13\
4810 allowKey = true;\13\
4811 allowChar = true;\13\
4812}\13\
4813\13\
4814--[[\13\
4815 @constructor\13\
4816 @desc Constructs the instance by resolving arguments and registering used properties\13\
4817]]\13\
4818function Input:__init__( ... )\13\
4819 self:resolve( ... )\13\
4820 self:register \"width\"\13\
4821\13\
4822 self:super()\13\
4823end\13\
4824\13\
4825--[[\13\
4826 @instance\13\
4827 @desc Sets the input to active if clicked on, sets active and focused to false if the mouse click was not on the input.\13\
4828 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
4829]]\13\
4830function Input:onMouseClick( event, handled, within )\13\
4831 if within and not handled then\13\
4832 self.active = true\13\
4833 else\13\
4834 self.active = false\13\
4835 self:unfocus()\13\
4836 end\13\
4837end\13\
4838\13\
4839--[[\13\
4840 @instance\13\
4841 @desc If the mouse up missed the input or the event was already handled, active and false are set to false.\13\
4842 If within and not handled and input is active focused is set to true. Active is set to false on all conditions.\13\
4843 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
4844]]\13\
4845function Input:onMouseUp( event, handled, within )\13\
4846 if not within or handled and self.focused then\13\
4847 self:unfocus()\13\
4848 elseif within and not handled and self.active then\13\
4849 if self.focused then\13\
4850 local application, pos, width, scroll = self.application, self.position, self.width, self.scroll\13\
4851 local clickedPos = math.min( #self.value, event.X - self.X + self.scroll )\13\
4852\13\
4853 if application:isPressed( keys.leftShift ) or application:isPressed( keys.rightShift ) then\13\
4854 if clickedPos ~= pos then\13\
4855 self.selection = clickedPos\13\
4856 else self.selection = false end\13\
4857 else self.position, self.selection = clickedPos, false end\13\
4858 else self:focus() end\13\
4859 end\13\
4860\13\
4861 self.active = false\13\
4862end\13\
4863\13\
4864--[[\13\
4865 @instance\13\
4866 @desc Catches char events and inserts the character pressed into the input's value.\13\
4867 @param <CharEvent - event>, <boolean - handled>\13\
4868]]\13\
4869function Input:onChar( event, handled )\13\
4870 if not self.focused or handled then return end\13\
4871\13\
4872 local value, position, selection = self.value, self.position, self.selection\13\
4873 if selection then\13\
4874 local start, stop = math.min( selection, position ), math.max( selection, position )\13\
4875 self.value, self.selection, self.position = value:sub( 1, start ) .. event.char .. value:sub( stop + 2 ), false, start + 1\13\
4876 self.changed = true\13\
4877 else\13\
4878 self.value = value:sub( 1, position ) .. event.char .. value:sub( position + 1 )\13\
4879 self.position = self.position + 1\13\
4880 end\13\
4881\13\
4882 self:executeCallbacks \"change\"\13\
4883\13\
4884 event.handled = true\13\
4885end\13\
4886\13\
4887--[[\13\
4888 @instance\13\
4889 @desc Catches key down events and performs an action depending on the key pressed\13\
4890 @param <KeyEvent - event>, <boolean - handled>\13\
4891]]\13\
4892function Input:onKeyDown( event, handled )\13\
4893 if not self.focused or handled then return end\13\
4894\13\
4895 local value, position = self.value, self.position\13\
4896 local valueLen = #value\13\
4897 if event.sub == \"DOWN\" then\13\
4898 local key, selection, position, application = event.keyName, self.selection, self.position, self.application\13\
4899 local isPressed, start, stop = application:isPressed( keys.leftShift ) or application:isPressed( keys.rightShift )\13\
4900\13\
4901 if selection then\13\
4902 start, stop = selection < position and selection or position, selection > position and selection + 1 or position + 1\13\
4903 else start, stop = position - 1, position end\13\
4904\13\
4905 if key == \"enter\" then\13\
4906 self:executeCallbacks( \"trigger\", self.value, self.selection and self:getSelectedValue() )\13\
4907 elseif selection then\13\
4908 if key == \"delete\" or key == \"backspace\" then\13\
4909 self.value, self.position = value:sub( 1, start ) .. value:sub( stop + 1 ), start\13\
4910 elseif not isPressed then\13\
4911 self.position = key == \"left\" and start or key == \"right\" and stop - 1 or position\13\
4912 self.selection = false\13\
4913 end\13\
4914 end\13\
4915\13\
4916 local cSelection = self.selection or self.position\13\
4917 local function set( offset )\13\
4918 if isPressed then self.selection = cSelection + offset\13\
4919 else self.position = self.position + offset end\13\
4920 end\13\
4921\13\
4922 if key == \"left\" then set( -1 )\13\
4923 elseif key == \"right\" then set( 1 ) else\13\
4924 if key == \"home\" then\13\
4925 set( isPressed and -cSelection or -position )\13\
4926 elseif key == \"end\" then\13\
4927 set( isPressed and valueLen - cSelection or valueLen - position )\13\
4928 elseif key == \"backspace\" and isPressed then\13\
4929 self.value, self.position = self.value:sub( stop + 1 ), 0\13\
4930 end\13\
4931 end\13\
4932\13\
4933 if not isPressed then\13\
4934 if key == \"backspace\" and start >= 0 and not selection then\13\
4935 self.value = value:sub( 1, start ) .. value:sub( stop + 1 )\13\
4936 self.position = start\13\
4937 elseif key == \"delete\" and not selection then\13\
4938 self.value, self.changed = value:sub( 1, stop ) .. value:sub( stop + 2 ), true\13\
4939 else\13\
4940 if key ~= \"left\" and key ~= \"right\" and key ~= \"home\" and key ~= \"end\" then\13\
4941 self.selection = false\13\
4942 end\13\
4943 end\13\
4944 end\13\
4945 end\13\
4946end\13\
4947\13\
4948function Input:onLabelClicked( label, event, handled, within )\13\
4949 self:focus()\13\
4950 event.handled = true\13\
4951end\13\
4952\13\
4953--[[\13\
4954 @instance\13\
4955 @desc Draws the inputs background and text to the parent canvas\13\
4956 @param [boolean - force]\13\
4957]]\13\
4958function Input:draw( force )\13\
4959 local raw = self.raw\13\
4960 if raw.changed or force then\13\
4961 local canvas, tc, bg = raw.canvas, raw.colour, raw.backgroundColour\13\
4962 if raw.focused then tc, bg = raw.focusedColour, raw.focusedBackgroundColour\13\
4963 elseif raw.active then tc, bg = raw.activeColour, raw.activeBackgroundColour end\13\
4964\13\
4965 canvas:clear( bg )\13\
4966\13\
4967 local position, width, value, selection = self.position, self.width, self.value, self.selection\13\
4968 if self.focused or not self.placeholder or #value > 0 then\13\
4969 if self.selection then\13\
4970 local start, stop = selection < position and selection or position, selection > position and selection + 1 or position + 1\13\
4971 local startPos = -self.scroll + 1\13\
4972\13\
4973 canvas:drawTextLine( startPos, 1, value:sub( 1, start + 1 ), tc, bg )\13\
4974 canvas:drawTextLine( startPos + start, 1, value:sub( start + 1, stop + 1 ), tc, self.focused and self.selectedBackgroundColour or bg )\13\
4975 canvas:drawTextLine( startPos + stop, 1, value:sub( stop + 1 ), tc, bg )\13\
4976 else\13\
4977 canvas:drawTextLine( -self.scroll + 1, 1, self.value, tc, bg )\13\
4978 end\13\
4979 else canvas:drawTextLine( 1, 1, self.placeholder:sub( 1, self.width ), self.placeholderColour, bg ) end\13\
4980\13\
4981 raw.changed = false\13\
4982 end\13\
4983end\13\
4984\13\
4985--[[\13\
4986 @instance\13\
4987 @desc Attempts to reposition the scroll of the input box depending on the position indicator\13\
4988 @param <number - indicator>\13\
4989]]\13\
4990function Input:repositionScroll( indicator )\13\
4991 if indicator >= self.width and indicator > ( self.scroll + self.width - 1 ) then\13\
4992 self.scroll = math.min( indicator - self.width + 1, #self.value - self.width + 1 )\13\
4993 elseif indicator <= self.scroll then\13\
4994 self.scroll = math.max( self.scroll - ( self.scroll - indicator ), 0 )\13\
4995 else self.scroll = math.max( math.min( self.scroll, #self.value - self.width + 1 ), 0 ) end\13\
4996end\13\
4997\13\
4998--[[\13\
4999 @instance\13\
5000 @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.\13\
5001 @param <number|boolean - selection>\13\
5002]]\13\
5003function Input:setSelection( selection )\13\
5004 if type( selection ) == \"number\" then\13\
5005 local newSelection = math.max( math.min( selection, #self.value ), 0 )\13\
5006 self.selection = newSelection ~= self.position and newSelection or false\13\
5007 else self.selection = selection end\13\
5008\13\
5009 self:repositionScroll( self.selection or self.position )\13\
5010 self.changed = true\13\
5011end\13\
5012\13\
5013--[[\13\
5014 @instance\13\
5015 @desc Returns the value of the input that is selected\13\
5016 @return <string - selectedValue>\13\
5017]]\13\
5018function Input:getSelectedValue()\13\
5019 local selection, position = self.selection, self.position\13\
5020 return self.value:sub( ( selection < position and selection or position ) + 1, ( selection > position and selection or position ) + 1 )\13\
5021end\13\
5022\13\
5023--[[\13\
5024 @instance\13\
5025 @desc If the given position is equal to the (inputs) selection, the selection will be reset.\13\
5026 If not equal, the value will be adjusted to fit inside the bounds of the input and then set.\13\
5027 @param <number - pos>\13\
5028]]\13\
5029function Input:setPosition( pos )\13\
5030 if self.selection == pos then self.selection = false end\13\
5031 self.position, self.changed = math.max( math.min( pos, #self.value ), 0 ), true\13\
5032\13\
5033 self:repositionScroll( self.position )\13\
5034end\13\
5035\13\
5036--[[\13\
5037 @instance\13\
5038 @desc When called, returns the state of the caret, its position (absolute) and colour.\13\
5039 @return <boolean - caretEnabled>, <number - caretX>, <number - caretY>, <colour - caretColour>\13\
5040]]\13\
5041function Input:getCaretInfo()\13\
5042 local sX, sY = self:getAbsolutePosition()\13\
5043\13\
5044 return not self.selection, sX + ( self.position - self.scroll ), sY, self.focusedColour\13\
5045end\13\
5046\13\
5047\13\
5048configureConstructor({\13\
5049 orderedArguments = { \"X\", \"Y\", \"width\" },\13\
5050 argumentTypes = { value = \"string\", position = \"number\", selection = \"number\", placeholder = \"string\", placeholderColour = \"colour\" },\13\
5051 useProxy = { \"toggled\" }\13\
5052}, true)\13\
5053",
5054["Theme.ti"]="--[[\13\
5055 The Theme class \"is\" a basic class \"designed\" to hold styling rules.\13\
5056\13\
5057 Themes are added to objects using the MThemeManager mixin (or a custom implementation). These themes then dictate the appearance of objects that utilize 'MThemeable'.\13\
5058]]\13\
5059local function getTagDetails( rule )\13\
5060 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 \"\" )\13\
5061end\13\
5062\13\
5063local function splitXMLTheme( queue, tree )\13\
5064 for i = 1, #tree do\13\
5065 local children = tree[ i ].children\13\
5066 if children then\13\
5067 for c = 1, #children do\13\
5068 queue[ #queue + 1 ] = { tree[ i ].type .. getTagDetails( tree[ i ] ), children[ c ], tree[ i ] }\13\
5069 end\13\
5070 end\13\
5071 end\13\
5072\13\
5073 return queue\13\
5074end\13\
5075\13\
5076class \"Theme\" {\13\
5077 name = false;\13\
5078\13\
5079 rules = {};\13\
5080}\13\
5081\13\
5082--[[\13\
5083 @constructor\13\
5084 @desc Constructs the Theme by setting the name.\13\
5085 @param <string - name>, [string - source]\13\
5086]]\13\
5087function Theme:__init__( name, source )\13\
5088 self.name = type( name ) == \"string\" and name or error(\"Failed to initialise Theme. Name '\"..tostring( name )..\"' is invalid, expected string.\")\13\
5089\13\
5090 if source then self.rules = Theme.parse( source ) end\13\
5091end\13\
5092\13\
5093--[[\13\
5094 @static\13\
5095 @desc Parses XML source code by lexing/parsing it into an XML tree. The XML is then parsed into theme rules\13\
5096 @param <string - source>\13\
5097 @return <table - rules>\13\
5098]]\13\
5099function Theme.static.parse( source )\13\
5100 local queue, rawRules, q = splitXMLTheme( {}, XMLParser( source ).tree ), {}, 1\13\
5101\13\
5102 local function processQueueEntry( entry )\13\
5103 local queryPrefix, rule = entry[ 1 ], entry[ 2 ]\13\
5104 local children = rule.children\13\
5105\13\
5106 if children then\13\
5107 for c = 1, #children do\13\
5108 if not Titanium.getClass( rule.type ) then\13\
5109 return error( \"Failed to generate theme data. Child target '\"..rule.type..\"' doesn't exist as a Titanium class\" )\13\
5110 end\13\
5111\13\
5112 queue[ #queue + 1 ] = { queryPrefix .. \" \" .. ( rule.arguments.direct and \"> \" or \"\" ) .. rule.type .. getTagDetails( rule ), children[ c ], rule }\13\
5113 end\13\
5114 elseif rule.content then\13\
5115 local owner = entry[ 3 ]\13\
5116 local ownerType = owner.type\13\
5117\13\
5118 local parentReg = Titanium.getClass( ownerType ).getRegistry()\13\
5119 local argumentTypes = parentReg.constructor and parentReg.constructor.argumentTypes or {}\13\
5120\13\
5121\13\
5122 if not rawRules[ ownerType ] then rawRules[ ownerType ] = {} end\13\
5123 table.insert( rawRules[ ownerType ], {\13\
5124 query = queryPrefix,\13\
5125 property = rule.type,\13\
5126 value = XMLParser.convertArgType( rule.content, argumentTypes[ parentReg.alias[ rule.type ] or rule.type ] ),\13\
5127 important = rule.arguments.important\13\
5128 })\13\
5129 else\13\
5130 return error( \"Failed to generate theme data. Invalid theme rule found. No value (XML_CONTENT) has been set for tag '\"..rule.type..\"'\" )\13\
5131 end\13\
5132 end\13\
5133\13\
5134 while q <= #queue do\13\
5135 processQueueEntry( queue[ q ] )\13\
5136 q = q + 1\13\
5137 end\13\
5138\13\
5139 return rawRules\13\
5140end\13\
5141\13\
5142--[[\13\
5143 @static\13\
5144 @desc Creates a Theme instance with the name passed and the source as the contents of the file at 'path'.\13\
5145 @param <string - name>, <string - path>\13\
5146 @return <Theme Instance - Theme>\13\
5147]]\13\
5148function Theme.static.fromFile( name, path )\13\
5149 if not fs.exists( path ) then\13\
5150 return error( \"Path '\"..tostring( path )..\"' cannot be found\" )\13\
5151 end\13\
5152\13\
5153 local h = fs.open( path, \"r\" )\13\
5154 local content = h.readAll()\13\
5155 h.close()\13\
5156\13\
5157 return Theme( name, content )\13\
5158end\13\
5159",
5160["ScrollContainer.ti"]="class \"ScrollContainer\" extends \"Container\" {\13\
5161 cache = {};\13\
5162\13\
5163 xScroll = 0;\13\
5164 yScroll = 0;\13\
5165\13\
5166 xScrollAllowed = true;\13\
5167 yScrollAllowed = true;\13\
5168 propagateMouse = true;\13\
5169\13\
5170 trayColour = 256;\13\
5171 scrollbarColour = 128;\13\
5172 activeScrollbarColour = colours.cyan;\13\
5173\13\
5174 mouse = {\13\
5175 selected = false;\13\
5176 origin = false;\13\
5177 };\13\
5178}\13\
5179\13\
5180function ScrollContainer:__init__( ... )\13\
5181 self:register( \"scrollbarColour\", \"activeScrollbarColour\", \"trayColour\" )\13\
5182 self:super( ... )\13\
5183end\13\
5184\13\
5185--[[ Event Listeners ]]--\13\
5186function ScrollContainer:onMouseClick( event, handled, within )\13\
5187 if handled or not within then return end\13\
5188\13\
5189 local cache, mouse, key = self.cache, self.mouse\13\
5190 local X, Y = event.X - self.X + 1, event.Y - self.Y + 1\13\
5191\13\
5192 if cache.yScrollActive and X == self.width and Y <= cache.displayHeight then\13\
5193 key = \"y\"\13\
5194 elseif cache.xScrollActive and Y == self.height and X <= cache.displayWidth then\13\
5195 key = \"x\"\13\
5196 else return end\13\
5197\13\
5198 local scrollFn = self[ \"set\"..key:upper()..\"Scroll\" ]\13\
5199 local edge, size = cache[ key .. \"ScrollPosition\" ], cache[ key .. \"ScrollSize\" ]\13\
5200 local cScale, dScale = cache[ \"content\" .. ( key == \"x\" and \"Width\" or \"Height\" ) ], cache[ \"display\" .. ( key == \"x\" and \"Width\" or \"Height\" ) ]\13\
5201\13\
5202 local rel = key == \"x\" and X or Y\13\
5203 if rel < edge then\13\
5204 event.handled = scrollFn( self, math.floor( cScale * ( rel / dScale ) - .5 ) )\13\
5205 elseif rel >= edge and rel <= edge + size - 1 then\13\
5206 mouse.selected, mouse.origin = key == \"x\" and \"h\" or \"v\", rel - edge + 1\13\
5207 elseif rel > edge + size - 1 then\13\
5208 event.handled = scrollFn( self, math.floor( cScale * ( ( rel - size + 1 ) / dScale ) - .5 ) )\13\
5209 end\13\
5210\13\
5211 self:cacheScrollbarPosition()\13\
5212 self.changed = true\13\
5213end\13\
5214\13\
5215function ScrollContainer:onMouseScroll( event, handled, within )\13\
5216 local cache, app = self.cache, self.application\13\
5217 if handled or not within or not ( cache.xScrollActive or cache.yScrollActive ) then return end\13\
5218\13\
5219 local isXScroll = ( cache.xScrollActive and ( not cache.yScrollActive or ( app:isPressed( keys.leftShift ) or app:isPressed( keys.rightShift ) ) ) )\13\
5220\13\
5221 event.handled = self[\"set\".. ( isXScroll and \"X\" or \"Y\" ) ..\"Scroll\"]( self, self[ ( isXScroll and \"x\" or \"y\" ) .. \"Scroll\" ] + event.button )\13\
5222 self:cacheScrollbarPosition()\13\
5223end\13\
5224\13\
5225function ScrollContainer:onMouseUp( event, handled, within )\13\
5226 if self.mouse.selected then\13\
5227 self.mouse.selected = false\13\
5228 self.changed = true\13\
5229 end\13\
5230end\13\
5231\13\
5232function ScrollContainer:onMouseDrag( event, handled, within )\13\
5233 local mouse, cache = self.mouse, self.cache\13\
5234 if handled or not mouse.selected then return end\13\
5235\13\
5236 local isV = mouse.selected == \"v\"\13\
5237 local key = isV and \"Y\" or \"X\"\13\
5238 local scaleKey = isV and \"Height\" or \"Width\"\13\
5239\13\
5240 event.handled = self[ \"set\" .. key .. \"Scroll\" ]( self, math.floor( cache[\"content\" .. scaleKey ] * ( ( ( event[ key ] - self[ key ] + 1 ) - mouse.origin ) / cache[\"display\" .. scaleKey ] ) - .5 ) )\13\
5241end\13\
5242\13\
5243function ScrollContainer:addNode( ... )\13\
5244 self.super:addNode( ... )\13\
5245\13\
5246 self:cacheContent()\13\
5247end\13\
5248\13\
5249--[[ Core Functions ]]--\13\
5250function ScrollContainer:handle( eventObj )\13\
5251 local cache, isWithin = self.cache, eventObj.isWithin\13\
5252 local cloneEv\13\
5253\13\
5254 if eventObj.main == \"MOUSE\" then\13\
5255 eventObj.isWithin = eventObj:withinParent( self )\13\
5256 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\13\
5257 cloneEv = eventObj:clone( self )\13\
5258 cloneEv.Y = cloneEv.Y + self.yScroll\13\
5259 cloneEv.X = cloneEv.X + self.xScroll\13\
5260 end\13\
5261 else cloneEv = eventObj end\13\
5262\13\
5263 if cloneEv then self:shipEvent( cloneEv ) end\13\
5264 if not self.super.super:handle( eventObj ) then return end\13\
5265\13\
5266 eventObj.isWithin = isWithin\13\
5267 return true\13\
5268end\13\
5269\13\
5270function ScrollContainer:isNodeInBounds( node, width, height )\13\
5271 local left, top = node.X - self.xScroll, node.Y - self.yScroll\13\
5272\13\
5273 return not ( ( left + node.width ) < 1 or left > ( width or self.width ) or top > ( height or self.height ) or ( top + node.height ) < 1 )\13\
5274end\13\
5275\13\
5276function ScrollContainer:draw( force )\13\
5277 if self.changed or force then\13\
5278 local raw = self.raw\13\
5279 local canvas = raw.canvas\13\
5280\13\
5281 local xScroll, yScroll = self.xScroll, self.yScroll\13\
5282 local width, height = self.width, self.height\13\
5283 local nodes, node = self.nodes\13\
5284\13\
5285 canvas:clear()\13\
5286 for i = 1, #nodes do\13\
5287 node = nodes[ i ]\13\
5288\13\
5289 if self:isNodeInBounds( node, width, height ) then\13\
5290 node:draw( force )\13\
5291\13\
5292 node.canvas:drawTo( canvas, node.X - xScroll, node.Y - yScroll )\13\
5293 end\13\
5294 end\13\
5295\13\
5296 self:drawScrollbars()\13\
5297\13\
5298 raw.changed = false\13\
5299 end\13\
5300end\13\
5301\13\
5302function ScrollContainer:drawScrollbars()\13\
5303 local cache, canvas = self.cache, self.canvas\13\
5304 local xEnabled, yEnabled = cache.xScrollActive, cache.yScrollActive\13\
5305\13\
5306 if xEnabled then\13\
5307 canvas:drawBox( 1, self.height, cache.displayWidth, 1, self.trayColour )\13\
5308 canvas:drawBox( cache.xScrollPosition, self.height, cache.xScrollSize, 1, self.mouse.selected == \"h\" and self.activeScrollbarColour or self.scrollbarColour )\13\
5309 end\13\
5310\13\
5311 if yEnabled then\13\
5312 canvas:drawBox( self.width, 1, 1, cache.displayHeight, self.trayColour )\13\
5313 canvas:drawBox( self.width, cache.yScrollPosition, 1, cache.yScrollSize, self.mouse.selected == \"v\" and self.activeScrollbarColour or self.scrollbarColour )\13\
5314 end\13\
5315\13\
5316 if yEnabled and xEnabled then\13\
5317 canvas:drawPoint( self.width, self.height, \" \", 1, self.trayColour )\13\
5318 end\13\
5319end\13\
5320\13\
5321function ScrollContainer:setYScroll( yScroll )\13\
5322 local oY, cache = self.yScroll, self.cache\13\
5323 self.yScroll = math.max( 0, math.min( cache.contentHeight - cache.displayHeight, yScroll ) )\13\
5324\13\
5325 self:cacheScrollbarPosition()\13\
5326 if ( not self.propagateMouse ) or oY ~= self.yScroll then\13\
5327 return true\13\
5328 end\13\
5329end\13\
5330\13\
5331function ScrollContainer:setXScroll( xScroll )\13\
5332 local oX, cache = self.xScroll, self.cache\13\
5333 self.xScroll = math.max( 0, math.min( cache.contentWidth - cache.displayWidth, xScroll ) )\13\
5334\13\
5335 self:cacheScrollbarPosition()\13\
5336 if ( not self.propagateMouse ) or oX ~= self.xScroll then\13\
5337 return true\13\
5338 end\13\
5339end\13\
5340\13\
5341--[[ Caching Functions ]]--\13\
5342function ScrollContainer:cacheContent()\13\
5343 self:cacheContentSize()\13\
5344 self:cacheActiveScrollbars()\13\
5345end\13\
5346\13\
5347function ScrollContainer:cacheContentSize()\13\
5348 local w, h = 0, 0\13\
5349\13\
5350 local nodes, node = self.nodes\13\
5351 for i = 1, #nodes do\13\
5352 node = nodes[ i ]\13\
5353\13\
5354 w = math.max( node.X + node.width - 1, w )\13\
5355 h = math.max( node.Y + node.height - 1, h )\13\
5356 end\13\
5357\13\
5358 self.cache.contentWidth, self.cache.contentHeight = w, h\13\
5359end\13\
5360\13\
5361function ScrollContainer:cacheDisplaySize()\13\
5362 local cache = self.cache\13\
5363 cache.displayWidth, cache.displayHeight = self.width - ( cache.yScrollActive and 1 or 0 ), self.height - ( cache.xScrollActive and 1 or 0 )\13\
5364\13\
5365 self:cacheScrollbarSize()\13\
5366end\13\
5367\13\
5368function ScrollContainer:cacheActiveScrollbars()\13\
5369 local cache = self.cache\13\
5370 local cWidth, cHeight, sWidth, sHeight = cache.contentWidth, cache.contentHeight, self.width, self.height\13\
5371 local xAllowed, yAllowed = self.xScrollAllowed, self.yScrollAllowed\13\
5372\13\
5373 local horizontal, vertical\13\
5374 if ( cWidth > sWidth and xAllowed ) or ( cHeight > sHeight and yAllowed ) then\13\
5375 cache.xScrollActive, cache.yScrollActive = cWidth > sWidth - 1 and xAllowed, cHeight > sHeight - 1 and yAllowed\13\
5376 else\13\
5377 cache.xScrollActive, cache.yScrollActive = false, false\13\
5378 end\13\
5379\13\
5380 self:cacheDisplaySize()\13\
5381end\13\
5382\13\
5383function ScrollContainer:cacheScrollbarSize()\13\
5384 local cache = self.cache\13\
5385 cache.xScrollSize, cache.yScrollSize = math.floor( cache.displayWidth * ( cache.displayWidth / cache.contentWidth ) + .5 ), math.floor( cache.displayHeight * ( cache.displayHeight / cache.contentHeight ) + .5 )\13\
5386\13\
5387 self:cacheScrollbarPosition()\13\
5388end\13\
5389\13\
5390function ScrollContainer:cacheScrollbarPosition()\13\
5391 local cache = self.cache\13\
5392 cache.xScrollPosition, cache.yScrollPosition = math.ceil( self.xScroll / cache.contentWidth * cache.displayWidth + .5 ), math.ceil( self.yScroll / cache.contentHeight * cache.displayHeight + .5 )\13\
5393\13\
5394 self.changed = true\13\
5395end\13\
5396\13\
5397configureConstructor {\13\
5398 argumentTypes = {\13\
5399 scrollbarColour = \"colour\",\13\
5400 activeScrollbarColour = \"colour\",\13\
5401 xScrollAllowed = \"boolean\",\13\
5402 yScrollAllowed = \"boolean\"\13\
5403 }\13\
5404}\13\
5405",
5406["MouseEvent.ti"]="local string_sub, string_upper = string.sub, string.upper\13\
5407\13\
5408class \"MouseEvent\" extends \"Event\" {\13\
5409 inBounds = false;\13\
5410 main = \"MOUSE\";\13\
5411\13\
5412 isWithin = true;\13\
5413}\13\
5414\13\
5415--[[\13\
5416 @constructor\13\
5417 @desc Sets the values given in their respective instance keys. Stores all values into a table 'data'.\13\
5418 @param <string - name>, <number - button>, <number - X>, <number - Y>, [string - sub]\13\
5419]]\13\
5420function MouseEvent:__init__( name, button, X, Y, sub )\13\
5421 self.name = name\13\
5422 self.button = button\13\
5423 self.X = X\13\
5424 self.Y = Y\13\
5425\13\
5426 self.sub = sub or string_upper( string_sub( name, 7 ) )\13\
5427\13\
5428 self.data = { name, button, X, Y }\13\
5429end\13\
5430\13\
5431--[[\13\
5432 @instance\13\
5433 @desc Returns true if the mouse event was inside of the bounds provided.\13\
5434 @param <number - x>, <number - y>, <number - w>, <number - h>\13\
5435 @return <boolean - inBounds>\13\
5436]]\13\
5437function MouseEvent:within( x, y, w, h )\13\
5438 local X, Y = self.X, self.Y\13\
5439\13\
5440 return X >= x and Y >= y and X <= -1 + x + w and Y <= -1 + y + h\13\
5441end\13\
5442\13\
5443--[[\13\
5444 @instance\13\
5445 @desc Returns true if the mouse event was inside the bounds of the parent provided (x, y, width and height)\13\
5446 @param <NodeContainer - parent>\13\
5447]]\13\
5448function MouseEvent:withinParent( parent )\13\
5449 return self:within( parent.X, parent.Y, parent.width, parent.height )\13\
5450end\13\
5451\13\
5452--[[\13\
5453 @instance\13\
5454 @desc Clones 'self' and adjusts it's X & Y positions so that they're relative to 'parent'.\13\
5455 @param <NodeContainer - parent>\13\
5456 @return <MouseEvent Instance - clone>\13\
5457\13\
5458 Note: The clone's 'handle' method has been adjusted to also call 'handle' on the master event obj (self).\13\
5459]]\13\
5460function MouseEvent:clone( parent )\13\
5461 local clone = MouseEvent( self.name, self.button, self.X - parent.X + 1, self.Y - parent.Y + 1, self.sub )\13\
5462\13\
5463 clone.handled = self.handled\13\
5464 clone.isWithin = self.isWithin\13\
5465 clone.setHandled = function( clone, handled )\13\
5466 clone.handled = handled\13\
5467 self.handled = handled --TODO: Make sure 'self' refers to the object which 'clone' was invoked on, not the clone.\13\
5468 end\13\
5469\13\
5470 return clone\13\
5471end\13\
5472",
5473["Label.ti"]="--[[\13\
5474 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.\13\
5475--]]\13\
5476\13\
5477class \"Label\" extends \"Node\" {\13\
5478 labelFor = false;\13\
5479\13\
5480 allowMouse = true;\13\
5481 active = false;\13\
5482}\13\
5483\13\
5484--[[\13\
5485 @constructor\13\
5486 @param <string - text>, [number - X], [number - Y]\13\
5487]]\13\
5488function Label:__init__( ... )\13\
5489 self:resolve( ... )\13\
5490 self.raw.width = #self.text\13\
5491\13\
5492 self:super()\13\
5493 self:register \"text\"\13\
5494end\13\
5495\13\
5496--[[\13\
5497 @instance\13\
5498 @desc Mouse click event handler. On click the label will wait for a mouse up, if found labelFor is notified\13\
5499 @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\13\
5500]]\13\
5501function Label:onMouseClick( event, handled, within )\13\
5502 self.active = self.labelFor and within and not handled\13\
5503end\13\
5504\13\
5505--[[\13\
5506 @instance\13\
5507 @desc If the mouse click handler has set the label to active, trigger the onLabelClicked callback\13\
5508 @param <MouseEvent Instance - event>, <boolean - handled>, <boolean - within>\13\
5509]]\13\
5510function Label:onMouseUp( event, handled, within )\13\
5511 if not self.labelFor then return end\13\
5512\13\
5513 local labelFor = self.application:getNode( self.labelFor, true )\13\
5514 if self.active and not handled and within and labelFor:can \"onLabelClicked\" then\13\
5515 labelFor:onLabelClicked( self, event, handled, within )\13\
5516 end\13\
5517\13\
5518 self.active = false\13\
5519end\13\
5520\13\
5521--[[\13\
5522 @instance\13\
5523 @desc Clears the Label's canvas and draws a line of text if the label has changed.\13\
5524 @param [boolean - force]\13\
5525]]\13\
5526function Label:draw( force )\13\
5527 local raw = self.raw\13\
5528 if raw.changed or force then\13\
5529 raw.canvas:drawTextLine( 1, 1, raw.text )\13\
5530\13\
5531 raw.changed = false\13\
5532 end\13\
5533end\13\
5534\13\
5535--[[\13\
5536 @instance\13\
5537 @desc Sets the text of a node. Once set, the nodes 'changed' status is set to true along with its parent(s)\13\
5538 @param <string - text>\13\
5539]]\13\
5540function Label:setText( text )\13\
5541 if self.text == text then return end\13\
5542\13\
5543 self.text = text\13\
5544 self.width = #text\13\
5545end\13\
5546\13\
5547configureConstructor({\13\
5548 orderedArguments = { \"text\", \"X\", \"Y\" },\13\
5549 requiredArguments = { \"text\" },\13\
5550 argumentTypes = { text = \"string\" }\13\
5551}, true)\13\
5552",
5553["Checkbox.ti"]="--[[\13\
5554 The checkbox is a node that can be toggled on and off\13\
5555]]\13\
5556\13\
5557class \"Checkbox\" extends \"Node\" mixin \"MActivatable\" mixin \"MTogglable\" {\13\
5558 checkedMark = \"x\";\13\
5559 uncheckedMark = \" \";\13\
5560\13\
5561 allowMouse = true;\13\
5562}\13\
5563\13\
5564--[[\13\
5565 @constructor\13\
5566 @desc Resolves arguments and calls super constructor\13\
5567 @param <number - X>, <number - Y>\13\
5568]]\13\
5569function Checkbox:__init__( ... )\13\
5570 self:resolve( ... )\13\
5571 self:super()\13\
5572\13\
5573 self:register(\"checkedMark\", \"uncheckedMark\")\13\
5574end\13\
5575\13\
5576--[[\13\
5577 @instance\13\
5578 @desc Sets the checkbox to 'active' when clicked\13\
5579 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
5580]]\13\
5581function Checkbox:onMouseClick( event, handled, within )\13\
5582 if not handled then\13\
5583 self.active = within\13\
5584\13\
5585 if within then\13\
5586 event.handled = true\13\
5587 end\13\
5588 end\13\
5589end\13\
5590\13\
5591--[[\13\
5592 @instance\13\
5593 @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.\13\
5594 @param <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
5595]]\13\
5596function Checkbox:onMouseUp( event, handled, within )\13\
5597 if not handled and within and self.active then\13\
5598 self:toggle( event, handled, within )\13\
5599\13\
5600 event.handled = true\13\
5601 end\13\
5602\13\
5603 self.active = false\13\
5604end\13\
5605\13\
5606--[[\13\
5607 @instance\13\
5608 @desc If a label which specifies this node as its 'labelFor' paramater is clicked this function will be called, causing the checkbox to toggle\13\
5609 @param <LabelInstance - label>, <MouseEvent - event>, <boolean - handled>, <boolean - within>\13\
5610]]\13\
5611function Checkbox:onLabelClicked( label, event, handled, within )\13\
5612 self:toggle( event, handled, within, label )\13\
5613 event.handled = true\13\
5614end\13\
5615\13\
5616--[[\13\
5617 @instance\13\
5618 @desc Draws the checkbox to the canvas\13\
5619 @param [boolean - force]\13\
5620]]\13\
5621function Checkbox:draw( force )\13\
5622 local raw = self.raw\13\
5623 if raw.changed or force then\13\
5624 local toggled, active = raw.toggled, raw.active\13\
5625 raw.canvas:drawPoint(\13\
5626 1, 1,\13\
5627 toggled and raw.checkedMark or raw.uncheckedMark,\13\
5628 toggled and raw.toggledColour or active and raw.activeColour or raw.colour,\13\
5629 toggled and raw.toggledBackgroundColour or active and raw.activeBackgroundColour or raw.backgroundColour\13\
5630 )\13\
5631\13\
5632 raw.changed = false\13\
5633 end\13\
5634end\13\
5635\13\
5636configureConstructor( {\13\
5637 orderedArguments = { \"X\", \"Y\" },\13\
5638 argumentTypes = {\13\
5639 checkedMark = \"string\",\13\
5640 uncheckedMark = \"string\"\13\
5641 }\13\
5642}, true, true )\13\
5643",
5644}
5645local scriptFiles = {
5646["Titanium.lua"]=true,
5647["Class.lua"]=true,
5648}
5649local preLoad = {
5650}
5651local loaded = {}
5652local function loadFile( name, verify )
5653 if loaded[ name ] then return end
5654
5655 local content = files[ name ]
5656 if content then
5657 local output, err = loadstring( content, name )
5658 if not output or err then return error( "Failed to load Lua chunk. File '"..name.."' has a syntax error: "..tostring( err ), 0 ) end
5659
5660 local ok, err = pcall( output )
5661 if not ok or err then return error( "Failed to execute Lua chunk. File '"..name.."' crashed: "..tostring( err ), 0 ) end
5662
5663 if verify then
5664 local className = name:gsub( "%..*", "" )
5665 local class = Titanium.getClass( className )
5666
5667 if class then
5668 if not class:isCompiled() then class:compile() end
5669 else return error( "File '"..name.."' failed to create class '"..className.."'" ) end
5670 end
5671
5672 loaded[ name ] = true
5673 else return error("Failed to load Titanium. File '"..tostring( name ).."' cannot be found.") end
5674end
5675
5676-- Load our class file
5677loadFile( "Class.lua" )
5678
5679Titanium.setClassLoader(function( name )
5680 local fName = name..".ti"
5681
5682 if not files[ fName ] then
5683 return error("Failed to find file '"..fName..", to load missing class '"..name.."'.", 3)
5684 else
5685 loadFile( fName, true )
5686 end
5687end)
5688
5689-- Load any files specified by our config file
5690for i = 1, #preLoad do loadFile( preLoad[ i ], not scriptFiles[ preLoad[ i ] ] ) end
5691
5692-- Load all class files
5693for name in pairs( files ) do if not scriptFiles[ name ] then
5694 loadFile( name, true )
5695end end
5696
5697-- Load all script files
5698for name in pairs( scriptFiles ) do loadFile( name, false ) end
5699