class Teacup::Stylesheet
Stylesheets in Teacup
act as a central configuration mechanism, they have two aims:
-
Allow you to store “Details” away from the main body of your code. (controllers shouldn't have to be filled with style rules)
-
Allow you to easily re-use configuration in many places.
The API really provides only two methods, {Stylesheet#style} to store properties on the Stylesheet
; and {Stylesheet#query} to get them out again:
@example
stylesheet = Teacup::Stylesheet.new stylesheet.style :buttons, :corners => :rounded # => nil stylesheet.query :buttons # => {:corners => :rounded}
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.
@example
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!"}
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.
@example
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}
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.
Attributes
Public Class Methods
# File lib/teacup/stylesheet.rb, line 78 def [] name stylesheets[name] end
# File lib/teacup/stylesheet.rb, line 82 def []= name, stylesheet stylesheets[name] = stylesheet end
Create a new Stylesheet
.
If a name is provided then a new constant will be created using that name.
@param name, The (optional) name to give. @param &block, The body of the Stylesheet
instance_eval'd.
@example
Teacup::Stylesheet.new(:ipadvertical) do import :ipadbase style :continue_button, top: 50 end Teacup::Stylesheet[:ipadvertical].query(:continue_button) # => {top: 50}
# File lib/teacup/stylesheet.rb, line 105 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
# File lib/teacup/stylesheet.rb, line 74 def stylesheets @stylesheets ||= {} end
Public Instance Methods
# File lib/teacup/stylesheet.rb, line 315 def exclude_instance_vars @exclude_instance_vars ||= [:@name, :@block, :@imported, :@styles, :@stylesheet_cache] end
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.
# File lib/teacup/stylesheet.rb, line 294 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
# File lib/teacup/stylesheet.rb, line 141 def get_stylesheet_cache(stylename, target_class, orientation) self.stylesheet_cache[stylename][target_class][orientation] end
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.
@param Symbol|Teacup::Stylesheet the name of the stylesheet,
or the stylesheet to include.
When defining a stylesheet declaratively, it is better to use the symbol that represents a stylesheet, as the constant may not be defined yet:
@example
Teacup::Stylesheet.new(:ipadvertical) do import :ipadbase import :verticaltweaks end
If you are using anonymous stylesheets however, then it will be necessary to pass an actual stylesheet object.
@example
@stylesheet.import(base_stylesheet)
# File lib/teacup/stylesheet.rb, line 172 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
# File lib/teacup/stylesheet.rb, line 319 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
The array of Stylesheets that have been imported into this one.
@return Array
# File lib/teacup/stylesheet.rb, line 277 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
A unique and hopefully meaningful description of this Object.
@return String
# File lib/teacup/stylesheet.rb, line 270 def inspect "#{self.class.name}[#{name.inspect}] = #{styles.inspect}" end
supports the 'new-style' stylesheet syntax. @example
# File lib/teacup/stylesheet.rb, line 330 def method_missing(stylename, &styles) properties = Limelight.new(&styles).styles style(stylename, properties) end
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.
@param Symbol stylename, the stylename to look up. @return Hash[Symbol, *] the resulting properties. @example
Teacup::Stylesheet[:ipadbase].query(:continue_button) # => {backgroundImage: UIImage.imageNamed("big_red_shiny_button"), title: "Continue!", top: 50}
# File lib/teacup/stylesheet.rb, line 216 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
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.
@return true if the block was run. false otherwise
# File lib/teacup/stylesheet.rb, line 195 def run_block if @block _block = @block @block = nil instance_eval &_block true end end
# File lib/teacup/stylesheet.rb, line 145 def set_stylesheet_cache(stylename, target_class, orientation, value) self.stylesheet_cache[stylename][target_class][orientation] = value end
Add a set of properties for a given stylename or multiple stylenames.
@param Symbol, *stylename @param Hash[Symbol, Object], properties
@example
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
# File lib/teacup/stylesheet.rb, line 248 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
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 @stylesheet_cache object is manipulated directly (to invalidate entries, or the entire cache)
# File lib/teacup/stylesheet.rb, line 129 def stylesheet_cache @stylesheet_cache ||= Hash.new(&lambda do |by_stylename,_stylename| by_stylename[_stylename] = Hash.new(&lambda do |by_target_class,_target_class| if _target_class && ! _target_class.is_a?(Class) _target_class = _target_class.class end by_target_class[_target_class] = {} end.weak!) end.weak!) end
Protected Instance Methods
The list of Stylesheets or names that have been imported into this one.
@return Array
# File lib/teacup/stylesheet.rb, line 340 def imported @imported ||= [] end
The styles hash contains Teacup::Style
objects, which get linked to the current stylesheet.
@return Hash[Symbol, Hash]
# File lib/teacup/stylesheet.rb, line 348 def styles @styles ||= Hash.new do |_styles, stylename| _styles[stylename] = Style.new _styles[stylename].stylename = stylename _styles[stylename].stylesheet = self _styles[stylename] end end