teacup 0.3.12 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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