teacup 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.lock +3 -3
  3. data/README.md +1172 -319
  4. data/Rakefile +8 -1
  5. data/app/app_delegate.rb +1 -1
  6. data/app/controllers/appearance_controller.rb +13 -0
  7. data/app/controllers/landscape_only_controller.rb +1 -1
  8. data/app/controllers/{first_controller.rb → main_controller.rb} +4 -3
  9. data/app/controllers/motion_layout_controller.rb +22 -0
  10. data/app/styles/appearance.rb +24 -0
  11. data/app/styles/main_styles.rb +8 -6
  12. data/app/views/custom_view.rb +1 -0
  13. data/lib/teacup/calculations.rb +2 -2
  14. data/lib/teacup/{z_core_extensions → core_extensions}/ca_layer.rb +0 -0
  15. data/lib/teacup/core_extensions/view_getters.rb +61 -0
  16. data/lib/teacup/handler.rb +14 -14
  17. data/lib/teacup/layout.rb +94 -17
  18. data/lib/teacup/stylesheet.rb +61 -26
  19. data/lib/teacup/stylesheet_extensions/transform.rb +88 -0
  20. data/lib/teacup/teacup_controller.rb +122 -0
  21. data/lib/teacup/teacup_util.rb +12 -7
  22. data/lib/teacup/teacup_view.rb +329 -0
  23. data/lib/teacup/version.rb +1 -1
  24. data/lib/teacup-ios/appearance.rb +96 -0
  25. data/lib/teacup-ios/core_extensions/teacup_handlers.rb +183 -0
  26. data/lib/teacup-ios/core_extensions/ui_view.rb +30 -0
  27. data/lib/teacup-ios/core_extensions/ui_view_controller.rb +110 -0
  28. data/lib/{dummy.rb → teacup-ios/dummy.rb} +2 -6
  29. data/lib/teacup-ios/handler.rb +23 -0
  30. data/lib/{teacup → teacup-ios}/style.rb +9 -10
  31. data/lib/teacup-ios/stylesheet_extensions/autoresize.rb +169 -0
  32. data/lib/{teacup/stylesheet_extensions/geometry.rb → teacup-ios/stylesheet_extensions/device.rb} +0 -0
  33. data/lib/teacup-osx/core_extensions/ns_view.rb +39 -0
  34. data/lib/teacup-osx/core_extensions/ns_view_controller.rb +21 -0
  35. data/lib/teacup-osx/core_extensions/ns_window.rb +39 -0
  36. data/lib/teacup-osx/core_extensions/ns_window_controller.rb +29 -0
  37. data/lib/{teacup/z_core_extensions/z_handlers.rb → teacup-osx/core_extensions/teacup_handlers.rb} +30 -47
  38. data/lib/teacup-osx/dummy.rb +80 -0
  39. data/lib/teacup-osx/handler.rb +16 -0
  40. data/lib/teacup-osx/style.rb +83 -0
  41. data/lib/teacup-osx/style_extensions/autoresize.rb +169 -0
  42. data/lib/teacup.rb +12 -11
  43. data/samples/Tweets/Gemfile +4 -0
  44. data/samples/Tweets/Gemfile.lock +16 -0
  45. data/samples/Tweets/README +7 -0
  46. data/samples/Tweets/Rakefile +9 -0
  47. data/samples/Tweets/app/app_delegate.rb +18 -0
  48. data/samples/Tweets/app/data_parser.rb +10 -0
  49. data/samples/Tweets/app/json_parser.rb +12 -0
  50. data/samples/Tweets/app/main_window.rb +99 -0
  51. data/samples/Tweets/app/menu.rb +108 -0
  52. data/samples/Tweets/app/stylesheet.rb +21 -0
  53. data/samples/Tweets/app/tweet.rb +11 -0
  54. data/samples/Tweets/resources/Credits.rtf +29 -0
  55. data/samples/Tweets/spec/main_spec.rb +9 -0
  56. data/samples/teacup-osx/.gitignore +1 -0
  57. data/samples/teacup-osx/Gemfile +4 -0
  58. data/samples/teacup-osx/Gemfile.lock +16 -0
  59. data/samples/teacup-osx/Rakefile +9 -0
  60. data/samples/teacup-osx/app/app_delegate.rb +23 -0
  61. data/samples/teacup-osx/app/controller.rb +11 -0
  62. data/samples/teacup-osx/app/menu.rb +108 -0
  63. data/samples/teacup-osx/app/window.rb +12 -0
  64. data/samples/teacup-osx/resources/Credits.rtf +29 -0
  65. data/samples/teacup-osx/resources/teacup.png +0 -0
  66. data/samples/teacup-osx/spec/main_spec.rb +9 -0
  67. data/spec/ios/appearance_spec.rb +18 -0
  68. data/spec/{calculations_spec.rb → ios/calculations_spec.rb} +0 -0
  69. data/spec/{constraints_spec.rb → ios/constraints_spec.rb} +0 -0
  70. data/spec/{custom_class_spec.rb → ios/custom_class_spec.rb} +0 -0
  71. data/spec/{gradient_spec.rb → ios/gradient_spec.rb} +1 -1
  72. data/spec/ios/layout_module_spec.rb +54 -0
  73. data/spec/ios/layout_spec.rb +50 -0
  74. data/spec/{main_spec.rb → ios/main_spec.rb} +52 -13
  75. data/spec/ios/motion_layout_spec.rb +44 -0
  76. data/spec/{present_modal_spec.rb → ios/present_modal_spec.rb} +0 -0
  77. data/spec/{style_spec.rb → ios/style_spec.rb} +1 -1
  78. data/spec/ios/stylesheet_extensions/autoresize_spec.rb +50 -0
  79. data/spec/{stylesheet_spec.rb → ios/stylesheet_spec.rb} +12 -0
  80. data/spec/{ui_view_getters_spec.rb → ios/ui_view_getters_spec.rb} +0 -0
  81. data/spec/{uiswitch_spec.rb → ios/uiswitch_spec.rb} +0 -0
  82. data/spec/{view_spec.rb → ios/view_spec.rb} +23 -2
  83. metadata +85 -35
  84. data/lib/teacup/stylesheet_extensions/autoresize.rb +0 -39
  85. data/lib/teacup/stylesheet_extensions/rotation.rb +0 -37
  86. data/lib/teacup/z_core_extensions/ui_view.rb +0 -262
  87. data/lib/teacup/z_core_extensions/ui_view_controller.rb +0 -263
  88. data/lib/teacup/z_core_extensions/ui_view_getters.rb +0 -58
