motion-kit 0.0.1 → 0.9.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.
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