teacup 0.0.1.pre → 0.3.1

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 (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