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.
- data/.gitignore +5 -3
- data/CHANGES.md +26 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +26 -0
- data/README.md +383 -95
- data/Rakefile +8 -36
- data/app/app_delegate.rb +14 -0
- data/app/controllers/first_controller.rb +71 -0
- data/app/controllers/landscape_only_controller.rb +16 -0
- data/app/controllers/tableview_controller.rb +0 -0
- data/app/styles/main_styles.rb +111 -0
- data/app/views/custom_view.rb +4 -0
- data/lib/dummy.rb +56 -0
- data/lib/teacup/handler.rb +99 -0
- data/lib/teacup/layout.rb +22 -74
- data/lib/teacup/merge_defaults.rb +45 -0
- data/lib/teacup/style.rb +93 -0
- data/lib/teacup/stylesheet.rb +242 -0
- data/lib/teacup/stylesheet_extensions/rotation.rb +38 -0
- data/lib/teacup/version.rb +1 -1
- data/lib/teacup/z_core_extensions/ca_layer.rb +6 -0
- data/lib/teacup/z_core_extensions/ui_view.rb +119 -0
- data/lib/teacup/z_core_extensions/ui_view_controller.rb +179 -0
- data/lib/teacup/z_core_extensions/ui_view_getters.rb +34 -0
- data/lib/teacup/z_core_extensions/z_handlers.rb +57 -0
- data/lib/teacup.rb +16 -33
- data/samples/Hai/Rakefile +7 -2
- data/samples/Hai/app/app_delegate.rb +2 -2
- data/samples/Hai/app/hai_controller.rb +8 -4
- data/samples/Hai/styles/iphone.rb +40 -0
- data/spec/main_spec.rb +226 -0
- data/spec/style_spec.rb +171 -0
- data/spec/stylesheet_spec.rb +348 -0
- data/spec/view_spec.rb +103 -0
- data/teacup.gemspec +13 -13
- metadata +47 -46
- data/.rspec +0 -2
- data/lib/teacup/contributors.rb +0 -7
- data/lib/teacup/core_extensions/ui_view.rb +0 -4
- data/lib/teacup/core_extensions/ui_view_controller.rb +0 -62
- data/lib/teacup/style_sheet.rb +0 -195
- data/lib/teacup/view.rb +0 -123
- data/pkg/teacup-0.0.0.gem +0 -0
- data/pkg/teacup-0.0.1.gem +0 -0
- data/proposals/other/README.md +0 -45
- data/proposals/other/app/config/application.rb +0 -1
- data/proposals/other/app/config/boot.rb +0 -1
- data/proposals/other/app/config/initializers/twitter.rb +0 -7
- data/proposals/other/app/controllers/events_controller.rb +0 -28
- data/proposals/other/app/controllers/venues_controller.rb +0 -4
- data/proposals/other/app/db/README.md +0 -16
- data/proposals/other/app/db/migrations/20120514201043_create_events.rb +0 -9
- data/proposals/other/app/db/migrations/20120514201044_add_price_to_events.rb +0 -5
- data/proposals/other/app/db/migrations/20120514201045_create_venues.rb +0 -8
- data/proposals/other/app/db/schema.rb +0 -19
- data/proposals/other/app/models/event.rb +0 -14
- data/proposals/other/app/models/venue.rb +0 -3
- data/proposals/other/app/views/events/edit.ipad.rb +0 -8
- data/proposals/other/app/views/events/edit.iphone.rb +0 -7
- data/proposals/other/app/views/events/show.ipad.rb +0 -2
- data/proposals/other/app/views/events/show.iphone.rb +0 -3
- data/samples/Hai/styles/ipad.rb +0 -11
- data/samples/Hai/styles/ipad_vertical.rb +0 -3
- data/spec/spec_helper.rb +0 -5
- data/spec/teacup/contributions_spec.rb +0 -13
- data/spec/teacup/version_spec.rb +0 -9
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
class FirstController < UIViewController
|
3
|
+
|
4
|
+
stylesheet :first
|
5
|
+
|
6
|
+
layout :root do
|
7
|
+
subview(CustomView, :background) do
|
8
|
+
@welcome = subview(UILabel, :welcome)
|
9
|
+
subview(UILabel, :footer)
|
10
|
+
@button = subview(UIButton.buttonWithType(UIButtonTypeRoundedRect), :next_message)
|
11
|
+
end
|
12
|
+
|
13
|
+
@button.addTarget(self, action: :next_message, forControlEvents:UIControlEventTouchUpInside)
|
14
|
+
end
|
15
|
+
|
16
|
+
# used in testing
|
17
|
+
def landscape_only
|
18
|
+
UIApplication.sharedApplication.windows[0].rootViewController = LandscapeOnlyController.alloc.init
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_view
|
22
|
+
landscape_only
|
23
|
+
end
|
24
|
+
|
25
|
+
def next_message
|
26
|
+
msg = messages.shift
|
27
|
+
if msg
|
28
|
+
@welcome.text = msg
|
29
|
+
else
|
30
|
+
@welcome.text = 'Next example...'
|
31
|
+
|
32
|
+
@button.removeTarget(self, action: :next_view, forControlEvents:UIControlEventTouchUpInside)
|
33
|
+
@button.addTarget(self, action: :next_view, forControlEvents:UIControlEventTouchUpInside)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def messages
|
38
|
+
@messages ||= [
|
39
|
+
'This is teacup',
|
40
|
+
'Welcome',
|
41
|
+
'This is teacup',
|
42
|
+
'Welcome to teacup',
|
43
|
+
'You can do anything at teacup',
|
44
|
+
'Anything at all',
|
45
|
+
'The only limit is yourself ',
|
46
|
+
'Welcome to teacup',
|
47
|
+
'Welcome to teacup',
|
48
|
+
'This is teacup',
|
49
|
+
'Welcome to teacup',
|
50
|
+
'This is teacup, Welcome',
|
51
|
+
'Yes, this is teacup',
|
52
|
+
'This is teacup and welcome to you who have come to teacup',
|
53
|
+
'Anything is possible at teacup',
|
54
|
+
'You can to anything teacup',
|
55
|
+
'The infinite is possible at teacup',
|
56
|
+
'The unattainable is unknown at teacup',
|
57
|
+
'Welcome to teacup',
|
58
|
+
'This is teacup',
|
59
|
+
'Welcome to teacup',
|
60
|
+
'Welcome',
|
61
|
+
'This is teacup',
|
62
|
+
'Welcome to teacup',
|
63
|
+
'Welcome to teacup',
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
def shouldAutorotateToInterfaceOrientation(orientation)
|
68
|
+
autorotateToOrientation(orientation)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
class LandscapeOnlyController < UIViewController
|
3
|
+
|
4
|
+
def viewDidLoad
|
5
|
+
UIApplication.sharedApplication.windows[0].rootViewController = FirstController.alloc.init
|
6
|
+
end
|
7
|
+
|
8
|
+
def shouldAutorotateToInterfaceOrientation(orientation)
|
9
|
+
if orientation == UIDeviceOrientationLandscapeLeft or orientation == UIDeviceOrientationLandscapeRight
|
10
|
+
true
|
11
|
+
else
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
File without changes
|
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
Teacup::Stylesheet.new(:first) do
|
3
|
+
|
4
|
+
# enable orientations on the root view
|
5
|
+
style :root,
|
6
|
+
left: 0,
|
7
|
+
top: 0,
|
8
|
+
width: 320,
|
9
|
+
height: 480,
|
10
|
+
backgroundColor: UIColor.yellowColor,
|
11
|
+
portrait: true,
|
12
|
+
upside_down: false,
|
13
|
+
|
14
|
+
layer: {
|
15
|
+
cornerRadius: 10.0,
|
16
|
+
},
|
17
|
+
|
18
|
+
landscape: {
|
19
|
+
backgroundColor: UIColor.redColor,
|
20
|
+
},
|
21
|
+
|
22
|
+
landscape_left: {
|
23
|
+
layer: {
|
24
|
+
transform: spin(identity, -pi / 2),
|
25
|
+
},
|
26
|
+
},
|
27
|
+
|
28
|
+
landscape_right: {
|
29
|
+
layer: {
|
30
|
+
transform: spin(identity, pi / 2),
|
31
|
+
},
|
32
|
+
}
|
33
|
+
|
34
|
+
style(UILabel, {
|
35
|
+
textColor: UIColor.blueColor,
|
36
|
+
})
|
37
|
+
|
38
|
+
style CustomView,
|
39
|
+
# for testing how styles override (this gets overridden by any property in
|
40
|
+
# a style block, regardless of whether it is in an orientation style)
|
41
|
+
portrait: {
|
42
|
+
alpha: 0.75
|
43
|
+
},
|
44
|
+
landscape: {
|
45
|
+
alpha: 0.75
|
46
|
+
}
|
47
|
+
|
48
|
+
style(:background, {
|
49
|
+
alpha: 0.5,
|
50
|
+
left: 10,
|
51
|
+
top: 30,
|
52
|
+
backgroundColor: UIColor.blackColor,
|
53
|
+
|
54
|
+
portrait: {
|
55
|
+
width: 300,
|
56
|
+
height: 440,
|
57
|
+
backgroundColor: UIColor.darkGrayColor,
|
58
|
+
},
|
59
|
+
|
60
|
+
landscape: {
|
61
|
+
width: 460,
|
62
|
+
height: 280,
|
63
|
+
alpha: 0.8,
|
64
|
+
backgroundColor: UIColor.lightGrayColor,
|
65
|
+
},
|
66
|
+
})
|
67
|
+
|
68
|
+
style(:welcome, {
|
69
|
+
left: 10,
|
70
|
+
top: 40,
|
71
|
+
width: 280,
|
72
|
+
height: 20,
|
73
|
+
text: "Welcome to teacup",
|
74
|
+
landscape: {
|
75
|
+
left: 90,
|
76
|
+
},
|
77
|
+
})
|
78
|
+
|
79
|
+
style(:footer, {
|
80
|
+
left: 10,
|
81
|
+
top: 410,
|
82
|
+
width: 280,
|
83
|
+
height: 20,
|
84
|
+
text: "This is a teacup example",
|
85
|
+
landscape: {
|
86
|
+
top: 250,
|
87
|
+
left: 90,
|
88
|
+
},
|
89
|
+
})
|
90
|
+
|
91
|
+
|
92
|
+
style :next_message,
|
93
|
+
width: 130,
|
94
|
+
height: 20,
|
95
|
+
portrait: nil,
|
96
|
+
title: "Next Message..."
|
97
|
+
|
98
|
+
# deliberately declaring this twice for testing purposes
|
99
|
+
# (declaring twice extends it)
|
100
|
+
style :next_message,
|
101
|
+
portrait: {
|
102
|
+
left: 150,
|
103
|
+
top: 370,
|
104
|
+
},
|
105
|
+
|
106
|
+
landscape: {
|
107
|
+
left: 20,
|
108
|
+
top: 200,
|
109
|
+
}
|
110
|
+
|
111
|
+
end
|
data/lib/dummy.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class DummyView < UIView
|
2
|
+
private
|
3
|
+
def dummy
|
4
|
+
setFrame(nil)
|
5
|
+
setOpaque(nil)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DummyScrollView < UIScrollView
|
10
|
+
private
|
11
|
+
def dummy
|
12
|
+
setScrollEnabled(nil)
|
13
|
+
setShowsVerticalScrollIndicator(nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class DummyActivityIndicatorView < UIActivityIndicatorView
|
18
|
+
private
|
19
|
+
def dummy
|
20
|
+
setHidesWhenStopped(nil)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class DummyLabel < UILabel
|
25
|
+
private
|
26
|
+
def dummy
|
27
|
+
setAdjustsFontSizeToFitWidth(nil)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class DummyTextField < UITextField
|
32
|
+
private
|
33
|
+
def dummy
|
34
|
+
setReturnKeyType(nil)
|
35
|
+
setAutocapitalizationType(nil)
|
36
|
+
setAutocorrectionType(nil)
|
37
|
+
setSpellCheckingType(nil)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class DummyLayer < CALayer
|
42
|
+
private
|
43
|
+
def dummy
|
44
|
+
setCornerRadius(nil)
|
45
|
+
setTransform(nil)
|
46
|
+
setMasksToBounds(nil)
|
47
|
+
setShadowOffset(nil)
|
48
|
+
setShadowOpacity(nil)
|
49
|
+
setShadowRadius(nil)
|
50
|
+
setShadowOffset(nil)
|
51
|
+
setShadowColor(nil)
|
52
|
+
setShadowPath(nil)
|
53
|
+
setOpaque(nil)
|
54
|
+
setTranslucent(nil)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Teacup handlers can modify (or alias) styling methods. For instance, the
|
2
|
+
# UIButton class doesn't have a `title` attribute, but we all know that what we
|
3
|
+
# *mean* when we say `title: "button title"` is `setTitle("button title",
|
4
|
+
# forControlState:UIControlStateNormal)`. A UIButton handler accomplishes this
|
5
|
+
# translation.
|
6
|
+
#
|
7
|
+
# You can write your own handlers!
|
8
|
+
#
|
9
|
+
# Teacup.handler UIButton, :title do |view, value|
|
10
|
+
# view.setTitle(value, forControlState:UIControlStateNormal)
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# You can declare multiple names in the Teacup.handler method to create aliases
|
14
|
+
# for your handler:
|
15
|
+
#
|
16
|
+
# Teacup.handler UIButton, :returnKeyType, :returnkey { |view, keytype|
|
17
|
+
# view.setReturnKeyType(keytype)
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Since teacup already supports translating a property like `returnKeyType` into
|
21
|
+
# the setter `setReturnKeyType`, you could just use an alias here instead.
|
22
|
+
# Assign a hash to `Teacup.alias`:
|
23
|
+
#
|
24
|
+
# Teacup.alias UIButton, :returnkey => :returnKeyType
|
25
|
+
module Teacup
|
26
|
+
module_function
|
27
|
+
|
28
|
+
# applies a Hash of styles, and converts the frame styles (origin, size, top,
|
29
|
+
# left, width, height) into one frame property.
|
30
|
+
def apply_hash(target, properties)
|
31
|
+
properties.each do |key, value|
|
32
|
+
Teacup.apply target, key, value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Applies a single style to a target. Delegates to a teacup handler if one is
|
37
|
+
# found.
|
38
|
+
def apply(target, key, value)
|
39
|
+
# note about `debug`: not all objects in this method are a UIView instance,
|
40
|
+
# so don't assume that the object *has* a debug method.
|
41
|
+
|
42
|
+
target.class.ancestors.each do |ancestor|
|
43
|
+
if Teacup.handlers[ancestor].has_key? key
|
44
|
+
NSLog "#{ancestor.name} is handling #{key} = #{value.inspect}" if target.respond_to? :debug and target.debug
|
45
|
+
Teacup.handlers[ancestor][key].call(target, value)
|
46
|
+
return
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# you can send methods to subviews (e.g. UIButton#titleLabel) and CALayers
|
51
|
+
# (e.g. UIView#layer) by assigning a hash to a style name.
|
52
|
+
if Hash === value
|
53
|
+
return Teacup.apply_hash target.send(key), value
|
54
|
+
end
|
55
|
+
|
56
|
+
if key =~ /^set[A-Z]/
|
57
|
+
assign = nil
|
58
|
+
setter = key.to_s + ':'
|
59
|
+
else
|
60
|
+
assign = :"#{key}="
|
61
|
+
setter = 'set' + key.to_s.sub(/^./) {|c| c.capitalize} + ':'
|
62
|
+
end
|
63
|
+
|
64
|
+
if assign and target.respond_to?(assign)
|
65
|
+
NSLog "Setting #{key} = #{value.inspect}" if target.respond_to? :debug and target.debug
|
66
|
+
target.send(assign, value)
|
67
|
+
elsif target.respondsToSelector(setter)
|
68
|
+
NSLog "Calling target(#{key}, #{value.inspect})" if target.respond_to? :debug and target.debug
|
69
|
+
target.send(setter, value)
|
70
|
+
else
|
71
|
+
NSLog "Teacup WARN: Can't apply #{setter.inspect}#{assign and " or " + assign.inspect or ""} to #{target.inspect}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def handlers
|
76
|
+
@teacup_handlers ||= Hash.new{ |hash,klass| hash[klass] = {} }
|
77
|
+
end
|
78
|
+
|
79
|
+
def handler klass, *stylenames, &block
|
80
|
+
if stylenames.length == 0
|
81
|
+
raise TypeError.new "No style names assigned in Teacup[#{klass.inspect}]##handler"
|
82
|
+
else
|
83
|
+
stylenames.each do |stylename|
|
84
|
+
Teacup.handlers[klass][stylename] = block
|
85
|
+
end
|
86
|
+
end
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def alias klass, aliases
|
91
|
+
aliases.each do |style_alias, stylename|
|
92
|
+
Teacup.handlers[klass][style_alias] = proc { |view, value|
|
93
|
+
Teacup.apply view, style_name, value
|
94
|
+
}
|
95
|
+
end
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/lib/teacup/layout.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
|
+
|
1
2
|
module Teacup
|
2
|
-
# Teacup::Layout defines a layout function that can be used to
|
3
|
-
# layout of views in your
|
3
|
+
# Teacup::Layout defines a layout and subview function that can be used to
|
4
|
+
# declare and configure the layout of views and the view hierarchy in your
|
5
|
+
# application.
|
4
6
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
+
# This module is included into UIView and UIViewController directly so these
|
8
|
+
# functions are available in the places you need them.
|
7
9
|
#
|
8
10
|
# In order to use layout() in a UIViewController most effectively you will want
|
9
11
|
# to define a stylesheet method that returns a stylesheet.
|
10
12
|
#
|
11
13
|
# @example
|
12
14
|
# class MyViewController < UIViewController
|
13
|
-
#
|
15
|
+
# layout(:my_view) do
|
14
16
|
# layout UIImage, :logo
|
15
17
|
# end
|
16
|
-
#
|
18
|
+
#
|
17
19
|
# def stylesheet
|
18
|
-
# Teacup::Stylesheet
|
20
|
+
# Teacup::Stylesheet[:logo]
|
19
21
|
# end
|
20
22
|
# end
|
21
|
-
#
|
22
23
|
module Layout
|
24
|
+
attr_accessor :stylesheet
|
23
25
|
|
24
26
|
# Alter the layout of a view
|
25
27
|
#
|
@@ -58,30 +60,29 @@ module Teacup
|
|
58
60
|
# subview(UIImage, backgroundColor: UIColor.colorWithImagePattern(image)
|
59
61
|
# }
|
60
62
|
#
|
61
|
-
def layout(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
def layout(view, name_or_properties=nil, properties_or_nil=nil, &block)
|
64
|
+
name = nil
|
65
|
+
properties = properties_or_nil
|
66
|
+
|
67
|
+
if Hash === name_or_properties
|
66
68
|
name = nil
|
67
69
|
properties = name_or_properties
|
68
|
-
|
70
|
+
elsif name_or_properties
|
69
71
|
name = name_or_properties.to_sym
|
70
|
-
properties = nil
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
view.stylesheet = stylesheet
|
75
|
+
view.stylename = name
|
76
|
+
view.style(properties) if properties
|
76
77
|
|
77
78
|
begin
|
78
|
-
superview_chain <<
|
79
|
-
instance_exec(
|
79
|
+
superview_chain << view
|
80
|
+
instance_exec(view, &block) if block_given?
|
80
81
|
ensure
|
81
82
|
superview_chain.pop
|
82
83
|
end
|
83
84
|
|
84
|
-
|
85
|
+
view
|
85
86
|
end
|
86
87
|
|
87
88
|
# Add a new subview to the view heirarchy.
|
@@ -123,8 +124,6 @@ module Teacup
|
|
123
124
|
# }
|
124
125
|
#
|
125
126
|
def subview(class_or_instance, *args, &block)
|
126
|
-
instance = Class === class_or_instance ? class_or_instance.new : class_or_instance
|
127
|
-
|
128
127
|
if Class === class_or_instance
|
129
128
|
unless class_or_instance <= UIView
|
130
129
|
raise "Expected subclass of UIView, got: #{class_or_instance.inspect}"
|
@@ -143,59 +142,8 @@ module Teacup
|
|
143
142
|
instance
|
144
143
|
end
|
145
144
|
|
146
|
-
# Returns a stylesheet to use to style the contents of this controller's
|
147
|
-
# view.
|
148
|
-
#
|
149
|
-
# This method will be queried each time {restyle!} is called, and also
|
150
|
-
# implicitly # whenever Teacup needs to draw your layout (currently only at
|
151
|
-
# view load time).
|
152
|
-
#
|
153
|
-
# @return Teacup::Stylesheet
|
154
|
-
#
|
155
|
-
# @example
|
156
|
-
#
|
157
|
-
# def stylesheet
|
158
|
-
# if [UIDeviceOrientationLandscapeLeft,
|
159
|
-
# UIDeviceOrientationLandscapeRight].include?(UIDevice.currentDevice.orientation)
|
160
|
-
# Teacup::Stylesheet::IPad
|
161
|
-
# else
|
162
|
-
# Teacup::Stylesheet::IPadVertical
|
163
|
-
# end
|
164
|
-
# end
|
165
|
-
def stylesheet
|
166
|
-
nil
|
167
|
-
end
|
168
|
-
|
169
|
-
# Instruct teacup to reapply styles to your subviews
|
170
|
-
#
|
171
|
-
# You should call this whenever the return value of your stylesheet meethod
|
172
|
-
# would change,
|
173
|
-
#
|
174
|
-
# @example
|
175
|
-
# def willRotateToInterfaceOrientation(io, duration: duration)
|
176
|
-
# restyle!
|
177
|
-
# end
|
178
|
-
def restyle!
|
179
|
-
top_level_view.stylesheet = stylesheet
|
180
|
-
end
|
181
|
-
|
182
145
|
protected
|
183
146
|
|
184
|
-
# Get's the top-level UIView for this object.
|
185
|
-
#
|
186
|
-
# This can either be 'self' if the current object is in fact a UIView,
|
187
|
-
# or 'view' if it's a controller.
|
188
|
-
#
|
189
|
-
# @return UIView
|
190
|
-
def top_level_view
|
191
|
-
case self
|
192
|
-
when UIViewController
|
193
|
-
view
|
194
|
-
when UIView
|
195
|
-
self
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
147
|
# Get's the current stack of views in nested calls to layout.
|
200
148
|
#
|
201
149
|
# The view at the end of the stack is the one into which subviews
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Teacup
|
2
|
+
module_function
|
3
|
+
|
4
|
+
# Merges two Hashes. This is similar to `Hash#merge`, except the values will
|
5
|
+
# be merged recursively (aka deep merge) when both values for a key are
|
6
|
+
# Hashes, and values for the *left* argument are preferred over values on the
|
7
|
+
# right.
|
8
|
+
#
|
9
|
+
# If you pass in a third argument, it will be acted upon directly instead of
|
10
|
+
# creating a new Hash. Usually used with `merge_defaults!`, which merges values
|
11
|
+
# from `right` into `left`.
|
12
|
+
def merge_defaults(left, right, target={})
|
13
|
+
if target != left
|
14
|
+
left.each do |key, value|
|
15
|
+
if target.has_key? key and value.is_a?(Hash) and target[key].is_a?(Hash)
|
16
|
+
target[key] = Teacup::merge_defaults(target[key], value)
|
17
|
+
else
|
18
|
+
if value.is_a?(Hash)
|
19
|
+
# make a copy of the Hash
|
20
|
+
value = Teacup::merge_defaults!({}, value)
|
21
|
+
end
|
22
|
+
target[key] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
right.each do |key, value|
|
28
|
+
if not target.has_key? key
|
29
|
+
if value.is_a?(Hash)
|
30
|
+
# make a copy of the Hash
|
31
|
+
value = Teacup::merge_defaults!({}, value)
|
32
|
+
end
|
33
|
+
target[key] = value
|
34
|
+
elsif value.is_a?(Hash) and left[key].is_a?(Hash)
|
35
|
+
target[key] = Teacup::merge_defaults(left[key], value, (left==target ? left[key] : {}))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
target
|
39
|
+
end
|
40
|
+
|
41
|
+
# modifies left by passing it in as the `target`.
|
42
|
+
def merge_defaults!(left, right)
|
43
|
+
Teacup::merge_defaults(left, right, left)
|
44
|
+
end
|
45
|
+
end
|
data/lib/teacup/style.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Teacup
|
2
|
+
# The Style class is where the precedence rules are applied. A Style can
|
3
|
+
# query the Stylesheet that created it to look up other styles (for
|
4
|
+
# `extends:`) and to import other Stylesheets. If it is handed a target (e.g.
|
5
|
+
# a `UIView` instance) and orientation, it will merge those in appropriately
|
6
|
+
# as well.
|
7
|
+
class Style < Hash
|
8
|
+
attr_accessor :stylename
|
9
|
+
attr_accessor :stylesheet
|
10
|
+
|
11
|
+
Orientations = [:portrait, :upside_up, :upside_down, :landscape, :landscape_left, :landscape_right]
|
12
|
+
Overrides = {
|
13
|
+
0 => [:portrait, :upside_up],
|
14
|
+
UIInterfaceOrientationPortrait => [:portrait, :upside_up],
|
15
|
+
UIInterfaceOrientationPortraitUpsideDown => [:portrait, :upside_down],
|
16
|
+
UIInterfaceOrientationLandscapeLeft => [:landscape, :landscape_left],
|
17
|
+
UIInterfaceOrientationLandscapeRight => [:landscape, :landscape_right],
|
18
|
+
}
|
19
|
+
|
20
|
+
# A hash of orientation => true/false. true means the orientation is
|
21
|
+
# supported.
|
22
|
+
def supports
|
23
|
+
@supports ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def supports? orientation_key
|
27
|
+
supports.has_key? orientation_key
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(target=nil, orientation=nil, seen={})
|
31
|
+
properties = Style.new.update(self)
|
32
|
+
properties.stylename = self.stylename
|
33
|
+
properties.stylesheet = self.stylesheet
|
34
|
+
|
35
|
+
# at this point, we really DO need the orientation
|
36
|
+
orientation = UIDevice.currentDevice.orientation unless orientation
|
37
|
+
|
38
|
+
# first, move orientation settings into properties "base" level.
|
39
|
+
if orientation
|
40
|
+
Overrides[orientation].each do |orientation_key|
|
41
|
+
if override = properties.delete(orientation_key)
|
42
|
+
# override is first, so it takes precedence
|
43
|
+
if override.is_a? Hash
|
44
|
+
Teacup::merge_defaults override, properties, properties
|
45
|
+
elsif not properties.has_key? orientation_key
|
46
|
+
properties[orientation_key] = override
|
47
|
+
end
|
48
|
+
properties.supports[orientation_key] = properties[orientation_key] ? true : false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# delete all of them from `properties`
|
54
|
+
Orientations.each do |orientation_key|
|
55
|
+
if properties.delete(orientation_key)
|
56
|
+
properties.supports[orientation_key] = true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# now we can merge extends, and importing. before merging, these will go
|
61
|
+
# through the same process that we just finished on the local style
|
62
|
+
if stylesheet
|
63
|
+
stylesheet.imported_stylesheets.reverse.each do |stylesheet|
|
64
|
+
imported_properties = stylesheet.query(self.stylename, target, orientation, seen)
|
65
|
+
Teacup::merge_defaults! properties, imported_properties
|
66
|
+
end
|
67
|
+
|
68
|
+
if also_includes = properties.delete(:extends)
|
69
|
+
also_includes = [also_includes] unless also_includes.is_a? Array
|
70
|
+
|
71
|
+
# turn style names into Hashes by querying them on the stylesheet
|
72
|
+
# (this does not pass `seen`, because this is a new query)
|
73
|
+
also_includes.each do |also_include|
|
74
|
+
extended_properties = stylesheet.query(also_include, target, orientation)
|
75
|
+
Teacup::merge_defaults! properties, extended_properties
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# if we know the class of the target, we can apply styles via class
|
80
|
+
# inheritance. We do not pass `target` in this case.
|
81
|
+
if target
|
82
|
+
target.class.ancestors.each do |ancestor|
|
83
|
+
extended_properties = stylesheet.query(ancestor, nil, orientation)
|
84
|
+
Teacup::merge_defaults!(properties, extended_properties)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
properties
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|