Module: Teacup

Defined in:
lib/teacup/handler.rb,
lib/teacup/layout.rb,
lib/teacup/restyle.rb,
lib/teacup/version.rb,
lib/teacup-ios/style.rb,
lib/teacup-osx/style.rb,
lib/teacup/constraint.rb,
lib/teacup/stylesheet.rb,
lib/teacup-ios/handler.rb,
lib/teacup-osx/handler.rb,
lib/teacup/teacup_util.rb,
lib/teacup/teacup_view.rb,
lib/teacup/calculations.rb,
lib/teacup-ios/appearance.rb,
lib/teacup/merge_defaults.rb,
lib/teacup/teacup_controller.rb,
lib/teacup/core_extensions/view_getters.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/table_view/table_view_delegate.rb,
lib/teacup-ios/stylesheet_extensions/autoresize.rb

Overview

Example:

Teacup::Stylesheet.new :main do
  style :root,
    # stays centered and grows in height
    autoresizingMask: autoresize.flexible_left | autoresize.flexible_right | autoresize.flexible_height
    # same, in block form
    autoresizingMask: autoresize { flexible_left | flexible_right | flexible_height }
end

Defined Under Namespace

Modules: Controller, ControllerClass, Layout, LayoutClass, TableViewDelegate, View Classes: Appearance, Autoresize, Constraint, Style, Stylesheet, TransformLayer, TransformView

Constant Summary collapse

Priorities =

