teacup 0.0.1.pre → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +5 -3
  2. data/CHANGES.md +26 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE +26 -0
  6. data/README.md +383 -95
  7. data/Rakefile +8 -36
  8. data/app/app_delegate.rb +14 -0
  9. data/app/controllers/first_controller.rb +71 -0
  10. data/app/controllers/landscape_only_controller.rb +16 -0
  11. data/app/controllers/tableview_controller.rb +0 -0
  12. data/app/styles/main_styles.rb +111 -0
  13. data/app/views/custom_view.rb +4 -0
  14. data/lib/dummy.rb +56 -0
  15. data/lib/teacup/handler.rb +99 -0
  16. data/lib/teacup/layout.rb +22 -74
  17. data/lib/teacup/merge_defaults.rb +45 -0
  18. data/lib/teacup/style.rb +93 -0
  19. data/lib/teacup/stylesheet.rb +242 -0
  20. data/lib/teacup/stylesheet_extensions/rotation.rb +38 -0
  21. data/lib/teacup/version.rb +1 -1
  22. data/lib/teacup/z_core_extensions/ca_layer.rb +6 -0
  23. data/lib/teacup/z_core_extensions/ui_view.rb +119 -0
  24. data/lib/teacup/z_core_extensions/ui_view_controller.rb +179 -0
  25. data/lib/teacup/z_core_extensions/ui_view_getters.rb +34 -0
  26. data/lib/teacup/z_core_extensions/z_handlers.rb +57 -0
  27. data/lib/teacup.rb +16 -33
  28. data/samples/Hai/Rakefile +7 -2
  29. data/samples/Hai/app/app_delegate.rb +2 -2
  30. data/samples/Hai/app/hai_controller.rb +8 -4
  31. data/samples/Hai/styles/iphone.rb +40 -0
  32. data/spec/main_spec.rb +226 -0
  33. data/spec/style_spec.rb +171 -0
  34. data/spec/stylesheet_spec.rb +348 -0
  35. data/spec/view_spec.rb +103 -0
  36. data/teacup.gemspec +13 -13
  37. metadata +47 -46
  38. data/.rspec +0 -2
  39. data/lib/teacup/contributors.rb +0 -7
  40. data/lib/teacup/core_extensions/ui_view.rb +0 -4
  41. data/lib/teacup/core_extensions/ui_view_controller.rb +0 -62
  42. data/lib/teacup/style_sheet.rb +0 -195
  43. data/lib/teacup/view.rb +0 -123
  44. data/pkg/teacup-0.0.0.gem +0 -0
  45. data/pkg/teacup-0.0.1.gem +0 -0
  46. data/proposals/other/README.md +0 -45
  47. data/proposals/other/app/config/application.rb +0 -1
  48. data/proposals/other/app/config/boot.rb +0 -1
  49. data/proposals/other/app/config/initializers/twitter.rb +0 -7
  50. data/proposals/other/app/controllers/events_controller.rb +0 -28
  51. data/proposals/other/app/controllers/venues_controller.rb +0 -4
  52. data/proposals/other/app/db/README.md +0 -16
  53. data/proposals/other/app/db/migrations/20120514201043_create_events.rb +0 -9
  54. data/proposals/other/app/db/migrations/20120514201044_add_price_to_events.rb +0 -5
  55. data/proposals/other/app/db/migrations/20120514201045_create_venues.rb +0 -8
  56. data/proposals/other/app/db/schema.rb +0 -19
  57. data/proposals/other/app/models/event.rb +0 -14
  58. data/proposals/other/app/models/venue.rb +0 -3
  59. data/proposals/other/app/views/events/edit.ipad.rb +0 -8
  60. data/proposals/other/app/views/events/edit.iphone.rb +0 -7
  61. data/proposals/other/app/views/events/show.ipad.rb +0 -2
  62. data/proposals/other/app/views/events/show.iphone.rb +0 -3
  63. data/samples/Hai/styles/ipad.rb +0 -11
  64. data/samples/Hai/styles/ipad_vertical.rb +0 -3
  65. data/spec/spec_helper.rb +0 -5
  66. data/spec/teacup/contributions_spec.rb +0 -13
  67. data/spec/teacup/version_spec.rb +0 -9