@@ -0,0 +1,88 @@
1
+
2
+ module Teacup
3
+ class Stylesheet
4
+
5
+ def pi
6
+ Math::PI
7
+ end
8
+
9
+ def transform_view
10
+ @@transform_layer ||= TransformView.new
11
+ end
12
+
13
+ def transform_layer
14
+ @@transform_layer ||= TransformLayer.new
15
+ end
16
+
17
+ def identity
18
+ NSLog("The Stylesheet method `identity` is deprecated, use `transform_layer.identity` instead")
19
+ [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
20
+ end
21
+
22
+ end
23
+
24
+ class TransformLayer
25
+
26
+ def identity
27
+ [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
28
+ end
29
+
30
+ # rotates the "up & down" direction. The bottom of the view will rotate
31
+ # towards the user as angle increases.
32
+ def flip(angle)
33
+ CATransform3DRotate(identity, angle, 1, 0, 0)
34
+ end
35
+
36
+ # rotates the "left & right" direction. The right side of the view will
37
+ # rotate towards the user as angle increases.
38
+ def twist(angle)
39
+ CATransform3DRotate(identity, angle, 0, 1, 0)
40
+ end
41
+
42
+ # spins, along the z axis. This is probably the one you want, for
43
+ # "spinning" a view like you might a drink coaster or paper napkin.
44
+ def spin(angle)
45
+ CATransform3DRotate(identity, angle, 0, 0, 1)
46
+ end
47
+
48
+ # rotates the layer arbitrarily
49
+ def rotate(angle, x, y, z)
50
+ CATransform3DRotate(identity, angle, x, y, z)
51
+ end
52
+
53
+ end
54
+
55
+ class TransformView
56
+
57
+ def identity
58
+ [1, 0, 0, 1, 0, 0]
59
+ end
60
+
61
+ # Rotates the view counterclockwise
62
+ def rotate angle
63
+ CGAffineTransformMakeRotation(angle)
64
+ end
65
+
66
+ # Scales the view
67
+ def scale scale_x, scale_y=nil
68
+ scale_y ||= scale_x
69
+ CGAffineTransformMakeScale(scale_x, scale_y)
70
+ end
71
+
72
+ # Translates the view
73
+ def translate point, y=nil
74
+ if point.respond_to?(:x) &&point.respond_to?(:y)
75
+ x = point.x
76
+ y = point.y
77
+ elsif point.is_a? Array
78
+ x = point[0]
79
+ y = point[1]
80
+ else
81
+ x = point
82
+ end
83
+ CGAffineTransformMakeTranslation(x, y)
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,122 @@
1
+ # Adds methods to the UIViewController/NSViewController/NSWindowController
2
+ # classes to make defining a layout and stylesheet very easy. Also provides
3
+ # rotation methods that analyze
4
+ module Teacup
5
+ module ControllerClass
6
+
7
+ attr_reader :layout_definition
8
+
9
+ # Define the layout of a controller's view.
10
+ #
11
+ # This function is analogous to Teacup::Layout#layout, though it is
12
+ # designed so you can create an entire layout in a declarative manner in
13
+ # your controller.
14
+ #
15
+ # The hope is that this declarativeness will allow us to automatically
16
+ # deal with common iOS programming tasks (like releasing views when
17
+ # low-memory conditions occur) for you. This is still not implemented
18
+ # though.
19
+ #
20
+ # @param name The stylename for your controller's view.
21
+ #
22
+ # @param properties Any extra styles that you want to apply.
23
+ #
24
+ # @param &block The block in which you should define your layout.
25
+ # It will be instance_exec'd in the context of a
26
+ # controller instance.
27
+ #
28
+ # @example
29
+ # class MyViewController < UIViewController
30
+ # layout :my_view do
31
+ # subview UILabel, title: "Test"
32
+ # subview UITextField, {
33
+ # frame: [[200, 200], [100, 100]]
34
+ # delegate: self
35
+ # }
36
+ # subview(UIView, :shiny_thing) {
37
+ # subview UIView, :centre_of_shiny_thing
38
+ # }
39
+ # end
40
+ # end
41
+ #
42
+ def layout(stylename=nil, properties={}, &block)
43
+ @layout_definition = [stylename, properties, block]
44
+ end
45
+
46
+ end
47
+
48
+ module Controller
49
+ def self.included(base)
50
+ base.extend ControllerClass
51
+ end
52
+
53
+ # Assigning a new stylesheet triggers {restyle!}.
54
+ #
55
+ # Assigning a stylesheet is an *alternative* to returning a Stylesheet in
56
+ # the {stylesheet} method. Note that {restyle!} calls {stylesheet}, so while
57
+ # assigning a stylesheet will trigger {restyle!}, your stylesheet will not
58
+ # be picked up if you don't return it in a custom stylesheet method.
59
+ #
60
+ # @return Teacup::Stylesheet
61
+ #
62
+ # @example
63
+ #
64
+ # stylesheet = Teacup::Stylesheet[:ipadhorizontal]
65
+ # stylesheet = :ipadhorizontal
66
+ def stylesheet=(new_stylesheet)
67
+ super
68
+ if self.viewLoaded?
69
+ self.view.restyle!
70
+ end
71
+ end
72
+
73
+ # Instantiate the layout from the class, and then call layoutDidLoad.
74
+ #
75
+ # If you want to use Teacup in your controller, please hook into layoutDidLoad,
76
+ # not viewDidLoad or windowDidLoad (they call this method).
77
+ def teacupDidLoad
78
+ # look for a layout_definition in the list of ancestors
79
+ layout_definition = nil
80
+ my_stylesheet = self.stylesheet
81
+ parent_class = self.class
82
+ while parent_class != NSObject and not (layout_definition && my_stylesheet)
83
+ if not my_stylesheet and parent_class.respond_to?(:stylesheet)
84
+ my_stylesheet = parent_class.stylesheet
85
+ end
86
+
87
+ if not layout_definition and parent_class.respond_to?(:layout_definition)
88
+ layout_definition = parent_class.layout_definition
89
+ end
90
+ parent_class = parent_class.superclass
91
+ end
92
+
93
+ should_restyle = Teacup.should_restyle_and_block
94
+
95
+ if my_stylesheet and not self.stylesheet
96
+ self.stylesheet = my_stylesheet
97
+ end
98
+
99
+ if layout_definition
100
+ stylename, properties, block = layout_definition
101
+ layout(top_level_view, stylename, properties, &block)
102
+ end
103
+
104
+ if should_restyle
105
+ Teacup.should_restyle!
106
+ self.top_level_view.restyle!
107
+ end
108
+
109
+ if defined? NSLayoutConstraint
110
+ self.top_level_view.apply_constraints
111
+ end
112
+
113
+ layoutDidLoad
114
+ end
115
+
116
+ def layoutDidLoad
117
+ true
118
+ end
119
+
120
+ end
121
+
122
+ end
@@ -1,17 +1,22 @@
1
1
  module Teacup
2
2
  module_function
3
3
 
4
+ # Returns all the subviews of `target` that have a stylename. `target` is not
5
+ # included in the list. Used by the motion-layout integration in layout.rb.
6
+ def get_styled_subviews(target)
7
+ retval = target.subviews.select { |v| v.stylename }
8
+ retval.concat(target.subviews.map do |subview|
9
+ get_styled_subviews(subview)
10
+ end)
11
+ retval.flatten
12
+ end
13
+
4
14
  def to_instance(class_or_instance)
5
15
  if class_or_instance.is_a? Class
6
- unless class_or_instance <= UIView
7
- raise "Expected subclass of UIView, got: #{class_or_instance.inspect}"
8
- end
9
16
  return class_or_instance.new
10
- elsif class_or_instance.is_a?(UIView)
11
- return class_or_instance
12
17
  else
13
- raise "Expected a UIView, got: #{class_or_instance.inspect}"
18
+ return class_or_instance
14
19
  end
15
20
  end
16
21
 
17
- end
22
+ end
@@ -0,0 +1,329 @@
1
+ # Teacup's View extensions defines some utility functions for View that enable a
2
+ # lot of the magic for Teacup::Layout.
3
+ #
4
+ # Users of teacup should be able to ignore the contents of this file for
5
+ # the most part.
6
+ module Teacup
7
+ module View
8
+
9
+ # The current stylename that is used to look up properties in the stylesheet.
10
+ attr_reader :stylename
11
+
12
+ # A list of style classes that will be merged in (lower priority than stylename)
13
+ attr_reader :style_classes
14
+
15
+ # Any class that includes Teacup::Layout gets a `layout` method, which assigns
16
+ # itself as the 'teacup_next_responder'.
17
+ attr_accessor :teacup_next_responder
18
+
19
+ # Enable debug messages for this object
20
+ attr_accessor :debug
21
+
22
+ # Alter the stylename of this view.
23
+ #
24
+ # This will cause new styles to be applied from the stylesheet.
25
+ #
26
+ # If you are using Pixate, it will also set the pixate `styleId` property.
27
+ #
28
+ # @param Symbol stylename
29
+ def stylename=(new_stylename)
30
+ @stylename = new_stylename
31
+ if respond_to?(:'setStyleId:')
32
+ setStyleId(new_stylename)
33
+ end
34
+ if respond_to?(:'setNuiClass:')
35
+ setNuiClass(new_stylename)
36
+ end
37
+ restyle!
38
+ end
39
+
40
+ # Why stop at just one stylename!? Assign a bunch of them using
41
+ # `style_classes`. These are distinct from `stylename`, and `stylename` styles
42
+ # are given priority of `style_classes`.
43
+ #
44
+ # If you are using Pixate, it will also set the pixate `styleClass` property.
45
+ #
46
+ # @param Array [Symbol] style_classes
47
+ def style_classes=(new_style_classes)
48
+ @style_classes = new_style_classes
49
+ if respond_to?(:setStyleClass)
50
+ setStyleClass(new_style_classes.join(' '))
51
+ end
52
+ restyle!
53
+ end
54
+
55
+ def style_classes
56
+ @style_classes ||= []
57
+ end
58
+
59
+ def add_style_class(stylename)
60
+ unless style_classes.include?
61
+ style_classes << stylename
62
+ restyle!
63
+ end
64
+ end
65
+
66
+ def remove_style_class(stylename)
67
+ if style_classes.delete(stylename)
68
+ restyle!
69
+ end
70
+ end
71
+
72
+ # Alter the stylesheet of this view.
73
+ #
74
+ # This will cause new styles to be applied using the current stylename,
75
+ # and will recurse into subviews.
76
+ #
77
+ # If you would prefer that a given UIView object does not inherit the
78
+ # stylesheet from its parents, override the 'stylesheet' method to
79
+ # return the correct value at all times.
80
+ #
81
+ # @param Teacup::Stylesheet stylesheet.
82
+ def stylesheet=(new_stylesheet)
83
+ should_restyle = Teacup.should_restyle_and_block
84
+
85
+ @stylesheet = new_stylesheet
86
+
87
+ if should_restyle
88
+ Teacup.should_restyle!
89
+ restyle!
90
+ end
91
+ end
92
+
93
+ def stylesheet
94
+ if @stylesheet.is_a? Symbol
95
+ @stylesheet = Teacup::Stylesheet[@stylesheet]
96
+ end
97
+ # is a stylesheet assigned explicitly?
98
+ retval = @stylesheet
99
+ return retval if retval
100
+
101
+ # the 'teacup_next_responder' is assigned in the `layout` method, and links
102
+ # any views created there to the custom class (could be a controller, could
103
+ # be any class that includes Teacup::Layout). That responder is checked
104
+ # next, but only if it wouldn't result in a circular loop.
105
+ if ! retval && @teacup_next_responder && teacup_next_responder != self
106
+ retval = @teacup_next_responder.stylesheet
107
+ end
108
+
109
+ # lastly, go up the chain; either a controller or superview
110
+ if ! retval && nextResponder && nextResponder.respond_to?(:stylesheet)
111
+ retval = nextResponder.stylesheet
112
+ end
113
+
114
+ return retval
115
+ end
116
+
117
+ def restyle!(orientation=nil)
118
+ if Teacup.should_restyle?
119
+ if stylesheet && stylesheet.is_a?(Teacup::Stylesheet)
120
+ style_classes.each do |stylename|
121
+ style(stylesheet.query(stylename, self, orientation))
122
+ end
123
+ style(stylesheet.query(self.stylename, self, orientation))
124
+ end
125
+ subviews.each { |subview| subview.restyle!(orientation) }
126
+ end
127
+ end
128
+
129
+ def get_ns_constraints
130
+ # gets the array of Teacup::Constraint objects
131
+ my_constraints = (@teacup_constraints || []).map { |constraint, relative_to|
132
+ if constraint.is_a?(Teacup::Constraint)
133
+ constraint
134
+ else
135
+ if relative_to == true
136
+ Teacup::Constraint.from_sym(constraint)
137
+ else
138
+ Teacup::Constraint.from_sym(constraint, relative_to)
139
+ end
140
+ end
141
+ }.flatten.map do |original_constraint|
142
+ constraint = original_constraint.copy
143
+
144
+ view_class = self.class
145
+
146
+ case original_constraint.target
147
+ when view_class
148
+ constraint.target = original_constraint.target
149
+ when :self
150
+ constraint.target = self
151
+ when :superview
152
+ constraint.target = self.superview
153
+ when Symbol, String
154
+ container = self
155
+ constraint.target = nil
156
+ while container && constraint.target.nil?
157
+ constraint.target = container.viewWithStylename(original_constraint.target)
158
+ container = container.superview
159
+ end
160
+ end
161
+
162
+ case original_constraint.relative_to
163
+ when nil
164
+ constraint.relative_to = nil
165
+ when view_class
166
+ constraint.relative_to = original_constraint.relative_to
167
+ when :self
168
+ constraint.relative_to = self
169
+ when :superview
170
+ constraint.relative_to = self.superview
171
+ when Symbol, String
172
+ # TODO: this re-checks lots of views - everytime it goes up to the
173
+ # superview, it checks all the leaves again.
174
+ container = self
175
+ constraint.relative_to = nil
176
+ while container && constraint.relative_to.nil?
177
+ constraint.relative_to = container.viewWithStylename(original_constraint.relative_to)
178
+ container = container.superview
179
+ end
180
+ end
181
+
182
+ if original_constraint.relative_to && ! constraint.relative_to
183
+ puts "Could not find #{original_constraint.relative_to.inspect}"
184
+ container = self
185
+ tab = ' '
186
+ while container && constraint.relative_to.nil?
187
+ tab << '->'
188
+ puts "#{tab} #{container.stylename.inspect}"
189
+ container = container.superview
190
+ end
191
+ end
192
+
193
+ # the return value, for the map
194
+ constraint.nslayoutconstraint
195
+ end
196
+
197
+ unless my_constraints.empty?
198
+ self.setTranslatesAutoresizingMaskIntoConstraints(false)
199
+ end
200
+
201
+ # now add all che child constraints
202
+ subviews.each do |subview|
203
+ my_constraints.concat(subview.get_ns_constraints)
204
+ end
205
+
206
+ my_constraints
207
+ end
208
+
209
+ def apply_constraints
210
+ if @teacup_added_constraints
211
+ @teacup_added_constraints.each do |constraint|
212
+ self.removeConstraint(constraint)
213
+ end
214
+ end
215
+ @teacup_added_constraints = nil
216
+ all_constraints = get_ns_constraints
217
+
218
+ return if all_constraints.empty?
219
+
220
+ @teacup_added_constraints = []
221
+ all_constraints.each do |ns_constraint|
222
+ @teacup_added_constraints << ns_constraint
223
+ self.addConstraint(ns_constraint)
224
+ end
225
+ end
226
+
227
+ # Applies styles pulled from a stylesheet, but does not assign those styles to
228
+ # any property. This is a one-shot use method, meant to be used as
229
+ # initialization or to apply styles that should not be reapplied during a
230
+ # rotation.
231
+ def apply_stylename(stylename)
232
+ if stylesheet && stylesheet.is_a?(Teacup::Stylesheet)
233
+ style(stylesheet.query(stylename, self))
234
+ end
235
+ end
236
+
237
+ # Animate a change to a new stylename.
238
+ #
239
+ # This is equivalent to wrapping a call to .stylename= inside
240
+ # UIView.beginAnimations.
241
+ #
242
+ # @param Symbol the new stylename
243
+ # @param Options the options for the animation (may include the
244
+ # duration and the curve)
245
+ #
246
+ def animate_to_stylename(stylename, options={})
247
+ return if self.stylename == stylename
248
+
249
+ teacup_animation(options) do
250
+ self.stylename = stylename
251
+ end
252
+ end
253
+
254
+ # Animate a change to a new list of style_classes.
255
+ #
256
+ # This is equivalent to wrapping a call to .style_classes= inside
257
+ # UIView.beginAnimations.
258
+ #
259
+ # @param Symbol the new stylename
260
+ # @param Options the options for the animation (may include the
261
+ # duration and the curve)
262
+ #
263
+ def animate_to_styles(style_classes, options={})
264
+ return if self.style_classes == style_classes
265
+
266
+ teacup_animation(options) do
267
+ self.style_classes = style_classes
268
+ end
269
+ end
270
+
271
+ # Animate a change to new styles
272
+ #
273
+ # This is equivalent to wrapping a call to .style() inside
274
+ # UIView.beginAnimations.
275
+ #
276
+ # @param Hash the new styles and options for the animation
277
+ #
278
+ def animate_to_style(style)
279
+ teacup_animation(options) do
280
+ self.style(style)
281
+ end
282
+ end
283
+
284
+ # Apply style properties to this element.
285
+ #
286
+ # Takes a hash of properties such as may have been read from a stylesheet
287
+ # or passed as parameters to {Teacup::Layout#layout}, and applies them to
288
+ # the element.
289
+ #
290
+ # Does a little bit of magic (that may be split out as 'sugarcube') to
291
+ # make properties work as you'd expect.
292
+ #
293
+ # If you try and assign something in properties that is not supported,
294
+ # a warning message will be emitted.
295
+ #
296
+ # @param Hash the properties to set.
297
+ def style(properties)
298
+ if properties.key?(:constraints)
299
+ add_uniq_constraints(properties.delete(:constraints))
300
+ end
301
+
302
+ apply_style_properties(properties)
303
+ end
304
+
305
+ def apply_style_properties(properties)
306
+ Teacup.apply_hash self, properties
307
+ end
308
+
309
+ def add_uniq_constraints(constraint)
310
+ @teacup_constraints ||= {}
311
+
312
+ if constraint.is_a? Array
313
+ constraint.each do |constraint|
314
+ add_uniq_constraints(constraint)
315
+ end
316
+ elsif constraint.is_a? Hash
317
+ constraint.each do |sym, relative_to|
318
+ @teacup_constraints[sym] = relative_to
319
+ end
320
+ elsif constraint.is_a?(Teacup::Constraint) || constraint.is_a?(Symbol)
321
+ @teacup_constraints[constraint] = true
322
+ else
323
+ raise "Unsupported constraint: #{constraint.inspect}"
324
+ end
325
+ end
326
+
327
+
328
+ end
329
+ end
@@ -1,5 +1,5 @@
1
1
  module Teacup
2
2
 
3
- VERSION = '1.3.4'
3
+ VERSION = '2.0.0'
4
4
 
5
5
  end
@@ -0,0 +1,96 @@
1
+ module Teacup
2
+ # An interface to style views using the UIAppearance protocol.
3
+ #
4
+ # Work similarly as the Stylesheet class.
5
+ #
6
+ # @example
7
+ #
8
+ # Teacup::Appearance.new do
9
+ #
10
+ # style UINavigationBar,
11
+ # tintColor: UIColor.colorWithRed(0.886, green:0.635, blue:0, alpha: 1)
12
+ #
13
+ # end
14
+ class Appearance < Stylesheet
15
+ TeacupAppearanceApplyNotification = 'TeacupAppearanceApplyNotification'
16
+
17
+ def self.apply
18
+ NSNotificationCenter.defaultCenter.postNotificationName(TeacupAppearanceApplyNotification, object:nil)
19
+ end
20
+
21
+ # Contains a list of styles associated with "containers". These do not get
22
+ # merged like the usual `style` declarations.
23
+ def when_contained_in
24
+ @when_contained_in ||= []
25
+ end
26
+
27
+ # disable the stylesheet 'name' parameter, and assign the "super secret"
28
+ # stylesheet name
29
+ def initialize(&block)
30
+ NSNotificationCenter.defaultCenter.addObserver(self,
31
+ selector: :'apply_appearance:',
32
+ name: UIApplicationDidFinishLaunchingNotification,
33
+ object: nil)
34
+ NSNotificationCenter.defaultCenter.addObserver(self,
35
+ selector: :'apply_appearance:',
36
+ name: TeacupAppearanceApplyNotification,
37
+ object: nil)
38
+
39
+ super(&block)
40
+ end
41
+
42
+ def exclude_instance_vars
43
+ @exclude_instance_vars ||= super + [:@when_contained_in]
44
+ end
45
+
46
+ # Styles that have the `when_contained_in` setting need to be kept separate
47
+ def style(*queries)
48
+ # do not modify queries, it gets passed to `super`
49
+ if queries[-1].is_a? Hash
50
+ properties = queries[-1]
51
+ else
52
+ # empty style declarations are allowed, but accomplish nothing.
53
+ return
54
+ end
55
+
56
+ if properties.include?(:when_contained_in)
57
+ # okay NOW modify queries
58
+ queries.pop
59
+ queries.each do |stylename|
60
+ style = Style.new
61
+ style.stylename = stylename
62
+ style.stylesheet = self
63
+ style.merge!(properties)
64
+
65
+ when_contained_in << [stylename, style]
66
+ end
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ # This block is only run once, and the properties object from
73
+ # when_contained_in is a copy (via Teacup::Style.new), so we remove the
74
+ # when_contained_in property using `delete`
75
+ def apply_appearance(notification=nil)
76
+ return unless run_block
77
+ NSNotificationCenter.defaultCenter.removeObserver(self, name:UIApplicationDidFinishLaunchingNotification, object:nil)
78
+ NSNotificationCenter.defaultCenter.removeObserver(self, name:TeacupAppearanceApplyNotification, object:nil)
79
+
80
+ when_contained_in.each do |klass, properties|
81
+ contained_in = properties.delete(:when_contained_in)
82
+ contained_in = [contained_in] unless contained_in.is_a?(NSArray)
83
+ # make a copy and add nil to the end
84
+ contained_in += [nil]
85
+ appearance = klass.send(:'appearanceWhenContainedIn:', *contained_in)
86
+ Teacup.apply_hash appearance, properties.build, klass
87
+ end
88
+ styles.each do |klass, properties|
89
+ appearance = klass.appearance
90
+ Teacup.apply_hash appearance, properties.build, klass
91
+ end
92
+ end
93
+
94
+ end
95
+
96
+ end