Class: UIC::Presentation

Inherits:
Object
  • Object
show all
Includes:
FileBacked
Defined in:
lib/ruic/presentation.rb

Direct Known Subclasses

Application::Presentation

Instance Attribute Summary

Attributes included from FileBacked

#doc, #file

Instance Method Summary collapse

Methods included from FileBacked

#file_found?, #filename

Constructor Details

#initialize(uip_path) ⇒ Presentation

Returns a new instance of Presentation.



3
4
5
6
# File 'lib/ruic/presentation.rb', line 3

def initialize( uip_path )
	self.file = uip_path
	load_from_file if file_found?
end

Instance Method Details

#asset_by_id(id) ⇒ Object



77
78
79
# File 'lib/ruic/presentation.rb', line 77

def asset_by_id( id )
	(@graph_by_id[id] && asset_for_el( @graph_by_id[id] ))
end

#asset_for_el(el) ⇒ Object



136
137
138
# File 'lib/ruic/presentation.rb', line 136

def asset_for_el(el)
	(@asset_by_el[el] ||= el['class'] ? @class_by_ref[el['class']].new(self,el) : app..new_instance(self,el))
end

#assetsObject

Get an array of all assets in the scene graph, in document order



99
100
101
# File 'lib/ruic/presentation.rb', line 99

def assets
	@graph_by_id.map{ |id,graph_element| asset_for_el(graph_element) }
end

#at(path, root = @graph) ⇒ Object Also known as: /



188
189
190
191
192
193
194
195
196
# File 'lib/ruic/presentation.rb', line 188

def at(path,root=@graph)
	name,path = path.split('.',2)
	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?(graph_element, attribute_name) ⇒ Boolean

Returns:

  • (Boolean)


266
267
268
# File 'lib/ruic/presentation.rb', line 266

def attribute_linked?(graph_element,attribute_name)
	!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][1] && @addsets_by_graph[graph_element][1].key?(attribute_name))
end

#child_assets(parent_graph_el) ⇒ Object



94
95
96
# File 'lib/ruic/presentation.rb', line 94

def child_assets( parent_graph_el )
	parent_graph_el.element_children.map{ |child| asset_for_el(child) }
end

#errorsObject



184
185
186
# File 'lib/ruic/presentation.rb', line 184

def errors
	(file_found? ? [] : ["File not found: '#{file}'"])
end

#errors?Boolean

Returns:

  • (Boolean)


180
181
182
# File 'lib/ruic/presentation.rb', line 180

def errors?
	(!errors.empty?)
end

#find(options = {}) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/ruic/presentation.rb', line 299

def find(options={})
	index = -1
	start = options[:under] ? options[:under].el : @graph
	(options[:attributes]||={})[:name]=options[:name] if options[:name]
	[].tap do |result|
		start.xpath('./descendant::*').each do |el|
			next if options.key?(:type)   && el.name    != options[:type]
			next if options.key?(:slide)  && !has_slide?(el,options[:slide])
			next if options.key?(:master) && master?(el)!= options[:master]
			asset = asset_for_el(el)
			next if options.key?(:attributes) && options[:attributes].any?{ |att,val|
				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 }.any?
						else value != val
					end
				end
			}
			yield asset, index+=1 if block_given?
			result << asset
		end
	end
end

#get_attribute(graph_element, property_name, slide_name_or_index) ⇒ Object



199
200
201
202
203
204
205
# File 'lib/ruic/presentation.rb', line 199

def get_attribute( graph_element, property_name, slide_name_or_index )
	((addsets=@addsets_by_graph[graph_element]) && ( # State (slide) don't have any addsets
		( addsets[slide_name_or_index] && addsets[slide_name_or_index][property_name] ) || # Try for a Set on the specific slide
		( addsets[0] && addsets[0][property_name] ) # …else try the master slide
	) || graph_element[property_name]) # …else try the graph
	# TODO: handle animation (child of addset)
end

#has_slide?(graph_element, slide_name_or_index) ⇒ Boolean

Returns:

  • (Boolean)