data/lib/teacup.rb CHANGED
@@ -1,41 +1,24 @@
1
1
  unless defined?(Motion::Project::Config)
2
- raise "This file must be required within a RubyMotion project Rakefile."
2
+ raise "The teacup gem must be required within a RubyMotion project Rakefile."
3
3
  end
4
4
 
5
- # In order that we can prepend our files to the load path after the user has
6
- # configured their app.files, we need to run code *after* the user has set up
7
- # their app.
8
- #
9
- # Unfortunately, the canonical place to require rubygems is at the top of the
10
- # file (while we could just add to the instructions "please `require 'teacup'`
11
- # at the bottom, that would be odd, and no-one would read the instructions).
12
- #
13
- # To this end, we tweak the App setup function so that whenever setup is called,
14
- # we configure teacup after that.
15
- #
16
- # This is not great though, as other gems may (following the instructions at
17
- # http://reality.hk/posts/2012/05/22/create-gems-for-rubymotion/) also call
18
- # setup...
19
- #
20
- # For sanity reasons, we therefore delete teacup requires from the load order
21
- # and re-add them to the front every single time {setup} is called.
22
- #
23
- # TODO: We should send a patch to rubymotion that adds first-class support for
24
- # app.gems. These could then be required *after* the user has finished running
25
- # the setup block, so that they can just run setup properly.
26
- #
27
- class << Motion::Project::App
28
5
 
29
- def setup_with_teacup
30
- setup_without_teacup do |app|
31
- yield app
32
-
33
- dirs = %w(teacup teacup/core_extensions)
34
- files = dirs.map{ |dir| Dir.glob("#{File.dirname(__FILE__)}/#{dir}/*.rb") }.flatten
35
- app.files = files + (app.files - files)
6
+ Motion::Project::App.setup do |app|
7
+ # scans app.files until it finds app/ (the default)
8
+ # if found, it inserts just before those files, otherwise it will insert to
9
+ # the end of the list
10
+ insert_point = 0
11
+ app.files.each_index do |index|
12
+ file = app.files[index]
13
+ if file =~ /^(?:\.\/)?app\//
14
+ # found app/, so stop looking
15
+ break
36
16
  end
17
+ insert_point = index + 1
37
18
  end
38
19
 
39
- alias setup_without_teacup setup
40
- alias setup setup_with_teacup
20
+ app.files.insert(insert_point, File.join(File.dirname(__FILE__), 'dummy.rb'))
21
+ Dir.glob(File.join(File.dirname(__FILE__), 'teacup/**/*.rb')).reverse.each do |file|
22
+ app.files.insert(insert_point, file)
23
+ end
41
24
  end
data/samples/Hai/Rakefile CHANGED
@@ -4,9 +4,14 @@ require 'motion/project'
4
4
  Motion::Project::App.setup do |app|
5
5
  app.name = 'Hai'
6
6
 
7
- app.device_family = :ipad
7
+ app.device_family = :iphone
8
8
 
9
- app.files += Dir.glob(File.join(app.project_dir, 'teacup/**/*.rb'))
9
+ app.files += Dir.glob(File.join(app.project_dir, '../../lib/**/*.rb'))
10
10
  app.files += Dir.glob(File.join(app.project_dir, 'styles/**/*.rb'))
11
11
  app.files += Dir.glob(File.join(app.project_dir, 'app/**/*.rb'))
12
+ app.files_dependencies './../../lib/teacup/layout.rb' => './../../lib/teacup/stylesheet.rb'
13
+ app.files_dependencies './../../lib/teacup/z_core_extensions/ui_view_controller.rb' => './../../lib/teacup/layout.rb'
14
+ app.files_dependencies './../../lib/teacup/z_core_extensions/ui_view.rb' => './../../lib/teacup/z_core_extensions/ui_view_controller.rb'
15
+ app.files_dependencies 'app/app_delegate.rb' => './../../lib/teacup/z_core_extensions/ui_view.rb'
16
+ app.files_dependencies 'app/hai_controller.rb' => './../../lib/teacup/z_core_extensions/ui_view.rb'
12
17
  end
