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.
- checksums.yaml +7 -0
- data/README.md +839 -0
- data/lib/motion-kit-cocoa/cocoa_util.rb +59 -0
- data/lib/motion-kit-cocoa/constraints/constraint.rb +765 -0
- data/lib/motion-kit-cocoa/constraints/constraint_placeholder.rb +22 -0
- data/lib/motion-kit-cocoa/constraints/constraints_layout.rb +536 -0
- data/lib/motion-kit-cocoa/constraints/constraints_target.rb +52 -0
- data/lib/motion-kit-cocoa/layouts/cagradientlayer_layout.rb +13 -0
- data/lib/motion-kit-cocoa/layouts/calayer_layout.rb +27 -0
- data/lib/motion-kit-cocoa/layouts/sugarcube_compat.rb +38 -0
- data/lib/motion-kit-ios/dummy.rb +93 -0
- data/lib/motion-kit-ios/ios_util.rb +20 -0
- data/lib/motion-kit-ios/layouts/constraints_layout.rb +22 -0
- data/lib/motion-kit-ios/layouts/layout_device.rb +32 -0
- data/lib/motion-kit-ios/layouts/layout_orientation.rb +47 -0
- data/lib/motion-kit-ios/layouts/uibutton_layout.rb +52 -0
- data/lib/motion-kit-ios/layouts/uiview_layout.rb +45 -0
- data/lib/motion-kit-ios/layouts/uiview_layout_autoresizing.rb +75 -0
- data/lib/motion-kit-ios/layouts/uiview_layout_constraints.rb +36 -0
- data/lib/motion-kit-ios/layouts/uiview_layout_frame.rb +307 -0
- data/lib/motion-kit-ios/layouts/uiview_layout_gradient.rb +20 -0
- data/lib/motion-kit-osx/dummy.rb +89 -0
- data/lib/motion-kit-osx/layouts/constraints_layout.rb +42 -0
- data/lib/motion-kit-osx/layouts/nsmenu_extensions.rb +186 -0
- data/lib/motion-kit-osx/layouts/nsmenu_layout.rb +109 -0
- data/lib/motion-kit-osx/layouts/nsmenuitem_extensions.rb +45 -0
- data/lib/motion-kit-osx/layouts/nstablecolumn_layout.rb +12 -0
- data/lib/motion-kit-osx/layouts/nstableview_layout.rb +20 -0
- data/lib/motion-kit-osx/layouts/nsview_layout.rb +47 -0
- data/lib/motion-kit-osx/layouts/nsview_layout_autoresizing.rb +75 -0
- data/lib/motion-kit-osx/layouts/nsview_layout_constraints.rb +34 -0
- data/lib/motion-kit-osx/layouts/nsview_layout_frame.rb +375 -0
- data/lib/motion-kit-osx/layouts/nswindow_frame.rb +14 -0
- data/lib/motion-kit-osx/layouts/nswindow_layout.rb +77 -0
- data/lib/motion-kit-osx/osx_util.rb +16 -0
- data/lib/motion-kit.rb +33 -0
- data/lib/motion-kit/calculate.rb +263 -0
- data/lib/motion-kit/layouts/base_layout.rb +299 -0
- data/lib/motion-kit/layouts/base_layout_class_methods.rb +43 -0
- data/lib/motion-kit/layouts/parent.rb +68 -0
- data/lib/motion-kit/layouts/view_layout.rb +327 -0
- data/lib/motion-kit/motion-kit.rb +18 -0
- data/lib/motion-kit/object.rb +16 -0
- data/lib/motion-kit/util.rb +20 -0
- data/lib/motion-kit/version.rb +1 -1
- data/spec/ios/apply_styles_spec.rb +21 -0
- data/spec/ios/autoresizing_helper_spec.rb +224 -0
- data/spec/ios/calculate_spec.rb +322 -0
- data/spec/ios/calculator_spec.rb +31 -0
- data/spec/ios/constraints_helpers/attribute_lookup_spec.rb +27 -0
- data/spec/ios/constraints_helpers/axis_lookup_spec.rb +13 -0
- data/spec/ios/constraints_helpers/center_constraints_spec.rb +419 -0
- data/spec/ios/constraints_helpers/constraint_placeholder_spec.rb +72 -0
- data/spec/ios/constraints_helpers/priority_lookup_spec.rb +19 -0
- data/spec/ios/constraints_helpers/relationship_lookup_spec.rb +27 -0
- data/spec/ios/constraints_helpers/relative_corners_spec.rb +274 -0
- data/spec/ios/constraints_helpers/relative_location_spec.rb +111 -0
- data/spec/ios/constraints_helpers/simple_constraints_spec.rb +2763 -0
- data/spec/ios/constraints_helpers/size_constraints_spec.rb +422 -0
- data/spec/ios/constraints_helpers/view_lookup_constraints_spec.rb +93 -0
- data/spec/ios/create_layout_spec.rb +40 -0
- data/spec/ios/custom_layout_spec.rb +13 -0
- data/spec/ios/deferred_spec.rb +89 -0
- data/spec/ios/device_helpers_spec.rb +51 -0
- data/spec/ios/frame_helper_spec.rb +1150 -0
- data/spec/ios/layer_layout_spec.rb +36 -0
- data/spec/ios/layout_extensions_spec.rb +70 -0
- data/spec/ios/layout_spec.rb +74 -0
- data/spec/ios/layout_state_spec.rb +27 -0
- data/spec/ios/motionkit_util_spec.rb +102 -0
- data/spec/ios/objc_selectors_spec.rb +10 -0
- data/spec/ios/orientation_helper_specs.rb +67 -0
- data/spec/ios/parent_layout_spec.rb +19 -0
- data/spec/ios/parent_spec.rb +45 -0
- data/spec/ios/remove_layout_spec.rb +25 -0
- data/spec/ios/root_layout_spec.rb +53 -0
- data/spec/ios/setters_spec.rb +63 -0
- data/spec/ios/uibutton_layout_spec.rb +24 -0
- data/spec/ios/uitextfield_spec.rb +14 -0
- data/spec/ios/view_attr_spec.rb +25 -0
- data/spec/osx/autoresizing_helper_spec.rb +224 -0
- data/spec/osx/constraints_helper_spec.rb +0 -0
- data/spec/osx/constraints_helpers/orientation_lookup_spec.rb +13 -0
- data/spec/osx/constraints_helpers/simple_constraints_spec.rb +2095 -0
- data/spec/osx/constraints_helpers/size_constraints_spec.rb +362 -0
- data/spec/osx/create_menu_spec.rb +14 -0
- data/spec/osx/create_via_extensions_spec.rb +63 -0
- data/spec/osx/deferred_spec.rb +85 -0
- data/spec/osx/frame_helper_spec.rb +1881 -0
- data/spec/osx/menu_extensions_spec.rb +376 -0
- data/spec/osx/menu_layout_spec.rb +157 -0
- data/spec/osx/menu_spec.rb +70 -0
- data/spec/osx/root_menu_spec.rb +15 -0
- 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
|
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
|