teacup 0.0.1.pre

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 (46) hide show
  1. data/.gitignore +3 -0
  2. data/Dofile +6 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +26 -0
  5. data/README.md +151 -0
  6. data/Rakefile +39 -0
  7. data/lib/teacup.rb +41 -0
  8. data/lib/teacup/contributors.rb +7 -0
  9. data/lib/teacup/core_extensions/ui_view.rb +4 -0
  10. data/lib/teacup/core_extensions/ui_view_controller.rb +62 -0
  11. data/lib/teacup/layout.rb +207 -0
  12. data/lib/teacup/style_sheet.rb +195 -0
  13. data/lib/teacup/version.rb +5 -0
  14. data/lib/teacup/view.rb +123 -0
  15. data/pkg/teacup-0.0.0.gem +0 -0
  16. data/pkg/teacup-0.0.1.gem +0 -0
  17. data/proposals/other/README.md +45 -0
  18. data/proposals/other/app/config/application.rb +1 -0
  19. data/proposals/other/app/config/boot.rb +1 -0
  20. data/proposals/other/app/config/initializers/twitter.rb +7 -0
  21. data/proposals/other/app/controllers/events_controller.rb +28 -0
  22. data/proposals/other/app/controllers/venues_controller.rb +4 -0
  23. data/proposals/other/app/db/README.md +16 -0
  24. data/proposals/other/app/db/migrations/20120514201043_create_events.rb +9 -0
  25. data/proposals/other/app/db/migrations/20120514201044_add_price_to_events.rb +5 -0
  26. data/proposals/other/app/db/migrations/20120514201045_create_venues.rb +8 -0
  27. data/proposals/other/app/db/schema.rb +19 -0
  28. data/proposals/other/app/models/event.rb +14 -0
  29. data/proposals/other/app/models/venue.rb +3 -0
  30. data/proposals/other/app/views/events/edit.ipad.rb +8 -0
  31. data/proposals/other/app/views/events/edit.iphone.rb +7 -0
  32. data/proposals/other/app/views/events/show.ipad.rb +2 -0
  33. data/proposals/other/app/views/events/show.iphone.rb +3 -0
  34. data/samples/Hai/.gitignore +5 -0
  35. data/samples/Hai/Rakefile +12 -0
  36. data/samples/Hai/app/app_delegate.rb +9 -0
  37. data/samples/Hai/app/hai_controller.rb +9 -0
  38. data/samples/Hai/spec/main_spec.rb +9 -0
  39. data/samples/Hai/styles/ipad.rb +11 -0
  40. data/samples/Hai/styles/ipad_vertical.rb +3 -0
  41. data/samples/README.md +4 -0
  42. data/spec/spec_helper.rb +5 -0
  43. data/spec/teacup/contributions_spec.rb +13 -0
  44. data/spec/teacup/version_spec.rb +9 -0
  45. data/teacup.gemspec +27 -0
  46. metadata +130 -0
