RUIC 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +201 -0
  4. data/bin/ruic +52 -0
  5. data/gui/MetaData.xml +387 -0
  6. data/gui/TODO +2 -0
  7. data/gui/appattributesmodel.rb +51 -0
  8. data/gui/appelementsmodel.rb +126 -0
  9. data/gui/launch.rb +20 -0
  10. data/gui/makefile +14 -0
  11. data/gui/resources.qrc +37 -0
  12. data/gui/resources/images/Objects-Behavior-Normal.png +0 -0
  13. data/gui/resources/images/Objects-Camera-Normal.png +0 -0
  14. data/gui/resources/images/Objects-Component-Normal.png +0 -0
  15. data/gui/resources/images/Objects-Effect-Normal.png +0 -0
  16. data/gui/resources/images/Objects-Group-Normal.png +0 -0
  17. data/gui/resources/images/Objects-Image-Normal.png +0 -0
  18. data/gui/resources/images/Objects-Layer-Normal.png +0 -0
  19. data/gui/resources/images/Objects-Light-Normal.png +0 -0
  20. data/gui/resources/images/Objects-Material-Normal.png +0 -0
  21. data/gui/resources/images/Objects-Model-Normal.png +0 -0
  22. data/gui/resources/images/Objects-Music-Normal.png +0 -0
  23. data/gui/resources/images/Objects-Property-Normal.png +0 -0
  24. data/gui/resources/images/Objects-References-Normal.png +0 -0
  25. data/gui/resources/images/Objects-Scene-Normal.png +0 -0
  26. data/gui/resources/images/Objects-Sound-Normal.png +0 -0
  27. data/gui/resources/images/Objects-Text-Normal.png +0 -0
  28. data/gui/resources/images/Objects-Unknown-Normal.png +0 -0
  29. data/gui/resources/images/Objects-Video-Normal.png +0 -0
  30. data/gui/resources/images/SCXML.ico +0 -0
  31. data/gui/resources/images/TSCXML.ico +0 -0
  32. data/gui/resources/images/UIA.ico +0 -0
  33. data/gui/resources/images/clipboard.png +0 -0
  34. data/gui/resources/images/console_arrow.png +0 -0
  35. data/gui/resources/images/cross.png +0 -0
  36. data/gui/resources/images/currentline.png +0 -0
  37. data/gui/resources/images/disk-black.png +0 -0
  38. data/gui/resources/images/disks-black.png +0 -0
  39. data/gui/resources/images/document--plus.png +0 -0
  40. data/gui/resources/images/document-copy.png +0 -0
  41. data/gui/resources/images/executable-actions-delete-active.png +0 -0
  42. data/gui/resources/images/executable-actions-delete-idle.png +0 -0
  43. data/gui/resources/images/executable-actions-enter-badge.png +0 -0
  44. data/gui/resources/images/executable-actions-exit-badge.png +0 -0
  45. data/gui/resources/images/folder-horizontal--plus.png +0 -0
  46. data/gui/resources/images/folder-horizontal-open.png +0 -0
  47. data/gui/resources/images/gear.png +0 -0
  48. data/gui/resources/images/invalid_breakpoint.png +0 -0
  49. data/gui/resources/images/scissors-blue.png +0 -0
  50. data/gui/resources/images/slide-16-active.png +0 -0
  51. data/gui/resources/images/slide-16-master.png +0 -0
  52. data/gui/resources/images/slide-16-normal.png +0 -0
  53. data/gui/resources/images/slide-32-active.png +0 -0
  54. data/gui/resources/images/slide-32-master.png +0 -0
  55. data/gui/resources/images/slide-32-normal.png +0 -0
  56. data/gui/resources/images/studio_architect32.ico +0 -0
  57. data/gui/resources/images/studio_architect32.png +0 -0
  58. data/gui/resources/images/textfile_icon.png +0 -0
  59. data/gui/resources/style/checkbox.png +0 -0
  60. data/gui/resources/style/dark.qss +459 -0
  61. data/gui/resources/style/down_arrow.png +0 -0
  62. data/gui/resources/style/handle.png +0 -0
  63. data/gui/window.rb +90 -0
  64. data/gui/window.ui +753 -0
  65. data/lib/ruic.rb +59 -0
  66. data/lib/ruic/application.rb +129 -0
  67. data/lib/ruic/asset_classes.rb +448 -0
  68. data/lib/ruic/behaviors.rb +31 -0
  69. data/lib/ruic/interfaces.rb +36 -0
  70. data/lib/ruic/presentation.rb +354 -0
  71. data/lib/ruic/statemachine.rb +111 -0
  72. data/lib/ruic/version.rb +3 -0
  73. data/ruic.gemspec +23 -0
  74. data/test/MetaData.xml +387 -0
  75. data/test/customclasses.ruic +21 -0
  76. data/test/filtering.ruic +39 -0
  77. data/test/nonmaster.ruic +21 -0
  78. data/test/projects/CustomClasses/Brush Strokes.effect +38 -0
  79. data/test/projects/CustomClasses/CustomClasses.uia +6 -0
  80. data/test/projects/CustomClasses/CustomClasses.uip +34 -0
  81. data/test/projects/CustomClasses/copper.material +194 -0
  82. data/test/projects/CustomClasses/maps/UV-Checker.png +0 -0
  83. data/test/projects/CustomClasses/maps/effects/brushnoise.dds +0 -0
  84. data/test/projects/CustomClasses/maps/materials/spherical_checker.png +0 -0
  85. data/test/projects/ReferencedMaterials/ReferencedMaterials.uia +6 -0
  86. data/test/projects/ReferencedMaterials/ReferencedMaterials.uip +39 -0
  87. data/test/properties.ruic +81 -0
  88. data/test/referencematerials.ruic +53 -0
  89. data/test/usage.ruic +20 -0
  90. metadata +166 -0
