Class: Teacup::Stylesheet

Inherits:
Object
  • Object
show all
Defined in:
lib/teacup/stylesheet.rb,
lib/teacup-osx/style_extensions/autoresize.rb,
lib/teacup/stylesheet_extensions/transform.rb,
lib/teacup-ios/stylesheet_extensions/device.rb,
lib/teacup/stylesheet_extensions/constraints.rb,
lib/teacup-ios/stylesheet_extensions/autoresize.rb

Overview

Stylesheets in Teacup act as a central configuration mechanism, they have two aims:

  1. Allow you to store “Details” away from the main body of your code. (controllers shouldn’t have to be filled with style rules)

  2. Allow you to easily re-use configuration in many places.

The API really provides only two methods, #style to store properties on the Stylesheet; and #query to get them out again:

In addition to this, two separate mechanisms are provided for sharing configuration within stylesheets.

Firstly, if you set the ‘:extends’ property for a given stylename, then on lookup the Stylesheet will merge the properties for the ‘:extends’ stylename into the return value. Conflicts are resolved so that properties with the original stylename are resolved in its favour.

Secondly, you can import Stylesheets into each other, in exactly the same way as you can include Modules into each other in Ruby. This allows you to share rules between Stylesheets.

As you’d expect, conflicts are resolved so that the Stylesheet on which you call query has the highest precedence.

The two merging mechanisms are considered independently, so you can override a property both in a ‘:extends’ rule, and also in an imported Stylesheet. In such a a case the Stylesheet inclusion conflicts are resolved independently; and then in a second phase, the ‘:extends’ chain is flattened.

Examples:

stylesheet = Teacup::Stylesheet.new
stylesheet.style :buttons, :corners => :rounded
# => nil
stylesheet.query :buttons
# => {:corners => :rounded}
Teacup::Stylesheet.new(:ipad) do
  style :button,
    backgroundImage: UIImage.imageNamed("big_red_shiny_button"),
    top: 100

  style :ok_button, extends: :button,
    title: "OK!",
    top: 200

end
Teacup::Stylesheet[:ipad].query(:ok_button)
# => {backgroundImage: UIImage.imageNamed("big_red_shiny_button"), top: 200, title: "OK!"}
Teacup::Stylesheet.new(:ipad) do
  style :ok_button,
    title: "OK!"
end

Teacup::Stylesheet.new(:ipadvertical) do
  import :ipad
  style :ok_button,
    width: 80
end
Teacup::Stylesheet[:ipadvertical].query(:ok_button)
# => {title: "OK!", width: 80}

Direct Known Subclasses

Appearance

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil, &block) ⇒ Stylesheet

Create a new Stylesheet.

If a name is provided then a new constant will be created using that name.

Examples:

Teacup::Stylesheet.new(:ipadvertical) do
  import :ipadbase
  style :continue_button,
     top: 50
end

Teacup::Stylesheet[:ipadvertical].query(:continue_button)
# => {top: 50}

Parameters:

  • name,

    The (optional) name to give.

  • &block,

    The body of the Stylesheet instance_eval’d.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/teacup/stylesheet.rb', line 103

def initialize(name=nil, &block)
  if name
    @name = name.to_sym
    if Teacup::Stylesheet[@name]
      NSLog("TEACUP WARNING: A stylesheet with the name #{@name.inspect} has already been created.")
    end
    Teacup::Stylesheet[@name] = self
  end

  # we just store the block for now, because some classes are not "ready"
  # for instance, calling `UIFont.systemFontOfSize()` will cause the
  # application to crash.  We will lazily-load this block in `query`, and
  # then set it to nil.
  @block = block
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



68
69
70
# File 'lib/teacup/stylesheet.rb', line 68

def name
  @name
end

Class Method Details

.[](name) ⇒ Object



76
77
78
# File 'lib/teacup/stylesheet.rb', line 76

def [] name
  stylesheets[name]
end

.[]=(name, stylesheet) ⇒ Object



80
81
82
# File 'lib/teacup/stylesheet.rb', line 80

def []= name, stylesheet
  stylesheets[name] = stylesheet
end

.stylesheetsObject



72
73
74
# File 'lib/teacup/stylesheet.rb', line 72

