sugarcube 0.11.3 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/README.md +104 -113
- data/Rakefile +3 -1
- data/app/app_delegate.rb +4 -0
- data/lib/sugarcube-gestures.rb +23 -0
- data/lib/sugarcube-gestures/gestures.rb +134 -0
- data/lib/sugarcube/adjust.rb +69 -64
- data/lib/sugarcube/calayer.rb +8 -0
- data/lib/sugarcube/core_graphics.rb +107 -459
- data/lib/sugarcube/modal.rb +25 -6
- data/lib/sugarcube/{array.rb → nsarray.rb} +7 -1
- data/lib/sugarcube/nsdata.rb +22 -0
- data/lib/sugarcube/nsdate.rb +23 -0
- data/lib/sugarcube/nserror.rb +16 -0
- data/lib/sugarcube/nsstring.rb +29 -19
- data/lib/sugarcube/nsstring_files.rb +6 -0
- data/lib/sugarcube/nsuserdefaults.rb +34 -1
- data/lib/sugarcube/symbol.rb +125 -17
- data/lib/sugarcube/timer.rb +7 -0
- data/lib/sugarcube/to_s/nslayoutconstraint.rb +97 -0
- data/lib/sugarcube/to_s/uilabel.rb +4 -0
- data/lib/sugarcube/to_s/uitextfield.rb +15 -0
- data/lib/sugarcube/to_s/uiview.rb +11 -1
- data/lib/sugarcube/uiactionsheet.rb +86 -0
- data/lib/sugarcube/uialertview.rb +1 -1
- data/lib/sugarcube/uicolor.rb +18 -9
- data/lib/sugarcube/uiimage.rb +158 -12
- data/lib/sugarcube/uitableview.rb +2 -2
- data/lib/sugarcube/uiview.rb +92 -51
- data/lib/sugarcube/uiviewcontroller.rb +1 -0
- data/lib/sugarcube/version.rb +1 -1
- data/spec/core_graphics_spec.rb +304 -0
- metadata +17 -4
data/lib/sugarcube/timer.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
if defined? NSLayoutConstraint
|
2
|
+
class NSLayoutConstraint
|
3
|
+
Relationships = {
|
4
|
+
equal: NSLayoutRelationEqual,
|
5
|
+
lte: NSLayoutRelationLessThanOrEqual,
|
6
|
+
gte: NSLayoutRelationGreaterThanOrEqual,
|
7
|
+
}
|
8
|
+
Attributes = {
|
9
|
+
left: NSLayoutAttributeLeft,
|
10
|
+
right: NSLayoutAttributeRight,
|
11
|
+
top: NSLayoutAttributeTop,
|
12
|
+
bottom: NSLayoutAttributeBottom,
|
13
|
+
leading: NSLayoutAttributeLeading,
|
14
|
+
trailing: NSLayoutAttributeTrailing,
|
15
|
+
width: NSLayoutAttributeWidth,
|
16
|
+
height: NSLayoutAttributeHeight,
|
17
|
+
center_x: NSLayoutAttributeCenterX,
|
18
|
+
center_y: NSLayoutAttributeCenterY,
|
19
|
+
baseline: NSLayoutAttributeBaseline,
|
20
|
+
}
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
target = firstItem
|
24
|
+
relative_to = secondItem
|
25
|
+
|
26
|
+
if firstItem and secondItem
|
27
|
+
if secondItem == firstItem
|
28
|
+
relative_to = :self
|
29
|
+
elsif firstItem.superview and secondItem == firstItem.superview
|
30
|
+
relative_to = :superview
|
31
|
+
elsif secondItem.respond_to?(:stylename) and secondItem.stylename
|
32
|
+
relative_to = secondItem.stylename
|
33
|
+
end
|
34
|
+
|
35
|
+
if secondItem.superview and firstItem == secondItem.superview
|
36
|
+
target = :superview
|
37
|
+
elsif firstItem.respond_to?(:stylename) and firstItem.stylename
|
38
|
+
target = firstItem.stylename
|
39
|
+
end
|
40
|
+
elsif firstItem
|
41
|
+
if firstItem.respond_to?(:stylename) and firstItem.stylename
|
42
|
+
target = firstItem.stylename
|
43
|
+
end
|
44
|
+
elsif secondItem
|
45
|
+
if secondItem.respond_to?(:stylename) and secondItem.stylename
|
46
|
+
target = secondItem.stylename
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
op = case _to_s_relationship_reverse relation
|
51
|
+
when :equal
|
52
|
+
'=='
|
53
|
+
when :gte
|
54
|
+
'>='
|
55
|
+
when :lte
|
56
|
+
'<='
|
57
|
+
end
|
58
|
+
formula = 'first.'
|
59
|
+
formula << _to_s_attribute_reverse(firstAttribute).to_s
|
60
|
+
formula << ' ' << op << ' '
|
61
|
+
if multiplier != 1
|
62
|
+
formula << multiplier.to_s << ' × '
|
63
|
+
end
|
64
|
+
if secondItem
|
65
|
+
if firstItem == secondItem
|
66
|
+
formula << 'first.'
|
67
|
+
else
|
68
|
+
formula << 'second.'
|
69
|
+
end
|
70
|
+
formula << _to_s_attribute_reverse(secondAttribute).to_s
|
71
|
+
end
|
72
|
+
if constant != 0
|
73
|
+
if secondItem
|
74
|
+
formula << ' + '
|
75
|
+
end
|
76
|
+
formula << constant.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
return "#<#{self.class}:##{object_id.to_s(16)}" +
|
80
|
+
" firstItem=#{target.inspect}" +
|
81
|
+
" secondItem=#{relative_to.inspect}" +
|
82
|
+
" priority=#{priority.inspect}" +
|
83
|
+
" formula=#{formula.inspect}" +
|
84
|
+
">"
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def _to_s_attribute_reverse(attribute)
|
89
|
+
Attributes.key(attribute) || :none
|
90
|
+
end
|
91
|
+
|
92
|
+
def _to_s_relationship_reverse(relationship)
|
93
|
+
Relationships.key(relationship)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class UITextField
|
2
|
+
|
3
|
+
def to_s(options={})
|
4
|
+
text = self.text
|
5
|
+
if text && text.length > 20
|
6
|
+
text = text[0..20] + '...'
|
7
|
+
end
|
8
|
+
placeholder = self.placeholder
|
9
|
+
if placeholder && placeholder.length > 20
|
10
|
+
placeholder = placeholder[0..20] + '...'
|
11
|
+
end
|
12
|
+
super options.merge(inner: {text: text, placeholder: placeholder, firstResponder?: firstResponder?})
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -7,8 +7,18 @@ class UIView
|
|
7
7
|
else
|
8
8
|
suffix = ''
|
9
9
|
end
|
10
|
+
if options[:inner].is_a? Hash
|
11
|
+
inner = ''
|
12
|
+
options[:inner].each do |key, value|
|
13
|
+
inner += ', ' if inner.length > 0
|
14
|
+
inner += "#{key}: #{value.inspect}"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
inner = options[:inner]
|
18
|
+
end
|
19
|
+
|
10
20
|
"#{self.class.name}(##{self.object_id.to_s(16)}, #{SugarCube::Adjust::format_frame(self.frame)}" +
|
11
|
-
(
|
21
|
+
(inner ? ', ' + inner : '') +
|
12
22
|
')' +
|
13
23
|
(options[:superview] && self.superview ? ", child of #{self.superview.class.name}(##{self.superview.object_id.to_s(16)})" : '') +
|
14
24
|
suffix
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class UIActionSheet
|
2
|
+
|
3
|
+
# UIActionSheet.alert("message",
|
4
|
+
# # The first button is considered the 'cancel' button, for the purposes of
|
5
|
+
# # whether the cancel or success handler gets called, the second button is
|
6
|
+
# # the 'destructive' button, and the rest are plain old buttons.
|
7
|
+
# buttons: %w"Cancel OK No-way",
|
8
|
+
# cancel: proc{ puts "nevermind" },
|
9
|
+
# destructive: proc{ puts "OHHH YEAAH!" },
|
10
|
+
# success: proc{ |pressed| puts "pressed: #{pressed}" },
|
11
|
+
# )
|
12
|
+
def self.alert(title, options={}, &block)
|
13
|
+
if options.is_a? String
|
14
|
+
options = {message: options}
|
15
|
+
end
|
16
|
+
|
17
|
+
# create the delegate
|
18
|
+
delegate = SugarCube::ActionSheetDelegate.new
|
19
|
+
delegate.on_success = options[:success] || block
|
20
|
+
delegate.on_destructive = options[:destructive] || block
|
21
|
+
delegate.on_cancel = options[:cancel]
|
22
|
+
delegate.send(:retain)
|
23
|
+
|
24
|
+
args = [title] # initWithTitle:
|
25
|
+
args << options[:message] # message:
|
26
|
+
args << delegate # delegate:
|
27
|
+
|
28
|
+
buttons = options[:buttons] || []
|
29
|
+
if buttons.empty?
|
30
|
+
# cancelButtonTitle: is first, so check for cancel
|
31
|
+
buttons << "Cancel" if options[:cancel]
|
32
|
+
# destructiveButtonTitle: is first, so check for cancel
|
33
|
+
buttons << "Cancel" if options[:cancel]
|
34
|
+
# otherButtonTitles:
|
35
|
+
buttons << "OK" if options[:success] or buttons.empty?
|
36
|
+
elsif buttons.length == 1 and options[:cancel]
|
37
|
+
raise "If you only have one button, use a :success handler, not :cancel (and definitely not BOTH)"
|
38
|
+
end
|
39
|
+
|
40
|
+
# the button titles. These are passed to the success handler.
|
41
|
+
delegate.buttons = buttons
|
42
|
+
|
43
|
+
# uses localized buttons in the actual alert
|
44
|
+
args.concat(buttons.map{ |s| s.localized })
|
45
|
+
args << nil # otherButtonTitles:..., nil
|
46
|
+
|
47
|
+
alert = self.alloc
|
48
|
+
alert.send('initWithTitle:message:delegate:cancelButtonTitle:destructiveButtonTitle:otherButtonTitles:', *args)
|
49
|
+
alert.show
|
50
|
+
alert
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def dummy
|
55
|
+
self.initWithTitle(nil, message:nil, delegate:nil, cancelButtonTitle:nil, destructiveButtonTitle:nil, otherButtonTitles:nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
module SugarCube
|
62
|
+
class ActionSheetDelegate
|
63
|
+
attr_accessor :buttons
|
64
|
+
attr_accessor :on_cancel
|
65
|
+
attr_accessor :on_destructive
|
66
|
+
attr_accessor :on_success
|
67
|
+
|
68
|
+
def alertSheet(alert, didDismissWithButtonIndex:index)
|
69
|
+
if index == alert.destructiveButtonIndex && on_destructive
|
70
|
+
on_destructive.call
|
71
|
+
elsif index == alert.cancelButtonIndex && on_cancel
|
72
|
+
on_cancel.call
|
73
|
+
elsif on_success
|
74
|
+
if on_success.arity == 0
|
75
|
+
on_success.call
|
76
|
+
else
|
77
|
+
button = buttons[index]
|
78
|
+
on_success.call(button)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
self.send(:autorelease)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/sugarcube/uicolor.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
1
|
class UIColor
|
3
2
|
def uicolor ; self ; end
|
3
|
+
alias cgcolor CGColor
|
4
4
|
|
5
5
|
def red
|
6
6
|
_sugarcube_colors[:red]
|
@@ -24,15 +24,24 @@ private
|
|
24
24
|
red = Pointer.new(:float)
|
25
25
|
green = Pointer.new(:float)
|
26
26
|
blue = Pointer.new(:float)
|
27
|
+
white = Pointer.new(:float)
|
27
28
|
alpha = Pointer.new(:float)
|
28
|
-
self.getRed(red, green:green, blue:blue, alpha:alpha)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
if self.getRed(red, green:green, blue:blue, alpha:alpha)
|
30
|
+
{
|
31
|
+
red: red[0],
|
32
|
+
green: green[0],
|
33
|
+
blue: blue[0],
|
34
|
+
alpha: alpha[0],
|
35
|
+
}
|
36
|
+
elsif self.getWhite(white, alpha:alpha)
|
37
|
+
{
|
38
|
+
red: white[0],
|
39
|
+
green: white[0],
|
40
|
+
blue: white[0],
|
41
|
+
alpha: alpha[0],
|
42
|
+
}
|
43
|
+
end
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
38
|
-
end
|
47
|
+
end
|
data/lib/sugarcube/uiimage.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
class UIImage
|
2
2
|
def uiimage ; self ; end
|
3
3
|
|
4
|
+
# @return [UIColor]
|
4
5
|
def uicolor(alpha=nil)
|
5
6
|
color = UIColor.colorWithPatternImage(self)
|
6
7
|
if not alpha.nil?
|
@@ -10,19 +11,89 @@ class UIImage
|
|
10
11
|
color
|
11
12
|
end
|
12
13
|
|
14
|
+
# @return [UIImageView]
|
13
15
|
def uiimageview
|
14
|
-
|
16
|
+
UIImageView.alloc.initWithImage(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [NSData] an NSData object in PNG format
|
20
|
+
def nsdata
|
21
|
+
UIImagePNGRepresentation(self)
|
15
22
|
end
|
16
23
|
|
17
24
|
##|
|
18
25
|
##| REALLY HANDY STUFF!
|
26
|
+
##| many of these methods are translated from:
|
27
|
+
##| <http://www.catamount.com/blog/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/>
|
28
|
+
##| <http://www.catamount.com/forums/viewtopic.php?f=21&t=967>
|
19
29
|
##|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
30
|
+
def in_rect(rect)
|
31
|
+
# not necessary, since we don't modify/examine the rect
|
32
|
+
# rect = SugarCube::CoreGraphics::Rect(rect)
|
33
|
+
imageRef = CGImageCreateWithImageInRect(self.CGImage, rect)
|
34
|
+
sub_image = UIImage.imageWithCGImage(imageRef)
|
35
|
+
|
36
|
+
return sub_image
|
37
|
+
end
|
38
|
+
|
39
|
+
def scale_to(new_size)
|
40
|
+
new_size = SugarCube::CoreGraphics::Size(new_size)
|
41
|
+
|
42
|
+
sourceImage = self
|
43
|
+
newImage = nil
|
44
|
+
|
45
|
+
image_size = sourceImage.size
|
46
|
+
width = image_size.width
|
47
|
+
height = image_size.height
|
48
|
+
|
49
|
+
target_width = new_size.width
|
50
|
+
target_height = new_size.height
|
51
|
+
|
52
|
+
scale_factor = 0.0
|
53
|
+
scaled_width = target_width
|
54
|
+
scaled_height = target_height
|
55
|
+
|
56
|
+
thumbnail_point = CGPoint.new(0.0, 0.0)
|
57
|
+
|
58
|
+
unless CGSizeEqualToSize(image_size, new_size)
|
59
|
+
width_factor = target_width / width
|
60
|
+
heightFactor = target_height / height
|
61
|
+
|
62
|
+
if width_factor < heightFactor
|
63
|
+
scale_factor = width_factor
|
64
|
+
else
|
65
|
+
scale_factor = heightFactor
|
66
|
+
end
|
67
|
+
|
68
|
+
scaled_width = width * scale_factor
|
69
|
+
scaled_height = height * scale_factor
|
70
|
+
|
71
|
+
# center the image
|
72
|
+
|
73
|
+
if width_factor < heightFactor
|
74
|
+
thumbnail_point.y = (target_height - scaled_height) * 0.5
|
75
|
+
elsif width_factor > heightFactor
|
76
|
+
thumbnail_point.x = (target_width - scaled_width) * 0.5
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# this is actually the interesting part:
|
81
|
+
|
82
|
+
UIGraphicsBeginImageContext(new_size)
|
83
|
+
|
84
|
+
thumbnail_rect = CGRectZero
|
85
|
+
thumbnail_rect.origin = thumbnail_point
|
86
|
+
thumbnail_rect.size.width = scaled_width
|
87
|
+
thumbnail_rect.size.height = scaled_height
|
88
|
+
|
89
|
+
sourceImage.drawInRect(thumbnail_rect)
|
90
|
+
|
91
|
+
new_image = UIGraphicsGetImageFromCurrentImageContext()
|
24
92
|
UIGraphicsEndImageContext()
|
25
|
-
|
93
|
+
|
94
|
+
raise "could not scale image" unless new_image
|
95
|
+
|
96
|
+
return new_image
|
26
97
|
end
|
27
98
|
|
28
99
|
def rounded(corner_radius=5)
|
@@ -30,18 +101,93 @@ class UIImage
|
|
30
101
|
path = UIBezierPath.bezierPathWithRoundedRect([[0, 0], size], cornerRadius:corner_radius)
|
31
102
|
path.addClip
|
32
103
|
self.drawInRect([[0, 0], size])
|
33
|
-
|
104
|
+
new_image = UIGraphicsGetImageFromCurrentImageContext()
|
105
|
+
UIGraphicsEndImageContext()
|
106
|
+
return new_image
|
107
|
+
end
|
34
108
|
|
109
|
+
# Accepts two options: brightness (default: -0.5) and saturation (default: -0.2)
|
110
|
+
# Returns a darkened version of the image.
|
111
|
+
def darken(options={})
|
112
|
+
filter_name = 'CIColorControls'
|
113
|
+
filter_options = {
|
114
|
+
inputBrightness: options[:brightness] || -0.5,
|
115
|
+
inputSaturation: options[:saturation] || -0.2,
|
116
|
+
}
|
117
|
+
|
118
|
+
cg_input_image = CIImage.alloc.initWithImage(self)
|
119
|
+
darken_filter = CIFilter.filterWithName(filter_name)
|
120
|
+
raise Exception.new("Filter not found: #{filter_name}") unless darken_filter
|
121
|
+
|
122
|
+
darken_filter.setDefaults
|
123
|
+
darken_filter.setValue(cg_input_image, forKey:'inputImage')
|
124
|
+
filter_options.each_pair do |key, value|
|
125
|
+
darken_filter.setValue(value, forKey:key)
|
126
|
+
end
|
127
|
+
output = darken_filter.valueForKey('outputImage')
|
128
|
+
|
129
|
+
context = CIContext.contextWithOptions(nil)
|
130
|
+
cg_output_image = context.createCGImage(output, fromRect:output.extent)
|
131
|
+
output_image = UIImage.imageWithCGImage(cg_output_image)
|
132
|
+
|
133
|
+
return output_image
|
134
|
+
end
|
135
|
+
|
136
|
+
##|
|
137
|
+
##| rotate images
|
138
|
+
##|
|
139
|
+
def rotate(angle_or_direction)
|
140
|
+
case angle_or_direction
|
141
|
+
when :left
|
142
|
+
radian = -90.degrees
|
143
|
+
when :right
|
144
|
+
radian = 90.degrees
|
145
|
+
when :flip
|
146
|
+
radian = 180.degrees
|
147
|
+
when Numeric
|
148
|
+
radian = angle_or_direction
|
149
|
+
else
|
150
|
+
raise "Unknown angle/direction #{angle_or_direction.inspect}"
|
151
|
+
end
|
152
|
+
|
153
|
+
w = (self.size.width * Math.cos(radian)).abs + (self.size.height * Math.sin(radian)).abs
|
154
|
+
h = (self.size.height * Math.cos(radian)).abs + (self.size.width * Math.sin(radian)).abs
|
155
|
+
new_size = CGSize.new(w, h)
|
156
|
+
new_size = self.size
|
157
|
+
|
158
|
+
# Create the bitmap context
|
159
|
+
UIGraphicsBeginImageContext(new_size)
|
160
|
+
bitmap = UIGraphicsGetCurrentContext()
|
161
|
+
|
162
|
+
# Move the origin to the middle of the image so we will rotate and scale around the center.
|
163
|
+
CGContextTranslateCTM(bitmap, new_size.width / 2, new_size.height / 2)
|
164
|
+
|
165
|
+
# Rotate the image context
|
166
|
+
CGContextRotateCTM(bitmap, radian)
|
167
|
+
|
168
|
+
# otherwise it'll be upside down:
|
169
|
+
CGContextScaleCTM(bitmap, 1.0, -1.0)
|
170
|
+
# Now, draw the rotated/scaled image into the context
|
171
|
+
CGContextDrawImage(bitmap, CGRectMake(-new_size.width / 2, -new_size.height / 2, new_size.width, new_size.height), self.CGImage)
|
172
|
+
|
173
|
+
new_image = UIGraphicsGetImageFromCurrentImageContext()
|
35
174
|
UIGraphicsEndImageContext()
|
36
|
-
return
|
175
|
+
return new_image
|
37
176
|
end
|
38
177
|
|
39
|
-
|
40
|
-
|
178
|
+
##|
|
179
|
+
##| resizableImageWithCapInsets
|
180
|
+
##|
|
181
|
+
def tileable(insets=UIEdgeInsetsZero)
|
182
|
+
# not necessary, since we don't modify/examine the insets
|
183
|
+
# insets = SugarCube::CoreGraphics::EdgeInsets(insets)
|
184
|
+
resizableImageWithCapInsets(insets, resizingMode:UIImageResizingModeTile)
|
41
185
|
end
|
42
186
|
|
43
|
-
def stretchable
|
44
|
-
|
187
|
+
def stretchable(insets=UIEdgeInsetsZero)
|
188
|
+
# not necessary, since we don't modify/examine the insets
|
189
|
+
# insets = SugarCube::CoreGraphics::EdgeInsets(insets)
|
190
|
+
resizableImageWithCapInsets(insets, resizingMode:UIImageResizingModeStretch)
|
45
191
|
end
|
46
192
|
|
47
193
|
end
|