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