teacup 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Dofile +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +26 -0
- data/README.md +151 -0
- data/Rakefile +39 -0
- data/lib/teacup.rb +41 -0
- data/lib/teacup/contributors.rb +7 -0
- data/lib/teacup/core_extensions/ui_view.rb +4 -0
- data/lib/teacup/core_extensions/ui_view_controller.rb +62 -0
- data/lib/teacup/layout.rb +207 -0
- data/lib/teacup/style_sheet.rb +195 -0
- data/lib/teacup/version.rb +5 -0
- data/lib/teacup/view.rb +123 -0
- data/pkg/teacup-0.0.0.gem +0 -0
- data/pkg/teacup-0.0.1.gem +0 -0
- data/proposals/other/README.md +45 -0
- data/proposals/other/app/config/application.rb +1 -0
- data/proposals/other/app/config/boot.rb +1 -0
- data/proposals/other/app/config/initializers/twitter.rb +7 -0
- data/proposals/other/app/controllers/events_controller.rb +28 -0
- data/proposals/other/app/controllers/venues_controller.rb +4 -0
- data/proposals/other/app/db/README.md +16 -0
- data/proposals/other/app/db/migrations/20120514201043_create_events.rb +9 -0
- data/proposals/other/app/db/migrations/20120514201044_add_price_to_events.rb +5 -0
- data/proposals/other/app/db/migrations/20120514201045_create_venues.rb +8 -0
- data/proposals/other/app/db/schema.rb +19 -0
- data/proposals/other/app/models/event.rb +14 -0
- data/proposals/other/app/models/venue.rb +3 -0
- data/proposals/other/app/views/events/edit.ipad.rb +8 -0
- data/proposals/other/app/views/events/edit.iphone.rb +7 -0
- data/proposals/other/app/views/events/show.ipad.rb +2 -0
- data/proposals/other/app/views/events/show.iphone.rb +3 -0
- data/samples/Hai/.gitignore +5 -0
- data/samples/Hai/Rakefile +12 -0
- data/samples/Hai/app/app_delegate.rb +9 -0
- data/samples/Hai/app/hai_controller.rb +9 -0
- data/samples/Hai/spec/main_spec.rb +9 -0
- data/samples/Hai/styles/ipad.rb +11 -0
- data/samples/Hai/styles/ipad_vertical.rb +3 -0
- data/samples/README.md +4 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/teacup/contributions_spec.rb +13 -0
- data/spec/teacup/version_spec.rb +9 -0
- data/teacup.gemspec +27 -0
- 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
|
data/lib/teacup/view.rb
ADDED
@@ -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,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,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`
|