motion-wiretap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ MotionWiretap
2
+ -------
3
+
4
+ An iOS / OS X wrapper heavily inspired by ReactiveCocoa.
5
+
6
+ gem coming soon, just need to write the gemspec
7
+
8
+ run the OSX specs using `rake spec platform=osx`
9
+
10
+ Showdown
11
+ -------
12
+
13
+ Open these side by side to see the comparison:
14
+
15
+ [reactive.rb](https://gist.github.com/colinta/d0a273f8d858a8f61c73)
16
+ [reactive.mm](https://gist.github.com/colinta/5cfa588fed7b929193ae)
@@ -0,0 +1,22 @@
1
+ module Wiretap
2
+
3
+ # a Wiretap::Signal is much like a Promise in functional programming. A
4
+ # Signal is triggered with a new value, or it is completed, or canceled with
5
+ # an error event.
6
+ class Signal
7
+
8
+ def initialize
9
+ end
10
+
11
+ def next(value)
12
+ end
13
+
14
+ def complete
15
+ end
16
+
17
+ def error
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,447 @@
1
+ $motion_wiretaps = []
2
+
3
+ module MotionWiretap
4
+
5
+ class Wiretap
6
+
7
+ def initialize(&block)
8
+ $motion_wiretaps << self # signal will be removed when it is completed
9
+
10
+ @is_torn_down = false
11
+ @is_completed = false
12
+ @is_error = false
13
+ @queue = nil
14
+
15
+ @listener_handlers = []
16
+ @completion_handlers = []
17
+ @error_handlers = []
18
+
19
+ listen &block if block
20
+ end
21
+
22
+ def dealloc
23
+ teardown
24
+ super
25
+ end
26
+
27
+ def teardown
28
+ return if @is_torn_down
29
+
30
+ @is_torn_down = true
31
+ $motion_wiretaps.remove(self)
32
+ end
33
+
34
+ # specify the GCD queue that the listeners should be run on
35
+ def queue(queue)
36
+ @queue = queue
37
+ return self
38
+ end
39
+
40
+ # send a block to the GCD queue
41
+ def enqueue(&block)
42
+ if @queue
43
+ @queue.async(&block)
44
+ else
45
+ block.call
46
+ end
47
+ end
48
+
49
+ ##|
50
+ ##| Wiretap events
51
+ ##|
52
+
53
+ # called when the value changes
54
+ def listen(wiretap=nil, &block)
55
+ raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
56
+ raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
57
+ @listener_handlers << (block || wiretap)
58
+ self
59
+ end
60
+
61
+ def trigger_changed(*values)
62
+ return if @is_torn_down || @is_completed || @is_error
63
+
64
+ @listener_handlers.each do |block_or_wiretap|
65
+ trigger_changed_on(block_or_wiretap, values)
66
+ end
67
+
68
+ return self
69
+ end
70
+
71
+ def trigger_changed_on(block_or_wiretap, values)
72
+ if block_or_wiretap.is_a? Wiretap
73
+ block_or_wiretap.trigger_changed(*values)
74
+ else
75
+ enqueue do
76
+ block_or_wiretap.call(*values)
77
+ end
78
+ end
79
+ end
80
+
81
+ # called when no more values are expected
82
+ def and_then(wiretap=nil, &block)
83
+ raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
84
+ raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
85
+ @completion_handlers << (block || wiretap)
86
+ if @is_completed
87
+ trigger_completed_on(block || wiretap)
88
+ end
89
+ self
90
+ end
91
+
92
+ def trigger_completed
93
+ return if @is_torn_down || @is_completed || @is_error
94
+ @is_completed = true
95
+
96
+ @completion_handlers.each do |block_or_wiretap|
97
+ trigger_completed_on(block_or_wiretap)
98
+ end
99
+
100
+ teardown
101
+ return self
102
+ end
103
+
104
+ def trigger_completed_on(block_or_wiretap)
105
+ if block_or_wiretap.is_a? Wiretap
106
+ block_or_wiretap.trigger_completed
107
+ else
108
+ enqueue do
109
+ block_or_wiretap.call
110
+ end
111
+ end
112
+ end
113
+
114
+ # called when an error occurs, and no more values are expected
115
+ def on_error(wiretap=nil, &block)
116
+ raise "Block or Wiretap is expected in #{self.class.name}##{__method__}" unless block || wiretap
117
+ raise "Only Block *or* Wiretap is expected in #{self.class.name}##{__method__}" if block && wiretap
118
+ @error_handlers << (block || wiretap)
119
+ if @is_error
120
+ trigger_error_on(block || wiretap, @is_error)
121
+ end
122
+ self
123
+ end
124
+
125
+ def trigger_error(error)
126
+ return if @is_torn_down || @is_completed || @is_error
127
+ error ||= true
128
+ @is_error = error
129
+
130
+ @error_handlers.each do |block_or_wiretap|
131
+ trigger_error_on(block_or_wiretap, error)
132
+ end
133
+
134
+ teardown
135
+ return self
136
+ end
137
+
138
+ def trigger_error_on(block_or_wiretap, error)
139
+ if block_or_wiretap.is_a? Wiretap
140
+ block_or_wiretap.trigger_error(error)
141
+ else
142
+ enqueue do
143
+ block_or_wiretap.call(error)
144
+ end
145
+ end
146
+ end
147
+
148
+ ##|
149
+ ##| Wiretap Predicates
150
+ ##|
151
+
152
+ # Returns a Wiretap that will only be called if the &condition block returns
153
+ # true
154
+ def filter(&block)
155
+ return WiretapFilter.new(self, block)
156
+ end
157
+
158
+ # Returns a Wiretap that combines all the values into one value (the values
159
+ # are all passed in at the same time)
160
+ def combine(&block)
161
+ return WiretapCombiner.new(self, block)
162
+ end
163
+
164
+ # Returns a Wiretap that passes each value through the block, and also the
165
+ # previous return value (memo).
166
+ # @example
167
+ # wiretap.reduce(0) do |memo, item|
168
+ # memo + item.price
169
+ # end
170
+ # # returns the total of all the prices
171
+ def reduce(memo=nil, &block)
172
+ return WiretapReducer.new(self, memo, block)
173
+ end
174
+
175
+ # Returns a Wiretap that passes each value through the provided block
176
+ def map(&block)
177
+ return WiretapMapper.new(self, block)
178
+ end
179
+
180
+ end
181
+
182
+ class WiretapTarget < Wiretap
183
+ attr :target
184
+
185
+ def initialize(target, &block)
186
+ @target = target
187
+ super(&block)
188
+ end
189
+
190
+ def teardown
191
+ @target = nil
192
+ super
193
+ end
194
+
195
+ end
196
+
197
+ class WiretapKvo < WiretapTarget
198
+ attr :property
199
+ attr :value
200
+
201
+ def initialize(target, property, &block)
202
+ @property = property
203
+ @value = nil
204
+ @initial_is_set = false
205
+ super(target, &block)
206
+
207
+ @target.addObserver(self,
208
+ forKeyPath: property.to_s,
209
+ options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial,
210
+ context: nil
211
+ )
212
+ end
213
+
214
+ def teardown
215
+ @target.removeObserver(self,
216
+ forKeyPath: @property.to_s
217
+ )
218
+ @property = nil
219
+ @value = nil
220
+ super
221
+ end
222
+
223
+ def trigger_changed(*values)
224
+ super(@value)
225
+ end
226
+
227
+ def bind_to(wiretap)
228
+ wiretap.listen do |value|
229
+ @target.send("#{@property}=", value)
230
+ end
231
+ wiretap.trigger_changed
232
+ end
233
+
234
+ def observeValueForKeyPath(path, ofObject: target, change: change, context: context)
235
+ @value = change[NSKeyValueChangeNewKey]
236
+ if @initial_is_set
237
+ trigger_changed(@value)
238
+ else
239
+ @initial_is_set = true
240
+ end
241
+ end
242
+
243
+ end
244
+
245
+ class WiretapArray < WiretapTarget
246
+ attr :targets
247
+
248
+ def initialize(targets, &block)
249
+ raise "Not only is listening to an empty array pointless, it will also cause errors" if targets.length == 0
250
+
251
+ # the complete trigger isn't called until all the wiretaps are complete
252
+ @uncompleted = targets.length
253
+
254
+ # targets can be an array of Wiretap objects (they will be monitored), or
255
+ # plain objects (they'll just be included in the sequence)
256
+ super(targets, &block)
257
+
258
+ # gets assigned to the wiretap value if it's a Wiretap, or the object
259
+ # itself if it is anything else.
260
+ @value = []
261
+ @initial_is_set = true
262
+ # maps the wiretap object (which is unique)
263
+ @wiretaps = {}
264
+
265
+ targets.each_with_index do |wiretap,index|
266
+ unless wiretap.is_a? Wiretap
267
+ @value << wiretap
268
+ # not a wiretap, so doesn't need to be "completed"
269
+ @uncompleted -= 1
270
+ else
271
+ raise "You cannot store a Wiretap twice in the same sequence (for now - do you really need this?)" if @wiretaps.key?(wiretap)
272
+ @wiretaps[wiretap] = index
273
+
274
+ @value << wiretap.value
275
+
276
+ wiretap.listen do |value|
277
+ indx = @wiretaps[wiretap]
278
+ @value[index] = wiretap.value
279
+ trigger_changed(*@value)
280
+ end
281
+
282
+ wiretap.on_error do |error|
283
+ trigger_error(error)
284
+ end
285
+
286
+ wiretap.and_then do |error|
287
+ @uncompleted -= 1
288
+ if @uncompleted == 0
289
+ trigger_completed
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ def teardown
297
+ super
298
+ @wiretaps = nil
299
+ end
300
+
301
+ def trigger_changed(*values)
302
+ values = @value if values.length == 0
303
+ super(*values)
304
+ end
305
+
306
+ end
307
+
308
+ class WiretapFilter < Wiretap
309
+
310
+ def initialize(parent, filter)
311
+ @parent = parent
312
+ @filter = filter
313
+
314
+ @parent.listen(self)
315
+
316
+ super()
317
+ end
318
+
319
+ # passes the values through the filter before passing up to the parent
320
+ # implementation
321
+ def trigger_changed(*values)
322
+ if ( @filter.call(*values) )
323
+ super(*values)
324
+ end
325
+ end
326
+
327
+ def teardown
328
+ super
329
+ @parent = nil
330
+ end
331
+
332
+ end
333
+
334
+ class WiretapCombiner < Wiretap
335
+
336
+ def initialize(parent, combiner)
337
+ @parent = parent
338
+ @combiner = combiner
339
+
340
+ @parent.listen(self)
341
+
342
+ super()
343
+ end
344
+
345
+ # passes the values through the combiner before passing up to the parent
346
+ # implementation
347
+ def trigger_changed(*values)
348
+ super(@combiner.call(*values))
349
+ end
350
+
351
+ def teardown
352
+ super
353
+ @parent = nil
354
+ end
355
+
356
+ end
357
+
358
+ class WiretapReducer < Wiretap
359
+
360
+ def initialize(parent, memo, reducer)
361
+ @parent = parent
362
+ @reducer = reducer
363
+ @memo = memo
364
+
365
+ @parent.listen(self)
366
+
367
+ super()
368
+ end
369
+
370
+ # passes each value through the @reducer, passing in the return value of the
371
+ # previous call (starting with @memo)
372
+ def trigger_changed(*values)
373
+ super(values.inject(@memo, &@reducer))
374
+ end
375
+
376
+ def teardown
377
+ super
378
+ @parent = nil
379
+ end
380
+
381
+ end
382
+
383
+ class WiretapMapper < Wiretap
384
+
385
+ def initialize(parent, mapper)
386
+ @parent = parent
387
+ @mapper = mapper
388
+
389
+ @parent.listen(self)
390
+
391
+ super()
392
+ end
393
+
394
+ # passes the values through the mapper before passing up to the parent
395
+ # implementation
396
+ def trigger_changed(*values)
397
+ super(*values.map { |value| @mapper.call(value) })
398
+ end
399
+
400
+ def teardown
401
+ super
402
+ @parent = nil
403
+ end
404
+
405
+ end
406
+
407
+ class WiretapProc < WiretapTarget
408
+
409
+ def initialize(target, queue=nil, &block)
410
+ @started = false
411
+ super(target, &block)
412
+ queue(queue) if queue
413
+ end
414
+
415
+ def queue(queue)
416
+ super
417
+ start
418
+ end
419
+
420
+ def start
421
+ unless @started
422
+ @started = true
423
+ enqueue do
424
+ begin
425
+ trigger_changed(@target.call)
426
+ rescue Exception => error
427
+ trigger_error(error)
428
+ else
429
+ trigger_completed
430
+ end
431
+ end
432
+ end
433
+ end
434
+
435
+
436
+ def listen(wiretap=nil, &block)
437
+ raise "WiretapProc does not support listeners (only the `completed` event is triggered)"
438
+ end
439
+
440
+ def and_then(wiretap=nil, &block)
441
+ super
442
+ start
443
+ end
444
+
445
+ end
446
+
447
+ end
@@ -0,0 +1,30 @@
1
+ class NSObject
2
+ attr_accessor :motion_wiretap_observers
3
+
4
+ def wiretap(property, &block)
5
+ MotionWiretap::WiretapKvo.new(self, property, &block)
6
+ end
7
+
8
+ end
9
+
10
+
11
+ class NSArray
12
+
13
+ def wiretap(property=nil, &block)
14
+ raise "`wiretap` is not supported on Arrays (arrays are not observable). You probably meant to use `wiretaps`."
15
+ end
16
+
17
+ def wiretaps(&block)
18
+ MotionWiretap::WiretapArray.new(self, &block)
19
+ end
20
+
21
+ end
22
+
23
+
24
+ class Proc
25
+
26
+ def wiretap(queue=nil, &block)
27
+ MotionWiretap::WiretapProc(queue, &block)
28
+ end
29
+
30
+ end
@@ -0,0 +1,84 @@
1
+ module MotionWiretap
2
+ class ControlEventNotFound < Exception
3
+ end
4
+
5
+ # Some UIControlEvent translators. Based on SugarCube's uicontroleevent
6
+ # constants.
7
+ module ControlEvents
8
+ module_function
9
+
10
+ def convert(control_events)
11
+ return control_events if control_events.is_a? Fixnum
12
+
13
+ case control_events
14
+ when NSArray
15
+ retval = 0
16
+ control_events.each do |event|
17
+ begin
18
+ retval |= ControlEvents.convert(event)
19
+ rescue ControlEventNotFound
20
+ raise "Could not merge control event #{event.inspect}"
21
+ end
22
+ end
23
+ return retval
24
+ when :touch
25
+ return UIControlEventTouchUpInside
26
+ when :touch_up
27
+ return UIControlEventTouchUpInside
28
+ when :touch_down
29
+ return UIControlEventTouchDown
30
+ when :touch_start
31
+ return UIControlEventTouchDown | UIControlEventTouchDragEnter
32
+ when :touch_stop
33
+ return UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit
34
+ when :change
35
+ return UIControlEventValueChanged | UIControlEventEditingChanged
36
+ when :begin
37
+ return UIControlEventEditingDidBegin
38
+ when :end
39
+ return UIControlEventEditingDidEnd
40
+ when :touch_down_repeat
41
+ return UIControlEventTouchDownRepeat
42
+ when :touch_drag_inside
43
+ return UIControlEventTouchDragInside
44
+ when :touch_drag_outside
45
+ return UIControlEventTouchDragOutside
46
+ when :touch_drag_enter
47
+ return UIControlEventTouchDragEnter
48
+ when :touch_drag_exit
49
+ return UIControlEventTouchDragExit
50
+ when :touch_up_inside
51
+ return UIControlEventTouchUpInside
52
+ when :touch_up_outside
53
+ return UIControlEventTouchUpOutside
54
+ when :touch_cancel
55
+ return UIControlEventTouchCancel
56
+ when :value_changed
57
+ return UIControlEventValueChanged
58
+ when :editing_did_begin
59
+ return UIControlEventEditingDidBegin
60
+ when :editing_changed
61
+ return UIControlEventEditingChanged
62
+ when :editing_did_change
63
+ return UIControlEventEditingChanged
64
+ when :editing_did_end
65
+ return UIControlEventEditingDidEnd
66
+ when :editing_did_end_on_exit
67
+ return UIControlEventEditingDidEndOnExit
68
+ when :all_touch
69
+ return UIControlEventAllTouchEvents
70
+ when :all_editing
71
+ return UIControlEventAllEditingEvents
72
+ when :application
73
+ return UIControlEventApplicationReserved
74
+ when :system
75
+ return UIControlEventSystemReserved
76
+ when :all
77
+ return UIControlEventAllEvents
78
+ else
79
+ raise ControlEventNotFound.new
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,24 @@
1
+ class UIView
2
+
3
+ def wiretap(property=nil, &block)
4
+ if property.nil?
5
+ MotionWiretap::WiretapView.new(self, &block)
6
+ else
7
+ MotionWiretap::WiretapKvo.new(self, property, &block)
8
+ end
9
+ end
10
+
11
+ end
12
+
13
+
14
+ class UIControl
15
+
16
+ def wiretap(property=nil, &block)
17
+ if property.nil?
18
+ MotionWiretap::WiretapControl.new(self, &block)
19
+ else
20
+ MotionWiretap::WiretapKvo.new(self, property, &block)
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,143 @@
1
+ module MotionWiretap
2
+ # Some gesture recognizer translators. Based on SugarCube's gesture recognizer
3
+ # methods.
4
+ module Gestures
5
+ module_function
6
+
7
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
8
+ # @overload tap(taps)
9
+ # @param taps [Fixnum] Number of taps
10
+ # @overload tap(options)
11
+ # @option options [Fixnum] :taps Number of taps before gesture is recognized
12
+ # @option options [Fixnum] :fingers Number of fingers before gesture is recognized
13
+ def tap(target, taps_or_options=nil)
14
+ taps = nil
15
+ fingers = nil
16
+
17
+ if taps_or_options
18
+ if taps_or_options.is_a? Hash
19
+ taps = taps_or_options[:taps] || taps
20
+ fingers = taps_or_options[:fingers] || fingers
21
+ else
22
+ taps = taps_or_options
23
+ end
24
+ end
25
+
26
+ recognizer = UITapGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
27
+ recognizer.numberOfTapsRequired = taps if taps
28
+ recognizer.numberOfTouchesRequired = fingers if fingers
29
+ return recognizer
30
+ end
31
+
32
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
33
+ def pinch(target)
34
+ recognizer = UIPinchGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
35
+ return recognizer
36
+ end
37
+
38
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
39
+ def rotate(target)
40
+ recognizer = UIRotationGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
41
+ return recognizer
42
+ end
43
+
44
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
45
+ # @overload swipe(taps)
46
+ # @param direction [Fixnum] Direction of swipe
47
+ # @overload swipe(options)
48
+ # @option options [Fixnum] :fingers Number of fingers before gesture is recognized
49
+ # @option options [Fixnum, Symbol] :direction Direction of swipe, as a UISwipeGestureRecognizerDirection constant or a symbol (`:left, :right, :up, :down`)
50
+ def swipe(target, direction_or_options)
51
+ direction = nil
52
+ fingers = nil
53
+
54
+ if direction_or_options
55
+ if direction_or_options.is_a? Hash
56
+ direction = direction_or_options[:direction] || direction
57
+ fingers = direction_or_options[:fingers] || fingers
58
+ else
59
+ direction = direction_or_options
60
+ end
61
+ end
62
+
63
+ case direction
64
+ when :left
65
+ direction = UISwipeGestureRecognizerDirectionLeft
66
+ when :right
67
+ direction = UISwipeGestureRecognizerDirectionRight
68
+ when :up
69
+ direction = UISwipeGestureRecognizerDirectionUp
70
+ when :down
71
+ direction = UISwipeGestureRecognizerDirectionDown
72
+ end
73
+
74
+ recognizer = UISwipeGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
75
+ recognizer.direction = direction if direction
76
+ recognizer.numberOfTouchesRequired = fingers if fingers
77
+ return recognizer
78
+ end
79
+
80
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
81
+ # @overload tap(taps)
82
+ # @param taps [Fixnum] Number of taps
83
+ # @overload tap(options)
84
+ # @option options [Fixnum] :min_fingers Minimum umber of fingers for gesture to be recognized
85
+ # @option options [Fixnum] :max_fingers Maximum number of fingers for gesture to be recognized
86
+ # @option options [Fixnum] :fingers If min_fingers or max_fingers is not assigned, this will be the default.
87
+ def pan(target, fingers_or_options=nil)
88
+ fingers = nil
89
+ min_fingers = nil
90
+ max_fingers = nil
91
+
92
+ if fingers_or_options
93
+ if fingers_or_options.is_a? Hash
94
+ fingers = fingers_or_options[:fingers] || fingers
95
+ min_fingers = fingers_or_options[:min_fingers] || min_fingers
96
+ max_fingers = fingers_or_options[:max_fingers] || max_fingers
97
+ else
98
+ fingers = fingers_or_options
99
+ end
100
+ end
101
+
102
+ # if fingers is assigned, but not min/max, assign it as a default
103
+ min_fingers ||= fingers
104
+ max_fingers ||= fingers
105
+
106
+ recognizer = UIPanGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
107
+ recognizer.maximumNumberOfTouches = min_fingers if min_fingers
108
+ recognizer.minimumNumberOfTouches = max_fingers if max_fingers
109
+ return recognizer
110
+ end
111
+
112
+ # @yield [recognizer] Handles the gesture event, and passes the recognizer instance to the block.
113
+ # @overload press(duration)
114
+ # @param duration [Fixnum] How long in seconds before gesture is recognized
115
+ # @overload press(options)
116
+ # @option options [Fixnum] :duration How long in seconds before gesture is recognized
117
+ # @option options [Fixnum] :taps Number of taps before gesture is recognized
118
+ # @option options [Fixnum] :fingers Number of fingers before gesture is recognized
119
+ def press(target, duration_or_options=nil)
120
+ duration = nil
121
+ taps = nil
122
+ fingers = nil
123
+
124
+ if duration_or_options
125
+ if duration_or_options.is_a? Hash
126
+ duration = duration_or_options[:duration] || duration
127
+ taps = duration_or_options[:taps] || taps
128
+ fingers = duration_or_options[:fingers] || fingers
129
+ else
130
+ duration = duration_or_options
131
+ end
132
+ end
133
+
134
+ recognizer = UILongPressGestureRecognizer.alloc.initWithTarget(target, action:'handle_gesture:')
135
+ recognizer.minimumPressDuration = duration if duration
136
+ recognizer.numberOfTapsRequired = taps if taps
137
+ recognizer.numberOfTouchesRequired = fingers if fingers
138
+ return recognizer
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,55 @@
1
+ module MotionWiretap
2
+
3
+ class WiretapView < WiretapTarget
4
+
5
+ def on(recognizer, options=nil, &block)
6
+ case recognizer
7
+ when :tap
8
+ recognizer = MotionWiretap::Gestures.tap(self, options)
9
+ when :pinch
10
+ recognizer = MotionWiretap::Gestures.pinch(self, options)
11
+ when :rotate
12
+ recognizer = MotionWiretap::Gestures.rotate(self, options)
13
+ when :swipe
14
+ recognizer = MotionWiretap::Gestures.swipe(self, options)
15
+ when :pan
16
+ recognizer = MotionWiretap::Gestures.pan(self, options)
17
+ when :press
18
+ recognizer = MotionWiretap::Gestures.press(self, options)
19
+ end
20
+
21
+ self.target.addGestureRecognizer(recognizer)
22
+ listen(&block) if block
23
+ return self
24
+ end
25
+
26
+ def handle_gesture(recognizer)
27
+ trigger_changed
28
+ end
29
+
30
+ end
31
+
32
+ class WiretapControl < WiretapView
33
+
34
+ # control_event can be any UIControlEventconstant, or any symbol found in
35
+ # wiretap_control_events.rb, or an array of UIControlEvent constants or
36
+ # symbols.
37
+ def on(control_event, options={}, &block)
38
+ begin
39
+ control_event = ControlEvents.convert(control_event)
40
+ self.target.addTarget(self, action:'handle_event:', forControlEvents:control_event)
41
+ listen(&block) if block
42
+ rescue ControlEventNotFound
43
+ super(control_event, options, &block)
44
+ end
45
+
46
+ return self
47
+ end
48
+
49
+ def handle_event(event)
50
+ trigger_changed
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,7 @@
1
+ class NSView
2
+
3
+ def wiretap(property, &block)
4
+ MotionWiretap::WiretapView.new(self, property, &block)
5
+ end
6
+
7
+ end
@@ -0,0 +1,6 @@
1
+ module MotionWiretap
2
+
3
+ class WiretapView < WiretapKvo
4
+ end
5
+
6
+ end
@@ -0,0 +1,3 @@
1
+ module MotionWiretap
2
+ Version = '0.1.0'
3
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/motion-wiretap/version.rb', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'motion-wiretap'
6
+ gem.version = MotionWiretap::Version
7
+
8
+ gem.authors = ['Colin T.A. Gray']
9
+ gem.email = ['colinta@gmail.com']
10
+ gem.summary = %{It's like ReactiveCocoa, but in RubyMotion}
11
+ gem.description = <<-DESC
12
+ ReactiveCocoa is an amazing system, and RubyMotion could benefit from the
13
+ lessons learned there!
14
+
15
+ Motion-Wiretap is, essentially, a wrapper for Key-Value coding and observation.
16
+ It exposes a +Wiretap+ class that you can use as a signal, or add listeners to
17
+ it.
18
+
19
+ Extensions are provided to listen to an +Array+ of +Wiretap+ objects, and the
20
+ `UIKit`/`AppKit` classes are augmented to provide actions as events (gestures,
21
+ mouse events, value changes).
22
+ DESC
23
+
24
+ gem.homepage = 'https://github.com/colinta/motion-wiretap'
25
+
26
+ gem.files = Dir.glob('lib/motion-wiretap/**/*.rb') + ['README.md', 'motion-wiretap.gemspec']
27
+ gem.test_files = gem.files.grep(%r{^spec/})
28
+
29
+ gem.require_paths = ['lib']
30
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-wiretap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Colin T.A. Gray
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-16 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'ReactiveCocoa is an amazing system, and RubyMotion could benefit from
15
+ the
16
+
17
+ lessons learned there!
18
+
19
+
20
+ Motion-Wiretap is, essentially, a wrapper for Key-Value coding and observation.
21
+
22
+ It exposes a +Wiretap+ class that you can use as a signal, or add listeners to
23
+
24
+ it.
25
+
26
+
27
+ Extensions are provided to listen to an +Array+ of +Wiretap+ objects, and the
28
+
29
+ `UIKit`/`AppKit` classes are augmented to provide actions as events (gestures,
30
+
31
+ mouse events, value changes).
32
+
33
+ '
34
+ email:
35
+ - colinta@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/motion-wiretap/all/signal.rb
41
+ - lib/motion-wiretap/all/wiretap.rb
42
+ - lib/motion-wiretap/all/wiretap_exts.rb
43
+ - lib/motion-wiretap/ios/wiretap_control_events.rb
44
+ - lib/motion-wiretap/ios/wiretap_exts_ios.rb
45
+ - lib/motion-wiretap/ios/wiretap_gestures.rb
46
+ - lib/motion-wiretap/ios/wiretap_ios.rb
47
+ - lib/motion-wiretap/osx/wiretap_exts_osx.rb
48
+ - lib/motion-wiretap/osx/wiretap_osx.rb
49
+ - lib/motion-wiretap/version.rb
50
+ - README.md
51
+ - motion-wiretap.gemspec
52
+ homepage: https://github.com/colinta/motion-wiretap
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.25
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: It's like ReactiveCocoa, but in RubyMotion
76
+ test_files: []