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,43 @@
1
+ # @provides MotionKit::BaseLayoutClassMethods
2
+ module MotionKit
3
+ module BaseLayoutClassMethods
4
+ def target_klasses
5
+ # We don't want subclasses, just BaseLayout
6
+ return BaseLayout.target_klasses unless self == BaseLayout
7
+ @target_klasses ||= {}
8
+ end
9
+
10
+ def targets(klass=nil)
11
+ return nil if klass.nil? && self == BaseLayout
12
+ return @targets || superclass.targets if klass.nil?
13
+ @targets = klass
14
+ BaseLayout.target_klasses[klass] = self
15
+ nil
16
+ end
17
+
18
+ # Instantiates a new Layout instance using `layout` as the root-level layout
19
+ def layout_for(layout, klass)
20
+ memoized_klass = memoize(klass)
21
+ memoized_klass.new_child(layout) if memoized_klass
22
+ end
23
+
24
+ # Cache registered classes
25
+ def memoize(klass)
26
+ @memoize ||= {}
27
+ @memoize[klass] ||= begin
28
+ while klass
29
+ break if registered_class = target_klasses[klass]
30
+ klass = klass.superclass
31
+ end
32
+ @memoize[klass] = registered_class if registered_class
33
+ end
34
+ @memoize[klass]
35
+ end
36
+
37
+ def new_child(layout=nil)
38
+ child = self.new
39
+ child.set_layout(layout)
40
+ child
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,68 @@
1
+ # @provides MotionKit::Parent
2
+ module MotionKit
3
+ # Simple class that returns data about the parent element
4
+ # for use while setting the styles of a child element.
5
+ class Parent
6
+
7
+ attr_reader :element
8
+
9
+ def initialize(element)
10
+ @element = element
11
+ end
12
+
13
+ def origin
14
+ try(:frame, :origin)
15
+ end
16
+
17
+ def size
18
+ try(:frame, :size)
19
+ end
20
+
21
+ def x
22
+ try(:frame, :origin, :x)
23
+ end
24
+
25
+ def y
26
+ try(:frame, :origin, :y)
27
+ end
28
+
29
+ def width
30
+ try(:frame, :size, :width)
31
+ end
32
+
33
+ def height
34
+ try(:frame, :size, :height)
35
+ end
36
+
37
+ def center_x
38
+ width / 2.0 if width
39
+ end
40
+
41
+ def center_y
42
+ height / 2.0 if height
43
+ end
44
+
45
+ def center
46
+ CGPointMake(center_x, center_y) if width && height
47
+ end
48
+
49
+ protected
50
+
51
+ # Convenience method that takes a list of method calls and tries
52
+ # them end-to-end, returning nil if any fail to respond to that
53
+ # method name.
54
+ # Very similar to ActiveSupport's `.try` method.
55
+ def try(*method_chain)
56
+ obj = self.element
57
+ method_chain.each do |m|
58
+ # We'll break out and return nil if any part of the chain
59
+ # doesn't respond properly.
60
+ (obj = nil) && break unless obj.respond_to?(m)
61
+ obj = obj.send(m)
62
+ end
63
+ obj
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,327 @@
1
+ # @provides MotionKit::ViewLayout
2
+ # @requires MotionKit::BaseLayout
3
+ module MotionKit
4
+ # A sensible parent class for any View-like layout class. Platform agnostic.
5
+ # Any platform-specific tasks are offloaded to child views (add_child,
6
+ # remove_child).
7
+ # Actually, "view like" is misleading, since technically it only assumes "tree
8
+ # like". You could use a ViewLayout subclass to construct a hierarchy
9
+ # representing a family tree, for instance. But that would be a silly use of
10
+ # MotionKit.
11
+ class ViewLayout < BaseLayout
12
+
13
+ class << self
14
+
15
+ # This is an `attr_reader`-like method that also calls `build_view` if the
16
+ # @view doesn't exist, and so you can use it to refer to views that are
17
+ # assigned to ivars in your `layout` method.
18
+ #
19
+ # @example
20
+ # class MyLayout < MK::Layout
21
+ # view :label
22
+ # view :login_button
23
+ #
24
+ # def layout
25
+ # # if element id and attr name match, no need to assign to ivar
26
+ # add UILabel, :label
27
+ # # if they don't match you must assign. If you are using
28
+ # # Key-Value observation you should use the setter:
29
+ # self.login_button = add UIButton, :button
30
+ # end
31
+ #
32
+ # end
33
+ def view(name)
34
+ ivar_name = "@#{name}"
35
+ define_method(name) do
36
+ unless instance_variable_get(ivar_name)
37
+ view = self.get(name)
38
+ unless view
39
+ build_view unless @view
40
+ view = instance_variable_get(ivar_name) || self.get(name)
41
+ end
42
+ self.send("#{name}=", view)
43
+ return view
44
+ end
45
+ return instance_variable_get(ivar_name)
46
+ end
47
+ # KVO compliance
48
+ attr_writer name
49
+ end
50
+
51
+ end
52
+
53
+ # The parent view. This method builds the layout and returns the root view.
54
+ def view
55
+ if @layout != self
56
+ return @layout.view
57
+ end
58
+ @view ||= build_view
59
+ end
60
+
61
+ # Assign a view to act as the 'root' view for this layout. This method can
62
+ # only be called once, and must be called before `add` is called for the
63
+ # first time (otherwise `add` will create a default root view). This method
64
+ # must be called from inside `layout`, otherwise you should just use
65
+ # `create`.
66
+ #
67
+ # You can also call this method with just an element_id, and the default
68
+ # root view will be created.
69
+ def root(element, element_id=nil, &block)
70
+ if @view
71
+ raise ContextConflictError.new("Already created the root view")
72
+ end
73
+ unless @assign_root
74
+ raise InvalidRootError.new("You should only create a 'root' view from inside the 'layout' method (use 'create' elsewhere)")
75
+ end
76
+ @assign_root = false
77
+
78
+ # this method can be called with just a symbol, to assign the root element_id
79
+ if element.is_a?(Symbol)
80
+ element_id = element
81
+ # See note below about why we don't need to `apply(:default_root)`
82
+ element = default_root
83
+ end
84
+
85
+ @view = initialize_view(element)
86
+ if block
87
+ if @context
88
+ raise ContextConflictError.new("Already in a context")
89
+ end
90
+
91
+ context(@view) do
92
+ # We're just using the `create` method for its side effects: calling the
93
+ # style method and calling the block.
94
+ create(@view, element_id, &block)
95
+ end
96
+ elsif element_id
97
+ create(@view, element_id)
98
+ end
99
+
100
+ return @view
101
+ end
102
+
103
+ # instantiates a view, possibly running a 'layout block' to add child views.
104
+ def create(element, element_id=nil, &block)
105
+ element = initialize_view(element)
106
+
107
+ if element_id
108
+ # We set the optional id and call the '_style' method, if it's been
109
+ # defined.
110
+ element.motion_kit_id = element_id
111
+ self.call_style_method(element, element_id)
112
+ end
113
+
114
+ if block
115
+ context(element, &block)
116
+ end
117
+
118
+ element
119
+ end
120
+
121
+ def call_style_method(element, element_id)
122
+ style_method = "#{element_id}_style"
123
+ if @layout.respond_to?(style_method)
124
+ @layout.context(element) do
125
+ @layout.send(style_method)
126
+ end
127
+ end
128
+ return element
129
+ end
130
+
131
+ # Calls the style method of all objects in the view hierarchy
132
+ def reapply!(root=nil)
133
+ root ||= self.view
134
+ @layout_state = :reapply
135
+ MotionKit.find_all_views(root) do |view|
136
+ call_style_method(view, view.motion_kit_id) if view.motion_kit_id
137
+ end
138
+ @layout_state = :initial
139
+
140
+ return self
141
+ end
142
+
143
+ # Calls the style method of all objects in the view hierarchy
144
+ def reapply(&block)
145
+ raise ArgumentError.new('Block required') unless block
146
+
147
+ if @layout_state == :reapply
148
+ yield
149
+ end
150
+ return self
151
+ end
152
+
153
+ def initial(&block)
154
+ raise ArgumentError.new('Block required') unless block
155
+
156
+ if @layout_state == :initial
157
+ yield
158
+ end
159
+ return self
160
+ end
161
+
162
+ # Delegates to `create` to instantiate a view and run a layout block, and
163
+ # adds the view to the current view on the view stack. If no view exists on
164
+ # the stack, a default root view can be created if that has been enabled.
165
+ def add(element, element_id=nil, &block)
166
+ # make sure we have a target - raises NoContextError if none exists
167
+ self.target
168
+
169
+ element = initialize_view(element)
170
+ unless @context
171
+ create_default_root_context
172
+ end
173
+ self.apply(:add_child, element)
174
+ create(element, element_id, &block)
175
+
176
+ element
177
+ end
178
+
179
+ # Retrieves a view by its element id. This will return the *first* view
180
+ # with this element_id in the tree, where *first* means the view closest to
181
+ # the root view. Aliased to `first` to distinguish it from `last`.
182
+ def get(element_id)
183
+ if @layout != self
184
+ return @layout.get(element_id)
185
+ end
186
+ self.get(element_id, in: self.view)
187
+ end
188
+ def first(element_id) ; get(element_id) ; end
189
+
190
+ # Same as `get`, but with the root view specified.
191
+ def get(element_id, in: root)
192
+ MotionKit.find_first_view(root) { |view| view.motion_kit_id == element_id }
193
+ end
194
+ def first(element_id, in: root) ; get(element_id, in: root) ; end
195
+
196
+ # Retrieves a view by its element id. This will return the *last* view
197
+ # with this element_id, where last means the view deepest and furthest from
198
+ # the root view.
199
+ def last(element_id)
200
+ if @layout != self
201
+ return @layout.last(element_id)
202
+ end
203
+ self.last(element_id, in: self.view)
204
+ end
205
+
206
+ # Same as `last`, but with the root view specified.
207
+ def last(element_id, in: root)
208
+ MotionKit.find_last_view(root) { |view| view.motion_kit_id == element_id }
209
+ end
210
+
211
+ # Returns all the elements with a given element_id in the view tree.
212
+ def all(element_id)
213
+ if @layout != self
214
+ return @layout.all(element_id)
215
+ end
216
+ self.all(element_id, in: self.view)
217
+ end
218
+
219
+ # Same as `all`, but with the root view specified.
220
+ def all(element_id, in: root)
221
+ MotionKit.find_all_views(root) { |view| view.motion_kit_id == element_id }
222
+ end
223
+
224
+ # Returns all the elements with a given element_id
225
+ def nth(element_id, index)
226
+ if @layout != self
227
+ return @layout.nth(element_id, index)
228
+ end
229
+ self.all(element_id, in: self.view)[index]
230
+ end
231
+
232
+ # Same as `nth`, but with the root view specified.
233
+ def nth(element_id, at: index, in: root)
234
+ self.all(element_id, in: root)[index]
235
+ end
236
+
237
+ # Removes a view (or several with the same name) from the hierarchy
238
+ # and forgets it entirely. Returns the views that were removed.
239
+ def remove(element_id)
240
+ if @layout != self
241
+ return @layout.remove(element_id)
242
+ end
243
+ self.remove(element_id, from: self.view)
244
+ end
245
+
246
+ # Same as `remove`, but with the root view specified.
247
+ def remove(element_id, from: root)
248
+ context(root) do
249
+ all(element_id).each do |subview|
250
+ self.apply(:remove_child, subview)
251
+ end
252
+ end
253
+ end
254
+
255
+ def create_default_root_context
256
+ if @assign_root
257
+ # Originally I thought default_root should be `apply`ied like other
258
+ # view-related methods, but actually this method *only* gets called
259
+ # from within the `layout` method, and so should already be inside the
260
+ # correct Layout subclass.
261
+ @context = root(default_root)
262
+ else
263
+ raise NoContextError.new("No top level view specified (missing outer 'create' method?)")
264
+ end
265
+ end
266
+
267
+ protected
268
+
269
+ # This method builds the layout and returns the root view.
270
+ def build_view
271
+ # Only in the 'layout' method will we allow default container to be
272
+ # created automatically (when 'add' is called)
273
+ @assign_root = true
274
+ was_top_level = @is_top_level
275
+ @is_top_level = true
276
+ layout
277
+ unless @view
278
+ if @assign_root
279
+ create_default_root_context
280
+ else
281
+ NSLog('Warning! No root view was set in ViewLayout#layout. Did you mean to call `root`?')
282
+ end
283
+ end
284
+ run_deferred(@view)
285
+ @is_top_level = was_top_level
286
+ @assign_root = false
287
+ # context can be set via the 'create_default_root_context' method, which
288
+ # may be outside a 'context' block, so make sure to restore context to
289
+ # it's previous value
290
+ @context = nil
291
+
292
+ @view
293
+ end
294
+
295
+ overrides :run_deferred
296
+ def run_deferred(top_level_context)
297
+ view_was = @view
298
+ @view = top_level_context
299
+ retval = super
300
+ @view = view_was
301
+
302
+ return retval
303
+ end
304
+
305
+ def layout
306
+ end
307
+
308
+ # Initializes an instance of a view. This will need to be smarter going
309
+ # forward as `new` isn't always the designated initializer.
310
+ #
311
+ # Accepts a view instance, a class (which is instantiated with 'new') or a
312
+ # `ViewLayout`, which returns the root view.
313
+ def initialize_view(elem)
314
+ if elem.is_a?(Class) && elem < ViewLayout
315
+ elem = elem.new_child(@layout, nil, self).view
316
+ elsif elem.is_a?(Class)
317
+ elem = elem.new
318
+ elsif elem.is_a?(ViewLayout)
319
+ elem = elem.view
320
+ end
321
+
322
+ return elem
323
+ end
324
+
325
+ end
326
+
327
+ end
@@ -0,0 +1,18 @@
1
+ # @provides MotionKit::Error
2
+ module MotionKit
3
+ class NoContextError < Exception
4
+ end
5
+ class ContextConflictError < Exception
6
+ end
7
+ class InvalidRootError < Exception
8
+ end
9
+ class InvalidDeferredError < Exception
10
+ end
11
+ class ApplyError < Exception
12
+ end
13
+ class NoSuperviewError < Exception
14
+ end
15
+ class NoCommonAncestorError < Exception
16
+ end
17
+ end
18
+ ::MK = MotionKit unless defined?(::MK)