Class: UIC::Presentation
- Inherits:
-
Object
- Object
- UIC::Presentation
- Includes:
- FileBacked
- Defined in:
- lib/ruic/presentation.rb
Overview
A Presentation
represents a .uip
presentation, created and edited by UI Composer Studio.
Direct Known Subclasses
Instance Attribute Summary
Attributes included from FileBacked
Instance Method Summary collapse
-
#asset_by_id(id) ⇒ MetaData::AssetBase
Find an asset in the presentation based on its internal XML identifier.
-
#assets ⇒ Object
Get an array of all assets in the scene graph, in document order.
-
#at(path, root = @graph) ⇒ MetaData::AssetBase
(also: #/)
Find an element or asset in this presentation by scripting path.
-
#attribute_linked?(asset, attribute_name) ⇒ Boolean
True if this asset's attribute is linked on the master slide.
-
#child_assets(parent_asset) ⇒ Array<MetaData::AssetBase>
Array of scene graph children of the specified asset.
-
#errors ⇒ Array<String>
An array (possibly empty) of all errors in this presentation.
-
#errors? ⇒ Boolean
True if there any errors with the presentation.
-
#find(criteria = {}) {|asset, index| ... } ⇒ Array<MetaData::AssetBase>
Find assets in this presentation matching criteria.
-
#get_attribute(asset, attr_name, slide_name_or_index) ⇒ Object
Fetch the value of an asset's attribute on a particular slide.
-
#has_slide?(asset, slide_name_or_index) ⇒ Boolean
True if the asset exists on the supplied slide.
-
#image_paths ⇒ Array<String>
Array of all image paths referenced by this presentation.
-
#image_usage ⇒ Hash
A mapping of image paths to arrays of the assets referencing them.
-
#initialize(uip_path = nil) ⇒ Presentation
constructor
Create a new presentation.
-
#load_from_file ⇒ nil
Load information for the presentation from disk.
-
#master?(asset) ⇒ Boolean
true
if the asset is added on the master slide. -
#owning_component(asset) ⇒ MetaData::AssetBase
The component (or Scene) asset that owns the supplied asset.
-
#parent_asset(child_asset) ⇒ MetaData::AssetBase
The scene graph parent of the child asset, or
nil
for the Scene. -
#path_to(asset, from_asset = nil) ⇒ String
Generate the script path for an asset in the presentation.
-
#rebuild_caches_from_document ⇒ nil
Update the presentation to be in-sync with the document.
-
#replace_asset(existing_asset, new_type, attributes = {}) ⇒ MetaData::AssetBase
Replace an existing asset with a new kind of asset.
-
#scene ⇒ MetaData::Scene
The root scene asset for the presentation.
-
#set_attribute(asset, property_name, slide_name_or_index, str) ⇒ Object
Set the value of an asset's attribute on a particular slide.
-
#slide_index(asset) ⇒ Integer
The index of the first slide where an asset is added (0 for master, non-zero for non-master).
-
#slides_for(asset) ⇒ SlideCollection
An array-like collection of all slides that the asset is available on.
-
#to_xml ⇒ String
The xml representation of this presentation.
-
#unlink_attribute(asset, attribute_name) ⇒ Boolean
Unlinks a master attribute, yielding distinct values on each slide.
Methods included from FileBacked
#file_found?, #filename, #save!, #save_as
Constructor Details
#initialize(uip_path = nil) ⇒ Presentation
Create a new presentation. If you do not specify the uip_path
to load from, you must
later set the .file =
for the presentation, and then call the #load_from_file method.
8 9 10 11 |
# File 'lib/ruic/presentation.rb', line 8 def initialize( uip_path=nil ) self.file = uip_path load_from_file if file_found? end |
Instance Method Details
#asset_by_id(id) ⇒ MetaData::AssetBase
Find an asset in the presentation based on its internal XML identifier.
91 92 93 |
# File 'lib/ruic/presentation.rb', line 91 def asset_by_id( id ) (@graph_by_id[id] && asset_for_el( @graph_by_id[id] )) end |
#assets ⇒ Object
Get an array of all assets in the scene graph, in document order
122 123 124 |
# File 'lib/ruic/presentation.rb', line 122 def assets @graph_by_id.map{ |id,graph_element| asset_for_el(graph_element) } end |
#at(path, root = @graph) ⇒ MetaData::AssetBase Also known as: /
Find an element or asset in this presentation by scripting path.
- If
root
is supplied, the path is resolved relative to that asset. - If
root
is not supplied, the path is resolved as a root-level path.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/ruic/presentation.rb', line 263 def at(path,root=@graph) name,path = path.split('.',2) root = root.el if root.respond_to?(:el) el = case name when 'parent' then root==@scene ? nil : root.parent when 'Scene' then @scene else root.element_children.find{ |el| asset_for_el(el).name==name } end path ? at(path,el) : asset_for_el(el) if el end |
#attribute_linked?(asset, attribute_name) ⇒ Boolean
Returns true if this asset's attribute is linked on the master slide.
408 409 410 411 |
# File 'lib/ruic/presentation.rb', line 408 def attribute_linked?( asset, attribute_name ) graph_element = asset.el !(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][1] && @addsets_by_graph[graph_element][1].key?(attribute_name)) end |
#child_assets(parent_asset) ⇒ Array<MetaData::AssetBase>
Returns array of scene graph children of the specified asset.
117 118 119 |
# File 'lib/ruic/presentation.rb', line 117 def child_assets( parent_asset ) parent_asset.el.element_children.map{ |child| asset_for_el(child) } end |
#errors ⇒ Array<String>
Returns an array (possibly empty) of all errors in this presentation.
237 238 239 |
# File 'lib/ruic/presentation.rb', line 237 def errors (file_found? ? [] : ["File not found: '#{file}'"]) end |
#errors? ⇒ Boolean
Returns true if there any errors with the presentation.
232 233 234 |
# File 'lib/ruic/presentation.rb', line 232 def errors? (!errors.empty?) end |
#find(criteria = {}) {|asset, index| ... } ⇒ Array<MetaData::AssetBase>
Find assets in this presentation matching criteria.
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/ruic/presentation.rb', line 510 def find(criteria={},&block) index = -1 start = criteria.key?(:_under) ? criteria.delete(:_under).el : @graph [].tap do |result| start.xpath('./descendant::*').each do |el| asset = asset_for_el(el) next unless criteria.all? do |att,val| case att when :_type then el.name == val when :_slide then (asset,val) when :_master then master?(asset)==val else if asset.properties[att.to_s] value = asset[att.to_s].value case val when Regexp then val =~ value.to_s when Numeric then (val-value).abs < 0.001 when Array then value.to_a.zip(val).map{ |a,b| !b || (a-b).abs<0.001 }.all? else value == val end end end end yield asset, index+=1 if block_given? result << asset end end end |
#get_attribute(asset, attr_name, slide_name_or_index) ⇒ Object
Fetch the value of an asset's attribute on a particular slide. Slide 0
is the Master Slide, slide 1
is the first non-master slide.
This method is used internally by assets; accessing attributes directly from the asset is generally more appropriate.
290 291 292 293 294 295 296 297 |
# File 'lib/ruic/presentation.rb', line 290 def get_attribute( asset, attr_name, ) graph_element = asset.el ((addsets=@addsets_by_graph[graph_element]) && ( # State (slide) don't have any addsets ( addsets[] && addsets[][attr_name] ) || # Try for a Set on the specific slide ( addsets[0] && addsets[0][attr_name] ) # …else try the master slide ) || graph_element[attr_name]) # …else try the graph # TODO: handle animation (child of addset) end |
#has_slide?(asset, slide_name_or_index) ⇒ Boolean
Returns true if the asset exists on the supplied slide.
388 389 390 391 392 393 394 395 396 |
# File 'lib/ruic/presentation.rb', line 388 def ( asset, ) graph_element = asset.el if graph_element == @scene # The scene is never actually added, so we'll treat it just like the first add, which is on the master slide of the scene ( asset_for_el( @addsets_by_graph.first.first ), ) else @addsets_by_graph[graph_element][] || @addsets_by_graph[graph_element][0] end end |
#image_paths ⇒ Array<String>
Returns array of all image paths referenced by this presentation.
157 158 159 |
# File 'lib/ruic/presentation.rb', line 157 def image_paths image_usage.keys end |
#image_usage ⇒ Hash
Returns a mapping of image paths to arrays of the assets referencing them.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ruic/presentation.rb', line 127 def image_usage # TODO: this returns the same asset multiple times, with no indication of which property is using it; should switch to an Asset/Property pair, or some such. asset_types = app..by_name.values + @class_by_ref.values image_properties_by_type = asset_types.flat_map do |type| type.properties.values .select{ |property| property.type=='Image' || property.type == 'Texture' } .map{ |property| [type,property] } end.group_by(&:first).tap{ |x| x.each{ |t,a| a.map!(&:last) } } Hash[ assets.each_with_object({}) do |asset,usage| if properties = image_properties_by_type[asset.class] properties.each do |property| asset[property.name].values.compact.each do |value| value = value['sourcepath'] if property.type=='Image' unless value.nil? || value.empty? value = value.gsub('\\','/').sub(/^.\//,'') usage[value] ||= [] usage[value] << asset end end end end end.sort_by do |path,assets| parts = path.downcase.split '/' [ parts.length, parts ] end ].tap{ |h| h.extend(UIC::PresentableHash) } end |
#load_from_file ⇒ nil
Load information for the presentation from disk.
If you supply a path to a .uip
file when creating the presentation
this method is automatically called.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/ruic/presentation.rb', line 17 def load_from_file # TODO: this method assumes an application to find the metadata on; the metadata should be part of this class instance instead, shared with the app when present @doc = Nokogiri.XML( File.read( file, encoding:'utf-8' ), &:noblanks ) @graph = @doc.at('Graph') @scene = @graph.at('Scene') @logic = @doc.at('Logic') @class_by_ref = {} @doc.xpath('/UIP/Project/Classes/*').each do |reference| path = app.path_to(reference['sourcepath']) raise "Cannot find file '#{path}' referenced by #{self.inspect}" unless File.exist?( path ) = case reference.name when 'CustomMaterial' = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData') from = app..by_name[ 'MaterialBase' ] app..create_class( , from, reference.name ) when 'Effect' = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData') from = app..by_name[ 'Effect' ] app..create_class( , from, reference.name ) when 'Behavior' lua = File.read(path,encoding:'utf-8') = lua[ /--\[\[(.+?)(?:--)?\]\]/m, 1 ] = Nokogiri.XML("<MetaData>#{}</MetaData>").root from = app..by_name[ 'Behavior' ] app..create_class( , from, reference.name ) end @class_by_ref[ "##{reference['id']}" ] = nil end rebuild_caches_from_document @asset_by_el = {} # indexed by asset graph element @slides_for = {} # indexed by asset graph element @slides_by_el = {} # indexed by slide state element end |
#master?(asset) ⇒ Boolean
Returns true
if the asset is added on the master slide.
453 454 455 456 |
# File 'lib/ruic/presentation.rb', line 453 def master?(asset) graph_element = asset.el (graph_element == @scene) || !!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0]) end |
#owning_component(asset) ⇒ MetaData::AssetBase
Returns the component (or Scene) asset that owns the supplied asset.
342 343 344 |
# File 'lib/ruic/presentation.rb', line 342 def owning_component( asset ) asset_for_el( owning_component_element( asset.el ) ) end |
#parent_asset(child_asset) ⇒ MetaData::AssetBase
Returns the scene graph parent of the child asset, or nil
for the Scene.
107 108 109 110 111 112 |
# File 'lib/ruic/presentation.rb', line 107 def parent_asset( child_asset ) child_graph_el = child_asset.el unless child_graph_el==@scene || child_graph_el.parent.nil? asset_for_el( child_graph_el.parent ) end end |
#path_to(asset, from_asset = nil) ⇒ String
Generate the script path for an asset in the presentation.
- If
from_asset
is supplied the path will be relative to that asset (e.g."parent.parent.Group.Model"
). - If
from_asset
is omitted the path will be absolute (e.g."Scene.Layer.Group.Model"
).
This is used internally by MetaData::AssetBase#path and MetaData::AssetBase#path_to; those methods are usually more convenient to use.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/ruic/presentation.rb', line 203 def path_to( asset, from_asset=nil ) el = asset.el to_parts = if el.ancestors('Graph') [].tap{ |parts| until el==@graph parts.unshift asset_for_el(el).name el = el.parent end } end if from_asset && from_asset.el.ancestors('Graph') from_el = from_asset.el from_parts = [].tap{ |parts| until from_el==@graph parts.unshift asset_for_el(from_el).name from_el = from_el.parent end } until to_parts.empty? || from_parts.empty? || to_parts.first!=from_parts.first to_parts.shift from_parts.shift end to_parts.unshift *(['parent']*from_parts.length) end to_parts.join('.') end |
#rebuild_caches_from_document ⇒ nil
Update the presentation to be in-sync with the document.
Must be called whenever the in-memory representation of the XML document is changed.
Called automatically by all necessary methods; only necessary if script (dangerously)
manipulates the .doc
of the presentation directly.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ruic/presentation.rb', line 68 def rebuild_caches_from_document @graph_by_id = {} @scene.traverse{ |x| @graph_by_id[x['id']]=x if x.is_a?(Nokogiri::XML::Element) } @graph_by_addset = {} @addsets_by_graph = {} = {} @logic.xpath('.//Add|.//Set').each do |addset| graph = @graph_by_id[addset['ref'][1..-1]] @graph_by_addset[addset] = graph @addsets_by_graph[graph] ||= {} = addset.parent name = ['name'] index = name == 'Master Slide' ? 0 : ([] ||= (.index('State') + 1)) @addsets_by_graph[graph][name] = addset @addsets_by_graph[graph][index] = addset end nil end |
#replace_asset(existing_asset, new_type, attributes = {}) ⇒ MetaData::AssetBase
Replace an existing asset with a new kind of asset.
439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/ruic/presentation.rb', line 439 def replace_asset( existing_asset, new_type, attributes={} ) old_el = existing_asset.el new_el = old_el.replace( "<#{new_type}/>" ).first attributes['id'] = old_el['id'] attributes.each{ |att,val| new_el[att.to_s] = val } asset_for_el( new_el ).tap do |new_asset| unsupported_attributes = ".//*[name()='Add' or name()='Set'][@ref='##{old_el['id']}']/@*[name()!='ref' and #{new_asset.properties.keys.map{|p| "name()!='#{p}'"}.join(' and ')}]" @logic.xpath(unsupported_attributes).remove rebuild_caches_from_document end end |
#scene ⇒ MetaData::Scene
Returns the root scene asset for the presentation.
177 178 179 |
# File 'lib/ruic/presentation.rb', line 177 def scene asset_for_el( @scene ) end |
#set_attribute(asset, property_name, slide_name_or_index, str) ⇒ Object
Set the value of an asset's attribute on a particular slide. Slide 0
is the Master Slide, slide 1
is the first non-master slide.
This method is used internally by assets; accessing attributes directly from the asset is generally more appropriate.
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/ruic/presentation.rb', line 317 def set_attribute( asset, property_name, , str ) graph_element = asset.el if attribute_linked?( asset, property_name ) if @addsets_by_graph[graph_element] @addsets_by_graph[graph_element][0][property_name] = str else raise "TODO" end else if @addsets_by_graph[graph_element] if @addsets_by_graph[graph_element][][property_name] = str else master = ( graph_element ) = master.xpath('count(./State)').to_i 0.upto().each{ |idx| set_attribute(asset,property_name,idx,str) } end else raise "TODO" end end end |
#slide_index(asset) ⇒ Integer
Returns the index of the first slide where an asset is added (0 for master, non-zero for non-master).
97 98 99 100 101 102 |
# File 'lib/ruic/presentation.rb', line 97 def (asset) # TODO: probably faster to .find the first @addsets_by_graph id = asset.el['id'] = @logic.at(".//Add[@ref='##{id}']/..") ( ? .xpath('count(ancestor::State) + count(preceding-sibling::State[ancestor::State])').to_i : 0) # the Scene is never added end |
#slides_for(asset) ⇒ SlideCollection
Returns an array-like collection of all slides that the asset is available on.
374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/ruic/presentation.rb', line 374 def ( asset ) graph_element = asset.el @slides_for[graph_element] ||= begin = [] master = ( graph_element ) << [master,0] if graph_element==@scene || (@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0]) .concat( master.xpath('./State').map.with_index{ |el,i| [el,i+1] } ) .map!{ |el,idx| @slides_by_el[el] ||= app..new_instance(self,el).tap{ |s| s.index=idx; s.name=el['name'] } } UIC::SlideCollection.new( ) end end |
#to_xml ⇒ String
Returns the xml representation of this presentation. Formatted to match UI Composer Studio's formatting as closely as possible (for minimal diffs after update).
56 57 58 59 60 |
# File 'lib/ruic/presentation.rb', line 56 def to_xml doc.to_xml( indent:1, indent_text:"\t" ) .gsub( %r{(<\w+(?: [\w:]+="[^"]*")*)(/?>)}i, '\1 \2' ) .sub('"?>','" ?>') end |
#unlink_attribute(asset, attribute_name) ⇒ Boolean
Unlinks a master attribute, yielding distinct values on each slide. If the asset is not on the master slide, or the attribute is already unlinked, no change occurs.
418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/ruic/presentation.rb', line 418 def unlink_attribute(asset,attribute_name) graph_element = asset.el if master?(asset) && attribute_linked?(asset,attribute_name) master_value = get_attribute( asset, attribute_name, 0 ) ( asset ).to_ary[1..-1].each do || addset = .el.at_xpath( ".//*[@ref='##{graph_element['id']}']" ) || .el.add_child("<Set ref='##{graph_element['id']}'/>").first addset[attribute_name] = master_value end rebuild_caches_from_document true else false end end |