motion-kit 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +839 -0
  3. data/lib/motion-kit-cocoa/cocoa_util.rb +59 -0
  4. data/lib/motion-kit-cocoa/constraints/constraint.rb +765 -0
  5. data/lib/motion-kit-cocoa/constraints/constraint_placeholder.rb +22 -0
  6. data/lib/motion-kit-cocoa/constraints/constraints_layout.rb +536 -0
  7. data/lib/motion-kit-cocoa/constraints/constraints_target.rb +52 -0
  8. data/lib/motion-kit-cocoa/layouts/cagradientlayer_layout.rb +13 -0
  9. data/lib/motion-kit-cocoa/layouts/calayer_layout.rb +27 -0
  10. data/lib/motion-kit-cocoa/layouts/sugarcube_compat.rb +38 -0
  11. data/lib/motion-kit-ios/dummy.rb +93 -0
  12. data/lib/motion-kit-ios/ios_util.rb +20 -0
  13. data/lib/motion-kit-ios/layouts/constraints_layout.rb +22 -0
  14. data/lib/motion-kit-ios/layouts/layout_device.rb +32 -0
  15. data/lib/motion-kit-ios/layouts/layout_orientation.rb +47 -0
  16. data/lib/motion-kit-ios/layouts/uibutton_layout.rb +52 -0
  17. data/lib/motion-kit-ios/layouts/uiview_layout.rb +45 -0
  18. data/lib/motion-kit-ios/layouts/uiview_layout_autoresizing.rb +75 -0
  19. data/lib/motion-kit-ios/layouts/uiview_layout_constraints.rb +36 -0
  20. data/lib/motion-kit-ios/layouts/uiview_layout_frame.rb +307 -0
  21. data/lib/motion-kit-ios/layouts/uiview_layout_gradient.rb +20 -0
  22. data/lib/motion-kit-osx/dummy.rb +89 -0
  23. data/lib/motion-kit-osx/layouts/constraints_layout.rb +42 -0
  24. data/lib/motion-kit-osx/layouts/nsmenu_extensions.rb +186 -0
  25. data/lib/motion-kit-osx/layouts/nsmenu_layout.rb +109 -0
  26. data/lib/motion-kit-osx/layouts/nsmenuitem_extensions.rb +45 -0
  27. data/lib/motion-kit-osx/layouts/nstablecolumn_layout.rb +12 -0
  28. data/lib/motion-kit-osx/layouts/nstableview_layout.rb +20 -0
  29. data/lib/motion-kit-osx/layouts/nsview_layout.rb +47 -0
  30. data/lib/motion-kit-osx/layouts/nsview_layout_autoresizing.rb +75 -0
  31. data/lib/motion-kit-osx/layouts/nsview_layout_constraints.rb +34 -0
  32. data/lib/motion-kit-osx/layouts/nsview_layout_frame.rb +375 -0
  33. data/lib/motion-kit-osx/layouts/nswindow_frame.rb +14 -0
  34. data/lib/motion-kit-osx/layouts/nswindow_layout.rb +77 -0
  35. data/lib/motion-kit-osx/osx_util.rb +16 -0
  36. data/lib/motion-kit.rb +33 -0
  37. data/lib/motion-kit/calculate.rb +263 -0
  38. data/lib/motion-kit/layouts/base_layout.rb +299 -0
  39. data/lib/motion-kit/layouts/base_layout_class_methods.rb +43 -0
  40. data/lib/motion-kit/layouts/parent.rb +68 -0
  41. data/lib/motion-kit/layouts/view_layout.rb +327 -0
  42. data/lib/motion-kit/motion-kit.rb +18 -0
  43. data/lib/motion-kit/object.rb +16 -0
  44. data/lib/motion-kit/util.rb +20 -0
  45. data/lib/motion-kit/version.rb +1 -1
  46. data/spec/ios/apply_styles_spec.rb +21 -0
  47. data/spec/ios/autoresizing_helper_spec.rb +224 -0
  48. data/spec/ios/calculate_spec.rb +322 -0
  49. data/spec/ios/calculator_spec.rb +31 -0
  50. data/spec/ios/constraints_helpers/attribute_lookup_spec.rb +27 -0
  51. data/spec/ios/constraints_helpers/axis_lookup_spec.rb +13 -0
  52. data/spec/ios/constraints_helpers/center_constraints_spec.rb +419 -0
  53. data/spec/ios/constraints_helpers/constraint_placeholder_spec.rb +72 -0
  54. data/spec/ios/constraints_helpers/priority_lookup_spec.rb +19 -0
  55. data/spec/ios/constraints_helpers/relationship_lookup_spec.rb +27 -0
  56. data/spec/ios/constraints_helpers/relative_corners_spec.rb +274 -0
  57. data/spec/ios/constraints_helpers/relative_location_spec.rb +111 -0
  58. data/spec/ios/constraints_helpers/simple_constraints_spec.rb +2763 -0
  59. data/spec/ios/constraints_helpers/size_constraints_spec.rb +422 -0
  60. data/spec/ios/constraints_helpers/view_lookup_constraints_spec.rb +93 -0
  61. data/spec/ios/create_layout_spec.rb +40 -0
  62. data/spec/ios/custom_layout_spec.rb +13 -0
  63. data/spec/ios/deferred_spec.rb +89 -0
  64. data/spec/ios/device_helpers_spec.rb +51 -0
  65. data/spec/ios/frame_helper_spec.rb +1150 -0
  66. data/spec/ios/layer_layout_spec.rb +36 -0
  67. data/spec/ios/layout_extensions_spec.rb +70 -0
  68. data/spec/ios/layout_spec.rb +74 -0
  69. data/spec/ios/layout_state_spec.rb +27 -0
  70. data/spec/ios/motionkit_util_spec.rb +102 -0
  71. data/spec/ios/objc_selectors_spec.rb +10 -0
  72. data/spec/ios/orientation_helper_specs.rb +67 -0
  73. data/spec/ios/parent_layout_spec.rb +19 -0
  74. data/spec/ios/parent_spec.rb +45 -0
  75. data/spec/ios/remove_layout_spec.rb +25 -0
  76. data/spec/ios/root_layout_spec.rb +53 -0
  77. data/spec/ios/setters_spec.rb +63 -0
  78. data/spec/ios/uibutton_layout_spec.rb +24 -0
  79. data/spec/ios/uitextfield_spec.rb +14 -0
  80. data/spec/ios/view_attr_spec.rb +25 -0
  81. data/spec/osx/autoresizing_helper_spec.rb +224 -0
  82. data/spec/osx/constraints_helper_spec.rb +0 -0
  83. data/spec/osx/constraints_helpers/orientation_lookup_spec.rb +13 -0
  84. data/spec/osx/constraints_helpers/simple_constraints_spec.rb +2095 -0
  85. data/spec/osx/constraints_helpers/size_constraints_spec.rb +362 -0
  86. data/spec/osx/create_menu_spec.rb +14 -0
  87. data/spec/osx/create_via_extensions_spec.rb +63 -0
  88. data/spec/osx/deferred_spec.rb +85 -0
  89. data/spec/osx/frame_helper_spec.rb +1881 -0
  90. data/spec/osx/menu_extensions_spec.rb +376 -0
  91. data/spec/osx/menu_layout_spec.rb +157 -0
  92. data/spec/osx/menu_spec.rb +70 -0
  93. data/spec/osx/root_menu_spec.rb +15 -0
  94. metadata +166 -14
