Class: RMX

Inherits:
Object
  • Object
show all
Defined in:
lib/motion/ui.rb,
lib/motion/env.rb,
lib/motion/base.rb,
lib/motion/util.rb,
lib/rmx/version.rb,
lib/motion/events.rb,
lib/motion/layout.rb,
lib/motion/accessors.rb

Defined Under Namespace

Classes: Layout

Constant Summary collapse

DEBUG_LONGTASK =
Env['rmx_debug_longtask'] == '1'
DEBUG_DEALLOC =
Env['rmx_debug_dealloc'] == '1'
DEBUG_EVENTS =
Env['rmx_debug_events'] == '1'
DEBUG_QUEUES =
Env['rmx_debug_queues'] == '1'
DEBUG =
Env['rmx_debug'] == '1'
VERSION =
"0.6.2"
CREATE_EVENT_PROXY =
proc do
  RMXEventsFromProxy.new
end
CREATE_WEAK_HOLDER =
proc do
  RMXWeakHolder.new
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(_object) ⇒ RMX

Returns a new instance of RMX.



3
4
5
6
# File 'lib/motion/base.rb', line 3

def initialize(_object)
  @unsafe_unretained_object_holder = RMXUnsafeUnretainedHolder.new(_object)
  self
end

Class Method Details

.appObject



3
4
5
# File 'lib/motion/ui.rb', line 3

def self.app
  UIApplication.sharedApplication
end

.assert_main_thread!Object

Raises an exception when called from a thread other than the main thread. Good for development and experimenting.



15
16
17
# File 'lib/motion/util.rb', line 15

def self.assert_main_thread!
  raise "Expected main thread. #{Dispatch::Queue.current.description}" unless NSThread.currentThread.isMainThread
end

.block_on_main_q(block, *args) ⇒ Object

call the block immediately if called on the main thread with the given args, otherwise call it async on the main queue. silently ignores nil blocks to avoid if !block.nil? checks, useful for async callbacks that optionally take a callback



35
36
37
38
39
40
41
# File 'lib/motion/util.rb', line 35

def self.block_on_main_q(block, *args)
  unless block.nil?
    inline_or_on_main_q do
      block.call(*args)
    end
  end
end

.currentKeyboardHeightObject



46
47
48
# File 'lib/motion/ui.rb', line 46

def self.currentKeyboardHeight
  @currentKeyboardHeight || 0
end

.inline_or_on_main_q(&block) ⇒ Object

call the block immediately if called on the main thread, otherwise call it async on the main queue



21
22
23
24
25
26
27
28
29
# File 'lib/motion/util.rb', line 21

def self.inline_or_on_main_q(&block)
  if NSThread.currentThread.isMainThread
    block.call
  else
    Dispatch::Queue.main do
      block.call
    end
  end
end

.ios_versionObject



7
8
9
# File 'lib/motion/ui.rb', line 7

def self.ios_version
  @ios_version ||= UIDevice.currentDevice.systemVersion.split(".").take(2).join(".").to_f    
end

.keyboardWillChangeFrame(notification) ⇒ Object



15
16
17
18
# File 'lib/motion/ui.rb', line 15

def self.keyboardWillChangeFrame(notification)
  @keyboardWillChangeFrameNotification = notification
  processKeyboardWillChange
end

.processKeyboardWillChangeObject



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/motion/ui.rb', line 20

def self.processKeyboardWillChange
  return unless notification = @keyboardWillChangeFrameNotification
  info = notification.userInfo
  keyboardFrame = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue
  bounds = UIScreen.mainScreen.bounds
  animationDuration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
  #  below the screen                              # above the screen                                                       # left of the screen                                                    # right of the screen
  currentKeyboardHeight = if keyboardFrame.origin.y >= bounds.size.height || keyboardFrame.origin.y <= bounds.origin.y - keyboardFrame.size.height || keyboardFrame.origin.x <= bounds.origin.x - keyboardFrame.size.width || keyboardFrame.origin.x >= bounds.size.width
    0
  else
    keyboardFrame.size.height
  end
  # p "================>"
  if currentKeyboardHeight != @currentKeyboardHeight
    @currentKeyboardHeight = currentKeyboardHeight
    # p "currentKeyboardHeight", currentKeyboardHeight
    # p "keyboardFrame", keyboardFrame
    # p "UIScreen.mainScreen.bounds", UIScreen.mainScreen.bounds
    NSNotificationCenter.defaultCenter.postNotificationName("rmxKeyboardChanged", object:nil, userInfo:{
      :height => currentKeyboardHeight,
      :animationDuration => animationDuration
    })
  end
  @keyboardWillChangeFrameNotification = nil
