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.
- 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
|