@@ -0,0 +1,14 @@
1
+ # @requires MotionKit::NSWindowLayout
2
+ module MotionKit
3
+ class NSWindowLayout
4
+
5
+ def frame(value, autosave_name=nil)
6
+ retval = target.setFrame(value, display: true)
7
+ if autosave_name
8
+ target.setFrameAutosaveName(autosave_name)
9
+ end
10
+ return retval
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ # @provides MotionKit::WindowLayout
2
+ # @provides MotionKit::NSWindowLayout
3
+ # @requires MotionKit::ViewLayout
4
+ module MotionKit
5
+ class WindowLayout < ViewLayout
6
+
7
+ # A more sensible name for the window that is created.
8
+ def window
9
+ self.view
10
+ end
11
+
12
+ # platform specific default root view
13
+ def default_root
14
+ # child WindowLayout classes can return *their* NSView subclass from self.nsview_class
15
+ view_class = self.class.targets || NSWindow
16
+ view_class.alloc.initWithContentRect([[0, 0], [0, 0]],
17
+ styleMask: NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask,
18
+ backing: NSBackingStoreBuffered,
19
+ defer: false)
20
+ end
21
+
22
+ def add_child(subview)
23
+ target.contentView.addSubview(subview)
24
+ end
25
+
26
+ def remove_child(subview)
27
+ subview.removeFromSuperview
28
+ end
29
+
30
+ # NSWindow doesn't have immediate children; restyle its contentView.
31
+ def reapply!(window=nil)
32
+ window ||= self.window
33
+ call_style_method(window, window.motion_kit_id) if window.motion_kit_id
34
+ super(window.contentView)
35
+ end
36
+
37
+ def get(element_id)
38
+ if self.window.motion_kit_id == element_id
39
+ return self.window
40
+ else
41
+ self.get(element_id, in: self.window.contentView)
42
+ end
43
+ end
44
+
45
+ def last(element_id)
46
+ if last = self.last(element_id, in: self.window.contentView)
47
+ last
48
+ elsif self.window.motion_kit_id == element_id
49
+ self.window
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ def all(element_id)
56
+ found = self.all(element_id, in: self.window.contentView)
57
+ if self.window.motion_kit_id == element_id
58
+ found << self.window
59
+ end
60
+ return found
61
+ end
62
+
63
+ def nth(element_id, index)
64
+ self.all(element_id, in: self.window.contentView)[index]
65
+ end
66
+
67
+ def remove(element_id)
68
+ self.remove(element_id, from: self.window.contentView)
69
+ end
70
+
71
+ end
72
+
73
+ class NSWindowLayout < WindowLayout
74
+ targets NSWindow
75
+ end
76
+
77
+ end
@@ -0,0 +1,16 @@
1
+ module MotionKit
2
+ module_function
3
+
4
+ def base_view_class
5
+ NSView
6
+ end
7
+
8
+ def default_view_class
9
+ NSView
10
+ end
11
+
12
+ def no_intrinsic_metric
13
+ NSViewNoInstrinsicMetric
14
+ end
15
+
16
+ end
data/lib/motion-kit.rb ADDED
@@ -0,0 +1,33 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "The MotionKit gem must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+
6
+ require 'dbt'
7
+
8
+ Motion::Project::App.setup do |app|
9
+ core_lib = File.join(File.dirname(__FILE__), 'motion-kit')
10
+ cocoa_lib = File.join(File.dirname(__FILE__), 'motion-kit-cocoa')
11
+ platform = app.respond_to?(:template) ? app.template : :ios
12
+ platform_lib = File.join(File.dirname(__FILE__), "motion-kit-#{platform}")
13
+ unless File.exists? platform_lib
14
+ raise "Sorry, the platform #{platform.inspect} is not supported by MotionKit"
15
+ end
16
+
17
+ # scans app.files until it finds app/ (the default)
18
+ # if found, it inserts just before those files, otherwise it will insert to
19
+ # the end of the list
20
+ insert_point = app.files.find_index { |file| file =~ /^(?:\.\/)?app\// } || 0
21
+
22
+ Dir.glob(File.join(platform_lib, '**/*.rb')).reverse.each do |file|
23
+ app.files.insert(insert_point, file)
24
+ end
25
+ Dir.glob(File.join(cocoa_lib, '**/*.rb')).reverse.each do |file|
26
+ app.files.insert(insert_point, file)
27
+ end
28
+ Dir.glob(File.join(core_lib, '**/*.rb')).reverse.each do |file|
29
+ app.files.insert(insert_point, file)
30
+ end
31
+
32
+ DBT.analyze(app)
33
+ end
@@ -0,0 +1,263 @@
1
+ # @provides MotionKit::Calculator
2
+ module MotionKit
3
+ module_function
4
+
5
+ def calculate(view, dimension, amount, full_view=nil)
6
+ if amount.is_a? Proc
7
+ return view.instance_exec(&amount)
8
+ elsif dimension == :origin || dimension == :center
9
+ return calculate_origin(view, dimension, amount, nil, full_view)
10
+ elsif dimension == :size
11
+ return calculate_size(view, amount, full_view)
12
+ elsif dimension == :frame
13
+ return calculate_frame(view, amount, full_view)
14
+ elsif amount == :full
15
+ return calculate(view, dimension, '100%', full_view)
16
+ elsif amount == :auto
17
+ size_that_fits = intrinsic_size(view)
18
+
19
+ return case dimension
20
+ when :width
21
+ return size_that_fits.width
22
+ when :height
23
+ return size_that_fits.height
24
+ end
25
+ elsif amount.is_a?(String) && amount.include?('%')
26
+ full_view ||= view.superview
27
+ unless full_view
28
+ raise NoSuperviewError.new("Cannot calculate #{amount}% of #{dimension.inspect} because view #{view} has no superview.")
29
+ end
30
+ calc = Calculator.scan(amount)
31
+
32
+ factor = calc.factor
33
+ constant = calc.constant
34
+
35
+ return case dimension
36
+ when :width
37
+ return (view.superview.frame.size.width * factor + constant).round
38
+ when :height
39
+ return (view.superview.frame.size.height * factor + constant).round
40
+ else
41
+ raise "Unknown dimension #{dimension}"
42
+ end
43
+ else
44
+ return amount
45
+ end
46
+ end
47
+
48
+ def calculate_origin(view, dimension, amount, my_size=nil, full_view)
49
+ my_size ||= view.frame.size
50
+
51
+ if amount == :center
52
+ x = calculate(view, :width, '50%', full_view) - my_size.width / 2.0
53
+ y = calculate(view, :height, '50%', full_view) - my_size.height / 2.0
54
+ return CGPoint.new(x, y)
55
+ elsif amount.is_a?(Array) || amount.is_a?(Hash)
56
+ x_offset = 0
57
+ y_offset = 0
58
+
59
+ if amount.is_a?(Hash)
60
+ if amount.fetch(:relative, false)
61
+ if amount.key?(:x)
62
+ x = amount[:x]
63
+ else
64
+ x = view.frame.origin.x
65
+ if dimension == :center
66
+ x += my_size.width / 2.0
67
+ end
68
+ end
69
+
70
+ if amount.key?(:y)
71
+ y = amount[:y]
72
+ else
73
+ y = view.frame.origin.y
74
+ if dimension == :center
75
+ y += my_size.height / 2.0
76
+ end
77
+ end
78
+
79
+ if amount.key?(:right)
80
+ x_offset = amount[:right]
81
+ elsif amount.key?(:left)
82
+ x_offset = -amount[:left]
83
+ end
84
+
85
+ if amount.key?(:down)
86
+ y_offset = amount[:down]
87
+ elsif amount.key?(:up)
88
+ y_offset = -amount[:up]
89
+ end
90
+ y_offset = -y_offset if amount[:flipped]
91
+ else
92
+ if amount.key?(:right)
93
+ x_offset = -my_size.width
94
+ x = amount.fetch(:right, view.frame.origin.x)
95
+ elsif amount.key?(:x) || amount.key?(:left)
96
+ x = amount[:x] || amount[:left]
97
+ elsif dimension == :center
98
+ x = view.frame.origin.x
99
+ x += my_size.width / 2
100
+ else
101
+ x = view.frame.origin.x
102
+ end
103
+
104
+ if amount.key?(:bottom)
105
+ y_offset = -my_size.height
106
+ y = amount.fetch(:bottom, view.frame.origin.y)
107
+ elsif amount.key?(:y) || amount.key?(:top)
108
+ y = amount[:y] || amount[:top]
109
+ elsif dimension == :center
110
+ y = view.frame.origin.y
111
+ y += my_size.height / 2
112
+ else
113
+ y = view.frame.origin.y
114
+ end
115
+ end
116
+ else
117
+ x = amount[0]
118
+ y = amount[1]
119
+ end
120
+
121
+ x = calculate(view, :width, x, full_view) + x_offset
122
+ y = calculate(view, :height, y, full_view) + y_offset
123
+ return CGPoint.new(x, y)
124
+ else
125
+ return amount
126
+ end
127
+ end
128
+
129
+ def calculate_size(view, amount, full_view)
130
+ if amount.is_a?(Array) || amount.is_a?(Hash)
131
+ if amount.is_a?(Hash)
132
+ w = amount.fetch(:w, amount.fetch(:width, view.frame.size.width))
133
+ h = amount.fetch(:h, amount.fetch(:height, view.frame.size.height))
134
+ else
135
+ w = amount[0]
136
+ h = amount[1]
137
+ end
138
+
139
+ # scaling is handled a little differently, because it is dependent on
140
+ # the other amount and the intrinsic size.
141
+ if w == :scale || h == :scale
142
+ if w == :scale && h == :scale
143
+ raise "Either width or height can be :scale, but not both"
144
+ elsif w == :scale
145
+ h = calculate(view, :height, h, full_view)
146
+ size = intrinsic_size(view)
147
+ w = h * size.width / size.height
148
+ elsif h == :scale
149
+ w = calculate(view, :width, w, full_view)
150
+ size = intrinsic_size(view)
151
+ h = w * size.height / size.width
152
+ end
153
+ else
154
+ w = calculate(view, :width, w, full_view)
155
+ h = calculate(view, :height, h, full_view)
156
+ end
157
+
158
+ return CGSize.new(w, h)
159
+ elsif amount == :full
160
+ w = calculate(view, :width, '100%', full_view)
161
+ h = calculate(view, :height, '100%', full_view)
162
+ return CGSize.new(w, h)
163
+ elsif amount == :auto
164
+ return intrinsic_size(view)
165
+ elsif amount.is_a?(Symbol)
166
+ raise "Unrecognized amount symbol #{amount.inspect} in MotionKit#calculate_size"
167
+ else
168
+ return amount
169
+ end
170
+ end
171
+
172
+ def calculate_frame(view, amount, full_view)
173
+ if amount.is_a?(Symbol)
174
+ case amount
175
+ when :full, :auto
176
+ size = calculate_size(view, amount, full_view)
177
+ origin = [0, 0]
178
+ when :center
179
+ size = view.frame.size
180
+ origin = calculate_origin(view, :center, ['50%', '50%'], size, full_view)
181
+ origin.x -= size.width / 2.0
182
+ origin.y -= size.height / 2.0
183
+ else
184
+ raise "Unrecognized amount symbol #{amount.inspect} in MotionKit#calculate_frame"
185
+ end
186
+
187
+ return CGRect.new(origin, size)
188
+ elsif amount.is_a?(Array)
189
+ if amount.length == 2
190
+ size = calculate_size(view, amount[1], full_view)
191
+ origin = calculate_origin(view, :origin, amount[0], size, full_view)
192
+ elsif amount.length == 4
193
+ size = calculate_size(view, [amount[2], amount[3]], full_view)
194
+ origin = calculate_origin(view, :origin, [amount[0], amount[1]], size, full_view)
195
+ else
196
+ raise "Don't know what to do with frame value #{amount.inspect}"
197
+ end
198
+
199
+ return CGRect.new(origin, size)
200
+ elsif amount.is_a?(Hash)
201
+ if amount.key?(:size)
202
+ size = calculate_size(view, amount[:size], full_view)
203
+ else
204
+ size = calculate_size(view, amount, full_view)
205
+ end
206
+
207
+ if amount.key?(:center)
208
+ origin = calculate_origin(view, :center, amount[:center], size, full_view)
209
+ origin.x -= size.width / 2
210
+ origin.y -= size.height / 2
211
+ elsif amount.key?(:origin)
212
+ origin = calculate_origin(view, :origin, amount[:origin], size, full_view)
213
+ else
214
+ origin = calculate_origin(view, :origin, amount, size, full_view)
215
+ end
216
+
217
+ return CGRect.new(origin, size)
218
+ else
219
+ return amount
220
+ end
221
+ end
222
+
223
+ def intrinsic_size(view)
224
+ size_that_fits = view.intrinsicContentSize
225
+ if size_that_fits.width == MotionKit.no_intrinsic_metric
226
+ size_that_fits.width = 0
227
+ end
228
+ if size_that_fits.height == MotionKit.no_intrinsic_metric
229
+ size_that_fits.height = 0
230
+ end
231
+ return size_that_fits
232
+ end
233
+
234
+ class Calculator
235
+ attr_accessor :factor, :constant
236
+
237
+ def self.memo
238
+ @memo ||= {}
239
+ end
240
+
241
+ def self.scan(amount)
242
+ amount = amount.gsub(' ', '')
243
+ return Calculator.memo[amount] if Calculator.memo[amount]
244
+
245
+ calc = Calculator.new
246
+
247
+ location = amount.index '%'
248
+ if location
249
+ calc.factor = amount.slice(0, location).to_f / 100.0
250
+ location += 1
251
+ else
252
+ calc.factor = 0
253
+ location = 0
254
+ end
255
+ calc.constant = amount.slice(location, amount.size).to_f
256
+
257
+ Calculator.memo[amount] = calc
258
+ return calc
259
+ end
260
+
261
+ end
262
+
263
+ end
@@ -0,0 +1,299 @@
1
+ # @provides MotionKit::BaseLayout
2
+ # @requires MotionKit::BaseLayoutClassMethods
3
+ module MotionKit
4
+ # Abstract base class, responsible for "registration" of layout classes with
5
+ # the class `targets` method.
6
+ #
7
+ # Very few methods are defined on BaseLayout, and any unknown methods are
8
+ # delegated to the 'apply' method, which accepts a method name, arguments, and
9
+ # an optional block to set the new context.
10
+ #
11
+ # The ViewLayout subclass defines methods that are appropriate for adding and
12
+ # removing views to a view hierarchy.
13
+ class BaseLayout
14
+ # Class methods reside in base_layout_class_methods.rb
15
+ extend BaseLayoutClassMethods
16
+
17
+ attr :parent
18
+
19
+ def initialize
20
+ # @layout is the object we look in for style methods
21
+ @layout = self
22
+ # the Layout object that implements custom style methods. Leave this as nil
23
+ # in the initializer.
24
+ @layout_delegate = nil
25
+ @layout_state = :initial
26
+ end
27
+
28
+ def set_layout(layout)
29
+ @layout = layout && WeakRef.new(layout)
30
+ end
31
+
32
+ def target
33
+ if @layout.nil? || @layout == self
34
+ # only the "root layout" instance is allowed to change the context.
35
+ # if there isn't a context set, try and create a root instance; this
36
+ # will fail if we're not in a state that allows the root to be created
37
+ @context ||= create_default_root_context
38
+ else
39
+ # child layouts get the context from the root layout
40
+ @layout.target
41
+ end
42
+ end
43
+ def v ; target ; end
44
+
45
+ # Runs a block of code with a new object as the 'context'. Methods from the
46
+ # Layout classes are applied to this target object, and missing methods are
47
+ # delegated to a new Layout instance that is created based on the new
48
+ # context.
49
+ #
50
+ # This method is part of the public API, you can pass in any object to have
51
+ # it become the 'context'.
52
+ #
53
+ # Example:
54
+ # def table_view_style
55
+ # content = target.contentView
56
+ # if content
57
+ # context(content) do
58
+ # background_color UIColor.clearColor
59
+ # end
60
+ # end
61
+ #
62
+ # # usually you use 'context' automatically via method_missing, by
63
+ # # passing a block to a method that returns an object. That object becomes
64
+ # # the new context.
65
+ # layer do
66
+ # # self is now a CALayerLayout instance
67
+ # corner_radius 5
68
+ # end
69
+ # end
70
+ def context(target, &block)
71
+ return target unless block
72
+ # this little line is incredibly important; the context is only set on
73
+ # the top-level Layout object.
74
+ return @layout.context(target, &block) if @layout != self
75
+
76
+ if target.is_a?(Symbol)
77
+ target = self.get(target)
78
+ end
79
+
80
+ context_was, parent_was, delegate_was = @context, @parent, @layout_delegate
81
+
82
+ was_top_level = @is_top_level
83
+ if @is_top_level.nil?
84
+ @is_top_level = true
85
+ else
86
+ @is_top_level = false
87
+ end
88
+ @parent = MK::Parent.new(context_was)
89
+ @context = target
90
+ @context.motion_kit_meta[:delegate] ||= Layout.layout_for(@layout, @context.class)
91
+ @layout_delegate = @context.motion_kit_meta[:delegate]
92
+ yield
93
+ @layout_delegate, @context, @parent = delegate_was, context_was, parent_was
94
+ if @is_top_level
95
+ run_deferred(target)
96
+ end
97
+ @is_top_level = was_top_level
98
+
99
+ target
100
+ end
101
+
102
+ # Blocks passed to `deferred` are run at the end of a "session", usually
103
+ # after a call to Layout#layout.
104
+ def deferred(&block)
105
+ if @layout != self
106
+ return @layout.add_deferred_block(self, &block)
107
+ else
108
+ return self.add_deferred_block(self, &block)
109
+ end
110
+ end
111
+
112
+ # Only intended for private use
113
+ def add_deferred_block(layout, &block)
114
+ raise InvalidDeferredError.new('deferred must be run inside of a context') if @is_top_level.nil?
115
+ raise ArgumentError.new('Block required') unless block
116
+
117
+ self.deferred_blocks << [@context, block]
118
+
119
+ self
120
+ end
121
+
122
+ # Only intended for private use
123
+ def deferred_blocks
124
+ @deferred_blocks ||= []
125
+ end
126
+
127
+ # Only intended for private use
128
+ def run_deferred(top_level_context)
129
+ deferred_blocks = self.deferred_blocks
130
+ @deferred_blocks = nil
131
+
132
+ deferred_blocks.each do |target, block|
133
+ context(target, &block)
134
+ end
135
+
136
+ if @deferred_blocks
137
+ run_deferred(top_level_context)
138
+ end
139
+ end
140
+
141
+ # @example
142
+ # def login_button_style
143
+ # frame [[0, 0], [100, 20]]
144
+ # title 'Login'
145
+ # end
146
+ #
147
+ # Methods that style the view start out as missing methods. This just calls
148
+ # 'apply', which searches for the method in the delegate
149
+ # (`@layout_delegate`) or using inspection (`respond_to?(:setFoo)`).
150
+ def method_missing(method_name, *args, &block)
151
+ self.apply(method_name, *args, &block)
152
+ end
153
+
154
+ # Tries to call the setter (`foo 'value'` => `view.setFoo('value')`), or
155
+ # assignment method (`foo 'value'` => `view.foo = 'value'`), or if a block
156
+ # is given, then the object returned by 'method_name' is assigned as the new
157
+ # context, and the block is executed.
158
+ #
159
+ # You can call this method directly, but usually it is called via
160
+ # method_missing.
161
+ def apply(method_name, *args, &block)
162
+ method_name = method_name.to_s
163
+ raise ApplyError.new("Cannot apply #{method_name.inspect} to instance of #{target.class.name}") if method_name.length == 0
164
+
165
+ # if there is no target, than we should raise the NoMethodError; someone
166
+ # called a method on the layout directly.
167
+ begin
168
+ target = self.target
169
+ rescue NoContextError => e
170
+ raise NoMethodError.new(method_name)
171
+ end
172
+
173
+ if args.length == 2 && args[1].is_a?(Hash) && !args[1].empty?
174
+ long_method_name = "#{method_name}:#{args[1].keys.join(':')}:"
175
+ long_method_args = [args[0]].concat args[1].values
176
+ else
177
+ long_method_name = nil
178
+ long_method_args = nil
179
+ end
180
+
181
+ @layout_delegate ||= Layout.layout_for(@layout, target.class)
182
+ if long_method_name && @layout_delegate.respond_to?(long_method_name)
183
+ return @layout_delegate.send(long_method_name, *long_method_args, &block)
184
+ elsif @layout_delegate.respond_to?(method_name)
185
+ return @layout_delegate.send(method_name, *args, &block)
186
+ end
187
+
188
+ if block
189
+ apply_with_context(method_name, *args, &block)
190
+ else
191
+ apply_with_target(method_name, *args)
192
+ end
193
+ end
194
+
195
+ def apply_with_context(method_name, *args, &block)
196
+ if args.length == 2 && args[1].is_a?(Hash) && !args[1].empty?
197
+ long_method_name = "#{method_name}:#{args[1].keys.join(':')}:"
198
+ long_method_args = [args[0]].concat args[1].values
199
+ else
200
+ long_method_name = nil
201
+ long_method_args = nil
202
+ end
203
+
204
+ if long_method_name && target.respond_to?(long_method_name)
205
+ new_context = target.send(long_method_name, *long_method_args)
206
+ self.context(new_context, &block)
207
+ elsif target.respond_to?(method_name)
208
+ new_context = target.send(method_name, *args)
209
+ self.context(new_context, &block)
210
+ elsif method_name.include?('_')
211
+ objc_name = MotionKit.objective_c_method_name(method_name)
212
+ self.apply(objc_name, *args, &block)
213
+ else
214
+ raise ApplyError.new("Cannot apply #{method_name.inspect} to instance of #{target.class.name}")
215
+ end
216
+ end
217
+
218
+ def apply_with_target(method_name, *args)
219
+ setter = MotionKit.setter(method_name)
220
+ assign = "#{method_name}="
221
+ if args.length == 2 && args[1].is_a?(Hash) && !args[1].empty?
222
+ long_method_name = "#{method_name}:#{args[1].keys.join(':')}:"
223
+ long_method_args = [args[0]].concat args[1].values
224
+ else
225
+ long_method_name = nil
226
+ long_method_args = nil
227
+ end
228
+
229
+ # The order is important here.
230
+ # - unchanged method name if no args are passed (e.g. `layer`)
231
+ # - setter (`setLayer(val)`)
232
+ # - assign (`layer=val`)
233
+ # - unchanged method name *again*, because many Ruby classes provide a
234
+ # combined getter/setter (`layer(val)`)
235
+ # - lastly, try again after converting to camelCase
236
+ if long_method_name && target.respond_to?(long_method_name)
237
+ target.send(long_method_name, *long_method_args)
238
+ elsif args.empty? && target.respond_to?(method_name)
239
+ target.send(method_name, *args)
240
+ elsif target.respond_to?(setter)
241
+ target.send(setter, *args)
242
+ elsif target.respond_to?(assign)
243
+ target.send(assign, *args)
244
+ elsif target.respond_to?(method_name)
245
+ target.send(method_name, *args)
246
+ # UIAppearance classes are a whole OTHER thing; they never return 'true'
247
+ elsif target.is_a?(MotionKit.appearance_class)
248
+ target.send(setter, *args)
249
+ # Finally, try again with camel case if there's an underscore.
250
+ elsif method_name.include?('_')
251
+ objc_name = MotionKit.objective_c_method_name(method_name)
252
+ self.apply(objc_name, *args)
253
+ else
254
+ target.send(setter, *args)
255
+ # raise ApplyError.new("Cannot apply #{method_name.inspect} to instance of #{target.class.name} (from #{@layout_delegate && @layout_delegate.class})")
256
+ end
257
+ end
258
+
259
+ public
260
+
261
+ class << self
262
+
263
+ def override_start
264
+ @allow_all_override = true
265
+ end
266
+
267
+ def override_stop
268
+ @allow_all_override = false
269
+ end
270
+
271
+ def overrides(method_name)
272
+ overridden_methods << method_name
273
+ end
274
+
275
+ def overridden_methods
276
+ @overridden_methods ||= []
277
+ end
278
+
279
+ # this last little "catch-all" method is helpful to warn against methods
280
+ # that are defined already. Since magic methods are so important, this
281
+ # warning can come in handy.
282
+ def method_added(method_name)
283
+ return if @allow_all_override
284
+
285
+ if self < BaseLayout && BaseLayout.method_defined?(method_name)
286
+ if overridden_methods.include?(method_name)
287
+ overridden_methods.delete(method_name)
288
+ else
289
+ NSLog("Warning! The method #{self.name}##{method_name} has already been defined on MotionKit::BaseLayout or one of its ancestors.")
290
+ end
291
+ end
292
+ end
293
+
294
+ end
295
+
296
+
297
+ end
298
+
299
+ end