@@ -1,9 +1,9 @@
1
1
  class AppDelegate
2
2
  def application(application, didFinishLaunchingWithOptions:launchOptions)
3
3
  @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
4
- @window.rootViewController = HaiViewController.alloc.init.tap{ |c| c.wantsFullScreenLayout = true }
4
+ @window.rootViewController = HaiViewController.alloc.init
5
5
  @window.makeKeyAndVisible
6
-
6
+
7
7
  true
8
8
  end
9
9
  end
@@ -1,9 +1,13 @@
1
1
  class HaiViewController < UIViewController
2
+ stylesheet :iphone
2
3
 
3
- def viewDidLoad
4
- view.addSubview(Teacup.style(:label, UILabel.new))
5
- self.setFrame([[0,0],[0,0]]) if rand > 1
6
- true
4
+ layout :hai do
5
+ subview(UILabel, :label)
6
+ subview(UILabel, :footer)
7
+ end
8
+
9
+ def shouldAutorotateToInterfaceOrientation(orientation)
10
+ autorotateToOrientation(orientation)
7
11
  end
8
12
 
9
13
  end
@@ -0,0 +1,40 @@
1
+
2
+ Teacup::Stylesheet.new(:iphone) do
3
+ label_color = UIColor.blueColor
4
+
5
+ style :hai,
6
+ landscape: true
7
+
8
+ style UILabel,
9
+ textColor: label_color
10
+
11
+ style :label,
12
+ text: 'Hai!',
13
+ backgroundColor: UIColor.whiteColor,
14
+ top: 10,
15
+ left: 100,
16
+ width: 50,
17
+ height: 20,
18
+ layer: {
19
+ transform: identity,
20
+ },
21
+ landscape_left: {
22
+ layer: {
23
+ transform: rotate(identity, pi/6, 0.3, 0.3, 0.3)
24
+ },
25
+ width: 75
26
+ }
27
+
28
+ style :footer,
29
+ text: 'brought to you by teacup',
30
+ backgroundColor: UIColor.lightGrayColor,
31
+ top: 440,
32
+ left: 120,
33
+ width: 200,
34
+ height: 20,
35
+ landscape: {
36
+ left: 280,
37
+ top: 280
38
+ }
39
+
40
+ end
data/spec/main_spec.rb ADDED
@@ -0,0 +1,226 @@
1
+ describe "Application 'Teacup'" do
2
+ before do
3
+ UIDevice.currentDevice.beginGeneratingDeviceOrientationNotifications
4
+ @app = UIApplication.sharedApplication
5
+ @view_ctrlr = @app.windows[0].rootViewController
6
+ end
7
+
8
+ after do
9
+ UIDevice.currentDevice.endGeneratingDeviceOrientationNotifications
10
+ end
11
+
12
+ it "has one window" do
13
+ @app.windows.size.should == 1
14
+ end
15
+
16
+ it "should have a root view" do
17
+ view_ctrlr_view = @view_ctrlr.view
18
+ view_ctrlr_view.subviews.length.should == 1
19
+ view_ctrlr_view.subviews[0].class.should == CustomView
20
+ end
21
+
22
+ describe "view controller" do
23
+
24
+ it "should be able to rotate" do
25
+ @view_ctrlr.shouldAutorotateToInterfaceOrientation(UIDeviceOrientationPortrait).should == true
26
+ @view_ctrlr.shouldAutorotateToInterfaceOrientation(UIDeviceOrientationLandscapeLeft).should == true
27
+ @view_ctrlr.shouldAutorotateToInterfaceOrientation(UIDeviceOrientationLandscapeRight).should == true
28
+ @view_ctrlr.shouldAutorotateToInterfaceOrientation(UIDeviceOrientationPortraitUpsideDown).should == false
29
+ end
30
+ end
31
+
32
+ describe "root view" do
33
+
34
+ before do
35
+ @root_view = @app.windows[0].subviews[0]
36
+ end
37
+
38
+ it "root view should be styled as 'root'" do
39
+ @root_view.stylename.should == :root
40
+ @root_view.frame.origin.x.should == 0
41
+ @root_view.frame.origin.y.should == 0
42
+ @root_view.frame.size.width.should == 320
43
+ @root_view.frame.size.height.should == 480
44
+ @root_view.backgroundColor.should == UIColor.yellowColor
45
+ end
46
+
47
+ end
48
+
49
+ describe "background view" do
50
+
51
+ before do
52
+ @background = @app.windows[0].subviews[0].subviews[0]
53
+ end
54
+
55
+ it "should be styled as :background" do
56
+ @background.stylename.should == :background
57
+ @background.frame.origin.x.should == 10
58
+ @background.frame.origin.y.should == 30
59
+ @background.frame.size.width.should == 300
60
+ @background.frame.size.height.should == 440
61
+ @background.backgroundColor.should == UIColor.darkGrayColor
62
+ end
63
+
64
+ it "background view should have subviews" do
65
+ @background.subviews.length.should == 3
66
+ @background.subviews[0].class.should == UILabel
67
+ @background.subviews[1].class.should == UILabel
68
+ @background.subviews[2].class.ancestors.include?(UIButton).should == true
69
+ end
70
+
71
+ it "should not have styles overridden by base classes" do
72
+ @background.alpha.should == 0.5
73
+ end
74
+
75
+ it "should have styles overridden orientation styles" do
76
+ @background.backgroundColor.should == UIColor.darkGrayColor
77
+ end
78
+
79
+ describe "background subviews" do
80
+
81
+ before do
82
+ @welcome = @background.subviews[0]
83
+ @footer = @background.subviews[1]
84
+ @button = @background.subviews[2]
85
+ end
86
+
87
+ it "should have all UILabels with color blue" do
88
+ @welcome.textColor.should == UIColor.blueColor
89
+ @footer.textColor.should == UIColor.blueColor
90
+ end
91
+
92
+ describe "welcome" do
93
+ it "should be styled as :welcome" do
94
+ @welcome.stylename.should == :welcome
95
+ @welcome.frame.origin.x.should == 10
96
+ @welcome.frame.origin.y.should == 40
97
+ @welcome.frame.size.width.should == 280
98
+ @welcome.frame.size.height.should == 20
99
+ @welcome.text.should == "Welcome to teacup"
100
+ end
101
+ end
102
+
103
+ describe "footer" do
104
+ it "should be styled as :footer" do
105
+ @footer.stylename.should == :footer
106
+ @footer.frame.origin.x.should == 10
107
+ @footer.frame.origin.y.should == 410
108
+ @footer.frame.size.width.should == 280
109
+ @footer.frame.size.height.should == 20
110
+ @footer.text.should == "This is a teacup example"
111
+ end
112
+ end
113
+
114
+ describe "button" do
115
+ it "should be styled as :next_message" do
116
+ @button.stylename.should == :next_message
117
+ @button.frame.origin.x.should == 150
118
+ @button.frame.origin.y.should == 370
119
+ @button.frame.size.width.should == 130
120
+ @button.frame.size.height.should == 20
121
+ @button.titleForState(UIControlStateNormal).should == "Next Message..."
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
129
+ describe "background view in landscape" do
130
+
131
+ before do
132
+ @background = @app.windows[0].subviews[0].subviews[0]
133
+ @view_ctrlr.landscape_only
134
+ UIApplication.sharedApplication.setStatusBarOrientation(UIDeviceOrientationLandscapeLeft, animated:false)
135
+ end
136
+
137
+ it "should be in landscape" do
138
+ # the rest of these tests *pass*, but the device orientation isn't actually
139
+ # updated to be landscape yet... :-/
140
+ UIDevice.currentDevice.orientation.should > 0
141
+ end
142
+
143
+ it "should be styled as :background - landscape" do
144
+ @background.stylename.should == :background
145
+ @background.frame.origin.x.should == 10
146
+ @background.frame.origin.y.should == 30
147
+ @background.frame.size.width.should == 460
148
+ @background.frame.size.height.should == 280
149
+ @background.backgroundColor.should == UIColor.lightGrayColor
150
+ end
151
+
152
+ it "should not have styles overridden by base classes" do
153
+ @background.alpha.should == 0.8
154
+ end
155
+
156
+ it "should have styles overridden orientation styles" do
157
+ @background.backgroundColor.should == UIColor.lightGrayColor
158
+ end
159
+
160
+ describe "background subviews in landscape" do
161
+
162
+ before do
163
+ @welcome = @background.subviews[0]
164
+ @footer = @background.subviews[1]
165
+ @button = @background.subviews[2]
166
+ end
167
+
168
+ describe "welcome" do
169
+ it "should be styled as :welcome - landscape" do
170
+ @welcome.stylename.should == :welcome
171
+ @welcome.frame.origin.x.should == 90
172
+ @welcome.frame.origin.y.should == 40
173
+ @welcome.frame.size.width.should == 280
174
+ @welcome.frame.size.height.should == 20
175
+ @welcome.text.should == "Welcome to teacup"
176
+ end
177
+ end
178
+
179
+ describe "footer" do
180
+ it "should be styled as :footer - landscape" do
181
+ @footer.stylename.should == :footer
182
+ @footer.frame.origin.x.should == 90
183
+ @footer.frame.origin.y.should == 250
184
+ @footer.frame.size.width.should == 280
185
+ @footer.frame.size.height.should == 20
186
+ @footer.text.should == "This is a teacup example"
187
+ end
188
+ end
189
+
190
+ describe "button" do
191
+ it "should be styled as :next_message - landscape" do
192
+ @button.stylename.should == :next_message
193
+ @button.frame.origin.x.should == 20
194
+ @button.frame.origin.y.should == 200
195
+ @button.frame.size.width.should == 130
196
+ @button.frame.size.height.should == 20
197
+ @button.titleForState(UIControlStateNormal).should == "Next Message..."
198
+ end
199
+ end
200
+
201
+ end
202
+
203
+ end
204
+
205
+ end
206
+
207
+ describe "Stylesheet 'first'" do
208
+ before do
209
+ @stylesheet = Teacup::Stylesheet[:first]
210
+ end
211
+
212
+ it "should exist" do
213
+ @stylesheet.nil?.should == false
214
+ end
215
+
216
+ it "should define some styles" do
217
+ @stylesheet.query(:footer)[:text].nil?.should == false
218
+ @stylesheet.query(:footer)[:text].should == "This is a teacup example"
219
+ end
220
+
221
+ it "should union the next_message styles" do
222
+ @stylesheet.query(:next_message)[:title].should == "Next Message..."
223
+ (@stylesheet.query(:next_message)[:portrait] ? true : false).should == false
224
+ end
225
+
226
+ end
@@ -0,0 +1,171 @@
1
+ describe "Teacup::Style" do
2
+
3
+ it "should be equal to a Hash" do
4
+ Teacup::Style.new.should == {}
5
+ end
6
+
7
+ it "should act like a Hash" do
8
+ style = Teacup::Style.new
9
+ style[:key] = :value
10
+ style[:key].should == :value
11
+ end
12
+
13
+ it "should use orientation to override" do
14
+ style = Teacup::Style.new
15
+ style[:name] = :default
16
+ style[:landscape] = {
17
+ name: :landscape_name,
18
+ }
19
+ style[:portrait] = {
20
+ name: :portrait_name,
21
+ }
22
+ style[:upside_down] = {
23
+ name: :upside_down_name,
24
+ }
25
+ # no orientation, which ends up being portrait anyway.
26
+ style.build()[:name].should == :portrait_name
27
+ # landscape
28
+ style.build(nil, UIInterfaceOrientationLandscapeLeft)[:name].should == :landscape_name
29
+
30
+ # upside down
31
+ style.build(nil, UIInterfaceOrientationPortraitUpsideDown)[:name].should == :upside_down_name
32
+ end
33
+
34
+ it "should look in extends" do
35
+ sheet = Teacup::Stylesheet.new do
36
+ style :extended,
37
+ key: :value
38
+ end
39
+ sheet.query(:extended)[:key].should == :value
40
+
41
+ style = Teacup::Style.new
42
+ style.stylename = :self
43
+ style.stylesheet = sheet
44
+ style[:extends] = :extended
45
+ style.build()[:key].should == :value
46
+ end
47
+
48
+ it "should look in import" do
49
+ imported_sheet = Teacup::Stylesheet.new do
50
+ style :extended,
51
+ imported: :imported,
52
+ override: :overridden
53
+ end
54
+
55
+ sheet = Teacup::Stylesheet.new do
56
+ import imported_sheet
57
+
58
+ style :extended,
59
+ key: :value,
60
+ override: :override
61
+ end
62
+ sheet.query(:extended)[:key].should == :value
63
+ sheet.query(:extended)[:imported].should == :imported
64
+ sheet.query(:extended)[:override].should == :override
65
+ end
66
+
67
+ it 'should merge :extends properties' do
68
+ sheet = Teacup::Stylesheet.new do
69
+ style :style1,
70
+ a: 'a',
71
+ b: 'b'
72
+ end
73
+ style2 = Teacup::Style.new
74
+ style2.stylesheet = sheet
75
+ style2[:a] = 'A'
76
+ style2[:extends] = :style1
77
+
78
+ built = style2.build()
79
+ built[:a].should == 'A'
80
+ built[:b].should == 'b'
81
+ end
82
+
83
+ it 'should merge hashes' do
84
+ sheet = Teacup::Stylesheet.new do
85
+ style :style1,
86
+ layer: { borderWidth: 20, opacity: 0.5 }
87
+ end
88
+
89
+ style2 = Teacup::Style.new
90
+ style2.stylesheet = sheet
91
+ style2[:layer] = { borderWidth: 10 }
92
+ style2[:extends] = :style1
93
+
94
+ built = style2.build()
95
+ built[:layer][:opacity].should == 0.5
96
+ built[:layer][:borderWidth].should == 10
97
+ end
98
+
99
+ it 'should merge multiple extends' do
100
+ sheet = Teacup::Stylesheet.new do
101
+ style :style1,
102
+ layer: { borderWidth: 20 }
103
+ style :style2,
104
+ layer: { borderWidth: 10, opacity: 0.5 }
105
+ end
106
+
107
+ style3 = Teacup::Style.new
108
+ style3.stylesheet = sheet
109
+ style3[:extends] = [:style1, :style2]
110
+
111
+ built = style3.build()
112
+ built[:layer][:opacity].should == 0.5
113
+ built[:layer][:borderWidth].should == 20
114
+ end
115
+
116
+ it 'should merge and flatten orientation rules' do
117
+ sheet = Teacup::Stylesheet.new do
118
+ style :style1,
119
+ portrait: { text: "ignored", tag: 1 }
120
+ end
121
+
122
+ style2 = Teacup::Style.new
123
+ style2.stylesheet = sheet
124
+ style2[:portrait] = { text: "text" }
125
+ style2[:extends] = :style1
126
+
127
+ built = style2.build(nil, UIInterfaceOrientationPortrait)
128
+ built[:tag].should == 1
129
+ built[:text].should == "text"
130
+ end
131
+
132
+ it 'should respect precedence rules' do
133
+ sheet = Teacup::Stylesheet.new do
134
+ style :style1,
135
+ portrait: { text: "extended", tag: 1 }
136
+ end
137
+
138
+ style2 = Teacup::Style.new
139
+ style2.stylesheet = sheet
140
+ style2[:text] = "text"
141
+ style2[:extends] = :style1
142
+
143
+ built = style2.build(nil, UIInterfaceOrientationPortrait)
144
+ built[:tag].should == 1
145
+ built[:text].should == "text"
146
+ end
147
+
148
+ it 'should respect merge based on class inheritance' do
149
+ class Foo
150
+ attr_accessor :bar
151
+ end
152
+
153
+ class Bar < Foo
154
+ attr_accessor :baz
155
+ end
156
+
157
+ sheet = Teacup::Stylesheet.new do
158
+ style Foo,
159
+ bar: 'bar'
160
+ end
161
+
162
+ style = Teacup::Style.new
163
+ style.stylesheet = sheet
164
+ style[:baz] = 'baz'
165
+
166
+ built = style.build(Bar.new)
167
+ built[:bar].should == 'bar'
168
+ built[:baz].should == 'baz'
169
+ end
170
+
171
+ end