motion-wiretap 0.1.0

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.
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: []