Some properties need to be assigned before others (size in particular, so that ‘:center_x` can work properly). This hash makes sure the lower priority styles (default priority is 0) get applied before higher ones.

{
  frame: 1,
  sizeToFit: 2,
  size: 2,
  width: 2,
  height: 2,
  center: 3,  # set the center last; this is the main reason for all this priority nonsense
  center_x: 3,
  center_y: 3,
}
VERSION =
'2.2.2'

Class Method Summary collapse

Class Method Details

.alias(klass, aliases) ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/teacup/handler.rb', line 119

def alias klass, aliases
  aliases.each do |style_alias, style_name|
    Teacup.handlers[klass][style_alias] = proc { |view, value|
      Teacup.apply view, style_name, value
    }
  end
  self
end

.AppearanceClassObject



19
20
21
# File 'lib/teacup-ios/handler.rb', line 19

def AppearanceClass
  @appearance_klass ||= UIView.appearance.class
end

.apply(target, key, value, klass = nil) ⇒ Object

Applies a single style to a target. Delegates to a teacup handler if one is found.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/teacup/handler.rb', line 60

def apply(target, key, value, klass=nil)
  # note about `debug`: not all objects in this method are a UIView instance,
  # so don't assume that the object *has* a debug method.
  if value.is_a? Proc
    if value.arity == 1
      value = value.call(target)
    else
      value = target.instance_exec(&value)
    end
  end

  klass ||= target.class
  handled = false
  klass.ancestors.each do |ancestor|
    if Teacup.handlers[ancestor].has_key? key
      NSLog "#{ancestor.name} is handling #{key} = #{value.inspect}"  if target.respond_to? :debug and target.debug
      if Teacup.handlers[ancestor][key].arity == 1
        target.instance_exec(value, &Teacup.handlers[ancestor][key])
      else
        Teacup.handlers[ancestor][key].call(target, value)
      end
      handled = true
      break
    end
  end
  return if handled

  # you can send methods to subviews (e.g. UIButton#titleLabel) and CALayers
  # (e.g. UIView#layer) by assigning a hash to a style name.
  if value.is_a? Hash
    return Teacup.apply_hash target.send(key), value
  end

  if key =~ /^set[A-Z]/
    assign = nil
    setter = key.to_s + ':'
  else
    assign = key.to_s + '='
    setter = 'set' + key.to_s.sub(/^./) {|c| c.capitalize} + ':'
  end

  Teacup.apply_method(target, assign, setter, value)
end

.apply_hash(target, properties, klass = nil) ⇒ Object

applies a Hash of styles, and converts the frame styles (origin, size, top, left, width, height) into one frame property.

For UIAppearance support, the class of the UIView class that is being modified can be passed in



48
49
50
51
52
53
54
55
56
# File 'lib/teacup/handler.rb', line 48

def apply_hash(target, properties, klass=nil)
  properties.sort do |a, b|
    priority_a = Priorities[a[0]]
    priority_b = Priorities[b[0]]
    priority_a <=> priority_b
  end.each do |key, value|
    Teacup.apply target, key, value, klass
  end
end

.apply_method(target, assign, setter, value) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/teacup-ios/handler.rb', line 4

def apply_method(target, assign, setter, value)
  if assign and target.respond_to?(assign)
    NSLog "Setting #{setter} = #{value.inspect}" if target.respond_to? :debug and target.debug
    target.send(assign, value)
  elsif target.respondsToSelector(setter)
    NSLog "Calling target.#{setter}(#{value.inspect})" if target.respond_to? :debug and target.debug
    target.send(setter, value)
  elsif target.is_a?(self.AppearanceClass)
    NSLog "Calling target.#{setter}(#{value.inspect})" if target.respond_to? :debug and target.debug
    target.send(setter, value)
  else
    NSLog "TEACUP WARNING: Can't apply #{setter.inspect}#{assign and " or " + assign.inspect or ""} to #{target.inspect}"
  end
end

.calculate(view, dimension, amount) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/teacup/calculations.rb', line 3

def calculate(view, dimension, amount)
  if amount.is_a? Proc
    view.instance_exec(&amount)
  elsif amount.is_a?(String) && amount.include?('%')
    location = amount.index '%'
    offset = amount.slice(location+1, amount.size).gsub(' ', '').to_f
    percent = amount.slice(0, location).to_f / 100.0

    case dimension
    when :width
      (view.superview.frame.size.width * percent + offset).round
    when :height
      (view.superview.frame.size.height * percent + offset).round
    else
      raise "Unknown dimension #{dimension}"
    end
  else
    amount
  end
end

.convert_constraints(constraints) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/teacup/merge_defaults.rb', line 61

def convert_constraints(constraints)
  if constraints.is_a? Array
    constraints.map do |constraint|
      convert_constraints(constraint)
    end
  elsif constraints.is_a? Hash
    constraints.map do |sym, relative_to|
      convert_constraints(Teacup::Constraint.from_sym(sym, relative_to))
    end
  elsif constraints.is_a?(Symbol)
    Teacup::Constraint.from_sym(constraints)
  elsif constraints.is_a?(Teacup::Constraint)
    constraints
  else
    raise "Unsupported constraint: #{constraints.inspect}"
  end
end

.dont_restyle?Boolean

Returns:

  • (Boolean)


4
5
6
# File 'lib/teacup/restyle.rb', line 4

def dont_restyle?
  @dont_restyle ||= nil
end

.handler(klass, *stylenames, &block) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'lib/teacup/handler.rb', line 108

def handler klass, *stylenames, &block
  if stylenames.length == 0
    raise TypeError.new "No style names assigned in Teacup[#{klass.inspect}]##handler"
  else
    stylenames.each do |stylename|
      Teacup.handlers[klass][stylename] = block
    end
  end
  self
end

.handlersObject



104
105
106
# File 'lib/teacup/handler.rb', line 104

def handlers
  @teacup_handlers ||= Hash.new{ |hash,klass| hash[klass] = {} }
end

.merge_constraints(left, right) ⇒ Object

constraints are a special case because when we merge an array of constraints we need to make sure not to add more than one constraint for a given attribute



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/teacup/merge_defaults.rb', line 41

def merge_constraints(left, right)
  # Return early if there are no constraints to merge.
  return right unless left

  left = left.map do |constraint|
    convert_constraints(constraint)
  end.flatten
  right = right.map do |constraint|
    convert_constraints(constraint)
  end.flatten

  constrained_attributes = left.map do |constraint|
    constraint.attribute
  end
  additional_constraints = right.reject do |constraint|
    constrained_attributes.include?(constraint.attribute)
  end
  left + additional_constraints
end

.merge_defaults(left, right, target = {}) ⇒ Object

Merges two Hashes. This is similar to ‘Hash#merge`, except the values will be merged recursively (aka deep merge) when both values for a key are Hashes, and values for the left argument are preferred over values on the right.

If you pass in a third argument, it will be acted upon directly instead of creating a new Hash. Usually used with ‘merge_defaults!`, which merges values from `right` into `left`.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/teacup/merge_defaults.rb', line 12

def merge_defaults(left, right, target={})
  if target != left
    left.each do |key, value|
      if target.has_key? key and value.is_a?(Hash) and target[key].is_a?(Hash)
        target[key] = Teacup::merge_defaults(target[key], value)
      else
        if value.is_a?(Hash)
          # make a copy of the Hash
          value = Teacup::merge_defaults!({}, value)
        end
        target[key] = value
      end
    end
  end

  right.each do |key, value|
    if not target.has_key? key
      target[key] = value
    elsif value.is_a?(Hash) and left[key].is_a?(Hash)
      target[key] = Teacup::merge_defaults(left[key], value, (left==target ? left[key] : {}))
    elsif key == :constraints
      left[key] = merge_constraints(left[key], value)
    end
  end
  target
end

.merge_defaults!(left, right) ⇒ Object

modifies left by passing it in as the ‘target`.



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

def merge_defaults!(left, right)
  Teacup::merge_defaults(left, right, left)
end

.should_restyle!(&block) ⇒ Object



18
19
20
21
22
23
24
25
26
27
# File 'lib/teacup/restyle.rb', line 18

def should_restyle! &block
  if block
    _dont_restyle = dont_restyle?
    @dont_restyle = nil
    yield
    @dont_restyle = _dont_restyle
  else
    @dont_restyle = nil
  end
end

.should_restyle?Boolean

Returns:

  • (Boolean)


8
9
10
# File 'lib/teacup/restyle.rb', line 8

def should_restyle?
  return ! self.dont_restyle?
end

.should_restyle_and_blockObject



12
13
14
15
16
# File 'lib/teacup/restyle.rb', line 12

def should_restyle_and_block
  should_restyle = self.should_restyle?
  @dont_restyle = true
  return should_restyle
end

.to_instance(class_or_instance) ⇒ Object



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

def to_instance(class_or_instance)
  if class_or_instance.is_a? Class
    return class_or_instance.new
  else
    return class_or_instance
  end
end