def stylesheets
  @stylesheets ||= {}
end

Instance Method Details

#app_sizeObject

returns the application frame, which takes the status bar into account



47
48
49
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 47

def app_size
  UIScreen.mainScreen.applicationFrame.size
end

#autoresize(&block) ⇒ Object



12
13
14
15
16
17
18
19
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 12

def autoresize &block
  @@autoresize ||= Autoresize.new
  if block
    return @@autoresize.instance_exec &block
  else
    return @@autoresize
  end
end

#constrain(target, attribute = nil) ⇒ Object



4
5
6
7
8
9
10
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 4

def constrain(target, attribute=nil)
  if attribute.nil?
    attribute = target
    target = :self
  end
  Teacup::Constraint.new(target, attribute)
end

#constrain_above(relative_to, margin = 0) ⇒ Object



77
78
79
80
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 77

def constrain_above(relative_to, margin=0)
  margin = 8 if margin == :auto
  Teacup::Constraint.new(:self, :bottom).equals(relative_to, :top).minus(margin)
end

#constrain_below(relative_to, margin = 0) ⇒ Object

|



72
73
74
75
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 72

def constrain_below(relative_to, margin=0)
  margin = 8 if margin == :auto
  Teacup::Constraint.new(:self, :top).equals(relative_to, :bottom).plus(margin)
end

#constrain_bottom(y) ⇒ Object



36
37
38
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 36

def constrain_bottom(y)
  Teacup::Constraint.new(:self, :bottom).equals(:superview, :bottom).plus(y)
end

#constrain_center_x(x = 0) ⇒ Object



28
29
30
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 28

def constrain_center_x(x=0)
  Teacup::Constraint.new(:self, :center_x).equals(:superview, :center_x).plus(x)
end

#constrain_center_y(y = 0) ⇒ Object



40
41
42
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 40

def constrain_center_y(y=0)
  Teacup::Constraint.new(:self, :center_y).equals(:superview, :center_y).plus(y)
end

#constrain_height(height) ⇒ Object



48
49
50
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 48

def constrain_height(height)
  Teacup::Constraint.new(:self, :height).equals(height)
end

#constrain_left(x) ⇒ Object



20
21
22
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 20

def constrain_left(x)
  Teacup::Constraint.new(:self, :left).equals(:superview, :left).plus(x)
end

#constrain_right(x) ⇒ Object



24
25
26
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 24

def constrain_right(x)
  Teacup::Constraint.new(:self, :right).equals(:superview, :right).plus(x)
end

#constrain_size(width, height) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 52

def constrain_size(width, height)
  if width.is_a? Numeric
    width_attr = nil
  else
    width_attr = :width
  end

  if height.is_a? Numeric
    height_attr = nil
  else
    height_attr = :height
  end

  [
    Teacup::Constraint.new(:self, :width).equals(width, width_attr),
    Teacup::Constraint.new(:self, :height).equals(height, height_attr),
  ]
end

#constrain_to_left(relative_to, margin = 0) ⇒ Object



87
88
89
90
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 87

def constrain_to_left(relative_to, margin=0)
  margin = 20 if margin == :auto
  Teacup::Constraint.new(:self, :right).equals(relative_to, :left).minus(margin)
end

#constrain_to_right(relative_to, margin = 0) ⇒ Object



82
83
84
85
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 82

def constrain_to_right(relative_to, margin=0)
  margin = 20 if margin == :auto
  Teacup::Constraint.new(:self, :left).equals(relative_to, :right).plus(margin)
end

#constrain_top(y) ⇒ Object



32
33
34
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 32

def constrain_top(y)
  Teacup::Constraint.new(:self, :top).equals(:superview, :top).plus(y)
end

#constrain_width(width) ⇒ Object



44
45
46
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 44

def constrain_width(width)
  Teacup::Constraint.new(:self, :width).equals(width)
end

#constrain_xy(x, y) ⇒ Object

|



13
14
15
16
17
18
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 13

def constrain_xy(x, y)
  [
    Teacup::Constraint.new(:self, :left).equals(:superview, :left).plus(x),
    Teacup::Constraint.new(:self, :top).equals(:superview, :top).plus(y),
  ]
