module Teacup

Fixes here should be copied to teacup-osx/style.rb

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

Example:

Teacup::Stylesheet.new :main do
  style :root,
    origin: [0, 0]

  # device returns a bitmask of devices,
  # device? expects a device bitmask, and returns true if it is present in
  # device (the conditions below are equivalent)
  if 0 < device & iPhone || device? iPhone
    style :root, width: 320

    if 0 < device & iPhone5 || device? iPhone5
      style :root, height: 548
    elsif 0 < device & iPhone || device? iPhone
      style :root, height: 460
    end
  elsif 0 < device & iPad || device? iPad
    style :root,
      width: 768,
      height: 1004
  end

  # that's a mess!  and this does the same thing anyway:
  style :root,
    frame: [[0, 0], app_size]
end

Fixes here should be copied to teacup-ios/style.rb

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

Methods to retrieve a subview using the stylename as a key Kinda similar to jQuery-style $(el).find('stylename')

Teacup handlers can modify (or alias) styling methods. For instance, the UIButton class doesn't have a `title` attribute, but we all know that what we mean when we say `title: “button title”` is `setTitle(“button title”, forControlState:UIControlStateNormal)`. A UIButton handler accomplishes this translation.

You can write your own handlers!

Teacup.handler UIButton, :title do |view, value|
  view.setTitle(value, forControlState:UIControlStateNormal)
end

You can declare multiple names in the Teacup.handler method to create aliases for your handler:

Teacup.handler UIButton, :returnKeyType, :returnkey { |view, keytype|
  view.setReturnKeyType(keytype)
}

Since teacup already supports translating a property like `returnKeyType` into the setter `setReturnKeyType`, you could just use an alias here instead. Assign a hash to `Teacup.alias`:

Teacup.alias UIButton, :returnkey => :returnKeyType

Adds methods to the UIViewController/NSViewController/NSWindowController classes to make defining a layout and stylesheet very easy. Also provides rotation methods that analyze

Teacup's View extensions defines some utility functions for View that enable a lot of the magic for Teacup::Layout.

Users of teacup should be able to ignore the contents of this file for the most part.

Constants

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.

VERSION

Public Instance Methods

AppearanceClass() click to toggle source
# File lib/teacup-ios/handler.rb, line 19
def AppearanceClass
  @appearance_klass ||= UIView.appearance.class
end
alias(klass, aliases) click to toggle source
# 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
apply(target, key, value, klass=nil) click to toggle source

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

# 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) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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?() click to toggle source
# File lib/teacup/restyle.rb, line 4
def dont_restyle?
  @dont_restyle ||= nil
end
handler(klass, *stylenames, &block) click to toggle source
# 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
handlers() click to toggle source
# File lib/teacup/handler.rb, line 104
def handlers
  @teacup_handlers ||= Hash.new{ |hash,klass| hash[klass] = {} }
end
merge_constraints(left, right) click to toggle source

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

# 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={}) click to toggle source

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

# 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) click to toggle source

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

# File lib/teacup/merge_defaults.rb, line 80
def merge_defaults!(left, right)
  Teacup::merge_defaults(left, right, left)
end
should_restyle!() { || ... } click to toggle source
# 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?() click to toggle source
# File lib/teacup/restyle.rb, line 8
def should_restyle?
  return ! self.dont_restyle?
end
should_restyle_and_block() click to toggle source
# 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) click to toggle source
# 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