end

.safe_block(block) ⇒ Object



3
4
5
6
7
8
9
10
11
# File 'lib/motion/util.rb', line 3

def self.safe_block(block)
  weak_block_owner_holder = RMXWeakHolder.new(block.owner)
  block.weak!
  proc do |*args|
    if wbo = weak_block_owner_holder.value
      block.call(*args)
    end
  end
end

.screen_pixelObject



11
12
13
# File 'lib/motion/ui.rb', line 11

def self.screen_pixel
  1.0 / UIScreen.mainScreen.scale
end

Instance Method Details

#_events_from_proxyObject



15
16
17
18
19
20
21
# File 'lib/motion/events.rb', line 15

def _events_from_proxy
  res = nil
  RMXEventsFromProxy::QUEUE.sync do
    res = ivar(:_rmx_events_from_proxy)
  end
  res
end

#debounce(unique_id, opts = {}, &block) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/motion/util.rb', line 94

def debounce(unique_id, opts={}, &block)
  if (seconds = opts[:seconds]) && seconds > 0
    debounce_seconds(seconds, unique_id, opts[:now], &block)
  else
    debounce_runloop(unique_id, opts[:now], &block)
  end
end

#debounce_runloop(unique_id, run_immediately = false, &block) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/motion/util.rb', line 102

def debounce_runloop(unique_id, run_immediately=false, &block)
  if object = unsafe_unretained_object
    lookup = Thread.current["rmx_debounce_runloop"] ||= {}
    key = [ object, unique_id ]
    lookup[key] ||= begin
      block.call if run_immediately
      CFRunLoopPerformBlock(
        CFRunLoopGetCurrent(),
        KCFRunLoopDefaultMode,
        lambda do
          lookup.delete(key)
          block.call
        end
      )
      true
    end
    nil
  end
end

#debounce_seconds(seconds, unique_id, run_immediately = false, &block) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/motion/util.rb', line 122

def debounce_seconds(seconds, unique_id, run_immediately=false, &block)
  if object = unsafe_unretained_object
    lookup = Thread.current["rmx_debounce_seconds"] ||= {}
    key = [ object, unique_id ]
    lookup[key] ||= begin
      block.call if run_immediately
      units = CFGregorianUnits.new
      units.seconds = seconds
      CFRunLoopAddTimer(
        CFRunLoopGetCurrent(),
        CFRunLoopTimerCreateWithHandler(
          KCFAllocatorDefault,
          CFAbsoluteTimeAddGregorianUnits(
            CFAbsoluteTimeGetCurrent(),
            nil,
            units
          ),
          0,
          0,
          0,
          lambda do |timer|
            lookup.delete(key)
            block.call
          end
        ),
        KCFRunLoopDefaultMode
      )
      true
    end
    nil
  end
end

#events_from_proxyObject



7
8
9
10
11
12
13
# File 'lib/motion/events.rb', line 7

def events_from_proxy
  res = nil
  RMXEventsFromProxy::QUEUE.sync do
    res = ivar(:_rmx_events_from_proxy, &CREATE_EVENT_PROXY)
  end
  res
end

#ivar(*args, &block) ⇒ Object

Shortcut to instance_variable_get and instance_variable_get: 1 arg for instance_variable_get 1 arg and block for instance_variable_get || instance_variable_set 2 args for instance_variable_set



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/motion/util.rb', line 59