end

#deviceObject

returns a bit-wise OR of the device masks



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 52

def device
  @@this_device ||= nil
  return @@this_device if @@this_device

  @@this_device = 0
  if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone
    @@this_device |= iPhone

    if UIScreen.mainScreen.respond_to?(:scale) && UIScreen.mainScreen.scale == 2
      @@this_device |= iPhoneRetina
    end

    if UIScreen.mainScreen.bounds.size.height == 568
      @@this_device |= iPhone4
    else
      @@this_device |= iPhone35
    end
  else
    @@this_device |= iPad
    if UIScreen.mainScreen.respond_to? :scale
      @@this_device |= iPadRetina
    end
  end

  return @@this_device
end

#device_is?(this_device) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
82
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 79

def device_is?(this_device)
  this_device = self.send(this_device) if this_device.is_a? Symbol
  return self.device & this_device > 0
end

#exclude_instance_varsObject



309
310
311
# File 'lib/teacup/stylesheet.rb', line 309

def exclude_instance_vars
  @exclude_instance_vars ||= [:@name, :@block, :@imported, :@styles, :@stylesheet_cache]
end

#extends_style?(stylename, extended_name, checked = []) ⇒ Boolean

In the [rare] case you need to know whether the style extends another style, this method will find out quickly. This is mostly useful in the ‘UIView#viewWithStylename` method.

Returns:

  • (Boolean)


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/teacup/stylesheet.rb', line 288

def extends_style?(stylename, extended_name, checked=[])
  return true if stylename == extended_name

  extended_style_names = styles[stylename][:extends]
  return false unless extended_style_names

  extended_style_names = [extended_style_names] unless extended_style_names.is_a? Array
  return true if extended_style_names.any? { |name| name == extended_name }
  retval = false
  extended_style_names.each do |recusive_check|
    next if checked.include?(recusive_check)

    checked << recusive_check
    if extends_style?(recusive_check, extended_name, checked)
      retval = true
      break
    end
  end
  return retval
end

#flexible_bottomObject



50
51
52
53
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 50

def flexible_bottom
  NSLog("The Stylesheet method `flexible_bottom` is deprecated, use `autoresize.flexible_bottom` instead")
  NSViewMinYMargin
end

#flexible_heightObject



45
46
47
48
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 45

def flexible_height
  NSLog("The Stylesheet method `flexible_height` is deprecated, use `autoresize.flexible_height` instead")
  NSViewHeightSizable
end

#flexible_leftObject

| | DEPRECATED |



25
26
27
28
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 25

def flexible_left
  NSLog("The Stylesheet method `flexible_left` is deprecated, use `autoresize.flexible_left` instead")
  NSViewMinXMargin
end

#flexible_rightObject



35
36
37
38
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 35

def flexible_right
  NSLog("The Stylesheet method `flexible_right` is deprecated, use `autoresize.flexible_right` instead")
  NSViewMaxXMargin
end

#flexible_topObject



40
41
42
43
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 40

def flexible_top
  NSLog("The Stylesheet method `flexible_top` is deprecated, use `autoresize.flexible_top` instead")
  NSViewMaxYMargin
end

#flexible_widthObject



30
31
32
33
# File 'lib/teacup-osx/style_extensions/autoresize.rb', line 30

def flexible_width
  NSLog("The Stylesheet method `flexible_width` is deprecated, use `autoresize.flexible_width` instead")
  NSViewWidthSizable
end

#flip(matrix, angle) ⇒ Object



22
23
24
25
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 22

def flip(matrix, angle)
  NSLog("The Stylesheet method `flip` is deprecated, use `transform_layer.flip` instead")
  transform_layer.flip(angle)
end

#get_stylesheet_cache(stylename, target_class, orientation) ⇒ Object



136
137
138
# File 'lib/teacup/stylesheet.rb', line 136

def get_stylesheet_cache(stylename, target_class, orientation)
  self.stylesheet_cache[stylename][target_class][orientation]
end

#identityObject



17
18
19
20
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 17

def identity
  NSLog("The Stylesheet method `identity` is deprecated, use `transform_layer.identity` instead")
  transform_layer.identity
end

