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
# File lib/teacup-ios/handler.rb, line 19 def AppearanceClass @appearance_klass ||= UIView.appearance.class end
# 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
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
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
# 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
# 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
# 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
# File lib/teacup/restyle.rb, line 4 def dont_restyle? @dont_restyle ||= nil end
# 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
# File lib/teacup/handler.rb, line 104 def handlers @teacup_handlers ||= Hash.new{ |hash,klass| hash[klass] = {} } end
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
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
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
# 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
# File lib/teacup/restyle.rb, line 8 def should_restyle? return ! self.dont_restyle? end
# File lib/teacup/restyle.rb, line 12 def should_restyle_and_block should_restyle = self.should_restyle? @dont_restyle = true return should_restyle end
# 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