@@ -0,0 +1,59 @@
1
+ #encoding:utf-8
2
+ class RUIC; end
3
+ module UIC; end
4
+
5
+ require 'nokogiri'
6
+ require_relative 'ruic/version'
7
+ require_relative 'ruic/asset_classes'
8
+ require_relative 'ruic/interfaces'
9
+ require_relative 'ruic/application'
10
+ require_relative 'ruic/behaviors'
11
+ require_relative 'ruic/statemachine'
12
+ require_relative 'ruic/presentation'
13
+
14
+ class RUIC
15
+ DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
16
+ def self.run(ruic_path)
17
+ script = File.read(ruic_path,encoding:'utf-8')
18
+ Dir.chdir(File.dirname(ruic_path)) do
19
+ self.new.instance_eval(script,ruic_path)
20
+ end
21
+ end
22
+ def initialize( metadata=DEFAULTMETADATA )
23
+ @metadata = metadata
24
+ @apps = {}
25
+ end
26
+ def metadata(path)
27
+ @metadata = path
28
+ end
29
+ def uia(path)
30
+ meta = UIC.Meta @metadata
31
+ name = @apps.empty? ? :app : :"app#{@apps.length+1}"
32
+ @apps[name] = UIC.App(meta,path)
33
+ end
34
+ def method_missing(name,*a)
35
+ @apps[name] || super
36
+ end
37
+ def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
38
+ if block && condition==:CONDITIONNOTSUPPLIED || condition.is_a?(String)
39
+ msg = condition.is_a?(String) ? condition : yield
40
+ condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
41
+ end
42
+ unless condition
43
+ file, line, _ = caller.first.split(':')
44
+ puts "#{msg && "#{msg} : "}assertion failed (#{file} line #{line})"
45
+ exit 1
46
+ end
47
+ end
48
+ def show(*a); puts *a.map(&:to_s); end
49
+ end
50
+
51
+ def RUIC(file_path=nil,&block)
52
+ if block
53
+ Dir.chdir(File.dirname($0)) do
54
+ RUIC.new.instance_eval(&block)
55
+ end
56
+ else
57
+ RUIC.run(file_path)
58
+ end
59
+ end
@@ -0,0 +1,129 @@
1
+ class UIC::Application
2
+ include UIC::FileBacked
3
+
4
+ attr_reader :metadata
5
+ def initialize(metadata,uia_path)
6
+ @metadata = metadata
7
+ self.file = uia_path
8
+ load_from_file if file_found?
9
+ end
10
+
11
+ def load_from_file
12
+ self.doc = Nokogiri.XML(File.read(file,encoding:'utf-8'))
13
+ @assets = @doc.search('assets *').map do |el|
14
+ case el.name
15
+ when 'behavior' then UIC::Application::Behavior
16
+ when 'statemachine' then UIC::Application::StateMachine
17
+ when 'presentation' then UIC::Application::Presentation
18
+ when 'renderplugin' then UIC::Application::RenderPlugin
19
+ end.new(self,el)
20
+ end.group_by{ |asset| asset.el.name }
21
+ end
22
+
23
+ def errors?
24
+ !errors.empty?
25
+ end
26
+
27
+ def errors
28
+ file_found? ? assets.flat_map(&:errors) : ["File not found: '#{file}'"]
29
+ end
30
+
31
+ def [](asset_id_or_path)
32
+ all = assets
33
+ if asset_id_or_path.start_with?('#')
34
+ id = asset_id_or_path[1..-1]
35
+ all.find{ |asset| asset.id==id }
36
+ else
37
+ full_path = File.expand_path(asset_id_or_path,File.dirname(file))
38
+ all.find{ |asset| asset.file==full_path }
39
+ end
40
+ end
41
+
42
+ def unused_files
43
+ referenced_files - directory_files
44
+ end
45
+
46
+ def referenced_files
47
+ # TODO: state machines can reference external scripts
48
+ # TODO: behaviors can reference external scripts
49
+ assets.map{ |asset| path_to(asset.src) }
50
+ + presentations.flat_map{ |pres| pres.presentation.referenced_files }
51
+ end
52
+
53
+ def assets
54
+ @assets.values.inject(:+)
55
+ end
56
+
57
+ def main_presentation
58
+ initial_id = @doc.at('assets')['initial']
59
+ presos = presentations
60
+ presos.find{ |pres| pres.id==initial_id } || presos.first
61
+ end
62
+
63
+ def main_presentation=(presentation)
64
+ # TODO: set to Presentation or PresentationAsset
65
+ # TODO: create a unique ID if none exists
66
+ @doc.at('assets')['initial'] = presentation.id
67
+ end
68
+
69
+ def image_usage
70
+ Hash[
71
+ (presentations + statemachines)
72
+ .map(&:image_usage)
73
+ .inject{ |h1,h2| h1.merge(h2){ |path,els1,els2| [*els1,*els2] } }
74
+ .sort_by do |path,assets|
75
+ parts = path.downcase.split '/'
76
+ [ parts.length, parts ]
77
+ end
78
+ ].tap{ |h| h.extend(UIC::PresentableHash) }
79
+ end
80
+
81
+ def image_paths
82
+ image_usage.keys
83
+ end
84
+
85
+ def presentations
86
+ @assets['presentation'] ||= []
87
+ end
88
+
89
+ def behaviors
90
+ @assets['behavior'] ||= []
91
+ end
92
+
93
+ def statemachines
94
+ @assets['statemachine'] ||= []
95
+ end
96
+
97
+ def renderplugins
98
+ @assets['renderplugin'] ||= []
99
+ end
100
+
101
+ def save_all!
102
+ # save!
103
+ presentations.each(&:save!)
104
+ end
105
+
106
+ def at(path)
107
+ parts = path.split(':')
108
+ preso = parts.length==2 ? self["##{parts.first}"] : main_presentation
109
+ raise "Cannot find presentation for #{id}" unless preso
110
+ preso.at(parts.last)
111
+ end
112
+ alias_method :/, :at
113
+
114
+ def xml
115
+ @doc.to_xml
116
+ end
117
+ end
118
+
119
+ class << UIC
120
+ def Application(metadata,uia_path)
121
+ UIC::Application.new( metadata, uia_path )
122
+ end
123
+ alias_method :App, :Application
124
+ end
125
+
126
+ __END__
127
+ <?xml version="1.0" encoding="UTF-8" ?>
128
+ <application xmlns="http://nvidia.com/uicomposer"><assets/></application>
129
+
@@ -0,0 +1,448 @@
1
+ #encoding: utf-8
2
+ require 'set'
3
+ $depth = 0
4
+ class UIC::Asset
5
+ class Root
6
+ @properties = {}
7
+ class << self
8
+ attr_reader :name
9
+ def properties
10
+ (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
11
+ end
12
+
13
+ def each
14
+ (@by_name.values - [self]).each{ |klass| yield klass }
15
+ end
16
+ include Enumerable
17
+
18
+ def inspect
19
+ "<#{@name}>"
20
+ end
21
+ end
22
+
23
+ def properties
24
+ self.class.properties
25
+ end
26
+
27
+ def at(sub_path)
28
+ presentation.at(sub_path,@el)
29
+ end
30
+ alias_method :/, :at
31
+
32
+ attr_accessor :presentation, :el
33
+ def initialize( presentation, element )
34
+ @presentation = presentation
35
+ @el = element
36
+ end
37
+
38
+ def type
39
+ self.class.name
40
+ # self.class.name.split('::').last
41
+ end
42
+
43
+ def parent
44
+ presentation.parent_asset(@el)
45
+ end
46
+
47
+ def children
48
+ presentation.child_assets(@el)
49
+ end
50
+
51
+ def find(criteria={},&block)
52
+ criteria[:under] ||= self
53
+ presentation.find(criteria,&block)
54
+ end
55
+
56
+ # Find the owning component (even if you are a component)
57
+ def component
58
+ presentation.owning_component(@el)
59
+ end
60
+
61
+ def component?
62
+ @el.name == 'Component'
63
+ end
64
+
65
+ def master?
66
+ presentation.master?(@el)
67
+ end
68
+
69
+ def slide?
70
+ false
71
+ end
72
+
73
+ def has_slide?(slide_name_or_index)
74
+ presentation.has_slide?(@el,slide_name_or_index)
75
+ end
76
+
77
+ def slides
78
+ presentation.slides_for(@el)
79
+ end
80
+
81
+ def on_slide(slide_name_or_index)
82
+ if has_slide?(slide_name_or_index)
83
+ UIC::SlideValues.new( self, slide_name_or_index )
84
+ end
85
+ end
86
+
87
+ def path
88
+ @path ||= @presentation.path_to(@el)
89
+ end
90
+
91
+ def name
92
+ properties['name'].get( self, presentation.slide_index(@el) )
93
+ end
94
+
95
+ def name=( new_name )
96
+ properties['name'].set( self, new_name, presentation.slide_index(@el) )
97
+ end
98
+
99
+ # Get the value(s) of an attribute
100
+ def [](attribute_name, slide_name_or_index=nil)
101
+ if property = properties[attribute_name]
102
+ if slide_name_or_index
103
+ property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
104
+ else
105
+ UIC::ValuesPerSlide.new(@presentation,self,property)
106
+ end
107
+ end
108
+ end
109
+
110
+ # Set the value of an attribute, either across all slides, or on a particular slide
111
+ # el['foo'] = 42
112
+ # el['foo',0] = 42
113
+ def []=( attribute_name, slide_name_or_index=nil, new_value )
114
+ if property = properties[attribute_name] then
115
+ property.set(self,new_value,slide_name_or_index)
116
+ end
117
+ end
118
+
119
+ def to_xml
120
+ @el.to_xml
121
+ end
122
+ def inspect
123
+ "<asset #{@el.name}##{@el['id']}>"
124
+ end
125
+
126
+ def to_s
127
+ "<#{type} #{path}>"
128
+ end
129
+
130
+ def ==(other)
131
+ (self.class==other.class) && (el==other.el)
132
+ end
133
+ alias_method :eql?, :==
134
+ end
135
+
136
+ attr_reader :by_name
137
+
138
+ HIER = {}
139
+ %w[Asset Slide Scene].each{ |s| HIER[s] = 'Root' }
140
+ %w[Node Behavior Effect Image Layer MaterialBase RenderPlugin].each{ |s| HIER[s]='Asset' }
141
+ %w[Camera Component Group Light Model Text].each{ |s| HIER[s]='Node' }
142
+ %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
143
+
144
+ def initialize(xml)
145
+ @by_name = {'Root'=>Root}
146
+
147
+ doc = Nokogiri.XML(xml)
148
+ hack_in_slide_names!(doc)
149
+
150
+ HIER.each do |class_name,parent_class_name|
151
+ parent_class = @by_name[parent_class_name]
152
+ el = doc.root.at(class_name)
153
+ @by_name[class_name] = create_class(el,parent_class,el.name)
154
+ UIC::Asset.const_set( el.name, @by_name[class_name] ) # give the class instance a name by pointing a constant to it :/
155
+ end
156
+
157
+ # Extend well-known classes with script interfaces after they are created
158
+ @by_name['State'] = @by_name['Slide']
159
+ @by_name['Slide'].instance_eval do
160
+ attr_accessor :index, :name
161
+ define_method :inspect do
162
+ "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
163
+ end
164
+ define_method(:slide?){ true }
165
+ end
166
+
167
+ refmat = @by_name['ReferencedMaterial']
168
+ @by_name['MaterialBase'].instance_eval do
169
+ define_method :replace_with_referenced_material do
170
+ type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
171
+ end
172
+ end
173
+ end
174
+
175
+ # Creates a class from MetaData.xml with accessors for the <Property> listed.
176
+ # Instances of the class are associated with a presentation and know how to
177
+ # get/set values in that XML based on value types, slides, defaults.
178
+ # Also used to create classes from effects, materials, and behavior preambles.
179
+ def create_class(el,parent_class,name='CustomAsset')
180
+ Class.new(parent_class) do
181
+ @name = name
182
+ @properties = Hash[ el.css("Property").map do |e|
183
+ type = e['type'] || (e['list'] ? 'String' : 'Float')
184
+ type = "Float" if type=="float"
185
+ property = UIC::Property.const_get(type).new(e)
186
+ [ property.name, UIC::Property.const_get(type).new(e) ]
187
+ end ]
188
+ end
189
+ end
190
+
191
+ def new_instance(presentation,el)
192
+ @by_name[el.name].new(presentation,el)
193
+ end
194
+
195
+ def hack_in_slide_names!(doc)
196
+ doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
197
+ end
198
+ end
199
+
200
+ def UIC.Meta(metadata_path)
201
+ UIC::Asset.new(File.read(metadata_path,encoding:'utf-8'))
202
+ end
203
+
204
+ class UIC::Property
205
+ class << self; attr_accessor :default; end
206
+ def initialize(el); @el = el; end
207
+ def name; @name||=@el['name']; end
208
+ def type; @type||=@el['type']; end
209
+ def formal; @formal||=@el['formalName'] || @el['name']; end
210
+ def min; @el['min']; end
211
+ def max; @el['max']; end
212
+ def description; @desc||=@el['description']; end
213
+ def default; @def ||= (@el['default'] || self.class.default); end
214
+ def get(asset,slide)
215
+ if asset.slide? || asset.has_slide?(slide)
216
+ asset.presentation.get_attribute(asset.el,name,slide) || default
217
+ end
218
+ end
219
+ def set(asset,new_value,slide_name_or_index)
220
+ asset.presentation.set_attribute(asset.el,name,slide_name_or_index,new_value)
221
+ end
222
+ def inspect
223
+ "<#{type} '#{name}'>"
224
+ end
225
+
226
+ class String < self
227
+ self.default = ''
228
+ end
229
+ MultiLineString = String
230
+
231
+ class Float < self
232
+ self.default = 0.0
233
+ def get(asset,slide); super.to_f; end
234
+ end
235
+ class Long < self
236
+ self.default = 0
237
+ def get(asset,slide); super.to_i; end
238
+ end
239
+ class Boolean < self
240
+ self.default = false
241
+ def get(asset,slide); super=='True'; end
242
+ def set(asset,new_value,slide_name_or_index)
243
+ super( asset, new_value ? 'True' : 'False', slide_name_or_index )
244
+ end
245
+ end
246
+ class Vector < self
247
+ self.default = '0 0 0'
248
+ def get(asset,slide)
249
+ VectorValue.new(asset,self,slide,super)
250
+ end
251
+ def set(asset,new_value,slide_name_or_index)
252
+ new_value = new_value.join(' ') if new_value.is_a?(Array)
253
+ super( asset, new_value, slide_name_or_index )
254
+ end
255
+ end
256
+ Rotation = Vector
257
+ Color = Vector
258
+ class Image < self
259
+ self.default = nil
260
+ def get(asset,slide)
261
+ if idref = super
262
+ result = asset.presentation.asset_by_id( idref[1..-1] )
263
+ slide ? result.on_slide( slide ) : result
264
+ end
265
+ end
266
+ def set(asset,new_value,slide)
267
+ raise "Setting image attributes not yet supported"
268
+ end
269
+ end
270
+ class Texture < String
271
+ def get(asset,slide)
272
+ if path=super
273
+ path.empty? ? nil : path
274
+ end
275
+ end
276
+ end
277
+
278
+ class ObjectRef < self
279
+ self.default = nil
280
+ def get(asset,slide)
281
+ ref = super
282
+ type = :absolute
283
+ obj = nil
284
+ unless ref=='' || ref.nil?
285
+ type = ref[0]=='#' ? :absolute : :path
286
+ ref = type==:absolute ? asset.presentation.asset_by_id( ref[1..-1] ) : asset.presentation.at( ref, asset.el )
287
+ end
288
+ ObjectReference.new(asset,self,slide,ref,type)
289
+ end
290
+ def set(asset,new_object,slide)
291
+ get(asset,slide).object = new_object
292
+ end
293
+ end
294
+
295
+ class ObjectReference
296
+ attr_reader :object, :type
297
+ def initialize(asset,property,slide,object=nil,type=nil)
298
+ @asset = asset
299
+ @name = property.name
300
+ @slide = slide
301
+ @object = object
302
+ @type = type
303
+ end
304
+ def object=(new_object)
305
+ raise "ObjectRef must be set to an asset (not a #{new_object.class.name})" unless new_object.is_a?(UIC::Asset::Root)
306
+ @object = new_object
307
+ write_value!
308
+ end
309
+ def type=(new_type)
310
+ raise "ObjectRef types must be either :absolute or :path (not #{new_type.inspect})" unless [:absolute,:path].include?(new_type)
311
+ @type = new_type
312
+ write_value!
313
+ end
314
+ private
315
+ def write_value!
316
+ path = case @object
317
+ when NilClass then ""
318
+ else case @type
319
+ when :absolute then "##{@object.el['id']}"
320
+ when :path then @asset.presentation.path_to( @object.el, @asset.el ).sub(/^[^:.]+:/,'')
321
+ # when :root then @asset.presentation.path_to( @object.el ).sub(/^[^:.]+:/,'')
322
+ end
323
+ end
324
+ @asset.presentation.set_attribute( @asset.el, @name, @slide, path )
325
+ end
326
+ end
327
+
328
+ Import = String #TODO: a real class
329
+ Mesh = String #TODO: a real class
330
+ Renderable = String #TODO: a real class
331
+ Font = String #TODO: a real class
332
+ FontSize = Long
333
+
334
+ StringListOrInt = String #TODO: a real class
335
+
336
+ class VectorValue
337
+ attr_reader :x, :y, :z
338
+ def initialize(asset,property,slide,str)
339
+ @asset = asset
340
+ @property = property
341
+ @slide = slide
342
+ @x, @y, @z = str.split(/\s+/).map(&:to_f)
343
+ end
344
+ def setall
345
+ @property.set( @asset, to_s, @slide )
346
+ end
347
+ def x=(n); @x=n; setall end
348
+ def y=(n); @y=n; setall end
349
+ def z=(n); @z=n; setall end
350
+ alias_method :r, :x
351
+ alias_method :g, :y
352
+ alias_method :b, :z
353
+ alias_method :r=, :x=
354
+ alias_method :g=, :y=
355
+ alias_method :b=, :z=
356
+ def inspect
357
+ "<#{@asset.path}.#{@property.name}: #{self}>"
358
+ end
359
+ def to_s
360
+ to_a.join(' ')
361
+ end
362
+ def to_a
363
+ [x,y,z]
364
+ end
365
+ end
366
+ end
367
+
368
+ class UIC::SlideCollection
369
+ include Enumerable
370
+ attr_reader :length
371
+ def initialize(slides)
372
+ @length = slides.length-1
373
+ @slides = slides
374
+ @lookup = {}
375
+ slides.each do |s|
376
+ @lookup[s.index] = s
377
+ @lookup[s.name] = s
378
+ end
379
+ end
380
+ def each
381
+ @slides.each{ |s| yield(s) }
382
+ end
383
+ def [](index_or_name)
384
+ @lookup[ index_or_name ]
385
+ end
386
+ def inspect
387
+ "[ #{@slides.map(&:inspect).join ', '} ]"
388
+ end
389
+ def to_ary
390
+ @slides
391
+ end
392
+ end
393
+
394
+ class UIC::ValuesPerSlide
395
+ def initialize(presentation,asset,property)
396
+ raise unless presentation.is_a?(UIC::Presentation)
397
+ raise unless asset.is_a?(UIC::Asset::Root)
398
+ raise unless property.is_a?(UIC::Property)
399
+ @preso = presentation
400
+ @asset = asset
401
+ @el = asset.el
402
+ @property = property
403
+ end
404
+ def value
405
+ values.first
406
+ end
407
+ def [](slide_name_or_index)
408
+ @property.get( @asset, slide_name_or_index )
409
+ end
410
+ def []=(slide_name_or_index,new_value)
411
+ @property.set( @asset, new_value, slide_name_or_index )
412
+ end
413
+ def linked?
414
+ @preso.attribute_linked?(@el,@property.name)
415
+ end
416
+ def unlink
417
+ @preso.unlink_attribute( @el, @property.name )
418
+ end
419
+ def link
420
+ @preso.link_attribute( @el, @property.name )
421
+ end
422
+ def values
423
+ @asset.slides.map{ |s| self[s.name] }
424
+ end
425
+ def inspect
426
+ "<Values of '#{@asset.name}.#{@property.name}' across slides>"
427
+ end
428
+ alias_method :to_s, :inspect
429
+ end
430
+
431
+ class UIC::SlideValues
432
+ def initialize( asset, slide )
433
+ @asset = asset
434
+ @slide = slide
435
+ end
436
+ def [](attribute_name)
437
+ @asset[attribute_name,@slide]
438
+ end
439
+ def []=( attribute_name, new_value )
440
+ @asset[attribute_name,@slide] = new_value
441
+ end
442
+ def method_missing( name, *args, &blk )
443
+ asset.send(name,*args,&blk)
444
+ end
445
+ def inspect
446
+ "<#{@asset.inspect} on slide #{@slide.inspect}>"
447
+ end
448
+ end