#import(name_or_stylesheet) ⇒ Object

Include another Stylesheet into this one, the rules defined within it will have lower precedence than those defined here in the case that they share the same keys.

When defining a stylesheet declaratively, it is better to use the symbol that represents a stylesheet, as the constant may not be defined yet:

If you are using anonymous stylesheets however, then it will be necessary to pass an actual stylesheet object.

Examples:

Teacup::Stylesheet.new(:ipadvertical) do
  import :ipadbase
  import :verticaltweaks
end
@stylesheet.import(base_stylesheet)

Parameters:

  • Symbol|Teacup::Stylesheet

    the name of the stylesheet, or the stylesheet to include.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/teacup/stylesheet.rb', line 167

def import(name_or_stylesheet)
  @stylesheet_cache = nil
  imported << name_or_stylesheet

  sheet = nil
  if name_or_stylesheet.is_a? Teacup::Stylesheet
    sheet = name_or_stylesheet
  elsif Teacup::Stylesheet.stylesheets.has_key? name_or_stylesheet
    sheet = Teacup::Stylesheet.stylesheets[name_or_stylesheet]
  end

  if sheet
    import_instance_vars(sheet)
  end
end

#import_instance_vars(from_stylesheet) ⇒ Object



313
314
315
316
317
318
319
320
# File 'lib/teacup/stylesheet.rb', line 313

def import_instance_vars(from_stylesheet)
  from_stylesheet.run_block

  from_stylesheet.instance_variables.each do |var|
    next if exclude_instance_vars.include? var
    self.instance_variable_set(var, from_stylesheet.instance_variable_get(var))
  end
end

#imported_stylesheetsObject

The array of Stylesheets that have been imported into this one.



271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/teacup/stylesheet.rb', line 271

def imported_stylesheets
  imported.map do |name_or_stylesheet|
    if name_or_stylesheet.is_a? Teacup::Stylesheet
      sheet = name_or_stylesheet
    elsif Teacup::Stylesheet.stylesheets.has_key? name_or_stylesheet
      sheet = Teacup::Stylesheet.stylesheets[name_or_stylesheet]
    else
      raise "Teacup tried to import Stylesheet #{name_or_stylesheet.inspect} into Stylesheet[#{self.name.inspect}], but it didn't exist"
    end

    sheet
  end
end

#inspectObject

A unique and hopefully meaningful description of this Object.

Returns:

  • String



264
265
266
# File 'lib/teacup/stylesheet.rb', line 264

def inspect
  "#{self.class.name}[#{name.inspect}] = #{styles.inspect}"
end

#iPadObject



33
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 33

def iPad         ; 1 << 5 ; end

#iPadRetinaObject



34
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 34

def iPadRetina   ; 1 << 6 ; end

#iPhoneObject



29
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 29

def iPhone       ; 1 << 1 ; end

#iPhone35Object



32
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 32

def iPhone35     ; 1 << 4 ; end

#iPhone4Object



31
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 31

def iPhone4      ; 1 << 3 ; end

#iPhone5Object



36
37
38
39
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 36

def iPhone5
  NSLog('TEACUP WARNING: iPhone5 method is deprecated in lieu of size-based method names (iPhone4, iPhone35)')
  1 << 3
end

#iPhoneRetinaObject



30
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 30

def iPhoneRetina ; 1 << 2 ; end

#piObject



5
6
7
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 5

def pi
  Math::PI
end

#query(stylename, target_class = nil, orientation = nil, seen = {}) ⇒ Object

Get the properties defined for the given stylename, in this Stylesheet and all those that have been imported.

