Class: Teacup::Stylesheet

Inherits:
Object
  • Object
show all
Defined in:
lib/teacup/stylesheet.rb,
lib/teacup/stylesheet_extensions/geometry.rb,
lib/teacup/stylesheet_extensions/rotation.rb,
lib/teacup/stylesheet_extensions/autoresize.rb,
lib/teacup/stylesheet_extensions/constraints.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}

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.



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

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



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

def [] name
  stylesheets[name]
end

.[]=(name, stylesheet) ⇒ Object



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

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

.stylesheetsObject



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

def stylesheets
  @stylesheets ||= {}
end

Instance Method Details

#app_sizeObject

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



41
42
43
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 41

def app_size
  UIScreen.mainScreen.applicationFrame.size
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



57
58
59
60
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 57

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

|



52
53
54
55
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 52

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



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

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

#constrain_height(height) ⇒ Object



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

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



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

def constrain_size(width, height)
  [
    Teacup::Constraint.new(:self, :width).equals(width),
    Teacup::Constraint.new(:self, :height).equals(height),
  ]
end

#constrain_to_left(relative_to, margin = 0) ⇒ Object



67
68
69
70
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 67

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



62
63
64
65
# File 'lib/teacup/stylesheet_extensions/constraints.rb', line 62

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



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

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

#constrain_width(width) ⇒ Object



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

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



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 46

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
      @@this_device |= iPhoneRetina
      if UIScreen.mainScreen.bounds.size.height == 568
        @@this_device |= iPhone5
      end
    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)


69
70
71
72
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 69

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

#flexible_bottomObject



34
35
36
# File 'lib/teacup/stylesheet_extensions/autoresize.rb', line 34

def flexible_bottom
  UIViewAutoresizingFlexibleBottomMargin
end

#flexible_heightObject



30
31
32
# File 'lib/teacup/stylesheet_extensions/autoresize.rb', line 30

def flexible_height
  UIViewAutoresizingFlexibleHeight
end

#flexible_leftObject



14
15
16
# File 'lib/teacup/stylesheet_extensions/autoresize.rb', line 14

def flexible_left
  UIViewAutoresizingFlexibleLeftMargin
end

#flexible_rightObject



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

def flexible_right
  UIViewAutoresizingFlexibleRightMargin
end

#flexible_topObject



26
27
28
# File 'lib/teacup/stylesheet_extensions/autoresize.rb', line 26

def flexible_top
  UIViewAutoresizingFlexibleTopMargin
end

#flexible_widthObject



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

def flexible_width
  UIViewAutoresizingFlexibleWidth
end

#flip(matrix, angle) ⇒ Object

rotates the “up & down” direction. The bottom of the view will rotate towards the user as angle increases.



15
16
17
# File 'lib/teacup/stylesheet_extensions/rotation.rb', line 15

def flip matrix, angle
  CATransform3DRotate(matrix, angle, 1, 0, 0)
end

#get_stylesheet_cache(stylename, target, orientation) ⇒ Object



133
134
135
# File 'lib/teacup/stylesheet.rb', line 133

def get_stylesheet_cache(stylename, target, orientation)
  stylesheet_cache[stylename][target][orientation]
end

#identityObject



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

def identity
  [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
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.



164
165
166
167
# File 'lib/teacup/stylesheet.rb', line 164

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

#imported_stylesheetsObject

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



245
246
247
248
249
250
251
252
253
254
255
# File 'lib/teacup/stylesheet.rb', line 245

def imported_stylesheets
  imported.map do |name_or_stylesheet|
    if name_or_stylesheet.is_a? Teacup::Stylesheet
      name_or_stylesheet
    elsif Teacup::Stylesheet.stylesheets.has_key? name_or_stylesheet
      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
  end
end

#inspectObject

A unique and hopefully meaningful description of this Object.

Returns:

  • String



238
239
240
# File 'lib/teacup/stylesheet.rb', line 238

def inspect
  "Teacup::Stylesheet[#{name.inspect}] = #{styles.inspect}"
end

#iPadObject



32
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 32

def iPad         ; 1 << 4 ; end

#iPadRetinaObject



33
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 33

def iPadRetina   ; 1 << 5 ; end

#iPhoneObject



29
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 29

def iPhone       ; 1 << 1 ; end

#iPhone5Object



31
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 31

def iPhone5      ; 1 << 3 ; end

#iPhoneRetinaObject



30
# File 'lib/teacup/stylesheet_extensions/geometry.rb', line 30

def iPhoneRetina ; 1 << 2 ; end

#noneObject



10
11
12
# File 'lib/teacup/stylesheet_extensions/autoresize.rb', line 10

def none
  UIViewAutoresizingNone
end

#piObject



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

def pi
  3.1415926
end

#query(stylename, target = 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.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/teacup/stylesheet.rb', line 181

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

  unless get_stylesheet_cache(stylename, target, orientation)
    # 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.
    if @block
      instance_eval &@block
      @block = nil
    end
    seen[self] = true

    set_stylesheet_cache(stylename, target, orientation, styles[stylename].build(target, orientation, seen))
  end

  # mutable hashes could mess with our cache, so return a duplicate
  get_stylesheet_cache(stylename, target, orientation).dup
end

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

rotates the layer arbitrarily



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

def rotate matrix, angle, x, y, z
  CATransform3DRotate(matrix, angle, x, y, z)
end

#screen_sizeObject

returns the device size in points, regardless of status bar



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

def screen_size
  UIScreen.mainScreen.bounds.size
end

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



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

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

#spin(matrix, angle) ⇒ Object

spins, along the z axis. This is probably the one you want, for “spinning” a view like you might a drink coaster or paper napkin.



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

def spin matrix, angle
  CATransform3DRotate(matrix, angle, 0, 0, 1)
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



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/teacup/stylesheet.rb', line 218

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

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

    # merge into styles[stylename], new properties "win"
    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)



125
126
127
128
129
130
131
# File 'lib/teacup/stylesheet.rb', line 125

def stylesheet_cache
  @stylesheet_cache ||= Hash.new { |cache,_stylename|
    cache[_stylename] = Hash.new { |_target,_orientation|
      _target[_orientation] = {}
    }
  }
end

#twist(matrix, angle) ⇒ Object

rotates the “left & right” direction. The right side of the view will rotate towards the user as angle increases.



21
22
23
# File 'lib/teacup/stylesheet_extensions/rotation.rb', line 21

def twist matrix, angle
  CATransform3DRotate(matrix, angle, 0, 1, 0)
end