def ivar(*args, &block)
  if object = unsafe_unretained_object
    key = args[0]
    val = nil
    if args.size == 1
      if block
        val = object.instance_variable_get("@#{key}")
        if val.nil?
          val = block.call
          object.instance_variable_set("@#{key}", val)
          val
        end
      else
        val = object.instance_variable_get("@#{key}")
      end
    elsif args.size == 2
      val = args[1]
      object.instance_variable_set("@#{key}", val)
    else
      raise "RMX#ivar called with invalid arguments: #{args.inspect}"
    end
    val
  end
end

#nil_instance_variables!Object



84
85
86
87
88
89
90
91
92
# File 'lib/motion/util.rb', line 84

def nil_instance_variables!
  if object = unsafe_unretained_object
    ivars = [] + object.instance_variables
    while ivar = ivars.pop
      object.instance_variable_set(ivar, nil)
    end
    true
  end
end

#now_and_on(event, opts = {}, &block) ⇒ Object



34
35
36
# File 'lib/motion/events.rb', line 34

def now_and_on(event, opts={}, &block)
  events_from_proxy.now_and_on(event, opts.dup, &block)
end

#off(event = nil, execution_block = nil) ⇒ Object

RMX(@model).off(:fire, &block) # remove :fire for specific handler RMX(@model).off(:fire) # remove all :fire in all knowns contexts RMX(@model).off # remove all events in all known contexts



48
49
50
51
52
# File 'lib/motion/events.rb', line 48

def off(event=nil, execution_block=nil)
  if proxy = _events_from_proxy
    proxy.off(event, execution_block)
  end
end

#on(event, opts = {}, &block) ⇒ Object

register a callback when an event is triggered on this object.



24
25
26
27
28
29
30
31
32
# File 'lib/motion/events.rb', line 24

def on(event, opts={}, &block)
  if object = unsafe_unretained_object
    _opts = opts.dup
    if _opts[:strong]
      _opts[:strong] = [ object, block.owner ]
    end
    events_from_proxy.on(event, _opts, &block)
  end
end

#once(event, opts = {}, &block) ⇒ Object

register a callback when an event is triggered on this object and remove it after it fires once



39
40
41
42
43
# File 'lib/motion/events.rb', line 39

def once(event, opts={}, &block)
  _opts = opts.dup
  _opts[:limit] = 1
  on(event, _opts, &block)
end

#own_methodsObject



49
50
51
52
53
# File 'lib/motion/util.rb', line 49

def own_methods
  if object = unsafe_unretained_object
    (object.methods - (object.superclass.methods)).sort
  end
end

#require_queue!(queue, file, line) ⇒ Object



43
44
45
46
47
# File 'lib/motion/util.rb', line 43

def require_queue!(queue, file, line)
  unless Dispatch::Queue.current.description == queue.description
    raise "WRONG QUEUE: was: #{Dispatch::Queue.current.description}, expected: #{queue.description}. #{@object.value.inspect} #{file}:#{line}, #{caller.inspect}"
  end
end

#trigger(event, *values) ⇒ Object

trigger an event with value on this object



55
56
57
58
59
# File 'lib/motion/events.rb', line 55

def trigger(event, *values)
  if proxy = _events_from_proxy
    proxy.trigger(event, *values)
  end
end

#unsafe_unretained_objectObject



8
9
10
# File 'lib/motion/base.rb', line 8

def unsafe_unretained_object
  @unsafe_unretained_object_holder.value
end

#weak_attr_accessor(*attrs) ⇒ Object

creates an attr_accessor like behavior, but the object is stored within an NSHashTable.weakObjectsHashTable and retrieved from the NSHashTable on demand. does not conform to KVO like a normal attr_accessor.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/motion/accessors.rb', line 11

def weak_attr_accessor(*attrs)
  if object = unsafe_unretained_object
    attrs.each do |attr|
      attr_holder = "#{attr}_holder"
      object.send(:define_method, attr) do
        if holder = RMX.new(self).ivar(attr_holder)
          holder.value
        end
      end
      object.send(:define_method, "#{attr}=") do |val|
        holder = RMX.new(self).ivar(attr_holder, &CREATE_WEAK_HOLDER)
        holder.value = val
        val
      end
    end
  end
end