The Style class handles precedence rules, and extending via ‘:extends` and `import`. If needs the orientation in order to merge and remove the appropriate orientation styles.

Examples:

Teacup::Stylesheet[:ipadbase].query(:continue_button)
# => {backgroundImage: UIImage.imageNamed("big_red_shiny_button"), title: "Continue!", top: 50}

Parameters:

  • Symbol

    stylename, the stylename to look up.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/teacup/stylesheet.rb', line 211

def query(stylename, target_class=nil, orientation=nil, seen={})
  return {} if seen[self]
  return {} unless stylename

  cached = get_stylesheet_cache(stylename, target_class, orientation)
  if cached
    # mutable hashes could mess with our cache, so return a duplicate
    return cached.dup
  else
    run_block
    seen[self] = true

    built = styles[stylename].build(target_class, orientation, seen)
    set_stylesheet_cache(stylename, target_class, orientation, built)
    return built.dup
  end
end

#rotate(matrix, angle, x, y, z) ⇒ Object



37
38
39
40
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 37

def rotate(matrix, angle, x, y, z)
  NSLog("The Stylesheet method `rotate` is deprecated, use `transform_layer.rotate` instead")
  transform_layer.rotate(angle, x, y, z)
end

#run_blockObject

the block handed to Stylesheet#new is not run immediately - it is run the first time the stylesheet is queried. This fixes bugs related to some resources (fonts) not available when the application is first started. The downside is that @instance variables and variables that should be closed over are not.

Returns:

  • true if the block was run. false otherwise



190
191
192
193
194
195
196
197
# File 'lib/teacup/stylesheet.rb', line 190

def run_block
  if @block
    _block = @block
    @block = nil
    instance_eval &_block
    true
  end
end

#screen_sizeObject

returns the device size in points, regardless of status bar



42
43
44
# File 'lib/teacup-ios/stylesheet_extensions/device.rb', line 42

def screen_size
  UIScreen.mainScreen.bounds.size
end

#set_stylesheet_cache(stylename, target_class, orientation, value) ⇒ Object



140
141
142
# File 'lib/teacup/stylesheet.rb', line 140

def set_stylesheet_cache(stylename, target_class, orientation, value)
  self.stylesheet_cache[stylename][target_class][orientation] = value
end

#spin(matrix, angle) ⇒ Object



32
33
34
35
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 32

def spin(matrix, angle)
  NSLog("The Stylesheet method `spin` is deprecated, use `transform_layer.spin` instead")
  transform_layer.spin(angle)
end

#style(*queries) ⇒ Object

Add a set of properties for a given stylename or multiple stylenames.

Examples:

Teacup::Stylesheet.new(:ipadbase) do
  style :pretty_button,
    backgroundImage: UIImage.imageNamed("big_red_shiny_button")

  style :continue_button, extends: :pretty_button,
    title: "Continue!",
    top: 50
end

Parameters:

  • Symbol,

    *stylename

  • Hash (Symbol, Object)

    , properties



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/teacup/stylesheet.rb', line 243

def style(*queries)
  if queries[-1].is_a? Hash
    properties = queries.pop
  else
    # empty style declarations are allowed, but accomplish nothing.
    return
  end

  queries.each do |stylename|
    # reset the stylesheet_cache for this stylename
    @stylesheet_cache.delete(stylename) if @stylesheet_cache

    # merge into styles[stylename] (an instance of Teacup::Style). new
    # properties "win" over existing properties.
    Teacup::merge_defaults(properties, styles[stylename], styles[stylename])
  end
end

#stylesheet_cacheObject

The stylesheet_cache stores “compiled” styles. It is reset whenever the stylesheet imports a new Stylesheet, and when a style entry is added or changed (then only that entry is removed)

This method builds the gnarly hash that stores this stuff - the get/set methods use this method to ensure the object exists, in other places the or the entire cache)



127
128
129
130
131
132
133
134
# File 'lib/teacup/stylesheet.rb', line 127

def stylesheet_cache
  @stylesheet_cache ||= Hash.new do |by_stylename,_stylename|
    by_stylename[_stylename] = Hash.new do |by_target_class,_target_class|
      by_orientation = {}
      by_target_class[_target_class] = by_orientation
    end
  end
end

#transform_layerObject



13
14
15
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 13

def transform_layer
  @@transform_layer ||= TransformLayer.new
end

#transform_viewObject



9
10
11
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 9

def transform_view
  @@transform_layer ||= TransformView.new
end

#twist(matrix, angle) ⇒ Object



27
28
29
30
# File 'lib/teacup/stylesheet_extensions/transform.rb', line 27

def twist(matrix, angle)
  NSLog("The Stylesheet method `twist` is deprecated, use `transform_layer.twist` instead")
  transform_layer.twist(angle)
end