teacup 0.0.1.pre

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