257
258
259
260
261
262
263
264
# File 'lib/ruic/presentation.rb', line 257

def has_slide?( graph_element, slide_name_or_index )
	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
		has_slide?( @addsets_by_graph.first.first, slide_name_or_index )
	else
		@addsets_by_graph[graph_element][slide_name_or_index] || @addsets_by_graph[graph_element][0]
	end
end

#image_pathsObject



132
133
134
# File 'lib/ruic/presentation.rb', line 132

def image_paths
	image_usage.keys
end

#image_usageObject

Returns a hash mapping image paths to arrays of the assets referencing them



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ruic/presentation.rb', line 104

def image_usage
	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

#inspectObject



326
327
328
# File 'lib/ruic/presentation.rb', line 326

def inspect
	"<#{self.class} #{File.basename(file)}>"
end

#load_from_fileObject



8
9
10
11
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
38
39
40
41
42
# File 'lib/ruic/presentation.rb', line 8

def load_from_file
	@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 )
		metaklass = case reference.name
			when 'CustomMaterial'
				meta = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData')
				from = app..by_name[ 'MaterialBase' ]
				app..create_class( meta, from, reference.name )
			when 'Effect'
				meta = Nokogiri.XML(File.read(path,encoding:'utf-8')).at('/*/MetaData')
				from = app..by_name[ 'Effect' ]
				app..create_class( meta, from, reference.name )
			when 'Behavior'
				lua  = File.read(path,encoding:'utf-8')
				meta = lua[ /--\[\[(.+?)(?:--)?\]\]/m, 1 ]
				meta = Nokogiri.XML("<MetaData>#{meta}</MetaData>").root
				from = app..by_name[ 'Behavior' ]
				app..create_class( meta, from, reference.name )
		end
		@class_by_ref[ "##{reference['id']}" ] = metaklass
	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?(graph_element) ⇒ Boolean

Is this element added on the master slide?

Returns:

  • (Boolean)


295
296
297
# File 'lib/ruic/presentation.rb', line 295

