agt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +7 -0
  3. data/README.md +5 -0
  4. data/app/assets/javascripts/agt/config.coffee +9 -0
  5. data/app/assets/javascripts/agt/dom.coffee +12 -0
  6. data/app/assets/javascripts/agt/function.coffee +317 -0
  7. data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
  8. data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
  9. data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
  10. data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
  11. data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
  12. data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
  13. data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
  14. data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
  15. data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
  16. data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
  17. data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
  18. data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
  19. data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
  20. data/app/assets/javascripts/agt/geom/point.coffee +454 -0
  21. data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
  22. data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
  23. data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
  24. data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
  25. data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
  26. data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
  27. data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
  28. data/app/assets/javascripts/agt/impulse.coffee +105 -0
  29. data/app/assets/javascripts/agt/index.coffee +56 -0
  30. data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
  31. data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
  32. data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
  33. data/app/assets/javascripts/agt/inheritance.coffee +132 -0
  34. data/app/assets/javascripts/agt/math.coffee +45 -0
  35. data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
  36. data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
  37. data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
  38. data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
  39. data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
  40. data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
  41. data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
  42. data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
  43. data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
  44. data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
  45. data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
  46. data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
  47. data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
  48. data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
  49. data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
  50. data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
  51. data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
  52. data/app/assets/javascripts/agt/net/router.coffee +165 -0
  53. data/app/assets/javascripts/agt/object.coffee +9 -0
  54. data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
  55. data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
  56. data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
  57. data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
  58. data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
  59. data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
  60. data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
  61. data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
  62. data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
  63. data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
  64. data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
  65. data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
  66. data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
  67. data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
  68. data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
  69. data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
  70. data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
  71. data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
  72. data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
  73. data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
  74. data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
  75. data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
  76. data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
  77. data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
  78. data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
  79. data/app/assets/javascripts/agt/particles/system.coffee +103 -0
  80. data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
  81. data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
  82. data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
  83. data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
  84. data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
  85. data/app/assets/javascripts/agt/promise.coffee +214 -0
  86. data/app/assets/javascripts/agt/random/random.coffee +100 -0
  87. data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
  88. data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
  89. data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
  90. data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
  91. data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
  92. data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
  93. data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
  94. data/app/assets/javascripts/agt/signal.coffee +272 -0
  95. data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
  96. data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
  97. data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
  98. data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
  99. data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
  100. data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
  101. data/lib/agt.rb +8 -0
  102. data/lib/agt/version.rb +5 -0
  103. metadata +173 -0