@@ -0,0 +1,195 @@
1
+ module Teacup
2
+ # Stylesheets in Teacup act as a central configuration mechanism,
3
+ # they have two aims:
4
+ #
5
+ # 1. Allow you to store "Details" away from the main body of your code.
6
+ # (controllers shouldn't have to be filled with style rules)
7
+ # 2. Allow you to easily re-use configuration in many places.
8
+ #
9
+ # The API really provides only two methods, {Stylesheet#style} to store properties
10
+ # on the Stylesheet; and {Stylesheet#query} to get them out again:
11
+ #
12
+ # @example
13
+ # stylesheet = Teacup::Stylesheet.new
14
+ # stylesheet.style :buttons, :corners => :rounded
15
+ # # => nil
16
+ # stylesheet.query :buttons
17
+ # # => {:corners => :rounded}
18
+ #
19
+ # In addition to this, two separate mechanisms are provided for sharing
20
+ # configuration within stylesheets.
21
+ #
22
+ # Firstly, if you set the ':extends' property for a given stylename, then on lookup
23
+ # the Stylesheet will merge the properties for the ':extends' stylename into the return
24
+ # value. Conflicts are resolved so that properties with the original stylename
25
+ # are resolved in its favour.
26
+ #
27
+ # @example
28
+ # Teacup::Stylesheet.new(:IPad) do
29
+ # style :button,
30
+ # background: UIColor.blackColor,
31
+ # top: 100
32
+ #
33
+ # style :ok_button, extends: :button,
34
+ # title: "OK!",
35
+ # top: 200
36
+ #
37
+ # end
38
+ # Teacup::Stylesheet::IPad.query(:ok_button)
39
+ # # => {background: UIColor.blackColor, top: 200, title: "OK!"}
40
+ #
41
+ # Secondly, you can import Stylesheets into each other, in exactly the same way as you
42
+ # can include Modules into each other in Ruby. This allows you to share rules between
43
+ # Stylesheets.
44
+ #
45
+ # As you'd expect, conflicts are resolve so that the Stylesheet on which you call query
46
+ # has the highest precedence.
47
+ #
48
+ # @example
49
+ # Teacup::Stylesheet.new(:IPad) do
50
+ # style :ok_button,
51
+ # title: "OK!"
52
+ # end
53
+ #
54
+ # Teacup::Stylesheet.new(:IPadVertical) do
55
+ # import :IPad
56
+ # style :ok_button,
57
+ # width: 80
58
+ # end
59
+ # Teacup::Stylesheet::IPadVertical.query(:ok_button)
60
+ # # => {title: "OK!", width: 80}
61
+ #
62
+ # The two merging mechanisms are considered independently, so you can override
63
+ # a property both in a ':extends' rule, and also in an imported Stylesheet. In such a
64
+ # a case the Stylesheet inclusion conflicts are resolved independently; and then in
65
+ # a second phase, the ':extends' chain is flattened.
66
+ #
67
+ class Stylesheet
68
+ attr_reader :name
69
+
70
+ # Create a new Stylesheet with the given name.
71
+ #
72
+ # @param name, The name to give.
73
+ # @param &block, The body of the Stylesheet instance_eval'd.
74
+ # @example
75
+ # Teacup::Stylesheet.new(:IPadVertical) do
76
+ # import :IPadBase
77
+ # style :continue_button,
78
+ # top: 50
79
+ # end
80
+ #
81
+ def initialize(name, &block)
82
+ @name = name.to_sym
83
+ Teacup::Stylesheet.const_set(@name, self)
84
+ instance_eval &block
85
+ self
86
+ end
87
+
88
+ # Include another Stylesheet into this one, the rules defined
89
+ # within it will have lower precedence than those defined here
90
+ # in the case that they share the same keys.
91
+ #
92
+ # @param Symbol the name of the stylesheet.
93
+ # @example
94
+ # Teacup::Stylesheet.new(:IPadVertical) do
95
+ # import :IPadBase
96
+ # import :VerticalTweaks
97
+ # end
98
+ def import(name_or_stylesheet)
99
+ if Stylesheet === name_or_stylesheet
100
+ imported << name_or_stylesheet.name
101
+ else
102
+ imported << name_or_stylesheet.to_sym
103
+ end
104
+ end
105
+
106
+ # Add a set of properties for a given stylename or set of stylenames.
107
+ #
108
+ # @param Symbol, *stylename
109
+ # @param Hash[Symbol, Object], properties
110
+ # @example
111
+ # Teacup::Stylesheet.new(:IPadBase) do
112
+ # style :pretty_button,
113
+ # backgroundColor: UIColor.blackColor
114
+ #
115
+ # style :continue_button, extends: :pretty_button,
116
+ # title: "Continue!",
117
+ # top: 50
118
+ # end
119
+ def style(*queries)
120
+ properties = queries.pop
121
+ queries.each do |stylename|
122
+ styles[stylename].update(properties)
123
+ end
124
+ end
125
+
126
+ # Get the properties defined for the given stylename, in this Stylesheet and all
127
+ # those that have been imported.
128
+ #
129
+ # If the ':extends' property is set, we then repeat the process with the value
130
+ # of that, and include them into the result with lower precedence.
131
+ #
132
+ # @param Symbol stylename, the stylename to look up.
133
+ # @return Hash[Symbol, *] the resulting properties.
134
+ # @example
135
+ # Teacup::Stylesheet::IPadBase.query(:continue_button)
136
+ # # => {backgroundColor: UIColor.blackColor, title: "Continue!", top: 50}
137
+ def query(stylename)
138
+ this_rule = properties_for(stylename)
139
+
140
+ if also_include = this_rule.delete(:extends)
141
+ query(also_include).merge(this_rule)
142
+ else
143
+ this_rule
144
+ end
145
+ end
146
+
147
+ # A unique and hopefully meaningful description of this Object.
148
+ #
149
+ # @return String
150
+ def inspect
151
+ "Teacup::Stylesheet:#{name.inspect}"
152
+ end
153
+
154
+ protected
155
+
156
+ # Get the properties for a given stylename, including any properties
157
+ # defined on stylesheets that have been imported into this one, but not
158
+ # resolving ':extends' inheritance.
159
+ #
160
+ # @param Symbol stylename, the stylename to search for.
161
+ # @param Hash so_far, the properties already found in stylesheets with
162
+ # lower precedence than this one.
163
+ # @param Hash seen, the Stylesheets that we've already visited, this is
164
+ # to avoid pathological cases where stylesheets
165
+ # have been imported recursively.
166
+ # @return Hash
167
+ def properties_for(stylename, so_far={}, seen={})
168
+ return so_far if seen[self]
169
+ seen[self] = true
170
+
171
+ imported.each do |name|
172
+ unless Teacup::Stylesheet.const_defined?(name)
173
+ raise "Teacup tried to import Stylesheet:#{name} into Stylesheet:#{self.name}, but it didn't exist"
174
+ end
175
+ Teacup::Stylesheet.const_get(name).properties_for(stylename, so_far, seen)
176
+ end
177
+
178
+ so_far.update(styles[stylename])
179
+ end
180
+
181
+ # The list of Stylesheet names that have been imported into this one.
182
+ #
183
+ # @return Array[Symbol]
184
+ def imported
185
+ @imported ||= []
186
+ end
187
+
188
+ # The actual contents of this stylesheet as a Hash from stylename to properties.
189
+ #
190
+ # @return Hash[Symbol, Hash]
191
+ def styles
192
+ @styles ||= Hash.new{ |h, k| h[k] = {} }
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,5 @@
1
+ module Teacup
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
@@ -0,0 +1,123 @@
1
+ module Teacup
2
+ # Teacup::View defines some utility functions for UIView that enable
3
+ # a lot of the magic for Teacup::Layout.
4
+ #
5
+ # Most users of teacup should be able to ignore the contents of this file
6
+ # for the most part.
7
+ module View
8
+ # The current stylename that is used to look up properties in the stylesheet.
9
+ attr_reader :stylename
10
+
11
+ # The current stylesheet will be looked at when properties are needed.
12
+ attr_reader :stylesheet
13
+
14
+ # Alter the stylename of this view.
15
+ #
16
+ # This will cause new styles to be applied from the stylesheet.
17
+ #
18
+ # @param Symbol stylename
19
+ def stylename=(stylename)
20
+ @stylename = stylename
21
+ style(stylesheet.query(stylename)) if stylesheet
22
+ end
23
+
24
+ # Alter the stylesheet of this view.
25
+ #
26
+ # This will cause new styles to be applied using the current stylename,
27
+ # and will recurse into subviews.
28
+ #
29
+ # If you would prefer that a given UIView object does not inherit the
30
+ # stylesheet from its parents, override the 'stylesheet' method to
31
+ # return the correct value at all times.
32
+ #
33
+ # @param Teacup::Stylesheet stylesheet.
34
+ def stylesheet=(stylesheet)
35
+ @stylesheet = stylesheet
36
+ style(stylesheet.query(stylename)) if stylename && stylesheet
37
+ subviews.each{ |subview| subview.stylesheet = stylesheet }
38
+ end
39
+
40
+ # Animate a change to a new stylename.
41
+ #
42
+ # This is equivalent to wrapping a call to .stylename= inside
43
+ # UIView.beginAnimations.
44
+ #
45
+ # @param Symbol the new stylename
46
+ # @param Options the options for the animation (may include the
47
+ # duration and the curve)
48
+ #
49
+ def animate_to_stylename(stylename, options={})
50
+ return if self.stylename == stylename
51
+ UIView.beginAnimations(nil, context: nil)
52
+ # TODO: This should be in a style-sheet!
53
+ UIView.setAnimationDuration(options[:duration]) if options[:duration]
54
+ UIView.setAnimationCurve(options[:curve]) if options[:curve]
55
+ self.stylename = stylename
56
+ UIView.commitAnimations
57
+ end
58
+
59
+ # Apply style properties to this element.
60
+ #
61
+ # Takes a hash of properties such as may have been read from a stylesheet
62
+ # or passed as parameters to {Teacup::Layout#layout}, and applies them to
63
+ # the element.
64
+ #
65
+ # Does a little bit of magic (that may be split out as 'sugarcube') to
66
+ # make properties work as you'd expect.
67
+ #
68
+ # If you try and assign something in properties that is not supported,
69
+ # a warning message will be emitted.
70
+ #
71
+ # @param Hash the properties to set.
72
+ def style(properties)
73
+ clean_properties! properties
74
+
75
+ properties.each do |key, value|
76
+ if key == :title && UIButton === self
77
+ setTitle(value, forState: UIControlStateNormal)
78
+ elsif respond_to?(:"#{key}=")
79
+ send(:"#{key}=", value)
80
+ elsif layer.respond_to?(:"#{key}=")
81
+ layer.send(:"#{key}=", value)
82
+ elsif key == :keyboardType
83
+ setKeyboardType(value)
84
+ else
85
+ $stderr.puts "Teacup WARN: Can't apply #{key} to #{inspect}"
86
+ end
87
+ end
88
+
89
+ #OUCH! Figure out why this is needed
90
+ if rand > 1
91
+ setCornerRadius(1.0)
92
+ setFrame([[0,0],[0,0]])
93
+ setTransform(nil)
94
+ setMasksToBounds(0)
95
+ setShadowOffset(0)
96
+ end
97
+ end
98
+
99
+ # merge definitions for 'frame' into one.
100
+ #
101
+ # To support 'extends' more nicely it's convenient to split left, top, width
102
+ # and height out of frame. Unfortunately that means we have to write ugly
103
+ # code like this to reassemble them into what the user actually meant.
104
+ #
105
+ # WARNING: this method *mutates* its parameter.
106
+ #
107
+ # @param Hash
108
+ # @return Hash
109
+ def clean_properties!(properties)
110
+ return unless [:frame, :left, :top, :width, :height].any?(&properties.method(:key?))
111
+
112
+ frame = properties.delete(:frame) || self.frame
113
+
114
+ frame[0][0] = properties.delete(:left) || frame[0][0]
115
+ frame[0][1] = properties.delete(:top) || frame[0][1]
116
+ frame[1][0] = properties.delete(:width) || frame[1][0]
117
+ frame[1][1] = properties.delete(:height) || frame[1][1]
118
+
119
+ properties[:frame] = frame
120
+ properties
121
+ end
122
+ end
123
+ end
Binary file
Binary file
@@ -0,0 +1,45 @@
1
+ # Railsifiying mobile development
2
+
3
+ ** This is a rough idea, which may have lots of holes, please comment on it, post issues, or fork. **
4
+
5
+
6
+ It's time we take control of iOS app development, because it's clunky,
7
+ slow, not obvious and because we love Ruby & Rails.
8
+
9
+ We can't have "just" styling, it's not a full solution we need a framework now
10
+ more then ever.
11
+
12
+ With that said, I believe Rails provides a solid MVC. It can help hide or augment
13
+ may of the typical problems with iOS development.
14
+
15
+
16
+ Directory structure:
17
+
18
+ .teacup
19
+ |____app
20
+ | |____config
21
+ | | |____application.rb
22
+ | | |____boot.rb
23
+ | | |____initializers
24
+ | | | |____twitter.rb
25
+ | |____controllers
26
+ | | |____events_controller.rb
27
+ | | |____venues_controller.rb
28
+ | |____db
29
+ | | |____migrations
30
+ | | | |____20120514201043_create_events.rb
31
+ | | | |____20120514201044_add_price_to_events.rb
32
+ | | | |____20120514201045_create_venues.rb
33
+ | | |____README.md
34
+ | | |____schema.rb
35
+ | |____models
36
+ | | |____event.rb
37
+ | | |____venue.rb
38
+ | |____views
39
+ | | |____events
40
+ | | | |____edit.ipad.rb
41
+ | | | |____edit.iphone.rb
42
+ | | | |____show.ipad.rb
43
+ | | | |____show.iphone.rb
44
+ | | |____venues
45
+ |____README.md
@@ -0,0 +1 @@
1
+ # Idea: framework runtime, starts to load the application, load initializers, run migrations, etc
@@ -0,0 +1 @@
1
+ # Idea: framework booting before application (if it's needed at all)
@@ -0,0 +1,7 @@
1
+ # 0. applicationDidFinishLaunching
2
+ # 1. boot
3
+ # 2. application
4
+ # 3. initializers
5
+ # 4. twitter configuration values loaded
6
+
7
+ Twitter.setup("XXX", "YYY")
@@ -0,0 +1,28 @@
1
+ class EventsController < Controller
2
+
3
+ # Idea: Need to think about this more, but in lue of generators, we could have
4
+ # over-rideable defaults based on a responds_to/actions
5
+ # Idea: This would also configure what actions a controller can respond to / route to (do we need routes?)
6
+ responds_to :only => [:new, :create, :edit, :update, :destroy, :index, :show] # default options
7
+
8
+ def show(id)
9
+ @event = Event.find(id)
10
+ render :show
11
+ end
12
+
13
+ def edit(id)
14
+ @event = Event.find(id)
15
+ end
16
+
17
+ def update(id)
18
+ # Note: How would this conflict with the previously set @event, should something be passed? augment params?
19
+ @event = Event.find(id)
20
+ @event.update_attributes(
21
+ # 1. data from the current/old view?
22
+ # 2. autosave was false
23
+ :price => self.view.price
24
+ )
25
+ # render :edit # Idea: (self.view is loaded)
26
+ end
27
+
28
+ end
@@ -0,0 +1,4 @@
1
+ class VenuesController < Controller
2
+ # Idea: We may want to force some kind of nesting in controller
3
+ belongs_to :event
4
+ end
@@ -0,0 +1,16 @@
1
+ # DB
2
+
3
+ ## CoreDataAdapter
4
+
5
+ Rationale: CD is widely used in iOS projects and may be useful for simpler
6
+ integration with UI.
7
+
8
+ ## SqlLiteAdapter
9
+
10
+ This would be responsible for handling CoreData+Sqlite backend.
11
+
12
+ ## RemoteAdapter
13
+
14
+ This would be responsible for handling remote data sources (initially just JSON).
15
+
16
+ * A RemoteAdapter would be migration-less, APIs would need to be backwards compatible or be versioned, see endpoint in `models/event.rb`
@@ -0,0 +1,9 @@
1
+ class CreateEvents < Migration
2
+ def change
3
+ create_table(:events) do |t|
4
+ t.string :name
5
+ t.references :venue
6
+ t.timestamps
7
+ end
8
+ end
9
+ end