teacup 0.3.12 → 1.0.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.
@@ -0,0 +1,69 @@
1
+ module Teacup
2
+ class Stylesheet
3
+
4
+ def constrain(target, attribute=nil)
5
+ if attribute.nil?
6
+ attribute = target
7
+ target = :self
8
+ end
9
+ Teacup::Constraint.new(target, attribute)
10
+ end
11
+
12
+ ##|
13
+ def constrain_xy(x, y)
14
+ [
15
+ Teacup::Constraint.new(:self, :left).equals(:superview, :left).plus(x),
16
+ Teacup::Constraint.new(:self, :top).equals(:superview, :top).plus(y),
17
+ ]
18
+ end
19
+
20
+ def constrain_left(x)
21
+ Teacup::Constraint.new(:self, :left).equals(:superview, :left).plus(x)
22
+ end
23
+
24
+ def constrain_right(x)
25
+ Teacup::Constraint.new(:self, :right).equals(:superview, :right).plus(x)
26
+ end
27
+
28
+ def constrain_top(y)
29
+ Teacup::Constraint.new(:self, :top).equals(:superview, :top).plus(y)
30
+ end
31
+
32
+ def constrain_bottom(y)
33
+ Teacup::Constraint.new(:self, :bottom).equals(:superview, :bottom).plus(y)
34
+ end
35
+
36
+ def constrain_width(width)
37
+ Teacup::Constraint.new(:self, :width).equals(width)
38
+ end
39
+
40
+ def constrain_height(height)
41
+ Teacup::Constraint.new(:self, :height).equals(height)
42
+ end
43
+
44
+ def constrain_size(width, height)
45
+ [
46
+ Teacup::Constraint.new(:self, :right).equals(:self, :left).plus(width),
47
+ Teacup::Constraint.new(:self, :bottom).equals(:self, :top).plus(height),
48
+ ]
49
+ end
50
+
51
+ ##|
52
+ def constrain_below(relative_to, margin=0)
53
+ Teacup::Constraint.new(:self, :top).equals(relative_to, :bottom).plus(margin)
54
+ end
55
+
56
+ def constrain_above(relative_to, margin=0)
57
+ Teacup::Constraint.new(:self, :bottom).equals(relative_to, :top).plus(margin)
58
+ end
59
+
60
+ def constrain_to_left(relative_to, margin=0)
61
+ Teacup::Constraint.new(:self, :right).equals(relative_to, :left).plus(margin)
62
+ end
63
+
64
+ def constrain_to_right(relative_to, margin=0)
65
+ Teacup::Constraint.new(:self, :left).equals(relative_to, :right).plus(margin)
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ # Example:
2
+ # Teacup::Stylesheet.new :main do
3
+ # style :root,
4
+ # origin: [0, 0]
5
+ #
6
+ # # device returns a bitmask of devices,
7
+ # # device? expects a device bitmask, and returns true if it is present in
8
+ # # device (the conditions below are equivalent)
9
+ # if 0 < device & iPhone || device? iPhone
10
+ # style :root, width: 320
11
+ #
12
+ # if 0 < device & iPhone5 || device? iPhone5
13
+ # style :root, height: 548
14
+ # elsif 0 < device & iPhone || device? iPhone
15
+ # style :root, height: 460
16
+ # end
17
+ # elsif 0 < device & iPad || device? iPad
18
+ # style :root,
19
+ # width: 768,
20
+ # height: 1004
21
+ # end
22
+ #
23
+ # # that's a mess! and this does the same thing anyway:
24
+ # style :root,
25
+ # frame: [[0, 0], app_size]
26
+ # end
27
+ module Teacup
28
+ class Stylesheet
29
+ def iPhone ; 1 << 1 ; end
30
+ def iPhoneRetina ; 1 << 2 ; end
31
+ def iPhone5 ; 1 << 3 ; end
32
+ def iPad ; 1 << 4 ; end
33
+ def iPadRetina ; 1 << 5 ; end
34
+
35
+ # returns the device size in points, regardless of status bar
36
+ def screen_size
37
+ UIScreen.mainScreen.bounds.size
38
+ end
39
+
40
+ # returns the application frame, which takes the status bar into account
41
+ def app_size
42
+ UIScreen.mainScreen.applicationFrame.size
43
+ end
44
+
45
+ # returns a bit-wise OR of the device masks
46
+ def device
47
+ @@this_device ||= nil
48
+ return @@this_device if @@this_device
49
+
50
+ @@this_device = 0
51
+ if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone
52
+ @@this_device |= iPhone
53
+ if UIScreen.mainScreen.respond_to? :scale
54
+ @@this_device |= iPhoneRetina
55
+ if UIScreen.mainScreen.bounds.size.height == 568
56
+ @@this_device |= iPhone5
57
+ end
58
+ end
59
+ else
60
+ @@this_device |= iPad
61
+ if UIScreen.mainScreen.respond_to? :scale
62
+ @@this_device |= iPadRetina
63
+ end
64
+ end
65
+
66
+ return @@this_device
67
+ end
68
+
69
+ def device_is?(this_device)
70
+ this_device = self.send(this_device) if this_device.is_a? Symbol
71
+ return device & this_device > 0
72
+ end
73
+
74
+ end
75
+ end
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Teacup
3
-
4
3
  class Stylesheet