@@ -0,0 +1,23 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.LinearCongruential
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.LinearCongruential','seed')
6
+ @include agt.mixins.Formattable('LinearCongruential','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (@seed=1) ->
11
+ plantSeed: (@seed=1) ->
12
+
13
+ get: ->
14
+ tmp = @seed
15
+ q = tmp
16
+ q = q << 1
17
+ p = tmp << 32
18
+ m = p + q
19
+ if m & 0x80000000
20
+ m = m & 0x7fffffff
21
+ m++
22
+ @seed = m
23
+ m / 0x80000000
@@ -0,0 +1,9 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.MathRandom
4
+ @include agt.mixins.Cloneable()
5
+ @include agt.mixins.Sourcable('chancejs.MathRandom')
6
+ @include agt.mixins.Formattable('MathRandom')
7
+
8
+ # Public:
9
+ get: -> Math.random()
@@ -0,0 +1,42 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.MersenneTwister
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.MersenneTwister','seed')
6
+ @include agt.mixins.Formattable('MersenneTwister','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (seed=0) ->
11
+ @mt = Array 623
12
+ @z = 0
13
+ @y = 0
14
+
15
+ @plantSeed seed
16
+
17
+ plantSeed: (seed=0) ->
18
+ @mt[0] = seed
19
+ @mt[i] = ((0x10dcd * @mt[i-1]) + 1) & 0xFFFFFFFF for i in [1..623]
20
+
21
+ get: ->
22
+ @generateNumbers() if @z >= 623
23
+ @extractNumber(@z++) / 0x80000000
24
+
25
+ ### Internal: ###
26
+
27
+ generateNumbers: ->
28
+ @z = 0
29
+ for i in [0..623]
30
+ @y = 0x80000000 & @mt[i] + 0x7FFFFFFF & (@mt[(i + 1) % 623])
31
+
32
+ if @y % 2 is 0
33
+ @mt[i] = @mt[(i + 397) % 623] ^ (@y >> 1)
34
+ else
35
+ @mt[i] = @mt[(i + 397) % 623] ^ (@y >> 1) ^ 0x9908B0DF
36
+
37
+ extractNumber: (i) ->
38
+ @y = @mt[i]
39
+ @y ^= (@y >> 11)
40
+ @y ^= (@y << 7) & 0x9d2c5680
41
+ @y ^= (@y << 15) & 0xefc60000
42
+ @y ^= (@y >> 18)
@@ -0,0 +1,12 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.NoRandom
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.NoRandom','seed')
6
+ @include agt.mixins.Formattable('NoRandom','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (@seed=0) ->
11
+
12
+ get: -> @seed
@@ -0,0 +1,19 @@
1
+ namespace('agt.random')
2
+ # Original Implementation License:
3
+ #
4
+ # The Central Randomizer 1.3 (C) 1997 by Paul Houle (paul@honeylocust.com)
5
+ # See: http://www.honeylocust.com/javascript/randomizer.html
6
+
7
+ # Public:
8
+ class agt.random.PaulHoule
9
+ @include agt.mixins.Cloneable('seed')
10
+ @include agt.mixins.Sourcable('chancejs.PaulHoule','seed')
11
+ @include agt.mixins.Formattable('PaulHoule','seed')
12
+
13
+ ### Public ###
14
+
15
+ constructor: (@seed) ->
16
+
17
+ get: ->
18
+ @seed = (@seed * 9301 + 49297) % 233280
19
+ @seed / 233280.0
@@ -0,0 +1,272 @@
1
+ # Public: Use a `Signal` object wherever you need to dispatch an event.
2
+ # A `Signal` is a dispatcher that have only one channel.
3
+ #
4
+ # Signals are generally defined as property of an object. And
5
+ # their name generally end with a past tense verb, such as in:
6
+ #
7
+ # ```coffeescript
8
+ # myObject.somethingChanged = new Signal
9
+ # ```
10
+ class agt.Signal
11
+
12
+ ### Public ###
13
+
14
+ # Creates a new `Signal` instance.
15
+ # Optionally, a signal can have a specific signature.
16
+ # A signature is a collection of argument names such:
17
+ #
18
+ # ```coffeescript
19
+ # positionChanged = new Signal 'target', 'position'
20
+ # ```
21
+ #
22
+ # If a signature is passed to a signal, every listener
23
+ # added to the signal must then match the signature.
24
+ #
25
+ # ```coffeescript
26
+ # # will be registered
27
+ # positionChanged.add (target, position) ->
28
+ #
29
+ # # will be registered too
30
+ # positionChanged.add (target, position, callback) ->
31
+ #
32
+ # # will not be registered
33
+ # positionChanged.add () -> # will throw an error
34
+ # ```
35
+ #
36
+ # In the case of an asynchronous listener, the callback argument
37
+ # is not considered as being part of the signature.
38
+ constructor: (@signature...) ->
39
+ @listeners = []
40
+ # The `asyncListeners` property stores the number of asynchronous
41
+ # listeners to use a synchronous dispatch when equal to 0.
42
+ @asyncListeners = 0
43
+
44
+ # Registers a listener for this signal to be called with
45
+ # the provided context.
46
+ # The context is the object that can be accessed through `this`
47
+ # inside the listener function body.
48
+ #
49
+ # An optional `priority` argument allow you to force
50
+ # an order of dispatch for a listener.
51
+ #
52
+ # Signals listeners can be asynchronous, in that case the last
53
+ # argument of the listener must be named `callback`. An async
54
+ # listener blocks the dispatch loop until the callback function
55
+ # passed to the listener is triggered.
56
+ #
57
+ # ```coffeescript
58
+ # # sync listener
59
+ # signal.add (a, b, c) ->
60
+ #
61
+ # # async listener
62
+ # signal.add (a, b, c, callback) -> callback()
63
+ # ```
64
+ #
65
+ # A listener can be registered several times, but only
66
+ # if the context object is different each time.
67
+ #
68
+ # In other words, the following is possible:
69
+ #
70
+ # ```coffeescript
71
+ # listener = ->
72
+ # context = {}
73
+ # myObject.signal.add listener
74
+ # myObject.signal.add listener, context
75
+ # ```
76
+ #
77
+ # When the following is not:
78
+ #
79
+ # ```coffeescript
80
+ # listener = ->
81
+ # myObject.signal.add listener
82
+ # myObject.signal.add listener
83
+ # ```
84
+ #
85
+ # listener - The {Function} to call when a signal is emitted.
86
+ # context - The `this` {Object} to call the function with.
87
+ # priority - A priority {Number}, the higher the priority the sooner
88
+ # the listener will be called in the dispatch loop.
89
+ add: (listener, context, priority=0) ->
90
+ @validate listener
91
+
92
+ if not @registered listener, context
93
+ @listeners.push [listener, context, false, priority]
94
+ @asyncListeners++ if @isAsync listener
95
+
96
+ # Listeners are sorted according to their order each time
97
+ # a new listener is added.
98
+ @sortListeners()
99
+
100
+ # Registers a listener for only one call.
101
+ #
102
+ # All the others rules are the same. So you can't add
103
+ # the same listener/context couple twice through the two methods.
104
+ #
105
+ # listener - The {Function} to call when a signal is emitted.
106
+ # context - The `this` {Object} to call the function with.
107
+ # priority - A priority {Number}, the higher the priority the sooner
108
+ # the listener will be called in the dispatch loop.
109
+ addOnce: (listener, context, priority = 0) ->
110
+ @validate listener
111
+ if not @registered listener, context
112
+ @listeners.push [listener, context, true, priority]
113
+ @asyncListeners++ if @isAsync listener
114
+ @sortListeners()
115
+
116
+ # Removes a listener from this signal, but only with the context that
117
+ # was registered with it.
118
+ #
119
+ # In this regards, avoid to register listeners without a context.
120
+ # If later in the application a context is forgotten or invalid
121
+ # when removing a listener from this signal, the listener
122
+ # without context will end up being removed.
123
+ #
124
+ # listener - The {Function} to remove from this signal.
125
+ # context - The `this` {Object} that was registered with the listener.
126
+ remove: (listener, context) ->
127
+ if @registered listener, context
128
+ @asyncListeners-- if @isAsync listener
129
+ @listeners.splice @indexOf(listener, context), 1
130
+
131
+ # Removes all listeners at once.
132
+ #
133
+ # ```coffeescript
134
+ # signal.removeAll()
135
+ # ```
136
+ removeAll: ->
137
+ @listeners = []
138
+ @asyncListeners = 0
139
+
140
+ # Internal: `indexOf` returns the position of the listener/context couple
141
+ # in the listeners array.
142
+ indexOf: (listener, context) ->
143
+ return i for [l,c],i in @listeners when listener is l and context is c
144
+ -1
145
+
146
+ # Returns true if the passed-in listener have been registered with the
147
+ # specified context in this signal.
148
+ #
149
+ # listener - The listener {Function} to verify.
150
+ # context - The context {Object} registered with the listener.
151
+ #
152
+ # Returns a {Boolean}.
153
+ registered: (listener, context) ->
154
+ @indexOf(listener, context) isnt -1
155
+
156
+ # Returns true if the signal has listeners.
157
+ #
158
+ # Returns a {Boolean}.
159
+ hasListeners: -> @listeners.length isnt 0
160
+
161
+ # Internal: The listeners are sorted according to their `priority`.
162
+ # The higher the priority the lower the listener will be
163
+ # in the call order.
164
+ sortListeners: ->
165
+ return if @listeners.length <= 1
166
+ @listeners.sort (a, b) ->
167
+ [pA, pB] = [a[3], b[3]]
168
+
169
+ if pA < pB then 1 else if pB < pA then -1 else 0
170
+
171
+ # Internal: Throws an error if the passed-in listener's signature
172
+ # doesn't match the signal's one.
173
+ #
174
+ # ```coffeescript
175
+ # signal = new Signal 'a', 'b', 'c'
176
+ # signal.validate () -> # false
177
+ # signal.validate (a,b,c) -> # true
178
+ # signal.validate (a,b,c,callback) -> # true
179
+ # ```
180
+ validate: (listener) ->
181
+ if @signature.length > 0
182
+ re = /[^(]+\(([^)]+)\).*$/m
183
+ listenerSignature = Function::toString.call(listener).split('\n').shift()
184
+ signature = listenerSignature.replace(re, '$1')
185
+ args = signature.split /\s*,\s*/g
186
+
187
+ args.shift() if args[0] is ''
188
+ args.pop() if args[args.length-1] is 'callback'
189
+
190
+ s1 = @signature.join()
191
+ s2 = args.join()
192
+
193
+ m = "The listener #{listener} doesn't match the signal's signature #{s1}"
194
+ throw new Error m if s2 isnt s1
195
+
196
+ # Returns `true` if the passed-in `listener` is asynchronous.
197
+ #
198
+ # ```coffeescript
199
+ # signal.isAsync(->) # false
200
+ # signal.isAsync((callback) ->) # true
201
+ # ```
202
+ #
203
+ # listener - The listner {Function} to test.
204
+ #
205
+ # Returns a {Boolean}.
206
+ isAsync: (listener) ->
207
+ Function::toString.call(listener).indexOf('callback)') != -1
208
+
209
+ # Dispatch a signal to the signal listeners.
210
+ # Signals are dispatched to all the listeners. All the arguments
211
+ # passed to the dispatch become the signal's message.
212
+ #
213
+ # ```coffeescript
214
+ # signal.dispatch this, true
215
+ # ```
216
+ #
217
+ # Listeners registered for only one call will be removed after
218
+ # the call.
219
+ #
220
+ # Optionally you can pass a callback argument to the dispatch function.
221
+ # In that case, the callback must be the last argument passed to the
222
+ # `dispatch` function. This function will be called at the end
223
+ # of the dispatch, allowing to execute code after all listeners,
224
+ # even asynchronous, have been triggered.
225
+ #
226
+ # ```coffeescript
227
+ # signal.dispatch this, true, ->
228
+ # # all listeners have finish their execution
229
+ # ```
230
+ #
231
+ # **Note:** As the dispatch function will automatically consider
232
+ # the last argument as the callback if its type is `function`, you should
233
+ # avoid using function as the sole argument or as the last argument
234
+ # for a listener. If that case occurs, consider either re-arranging the
235
+ # arguments order or using a value object to carry the function.
236
+ #
237
+ # args - The arguments to dispatch with the signal.
238
+ # callback - A {Function} to callback when all listeners have been notified.
239
+ dispatch: (args..., callback)->
240
+ unless typeof callback is 'function'
241
+ args.push callback
242
+ callback = null
243
+
244
+ listeners = @listeners.concat()
245
+ # If at leat one listener is async, the whole dispatch process is async
246
+ # otherwise the fast route is used.
247
+ if @asyncListeners > 0
248
+ next = (callback) =>
249
+ if listeners.length
250
+ [listener, context, once, priority] = listeners.shift()
251
+
252
+ if @isAsync listener
253
+ listener.apply context, args.concat =>
254
+ @remove listener, context if once
255
+ next callback
256
+ else
257
+ listener.apply context, args
258
+ @remove listener, context if once
259
+ next callback
260
+ else
261
+ callback?()
262
+
263
+ next callback
264
+ else
265
+ # The fast route is just a loop over the listeners.
266
+ # At that point, if your listener do async stuff, it will
267
+ # not prevent the dispatching until it's done.
268
+ for [listener, context, once, priority] in listeners
269
+ listener.apply context, arguments
270
+ @remove listener, context if once
271
+
272
+ callback?()
@@ -0,0 +1,13 @@
1
+ namespace('agt.sprites')
2
+
3
+ # Public:
4
+ class agt.sprites.Animation
5
+ constructor: (@image,
6
+ @width, @height,
7
+ @rowStart=0, @rowEnd=0,
8
+ @colStart=0, @colEnd=0
9
+ @durations) ->
10
+ @durations = (1000 / 24 for i in [@colStart..@colEnd]) unless @durations?
11
+ @rowLength = @rowEnd - @rowStart + 1
12
+ @colLength = @colEnd - @colStart + 1
13
+ @length = @rowLength * @colLength
@@ -0,0 +1,30 @@
1
+ namespace('agt.sprites')
2
+
3
+ # Public:
4
+ class agt.sprites.Sprite
5
+ constructor: (@animation) ->
6
+ @position = new agt.geom.Point
7
+ @center = new agt.geom.Point
8
+ @frame = 0
9
+ @time = 0
10
+
11
+ animate: (bias) ->
12
+ duration = @animation.durations[@frame]
13
+ if @time > duration
14
+ @frame = (@frame + 1) % @animation.length
15
+ @time = @time % duration
16
+ @time += bias
17
+
18
+ render: (context) ->
19
+ animation = @animation
20
+ col = @frame % animation.colLength
21
+ row = (@frame - col) / animation.colLength
22
+ clipX = (animation.colStart + col) * animation.width
23
+ clipY = (animation.rowStart + row) * animation.height
24
+
25
+ x = @position.x - @center.x
26
+ y = @position.y - @center.y
27
+
28
+ context.drawImage animation.image,
29
+ clipX, clipY, animation.width, animation.height,
30
+ x, y, animation.width, animation.height
@@ -0,0 +1,36 @@
1
+ namespace('agt.widgets')
2
+
3
+ # Public:
4
+ class agt.widgets.Hash
5
+ constructor: ->
6
+ @clear()
7
+
8
+ clear: ->
9
+ @keys = []
10
+ @values = []
11
+
12
+ set: (key, value) ->
13
+ if @hasKey key
14
+ index = @keys.indexOf key
15
+ @keys[index] = key
16
+ @values[index] = value
17
+ else
18
+ @keys.push key
19
+ @values.push value
20
+
21
+ get: (key) -> @values[ @keys.indexOf key ]
22
+
23
+ getKey: (value) -> @keys[ @values.indexOf value ]
24
+
25
+ hasKey: (key) -> @keys.indexOf(key) > 0
26
+
27
+ unset: (key) ->
28
+ index = @keys.indexOf key
29
+ @keys.splice index, 1
30
+ @values.splice index, 1
31
+
32
+ each: (block) -> @values.forEach block
33
+
34
+ eachKey: (block) -> @keys.forEach block
35
+
36
+ eachPair: (block) -> @keys.forEach (key) => block? key, @get key