sugarcube 0.11.3 → 0.12
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.
- 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
|