teacup 1.3.4 → 2.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.
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