4
+
5
5
  def identity
6
6
  [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
7
7
  end
@@ -34,5 +34,4 @@ module Teacup
34
34
  end
35
35
 
36
36
  end
37
-
38
37
  end
@@ -1,5 +1,5 @@
1
1
  module Teacup
2
2
 
3
- VERSION = '0.3.12'
3
+ VERSION = '1.0.0'
4
4
 
5
5
  end
@@ -12,17 +12,6 @@ class UIView
12
12
  # Enable debug messages for this object
13
13
  attr_accessor :debug
14
14
 
15
- # The current stylesheet will be looked at when properties are needed. It
16
- # is loaded lazily, so that assignment can occur before the Stylesheet has
17
- # been created.
18
- def stylesheet
19
- if @stylesheet.is_a? Symbol
20
- @stylesheet = Teacup::Stylesheet[@stylesheet]
21
- end
22
-
23
- @stylesheet
24
- end
25
-
26
15
  # Alter the stylename of this view.
27
16
  #
28
17
  # This will cause new styles to be applied from the stylesheet.
@@ -50,8 +39,8 @@ class UIView
50
39
  subviews.each{ |subview| subview.set_stylesheet_quickly(new_stylesheet) }
51
40
 
52
41
  if should_restyle
53
- restyle!
54
42
  Teacup.should_restyle!
43
+ restyle!
55
44
  end
56
45
  end
57
46
 
@@ -62,7 +51,9 @@ class UIView
62
51
 
63
52
  def restyle!(orientation=nil)
64
53
  if Teacup.should_restyle?
65
- if stylesheet
54
+ resetTeacupConstraints
55
+
56
+ if stylesheet && stylesheet.is_a?(Teacup::Stylesheet)
66
57
  style(stylesheet.query(stylename, self, orientation))
67
58
  end
68
59
  subviews.each{ |subview| subview.restyle!(orientation) }
@@ -118,11 +109,77 @@ class UIView
118
109
  #
119
110
  # @param Hash the properties to set.
120
111
  def style(properties, orientation=nil)
121
- Teacup.apply_hash self, properties
122
- properties.each do |key, value|
123
- Teacup.apply self, key, value
112
+ if properties.key?(:constraints)
113
+ new_constraints = add_uniq_constraints(properties.delete(:constraints))
114
+
115
+ self.setTranslatesAutoresizingMaskIntoConstraints(false) unless new_constraints.empty?
116
+
117
+ @teacup_added_constraints ||= []
118
+ new_constraints.each do |original_constraint|
119
+ constraint = original_constraint.copy
120
+
121
+ case original_constraint.target
122
+ when :self
123
+ constraint.target = self
124
+ when :superview
125
+ constraint.target = self.superview
126
+ when Symbol, String
127
+ container = self
128
+ constraint.target = nil
129
+ while container.superview && constraint.target == nil
130
+ constraint.target = container.viewWithStylename(original_constraint.target)
131
+ container = container.superview
132
+ end
133
+ end
134
+
135
+ case original_constraint.relative_to
136
+ when :self
137
+ constraint.relative_to = self
138
+ when :superview
139
+ constraint.relative_to = self.superview
140
+ when Symbol, String
141
+ # TODO: this re-checks lots of views - everytime it goes up to the
142
+ # superview, it checks all the leaves again.
143
+ container = self
144
+ constraint.relative_to = nil
145
+ while container.superview && constraint.relative_to == nil
146
+ constraint.relative_to = container.viewWithStylename(original_constraint.relative_to)
147
+ container = container.superview
148
+ end
149
+ end
150
+
151
+ add_constraint_to = nil
152
+ if constraint.target == constraint.relative_to || constraint.relative_to.nil?
153
+ add_constraint_to = constraint.target.superview
154
+ elsif constraint.target.isDescendantOfView(constraint.relative_to)
155
+ add_constraint_to = constraint.relative_to
156
+ elsif constraint.relative_to.isDescendantOfView(constraint.target)
157
+ add_constraint_to = constraint.target
158
+ else
159
+ parent = constraint.relative_to.superview
160
+ while parent
161
+ if constraint.target.isDescendantOfView(parent)
162
+ add_constraint_to = parent
163
+ parent = nil
164
+ elsif parent.superview
165
+ parent = parent.superview
166
+ end
167
+ end
168
+ end
169
+
170
+ if add_constraint_to
171
+ ns_constraint = constraint.nslayoutconstraint
172
+
173
+ @teacup_added_constraints << { target: add_constraint_to, constraint: ns_constraint }
174
+ add_constraint_to.addConstraint(ns_constraint)
175
+ else
176
+ raise "The two views #{original_constraint.target} and #{original_constraint.relative_to} do not have a common ancestor"
177
+ end
178
+ end
124
179
  end
125
180
 
181
+ Teacup.apply_hash self, properties
182
+
126
183
  self.setNeedsDisplay
127
184
  self.setNeedsLayout
128
185
  end
@@ -131,4 +188,46 @@ class UIView
131
188
  return self
132
189
  end
133
190
 
191
+ def teacup_added_constraints ; @teacup_added_constraints ; end
192
+
193
+ def resetTeacupConstraints
194
+ if @teacup_added_constraints
195
+ @teacup_added_constraints.each do |added_constraint|
196
+ target = added_constraint[:target]
197
+ constraint = added_constraint[:constraint]
198
+ target.removeConstraint(constraint)
199
+ end
200
+ end
201
+ @teacup_added_constraints = nil
202
+ @teacup_constrain_just_once = nil
203
+ end
204
+
205
+ def add_uniq_constraints(constraint)
206
+ @teacup_constrain_just_once ||= {}
207
+
208
+ if constraint.is_a? Array
209
+ new_consraints = constraint.map{|constraint| add_uniq_constraints(constraint) }.flatten
210
+ elsif constraint.is_a? Hash
211
+ new_consraints = constraint.select{|sym, relative_to|
212
+ @teacup_constrain_just_once[sym].nil?
213
+ }.map{|sym, relative_to|
214
+ @teacup_constrain_just_once[sym] = true
215
+ Teacup::Constraint.from_sym(sym, relative_to)
216
+ }
217
+ else
218
+ if @teacup_constrain_just_once[constraint]
219
+ new_consraints = []
220
+ else
221
+ @teacup_constrain_just_once[constraint] = true
222
+ if constraint.is_a? Symbol
223
+ new_consraints = [Teacup::Constraint.from_sym(constraint)]
224
+ else
225
+ new_consraints = [constraint]
226
+ end
227
+ end
228
+ end
229
+
230
+ return new_consraints
231
+ end
232
+
134
233
  end
@@ -53,30 +53,6 @@ class UIViewController
53
53
 
54
54
  end # class << self
55
55
 
56
- # Returns a stylesheet to use to style the contents of this controller's
57
- # view. You can also assign a stylesheet to {stylesheet=}, which will in
58
- # turn call {restyle!}.
59
- #
60
- # This method will be queried each time {restyle!} is called, and also
61
- # implicitly whenever Teacup needs to draw your layout (currently only at
62
- # view load time).
63
- #
64
- # @return Teacup::Stylesheet
65
- #
66
- # @example
67
- #
68
- # def stylesheet
69
- # if [UIInterfaceOrientationLandscapeLeft,
70
- # UIInterfaceOrientationLandscapeRight].include?(UIInterface.currentDevice.orientation)
71
- # Teacup::Stylesheet[:ipad]
72
- # else
73
- # Teacup::Stylesheet[:ipadvertical]
74
- # end
75
- # end
76
- def stylesheet
77
- @stylesheet
78
- end
79
-
80
56
  # Assigning a new stylesheet triggers {restyle!}, so do this during a
81
57
  # rotation to get your different layouts applied.
82
58
  #
@@ -129,14 +105,27 @@ class UIViewController
129
105
 
130
106
  if layout_definition
131
107
  stylename, properties, block = layout_definition
108
+ should_restyle = Teacup.should_restyle_and_block
132
109
  layout(view, stylename, properties, &block)
110
+ Teacup.should_restyle! if should_restyle
133
111
  end
134
112
 
135
113
  layoutDidLoad
136
114
  end
137
115
 
116
+ alias old_viewWillAppear viewWillAppear
117
+
138
118
  def viewWillAppear(animated)
139
- self.view.restyle!
119
+ old_viewWillAppear(animated)
120
+ self.view.restyle! unless @teacup_view_appeared
121
+ @teacup_view_appeared = true
122
+ end
123
+
124
+ alias old_viewDidDisappear viewDidDisappear
125
+
126
+ def viewDidDisappear(animated)
127
+ old_viewDidDisappear(animated)
128
+ @teacup_view_appeared = false
140
129
  end
141
130
 
142
131
  def layoutDidLoad
@@ -152,7 +141,7 @@ class UIViewController
152
141
  #
153
142
  # the teacup developers apologize for any inconvenience. :-)
154
143
  def autorotateToOrientation(orientation)
155
- if view.stylesheet and view.stylename
144
+ if view.stylesheet and view.stylesheet.is_a?(Teacup::Stylesheet) and view.stylename
156
145
  properties = view.stylesheet.query(view.stylename, self, orientation)
157
146
 
158
147
  # check for orientation-specific properties
@@ -185,6 +174,45 @@ class UIViewController
185
174
  return orientation == UIInterfaceOrientationPortrait
186
175
  end
187
176
 
177
+ def autorotateMask
178
+ if view.stylesheet and view.stylesheet.is_a?(Teacup::Stylesheet) and view.stylename
179
+ properties = view.stylesheet.query(view.stylename, self, orientation)
180
+ device = UIDevice.currentDevice.userInterfaceIdiom
181
+ device == UIUserInterfaceIdiomPhone
182
+
183
+ orientations = 0
184
+ if properties.supports?(:portrait) or properties.supports?(:upside_up)
185
+ orientations |= UIInterfaceOrientationPortrait
186
+ end
187
+
188
+ if device == UIUserInterfaceIdiomPhone
189
+ # :portrait does not imply upside_down on the iphone
190
+ if properties.supports?(:upside_down)
191
+ orientations |= UIInterfaceOrientationPortraitUpsideDown
192
+ end
193
+ else
194
+ # but does on the ipad
195
+ if properties.supports?(:portrait) or properties.supports?(:upside_down)
196
+ orientations |= UIInterfaceOrientationPortraitUpsideDown
197
+ end
198
+ end
199
+
200
+ if properties.supports?(:landscape) or properties.supports?(:landscape_left)
201
+ orientations |= UIInterfaceOrientationLandscapeLeft
202
+ end
203
+
204
+ if properties.supports?(:landscape) or properties.supports?(:landscape_right)
205
+ orientations |= UIInterfaceOrientationLandscapeRight
206
+ end
207
+
208
+ if orientations == 0
209
+ orientations |= UIInterfaceOrientationPortrait
210
+ end
211
+ return orientations
212
+ end
213
+ return UIInterfaceOrientationPortrait
214
+ end
215
+
188
216
  def willAnimateRotationToInterfaceOrientation(orientation, duration:duration)
189
217
  view.restyle!(orientation)
190
218
  end
@@ -2,8 +2,9 @@
2
2
  # Kinda similar to jQuery-style $().find('stylename')
3
3
  class UIView
4
4
 
5
- # get one stylesheet by stylename
6
- # my_view[:button] :button => #<UIButton..>
5
+ # get one subview by stylename or class
6
+ # my_view.viewWithStylename :button => #<UIButton..>
7
+ # my_view.viewWithStylename UIButton => #<UIButton..>
7
8
  def viewWithStylename name_or_class
8
9
  if name_or_class.is_a? Class
9
10
  view = subviews.find { |view| view.is_a? name_or_class }
@@ -21,8 +22,9 @@ class UIView
21
22
  nil # couldn't find it
22
23
  end
23
24
 
24
- # get stylesheets by stylename
25
- # my_view.all :button => [#<UIButton..>, #<UIButton...>]
25
+ # get all subviews by stylename or class
26
+ # my_view.viewsWithStylename :button => [#<UIButton..>, #<UIButton...>]
27
+ # my_view.viewsWithStylename UIButton => [#<UIButton..>, #<UIButton...>]
26
28
  def viewsWithStylename name_or_class
27
29
  r = []
28
30
  subviews.each do |view|
@@ -30,10 +32,10 @@ class UIView
30
32
  if view.is_a? name_or_class
31
33
  r << view
32
34
  end
33
- else view.stylename == name_or_class
35
+ elsif view.stylename == name_or_class
34
36
  r << view
35
37
  end
36
- r += view.viewsWithStylename name_or_class
38
+ r.concat view.viewsWithStylename name_or_class
37
39
  end
38
40
  r
39
41
  end
@@ -1,51 +1,71 @@
1
+ module Teacup
2
+ module_function
3
+ def calculate(view, dimension, percent)
4
+ if percent.is_a? Proc
5
+ view.instance_exec(&percent)
6
+ elsif percent.is_a? String and percent[-1] == '%'
7
+ percent = percent[0...-1].to_f / 100.0
8
+
9
+ case dimension
10
+ when :width
11
+ CGRectGetWidth(view.superview.bounds) * percent
12
+ when :height
13
+ CGRectGetHeight(view.superview.bounds) * percent
14
+ end
15
+ else
16
+ percent
17
+ end
18
+ end
19
+ end
20
+
1
21
  ##|
2
22
  ##| UIView.frame
3
23
  ##|
4
24
  Teacup.handler UIView, :left, :x { |x|
5
25
  f = self.frame
6
- f.origin.x = x
26
+ f.origin.x = Teacup::calculate(self, :width, x)
7
27
  self.frame = f
8
28
  }
9
29
 
10
30
  Teacup.handler UIView, :right { |r|
11
31
  f = self.frame
12
- f.origin.x = r - f.size.width
32
+ f.origin.x = Teacup::calculate(self, :width, r) - f.size.width
13
33
  self.frame = f
14
34
  }
15
35
 
16
36
  Teacup.handler UIView, :center_x, :middle_x { |x|
17
37
  c = self.center
18
- c.x = x
38
+ c.x = Teacup::calculate(self, :width, x)
19
39
  self.center = c
20
40
  }
21
41
 
22
42
  Teacup.handler UIView, :top, :y { |y|
23
43
  f = self.frame
24
- f.origin.y = y
44
+ f.origin.y = Teacup::calculate(self, :height, y)
25
45
  self.frame = f
26
46
  }
27
47
 
28
48
  Teacup.handler UIView, :bottom { |b|
29
49
  f = self.frame
30
- f.origin.y = b - f.size.height
50
+ f.origin.y = Teacup::calculate(self, :height, b) - f.size.height
31
51
  self.frame = f
32
52
  }
33
53
 
34
54
  Teacup.handler UIView, :center_y, :middle_y { |y|
35
55
  c = self.center
36
- c.y = y
56
+ c.y = Teacup::calculate(self, :height, y)
37
57
  self.center = c
38
58
  }
39
59
 
40
60
  Teacup.handler UIView, :width { |w|
41
61
  f = self.frame
42
- f.size.width = w
62
+ f.size.width = Teacup::calculate(self, :width, w)
43
63
  self.frame = f
44
64
  }
45
65
 
46
66
  Teacup.handler UIView, :height { |h|
47
67
  f = self.frame
48
- f.size.height = h
68
+ f.size.height = Teacup::calculate(self, :height, h)
49
69
  self.frame = f
50
70
  }
51
71
 
@@ -56,12 +76,15 @@ Teacup.handler UIView, :origin { |origin|
56
76
  }
57
77
 
58
78
  Teacup.handler UIView, :size { |size|
79
+ # odd... if I changed these to .is_a?, weird errors happen. Use ===
59
80
  if Symbol === size && size == :full
60
81
  if self.superview
61
- size = Size(self.superview.bounds.size)
82
+ size = self.superview.bounds.size
62
83
  else
63
84
  size = self.frame.size
64
85
  end
86
+ elsif Array === size
87
+ size = [Teacup::calculate(self, :width, size[0]), Teacup::calculate(self, :height, size[1])]
65
88
  end
66
89
  f = self.frame
67
90
  f.size = size
@@ -69,12 +92,23 @@ Teacup.handler UIView, :size { |size|
69
92
  }
70
93
 
71
94
  Teacup.handler UIView, :frame { |frame|
95
+ # odd... if I changed these to .is_a?, weird errors happen. Use ===
72
96
  if Symbol === frame && frame == :full
73
97
  if self.superview
74
- frame = Rect(self.superview.bounds)
98
+ frame = self.superview.bounds
75
99
  else
76
100
  frame = self.frame
77
101
  end
102
+ elsif Array === frame && frame.length == 4
103
+ frame = [
104
+ [Teacup::calculate(self, :width, frame[0]), Teacup::calculate(self, :height, frame[1])],
105
+ [Teacup::calculate(self, :width, frame[2]), Teacup::calculate(self, :height, frame[3])]
106
+ ]
107
+ elsif Array === frame && frame.length == 2
108
+ frame = [
109
+ [Teacup::calculate(self, :width, frame[0][0]), Teacup::calculate(self, :height, frame[0][1])],
110
+ [Teacup::calculate(self, :width, frame[1][0]), Teacup::calculate(self, :height, frame[1][1])]
111
+ ]
78
112
  end
79
113
  self.frame = frame
80
114
  }
@@ -92,7 +126,7 @@ Teacup.handler UIButton, :titleColor { |color|
92
126
  }
93
127
 
94
128
 
95
- Teacup.handler UIButton, :titleFont { |font|
129
+ Teacup.handler UIButton, :titleFont, :font { |font|
96
130
  font = font.uifont
97
131
  self.titleLabel.font = font
98
132
  }
data/lib/teacup.rb CHANGED
@@ -21,4 +21,6 @@ Motion::Project::App.setup do |app|
21
21
  Dir.glob(File.join(File.dirname(__FILE__), 'teacup/**/*.rb')).reverse.each do |file|
22
22
  app.files.insert(insert_point, file)
23
23
  end
24
+
25
+ app.vendor_project File.join(File.dirname(__FILE__), '../vendor/TeacupDummy'), :static
24
26
  end