def master?(graph_element)
	(graph_element == @scene) || !!(@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
end

#master_slide_for(graph_element) ⇒ Object



241
242
243
244
# File 'lib/ruic/presentation.rb', line 241

def master_slide_for( graph_element )
	comp = owning_or_self_component_element( graph_element )
	@logic.at("./State[@component='##{comp['id']}']")
end

#owning_component(graph_element) ⇒ Object



229
230
231
# File 'lib/ruic/presentation.rb', line 229

def owning_component( graph_element )
	asset_for_el( owning_component_element( graph_element ) )
end

#owning_component_element(graph_element) ⇒ Object



233
234
235
# File 'lib/ruic/presentation.rb', line 233

def owning_component_element( graph_element )
	graph_element.at_xpath('(ancestor::Component[1] | ancestor::Scene[1])[last()]')
end

#owning_or_self_component_element(graph_element) ⇒ Object



237
238
239
# File 'lib/ruic/presentation.rb', line 237

def owning_or_self_component_element( graph_element )
	graph_element.at_xpath('(ancestor-or-self::Component[1] | ancestor-or-self::Scene[1])[last()]')
end

#parent_asset(child_graph_el) ⇒ Object



88
89
90
91
92
# File 'lib/ruic/presentation.rb', line 88

def parent_asset( child_graph_el )
	unless child_graph_el==@scene || child_graph_el.parent.nil?
		asset_for_el( child_graph_el.parent )
	end
end

#path_to(el, from_el = nil) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/ruic/presentation.rb', line 155

def path_to( el, from_el=nil )
	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_el && from_el.ancestors('Graph')
		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_documentObject



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ruic/presentation.rb', line 58

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 = {}
	slideindex = {}
	@logic.xpath('.//Add|.//Set').each do |addset|
		graph = @graph_by_id[addset['ref'][1..-1]]
		@graph_by_addset[addset] = graph
		@addsets_by_graph[graph] ||= {}
		slide = addset.parent
		name  = slide['name']
		index = name == 'Master Slide' ? 0 : (slideindex[slide] ||= (slide.index('State') + 1))
		@addsets_by_graph[graph][name]  = addset
		@addsets_by_graph[graph][index] = addset
	end
end

#referenced_filesObject



143
144
145
146
147
148
149
# File 'lib/ruic/presentation.rb', line 143

def referenced_files
	(
		(images + behaviors + effects + meshes + materials ).map(&:file)
		+ effects.flat_map(&:images)
		+ fonts
	).sort_by{ |f| parts = f.split(/[\/\\]/); [parts.length,parts] }
end

#replace_asset(existing_asset, new_type, attributes = {}) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
# File 'lib/ruic/presentation.rb', line 282

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

#save!Object



50
51
52
# File 'lib/ruic/presentation.rb', line 50

def save!
	File.open(file,'w:utf-8'){ |f| f << to_xml }
end

#save_as(new_file) ⇒ Object



54
55
56
# File 'lib/ruic/presentation.rb', line 54

def save_as(new_file)
	File.open(new_file,'w:utf-8'){ |f| f << to_xml }
end

#sceneObject



151
152
153
# File 'lib/ruic/presentation.rb', line 151

def scene
	asset_for_el( @scene )
end

#set_attribute(graph_element, property_name, slide_name_or_index, str) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/ruic/presentation.rb', line 207

def set_attribute( graph_element, property_name, slide_name_or_index, str )
	if attribute_linked?( graph_element, 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 slide_name_or_index
				@addsets_by_graph[graph_element][slide_name_or_index][property_name] = str
			else
				master = master_slide_for( graph_element )
				slide_count = master.xpath('count(./State)').to_i
				0.upto(slide_count).each{ |idx| set_attribute(graph_element,property_name,idx,str) }
			end
		else
			raise "TODO"
		end
	end
end

#slide_index(graph_element) ⇒ Object

Find the index of the slide where an element is added



82
83
84
85
86
# File 'lib/ruic/presentation.rb', line 82

def slide_index(graph_element)
	# TODO: probably faster to .find the first @addsets_by_graph
	slide = @logic.at(".//Add[@ref='##{graph_element['id']}']/..")
	(slide ? slide.xpath('count(ancestor::State) + count(preceding-sibling::State[ancestor::State])').to_i : 0) # the Scene is never added
end

#slides_for(graph_element) ⇒ Object



246
247
248
249
250
251
252
253
254
255
# File 'lib/ruic/presentation.rb', line 246

def slides_for( graph_element )
	@slides_for[graph_element] ||= begin
		slides = []
		master = master_slide_for( graph_element )
		slides << [master,0] if graph_element==@scene || (@addsets_by_graph[graph_element] && @addsets_by_graph[graph_element][0])
		slides.concat( master.xpath('./State').map.with_index{ |el,i| [el,i+1] } )
		slides.map!{ |el,idx| @slides_by_el[el] ||= app..new_instance(self,el).tap{ |s| s.index=idx; s.name=el['name'] } }
		UIC::SlideCollection.new( slides )
	end
end

#to_xmlObject



44
45
46
47
48
# File 'lib/ruic/presentation.rb', line 44

def to_xml
	@doc.to_xml( indent:1, indent_text:"\t" )
	    .gsub( %r{(<\w+(?: [\w:]+="[^"]*")*)(/?>)}i, '\1 \2' )
	    .sub('"?>','" ?>')
end


270
271
272
273
274
275
276
277
278
279
280
# File 'lib/ruic/presentation.rb', line 270

def unlink_attribute(graph_element,attribute_name)
	if master?(graph_element) && attribute_linked?(graph_element,attribute_name)
		master_value = get_attribute( graph_element, attribute_name, 0 )
		slides_for( graph_element ).to_ary[1..-1].each do |slide|
			addset = slide.el.at_xpath( ".//*[@ref='##{graph_element['id']}']" ) || slide.el.add_child("<Set ref='##{graph_element['id']}'/>").first
			addset[attribute_name] = master_value
		end
		rebuild_caches_from_document
		true
	end
end