RUIC 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -3
  3. data/HISTORY +63 -52
  4. data/README.md +220 -220
  5. data/bin/ruic +12 -11
  6. data/gui/TODO +2 -2
  7. data/gui/appattributesmodel.rb +51 -51
  8. data/gui/appelementsmodel.rb +126 -126
  9. data/gui/launch.rb +19 -19
  10. data/gui/makefile +14 -14
  11. data/gui/resources/style/dark.qss +459 -459
  12. data/gui/window.rb +90 -90
  13. data/gui/window.ui +753 -753
  14. data/lib/ruic.rb +182 -175
  15. data/lib/ruic/application.rb +2 -2
  16. data/lib/ruic/assets.rb +436 -421
  17. data/lib/ruic/attributes.rb +170 -165
  18. data/lib/ruic/behaviors.rb +1 -1
  19. data/lib/ruic/interfaces.rb +23 -1
  20. data/lib/ruic/presentation.rb +100 -34
  21. data/lib/ruic/ripl-after-result.rb +24 -24
  22. data/lib/ruic/statemachine.rb +1 -1
  23. data/lib/ruic/version.rb +3 -3
  24. data/ruic.gemspec +25 -25
  25. data/test/MetaData-simple.xml +28 -28
  26. data/test/MetaData.xml +435 -435
  27. data/test/customclasses.ruic +31 -21
  28. data/test/filtering.ruic +43 -43
  29. data/test/futureassets.ruic +8 -8
  30. data/test/nonmaster.ruic +0 -0
  31. data/test/paths.ruic +18 -18
  32. data/test/projects/CustomClasses/CustomClasses.uia +7 -7
  33. data/test/projects/CustomClasses/CustomClasses.uip +7 -1
  34. data/test/projects/CustomClasses/FutureAsset.uip +17 -17
  35. data/test/projects/CustomClasses/scripts/DataDrivenTime.lua +58 -0
  36. data/test/projects/CustomClasses/scripts/TimeDrivenAttribute.lua +49 -0
  37. data/test/projects/Paths/Paths.uia +4 -4
  38. data/test/projects/Paths/Paths.uip +98 -98
  39. data/test/projects/SimpleScene/SimpleScene.uia +4 -4
  40. data/test/projects/SimpleScene/SimpleScene.uip +35 -35
  41. data/test/properties.ruic +82 -82
  42. data/test/referencematerials.ruic +52 -52
  43. data/test/usage.ruic +20 -20
  44. metadata +32 -4
@@ -1,176 +1,183 @@
1
- #encoding:utf-8
2
-
3
- class RUIC; end
4
- module UIC; end
5
-
6
- require 'nokogiri'
7
- require_relative 'ruic/version'
8
- require_relative 'ruic/attributes'
9
- require_relative 'ruic/assets'
10
- require_relative 'ruic/interfaces'
11
- require_relative 'ruic/application'
12
- require_relative 'ruic/behaviors'
13
- require_relative 'ruic/statemachine'
14
- require_relative 'ruic/presentation'
15
-
16
- # The `RUIC` class provides the interface for running scripts using the special DSL,
17
- # and for running the interactive REPL.
18
- # See the {file:README.md README} file for description of the DSL.
19
- class RUIC
20
- DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
21
-
22
- # Execute a script and/or launch the interactive REPL.
23
- #
24
- # If you both run a `:script` and then enter the `:repl` all local variables created
25
- # by the script will be available in the REPL.
26
- #
27
- # # Just run a script
28
- # RUIC.run script:'my.ruic'
29
- #
30
- # # Load an application and then enter the REPL
31
- # RUIC.run uia:'my.uia', repl:true
32
- #
33
- # # Run a script and then drop into the REPL
34
- # RUIC.run script:'my.ruic', repl:true
35
- #
36
- # The working directory for scripts is set to the directory of the script.
37
- #
38
- # The working directory for the repl is set to the directory of the first
39
- # loaded application (if any);
40
- # failing that, the directory of the script (if any);
41
- # failing that, the current directory.
42
- #
43
- # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
44
- # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
45
- # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
46
- # @return [nil]
47
- def self.run(opts={})
48
- opts = opts
49
- ruic = nil
50
- if opts[:script]
51
- script = File.read(opts[:script],encoding:'utf-8')
52
- Dir.chdir(File.dirname(opts[:script])) do
53
- ruic = self.new
54
- ruic.uia(opts[:uia]) if opts[:uia]
55
- ruic.env.eval(script,opts[:script])
56
- end
57
- end
58
-
59
- if opts[:repl]
60
- location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
61
- Dir.chdir( File.dirname(location) ) do
62
- ruic ||= self.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }
63
- require 'ripl/irb'
64
- require 'ripl/multi_line'
65
- require 'ripl/multi_line/live_error.rb'
66
- require_relative 'ruic/ripl-after-result'
67
- Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
68
- Ripl::Shell.include Ripl::MultiLine.engine
69
- Ripl::Shell.include Ripl::AfterResult
70
- Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n"
71
- ARGV.clear # So that RIPL doesn't try to interpret the options
72
- puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
73
- ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
74
- puts "" # blank line before first input
75
- Ripl.start binding:ruic.env
76
- end
77
- end
78
- end
79
-
80
- # Creates a new environment for executing a RUIC script.
81
- # @param metadata [String] Path to the `MetaData.xml` file to use.
82
- def initialize( metadata=DEFAULTMETADATA )
83
- @metadata = metadata
84
- @apps = {}
85
- end
86
-
87
- # Set the metadata to use; generally called from the RUIC DSL.
88
- # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
89
- def metadata(path)
90
- @metadata = path
91
- end
92
-
93
- # Load an application, making it available as `app`, `app2`, etc.
94
- # @param path [String] Path to the `*.uia` application file.
95
- # @return [UIC::Application] The new application loaded.
96
- def uia(path)
97
- meta = UIC.MetaData @metadata
98
- name = @apps.empty? ? :app : :"app#{@apps.length+1}"
99
- @apps[name] = UIC.Application(meta,path)
100
- end
101
-
102
- # @return [Binding] the shared binding used for evaluating the script and REPL
103
- def env
104
- @env ||= binding
105
- end
106
-
107
- # @private used as a one-off
108
- module SelfInspecting; def inspect; to_s; end; end
109
-
110
- # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
111
- # @return [UIC::Application] the new application loaded.
112
- def method_missing(name,*a)
113
- @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
114
- end
115
-
116
- # Simple assertion mechanism to be used within scripts.
117
- #
118
- # @example 1) simple call syntax
119
- # # Provides a generic failure message
120
- # assert a==b
121
- # #=> assertion failed (my.ruic line 17)
122
- #
123
- # # Provides a custom failure message
124
- # assert a==b, "a should equal b"
125
- # #=> a should equal b : assertion failed (my.ruic line 17)
126
- #
127
- # @example 2) block with string syntax
128
- # # The code in the string to eval is also the failure message
129
- # assert{ "a==b" }
130
- # #=> a==b : assertion failed (my.ruic line 17)
131
- #
132
- # @param condition [Boolean] the value to evaluate.
133
- # @param msg [String] the nice error message to display.
134
- # @yieldreturn [String] the code to evaluate as a condition.
135
- def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
136
- if block && condition==:CONDITIONNOTSUPPLIED
137
- msg = yield
138
- condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
139
- end
140
- condition || begin
141
- file, line, _ = caller.first.split(':')
142
- puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
143
- exit 1
144
- end
145
- end
146
-
147
- # Nicer name for `puts` to be used in the DSL, printing the
148
- # 'nice' string equivalent for all supplied arguments.
149
- def show(*a); puts *a.map(&:to_s); end
150
-
151
- def inspect
152
- "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
153
- end
154
- end
155
-
156
- # Run a series of commands inside the RUIC DSL.
157
- #
158
- # @example
159
- # require 'ruic'
160
- # RUIC do
161
- # uia 'test/MyProject/MyProject.uia'
162
- # show app
163
- # #=>UIC::Application 'MyProject.uia'>
164
- # end
165
- #
166
- # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
167
- # @option opts [String] :uia Optionally load an application before running the script.
168
- def RUIC(opts={},&block)
169
- if block
170
- Dir.chdir(File.dirname($0)) do
171
- RUIC.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }.instance_eval(&block)
172
- end
173
- else
174
- RUIC.run(opts)
175
- end
1
+ #encoding:utf-8
2
+
3
+ class RUIC; end
4
+ module UIC; end
5
+
6
+ require 'nokogiri'
7
+ require_relative 'ruic/version'
8
+ require_relative 'ruic/attributes'
9
+ require_relative 'ruic/assets'
10
+ require_relative 'ruic/interfaces'
11
+ require_relative 'ruic/application'
12
+ require_relative 'ruic/behaviors'
13
+ require_relative 'ruic/statemachine'
14
+ require_relative 'ruic/presentation'
15
+
16
+ # The `RUIC` class provides the interface for running scripts using the special DSL,
17
+ # and for running the interactive REPL.
18
+ # See the {file:README.md README} file for description of the DSL.
19
+ class RUIC
20
+ DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
21
+
22
+ # Execute a script and/or launch the interactive REPL.
23
+ #
24
+ # If you both run a `:script` and then enter the `:repl` all local variables created
25
+ # by the script will be available in the REPL.
26
+ #
27
+ # # Just run a script
28
+ # RUIC.run script:'my.ruic'
29
+ #
30
+ # # Load an application and then enter the REPL
31
+ # RUIC.run uia:'my.uia', repl:true
32
+ #
33
+ # # Run a script and then drop into the REPL
34
+ # RUIC.run script:'my.ruic', repl:true
35
+ #
36
+ # The working directory for scripts is set to the directory of the script.
37
+ #
38
+ # The working directory for the repl is set to the directory of the first
39
+ # loaded application (if any);
40
+ # failing that, the directory of the script (if any);
41
+ # failing that, the current directory.
42
+ #
43
+ # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
44
+ # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
45
+ # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
46
+ # @return [nil]
47
+ def self.run(opts={})
48
+ opts = opts
49
+ ruic = nil
50
+ if opts[:script]
51
+ script = File.read(opts[:script],encoding:'utf-8')
52
+ Dir.chdir(File.dirname(opts[:script])) do
53
+ ruic = self.new
54
+ ruic.metadata opts[:metadata] if opts[:metadata]
55
+ ruic.uia opts[:uia] if opts[:uia]
56
+ ruic.env.eval(script,opts[:script])
57
+ end
58
+ end
59
+
60
+ if opts[:repl]
61
+ location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
62
+ Dir.chdir( File.dirname(location) ) do
63
+ ruic ||= self.new.tap do |r|
64
+ r.metadata opts[:metadata] if opts[:metadata]
65
+ r.uia opts[:uia] if opts[:uia]
66
+ end
67
+ require 'ripl/irb'
68
+ require 'ripl/multi_line'
69
+ require 'ripl/multi_line/live_error.rb'
70
+ require_relative 'ruic/ripl-after-result'
71
+ Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
72
+ Ripl::Shell.include Ripl::MultiLine.engine
73
+ Ripl::Shell.include Ripl::AfterResult
74
+ Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n"
75
+ ARGV.clear # So that RIPL doesn't try to interpret the options
76
+ puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
77
+ ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
78
+ puts "" # blank line before first input
79
+ Ripl.start binding:ruic.env
80
+ end
81
+ end
82
+ end
83
+
84
+ # Creates a new environment for executing a RUIC script.
85
+ # @param metadata [String] Path to the `MetaData.xml` file to use.
86
+ def initialize( metadata=DEFAULTMETADATA )
87
+ @metadata = metadata
88
+ @apps = {}
89
+ end
90
+
91
+ # Set the metadata to use; generally called from the RUIC DSL.
92
+ # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
93
+ def metadata(path)
94
+ @metadata = path
95
+ end
96
+
97
+ # Load an application, making it available as `app`, `app2`, etc.
98
+ # @param path [String] Path to the `*.uia` application file.
99
+ # @return [UIC::Application] The new application loaded.
100
+ def uia(path)
101
+ meta = UIC.MetaData @metadata
102
+ name = @apps.empty? ? :app : :"app#{@apps.length+1}"
103
+ @apps[name] = UIC.Application(meta,path)
104
+ end
105
+
106
+ # @return [Binding] the shared binding used for evaluating the script and REPL
107
+ def env
108
+ @env ||= binding
109
+ end
110
+
111
+ # @private used as a one-off
112
+ module SelfInspecting; def inspect; to_s; end; end
113
+
114
+ # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
115
+ # @return [UIC::Application] the new application loaded.
116
+ def method_missing(name,*a)
117
+ @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
118
+ end
119
+
120
+ # Simple assertion mechanism to be used within scripts.
121
+ #
122
+ # @example 1) simple call syntax
123
+ # # Provides a generic failure message
124
+ # assert a==b
125
+ # #=> assertion failed (my.ruic line 17)
126
+ #
127
+ # # Provides a custom failure message
128
+ # assert a==b, "a should equal b"
129
+ # #=> a should equal b : assertion failed (my.ruic line 17)
130
+ #
131
+ # @example 2) block with string syntax
132
+ # # The code in the string to eval is also the failure message
133
+ # assert{ "a==b" }
134
+ # #=> a==b : assertion failed (my.ruic line 17)
135
+ #
136
+ # @param condition [Boolean] the value to evaluate.
137
+ # @param msg [String] the nice error message to display.
138
+ # @yieldreturn [String] the code to evaluate as a condition.
139
+ def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
140
+ if block && condition==:CONDITIONNOTSUPPLIED
141
+ msg = yield
142
+ condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
143
+ end
144
+ condition || begin
145
+ file, line, _ = caller.first.split(':')
146
+ puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
147
+ exit 1
148
+ end
149
+ end
150
+
151
+ # Nicer name for `puts` to be used in the DSL, printing the
152
+ # 'nice' string equivalent for all supplied arguments.
153
+ def show(*a); puts *a.map(&:to_s); end
154
+
155
+ def inspect
156
+ "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
157
+ end
158
+ end
159
+
160
+ # Run a series of commands inside the RUIC DSL.
161
+ #
162
+ # @example
163
+ # require 'ruic'
164
+ # RUIC do
165
+ # uia 'test/MyProject/MyProject.uia'
166
+ # show app
167
+ # #=>UIC::Application 'MyProject.uia'>
168
+ # end
169
+ #
170
+ # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
171
+ # @option opts [String] :uia Optionally load an application before running the script.
172
+ def RUIC(opts={},&block)
173
+ if block
174
+ Dir.chdir(File.dirname($0)) do
175
+ RUIC.new.tap do |r|
176
+ r.metadata opts[:metadata] if opts[:metadata]
177
+ r.uia opts[:uia] if opts[:uia]
178
+ end.instance_eval(&block)
179
+ end
180
+ else
181
+ RUIC.run(opts)
182
+ end
176
183
  end
@@ -80,8 +80,8 @@ class UIC::Application
80
80
  def referenced_files
81
81
  # TODO: state machines can reference external scripts
82
82
  # TODO: behaviors can reference external scripts
83
- assets.map{ |asset| path_to(asset.src) }
84
- + presentations.flat_map{ |pres| pres.presentation.referenced_files }
83
+ assets.map{ |asset| resolve_file_path(asset.src) }
84
+ + presentations.flat_map{ |pres| pres.referenced_files }
85
85
  end
86
86
 
87
87
  # @return [Array] all assets referenced by the application. Ordered by the order they appear in the `.uia`.
@@ -1,422 +1,437 @@
1
- #encoding: utf-8
2
- class UIC::MetaData
3
-
4
- # The base class for all assets. All other classes are dynamically created when a `MetaData.xml` file is loaded.
5
- class AssetBase
6
- @properties = {}
7
- @name = "AssetBase"
8
-
9
- class << self
10
- # @return [String] The scene graph name of the asset.
11
- attr_reader :name
12
-
13
- # @return [Hash] a hash mapping attribute names to {Property} instances.
14
- def properties
15
- (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
16
- end
17
-
18
- # @private
19
- def inspect
20
- "<#{@name}>"
21
- end
22
- end
23
-
24
- # @return [Hash] a hash mapping attribute names to {Property} instances.
25
- def properties
26
- self.class.properties
27
- end
28
-
29
- # Find an asset by relative scripting path.
30
- #
31
- # @example
32
- # preso = app.main
33
- # layer = preso/"Scene.Layer"
34
- # cam1 = app/"main:Scene.Layer.Camera"
35
- # cam2 = preso/"Scene.Layer.Camera"
36
- # cam3 = layer/"Camera"
37
- # cam4 = cam1/"parent.Camera"
38
- #
39
- # assert cam1==cam2 && cam2==cam3 && cam3==cam4
40
- #
41
- # @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
42
- #
43
- # @see Application#at
44
- # @see Presentation#at
45
- def at(sub_path)
46
- presentation.at(sub_path,@el)
47
- end
48
- alias_method :/, :at
49
-
50
- # @return [Presentation] the presentation that this asset is part of.
51
- attr_accessor :presentation
52
-
53
- # @return [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
54
- attr_accessor :el
55
-
56
- # Create a new asset. This is called for you automatically; you likely should not be using it.
57
- # @param presentation [Presentation] the presentation owning this asset.
58
- # @param element [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
59
- def initialize( presentation, element )
60
- @presentation = presentation
61
- @el = element
62
- end
63
-
64
- # @return [String] the type of this asset. For example: `"Model"`, `"Material"`, `"ReferencedMaterial"`, `"PathAnchorPoint"`, etc.
65
- def type
66
- self.class.name
67
- end
68
-
69
- # @return [AssetBase] the parent of this asset in the scene graph.
70
- # @see Presentation#parent_asset
71
- def parent
72
- presentation.parent_asset(self)
73
- end
74
-
75
- # @return [Array<AssetBase>] array of child assets in the scene graph. Children are in scene graph order.
76
- # @see Presentation#child_assets
77
- def children
78
- presentation.child_assets(self)
79
- end
80
-
81
- # Find descendant assets matching criteria.
82
- # This method is the same as (but more convenient than) {Presentation#find} using the `:_under` criteria.
83
- # See that method for documentation of the `criteria`.
84
- #
85
- # @example
86
- # preso = app.main
87
- # group = preso/"Scene.Layer.Vehicle"
88
- # tires = group.find name:/^Tire/
89
- #
90
- # # alternative
91
- # tires = preso.find name:/^Tire/, _under:group
92
- #
93
- # @return [Array<AssetBase>]
94
- #
95
- # @see Presentation#find
96
- def find(criteria={},&block)
97
- criteria[:_under] ||= self
98
- presentation.find(criteria,&block)
99
- end
100
-
101
- # @return [AssetBase] the component or scene that owns this asset.
102
- # If this asset is a component, does not return itself.
103
- # @see Presentation#owning_component
104
- def component
105
- presentation.owning_component(self)
106
- end
107
-
108
- # @return [Boolean] `true` if this asset is a component or Scene.
109
- def component?
110
- @el.name=='Component' || @el.name=='Scene'
111
- end
112
-
113
- # @return [Boolean] `true` if this asset is on the master slide.
114
- # @see Presentation#master?
115
- def master?
116
- presentation.master?(self)
117
- end
118
-
119
- # @return [Boolean] `true` if this asset is a Slide.
120
- def slide?
121
- false
122
- end
123
-
124
- # @param slide_name_or_index [Integer,String] the slide number of name to check for presence on.
125
- # @return [Boolean] `true` if this asset is present on the specified slide.
126
- # @see Presentation#has_slide?
127
- def has_slide?(slide_name_or_index)
128
- presentation.has_slide?(self,slide_name_or_index)
129
- end
130
-
131
- # @return [SlideCollection] an array-like collection of all slides that the asset is available on.
132
- # @see Presentation#slides_for
133
- def slides
134
- presentation.slides_for(self)
135
- end
136
-
137
- # @example
138
- # logo = app/"main:Scene.UI.Logo"
139
- # assert logo.master? # It's a master object
140
- #
141
- # show logo['endtime'].values #=> [10000,500,1000,750]
142
- # show logo['opacity'].values #=> [100,0,0,100]
143
- # logo1 = logo.on_slide(1)
144
- # logo2 = logo.on_slide(2)
145
- # show logo1['endtime'] #=> 500
146
- # show logo2['endtime'] #=> 1000
147
- # show logo1['opacity'] #=> 0
148
- # show logo2['opacity'] #=> 0
149
- #
150
- # logo2['opacity'] = 66
151
- # show logo['opacity'].values #=> [100,0,66,100]
152
- #
153
- # @param slide_name_or_index [Integer,String] the slide number or name to create the proxy for.
154
- # @return [SlideValues] a proxy that yields attribute values for a specific slide.
155
- def on_slide(slide_name_or_index)
156
- if has_slide?(slide_name_or_index)
157
- UIC::SlideValues.new( self, slide_name_or_index )
158
- end
159
- end
160
-
161
- # @return [String] the script path to this asset.
162
- # @see #path_to
163
- # @see Presentation#path_to
164
- def path
165
- @path ||= @presentation.path_to(self)
166
- end
167
-
168
- # @param other_asset [AssetBase] the asset to find the relative path to.
169
- # @return [String] the script path to another asset, relative to this one.
170
- # @see #path
171
- # @see Presentation#path_to
172
- def path_to(other_asset)
173
- @presentation.path_to(other_asset,self)
174
- end
175
-
176
- # @return [String] the name of this asset in the scene graph.
177
- def name
178
- properties['name'].get( self, presentation.slide_index(self) )
179
- end
180
-
181
- # Change the name of the asset in the scene graph.
182
- # @param new_name [String] the new name for this asset.
183
- # @return [String] the new name.
184
- def name=( new_name )
185
- @path = nil # invalidate the memoization
186
- properties['name'].set( self, new_name, presentation.slide_index(self) )
187
- end
188
-
189
- # Get the value(s) of an attribute.
190
- # If `slide_name_or_index` is omitted, creates a ValuesPerSlide proxy for the specified attribute.
191
- # @example
192
- # logo = app/"main:Scene.UI.Logo"
193
- # show logo.master? #=> true (it's a master object)
194
- # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
195
- # show logo['endtime'].values #=> [10000,500,1000,750]
196
- # show logo['endtime',0] #=> 10000 (the master slide value)
197
- # show logo['endtime',"Slide 1"] #=> 500
198
- # show logo['endtime',2] #=> 1000
199
- #
200
- # @param attribute_name [String,Symbol] the name of the attribute.
201
- # @param slide_name_or_index [Integer,String] the slide number or name to find the value on.
202
- # @return [Object] the value of the property on the given slide.
203
- # @return [ValuesPerSlide] if no slide is specified.
204
- # @see #on_slide
205
- # @see Presentation#get_attribute
206
- def [](attribute_name, slide_name_or_index=nil)
207
- if property = properties[attribute_name.to_s]
208
- if slide_name_or_index
209
- property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
210
- else
211
- UIC::ValuesPerSlide.new(@presentation,self,property)
212
- end
213
- end
214
- end
215
-
216
- # Set the value of an attribute, either across all slides, or on a particular slide.
217
- #
218
- # @example
219
- # logo = app/"main:Scene.UI.Logo"
220
- # show logo.master? #=> true (it's a master object)
221
- # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
222
- # show logo['endtime'].values #=> [10000,500,1000,750]
223
- #
224
- # logo['endtime',1] = 99
225
- # show logo['endtime'].values #=> [10000,99,1000,750]
226
- #
227
- # logo['endtime'] = 42
228
- # show logo['endtime'].values #=> [42,42,42,42]
229
- # show logo['endtime'].linked? #=> false (the endtime property is still unlinked)
230
- #
231
- # @param attribute_name [String,Symbol] the name of the attribute.
232
- # @param slide_name_or_index [Integer,String] the slide number or name to set the value on.
233
- # @param new_value [Numeric,String] the new value for the attribute.
234
- # @see Presentation#set_attribute
235
- def []=( attribute_name, slide_name_or_index=nil, new_value )
236
- if property = properties[attribute_name.to_s] then
237
- property.set(self,new_value,slide_name_or_index)
238
- end
239
- end
240
-
241
- # @return [String] the XML representation of the scene graph element.
242
- def to_xml
243
- @el.to_xml
244
- end
245
-
246
- # @private no need to document this
247
- def inspect
248
- "<asset #{@el.name}##{@el['id']}>"
249
- end
250
-
251
- # @private no need to document this
252
- def to_s
253
- "<#{type} #{path}>"
254
- end
255
-
256
- # @private no need to document this
257
- def ==(other)
258
- (self.class==other.class) && (el==other.el)
259
- end
260
- alias_method :eql?, :==
261
- end
262
-
263
- attr_reader :by_name
264
-
265
- HIER = {}
266
-
267
- %w[Asset Slide Scene].each{ |s| HIER[s] = 'AssetBase' }
268
- %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
269
- %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
270
- %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
271
-
272
- def initialize(xml)
273
-
274
- @by_name = {'AssetBase'=>AssetBase}
275
-
276
- doc = Nokogiri.XML(xml)
277
- hack_in_slide_names!(doc)
278
-
279
- HIER.each do |class_name,parent_class_name|
280
- parent_class = @by_name[parent_class_name]
281
- el = doc.root.at(class_name)
282
- @by_name[class_name] = create_class(el,parent_class,el.name)
283
- end
284
-
285
- # Extend well-known classes with script interfaces after they are created
286
- @by_name['State'] = @by_name['Slide']
287
- @by_name['Slide'].instance_eval do
288
- attr_accessor :index, :name
289
- define_method :inspect do
290
- "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
291
- end
292
- define_method(:slide?){ true }
293
- end
294
-
295
- refmat = @by_name['ReferencedMaterial']
296
- @by_name['MaterialBase'].instance_eval do
297
- define_method :replace_with_referenced_material do
298
- type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
299
- end
300
- end
301
-
302
- @by_name['Path'].instance_eval do
303
- define_method(:anchors){ find _type:'PathAnchorPoint' }
304
- end
305
-
306
- end
307
-
308
- # Creates a class from MetaData.xml with accessors for the <Property> listed.
309
- # Instances of the class are associated with a presentation and know how to
310
- # get/set values in that XML based on value types, slides, defaults.
311
- # Also used to create classes from effects, materials, and behavior preambles.
312
- def create_class(el,parent_class,name='CustomAsset')
313
- Class.new(parent_class) do
314
- @name = name.to_s
315
- @properties = Hash[ el.css("Property").map do |e|
316
- type = e['type'] || (e['list'] ? 'String' : 'Float')
317
- type = "Float" if type=="float"
318
- property = UIC::Property.const_get(type).new(e)
319
- [ property.name, UIC::Property.const_get(type).new(e) ]
320
- end ]
321
- def self.inspect
322
- @name
323
- end
324
- end
325
- end
326
-
327
- def new_instance(presentation,el)
328
- klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
329
- klass.new(presentation,el)
330
- end
331
-
332
- def hack_in_slide_names!(doc)
333
- doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
334
- end
335
- end
336
-
337
- def UIC.MetaData(metadata_path)
338
- UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
339
- end
340
-
341
- class UIC::SlideCollection
342
- include Enumerable
343
- attr_reader :length
344
- def initialize(slides)
345
- @length = slides.length-1
346
- @slides = slides
347
- @lookup = {}
348
- slides.each do |s|
349
- @lookup[s.index] = s
350
- @lookup[s.name] = s
351
- end
352
- end
353
- def each
354
- @slides.each{ |s| yield(s) }
355
- end
356
- def [](index_or_name)
357
- @lookup[ index_or_name ]
358
- end
359
- def inspect
360
- "[ #{@slides.map(&:inspect).join ', '} ]"
361
- end
362
- def to_ary
363
- @slides
364
- end
365
- end
366
-
367
- class UIC::ValuesPerSlide
368
- def initialize(presentation,asset,property)
369
- raise unless presentation.is_a?(UIC::Presentation)
370
-
371
- raise unless asset.is_a?(UIC::MetaData::AssetBase)
372
- raise unless property.is_a?(UIC::Property)
373
- @preso = presentation
374
- @asset = asset
375
- @el = asset.el
376
- @property = property
377
- end
378
- def value
379
- values.first
380
- end
381
- def [](slide_name_or_index)
382
- @property.get( @asset, slide_name_or_index )
383
- end
384
- def []=(slide_name_or_index,new_value)
385
- @property.set( @asset, new_value, slide_name_or_index )
386
- end
387
- def linked?
388
- @preso.attribute_linked?( @asset, @property.name )
389
- end
390
- def unlink
391
- @preso.unlink_attribute( @asset, @property.name )
392
- end
393
- def link
394
- @preso.link_attribute( @asset, @property.name )
395
- end
396
- def values
397
- @asset.slides.map{ |s| self[s.name] }
398
- end
399
- def inspect
400
- "<Values of '#{@asset.name}.#{@property.name}' across slides>"
401
- end
402
- alias_method :to_s, :inspect
403
- end
404
-
405
- class UIC::SlideValues
406
- def initialize( asset, slide )
407
- @asset = asset
408
- @slide = slide
409
- end
410
- def [](attribute_name)
411
- @asset[attribute_name,@slide]
412
- end
413
- def []=( attribute_name, new_value )
414
- @asset[attribute_name,@slide] = new_value
415
- end
416
- def method_missing( name, *args, &blk )
417
- asset.send(name,*args,&blk)
418
- end
419
- def inspect
420
- "<#{@asset.inspect} on slide #{@slide.inspect}>"
421
- end
1
+ #encoding: utf-8
2
+ class UIC::MetaData
3
+
4
+ # The base class for all assets. All other classes are dynamically created when a `MetaData.xml` file is loaded.
5
+ class AssetBase
6
+ @properties = {}
7
+ @name = "AssetBase"
8
+
9
+ class << self
10
+ # @return [String] The scene graph name of the asset.
11
+ attr_reader :name
12
+
13
+ # @return [Hash] a hash mapping attribute names to {Property} instances.
14
+ def properties
15
+ (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
16
+ end
17
+
18
+ # @private
19
+ def inspect
20
+ "<#{@name}>"
21
+ end
22
+ end
23
+
24
+ # @return [Hash] a hash mapping attribute names to {Property} instances.
25
+ def properties
26
+ self.class.properties
27
+ end
28
+
29
+ # Find an asset by relative scripting path.
30
+ #
31
+ # @example
32
+ # preso = app.main
33
+ # layer = preso/"Scene.Layer"
34
+ # cam1 = app/"main:Scene.Layer.Camera"
35
+ # cam2 = preso/"Scene.Layer.Camera"
36
+ # cam3 = layer/"Camera"
37
+ # cam4 = cam1/"parent.Camera"
38
+ #
39
+ # assert cam1==cam2 && cam2==cam3 && cam3==cam4
40
+ #
41
+ # @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
42
+ #
43
+ # @see Application#at
44
+ # @see Presentation#at
45
+ def at(sub_path)
46
+ presentation.at(sub_path,@el)
47
+ end
48
+ alias_method :/, :at
49
+
50
+ # @return [Presentation] the presentation that this asset is part of.
51
+ attr_accessor :presentation
52
+
53
+ # @return [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
54
+ attr_accessor :el
55
+
56
+ # Create a new asset. This is called for you automatically; you likely should not be using it.
57
+ # @param presentation [Presentation] the presentation owning this asset.
58
+ # @param element [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
59
+ def initialize( presentation, element )
60
+ @presentation = presentation
61
+ @el = element
62
+ end
63
+
64
+ # @return [String] the type of this asset. For example: `"Model"`, `"Material"`, `"ReferencedMaterial"`, `"PathAnchorPoint"`, etc.
65
+ def type
66
+ self.class.name
67
+ end
68
+
69
+ # @return [AssetBase] the parent of this asset in the scene graph.
70
+ # @see Presentation#parent_asset
71
+ def parent
72
+ presentation.parent_asset(self)
73
+ end
74
+
75
+ # @return [Array<AssetBase>] array of child assets in the scene graph. Children are in scene graph order.
76
+ # @see Presentation#child_assets
77
+ def children
78
+ presentation.child_assets(self)
79
+ end
80
+
81
+ # @return [String] the hierarchy under the element (for debugging purposes).
82
+ def hierarchy
83
+ presentation.hierarchy(self)
84
+ end
85
+
86
+ # Find descendant assets matching criteria.
87
+ # This method is the same as (but more convenient than) {Presentation#find} using the `:_under` criteria.
88
+ # See that method for documentation of the `criteria`.
89
+ #
90
+ # @example
91
+ # preso = app.main
92
+ # group = preso/"Scene.Layer.Vehicle"
93
+ # tires = group.find name:/^Tire/
94
+ #
95
+ # # alternative
96
+ # tires = preso.find name:/^Tire/, _under:group
97
+ #
98
+ # @return [Array<AssetBase>]
99
+ #
100
+ # @see Presentation#find
101
+ def find(criteria={},&block)
102
+ criteria[:_under] ||= self
103
+ presentation.find(criteria,&block)
104
+ end
105
+
106
+ # @return [AssetBase] the component or scene that owns this asset.
107
+ # If this asset is a component, does not return itself.
108
+ # @see Presentation#owning_component
109
+ def component
110
+ presentation.owning_component(self)
111
+ end
112
+
113
+ # @return [Boolean] `true` if this asset is a component or Scene.
114
+ def component?
115
+ @el.name=='Component' || @el.name=='Scene'
116
+ end
117
+
118
+ # @return [Boolean] `true` if this asset is on the master slide.
119
+ # @see Presentation#master?
120
+ def master?
121
+ presentation.master?(self)
122
+ end
123
+
124
+ # @return [Boolean] `true` if this asset is a Slide.
125
+ def slide?
126
+ false
127
+ end
128
+
129
+ # @param slide_name_or_index [Integer,String] the slide number of name to check for presence on.
130
+ # @return [Boolean] `true` if this asset is present on the specified slide.
131
+ # @see Presentation#has_slide?
132
+ def has_slide?(slide_name_or_index)
133
+ presentation.has_slide?(self,slide_name_or_index)
134
+ end
135
+
136
+ # @return [SlideCollection] an array-like collection of all slides that the asset is available on.
137
+ # @see Presentation#slides_for
138
+ def slides
139
+ presentation.slides_for(self)
140
+ end
141
+
142
+ # @example
143
+ # logo = app/"main:Scene.UI.Logo"
144
+ # assert logo.master? # It's a master object
145
+ #
146
+ # show logo['endtime'].values #=> [10000,500,1000,750]
147
+ # show logo['opacity'].values #=> [100,0,0,100]
148
+ # logo1 = logo.on_slide(1)
149
+ # logo2 = logo.on_slide(2)
150
+ # show logo1['endtime'] #=> 500
151
+ # show logo2['endtime'] #=> 1000
152
+ # show logo1['opacity'] #=> 0
153
+ # show logo2['opacity'] #=> 0
154
+ #
155
+ # logo2['opacity'] = 66
156
+ # show logo['opacity'].values #=> [100,0,66,100]
157
+ #
158
+ # @param slide_name_or_index [Integer,String] the slide number or name to create the proxy for.
159
+ # @return [SlideValues] a proxy that yields attribute values for a specific slide.
160
+ def on_slide(slide_name_or_index)
161
+ if has_slide?(slide_name_or_index)
162
+ UIC::SlideValues.new( self, slide_name_or_index )
163
+ end
164
+ end
165
+
166
+ # @return [String] the script path to this asset.
167
+ # @see #path_to
168
+ # @see Presentation#path_to
169
+ def path
170
+ @path ||= @presentation.path_to(self)
171
+ end
172
+
173
+ # @param other_asset [AssetBase] the asset to find the relative path to.
174
+ # @return [String] the script path to another asset, relative to this one.
175
+ # @see #path
176
+ # @see Presentation#path_to
177
+ def path_to(other_asset)
178
+ @presentation.path_to(other_asset,self)
179
+ end
180
+
181
+ # @return [String] the name of this asset in the scene graph.
182
+ def name
183
+ properties['name'] ? properties['name'].get( self, presentation.slide_index(self) ) : type
184
+ end
185
+
186
+ # Change the name of the asset in the scene graph.
187
+ # @param new_name [String] the new name for this asset.
188
+ # @return [String] the new name.
189
+ def name=( new_name )
190
+ @path = nil # invalidate the memoization
191
+ properties['name'].set( self, new_name, presentation.slide_index(self) )
192
+ end
193
+
194
+ # Get the value(s) of an attribute.
195
+ # If `slide_name_or_index` is omitted, creates a ValuesPerSlide proxy for the specified attribute.
196
+ # @example
197
+ # logo = app/"main:Scene.UI.Logo"
198
+ # show logo.master? #=> true (it's a master object)
199
+ # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
200
+ # show logo['endtime'].values #=> [10000,500,1000,750]
201
+ # show logo['endtime',0] #=> 10000 (the master slide value)
202
+ # show logo['endtime',"Slide 1"] #=> 500
203
+ # show logo['endtime',2] #=> 1000
204
+ #
205
+ # @param attribute_name [String,Symbol] the name of the attribute.
206
+ # @param slide_name_or_index [Integer,String] the slide number or name to find the value on.
207
+ # @return [Object] the value of the property on the given slide.
208
+ # @return [ValuesPerSlide] if no slide is specified.
209
+ # @see #on_slide
210
+ # @see Presentation#get_attribute
211
+ def [](attribute_name, slide_name_or_index=nil)
212
+ if property = properties[attribute_name.to_s]
213
+ if slide_name_or_index
214
+ property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
215
+ else
216
+ UIC::ValuesPerSlide.new(@presentation,self,property)
217
+ end
218
+ end
219
+ end
220
+
221
+ # Set the value of an attribute, either across all slides, or on a particular slide.
222
+ #
223
+ # @example
224
+ # logo = app/"main:Scene.UI.Logo"
225
+ # show logo.master? #=> true (it's a master object)
226
+ # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
227
+ # show logo['endtime'].values #=> [10000,500,1000,750]
228
+ #
229
+ # logo['endtime',1] = 99
230
+ # show logo['endtime'].values #=> [10000,99,1000,750]
231
+ #
232
+ # logo['endtime'] = 42
233
+ # show logo['endtime'].values #=> [42,42,42,42]
234
+ # show logo['endtime'].linked? #=> false (the endtime property is still unlinked)
235
+ #
236
+ # @param attribute_name [String,Symbol] the name of the attribute.
237
+ # @param slide_name_or_index [Integer,String] the slide number or name to set the value on.
238
+ # @param new_value [Numeric,String] the new value for the attribute.
239
+ # @see Presentation#set_attribute
240
+ def []=( attribute_name, slide_name_or_index=nil, new_value )
241
+ if property = properties[attribute_name.to_s] then
242
+ property.set(self,new_value,slide_name_or_index)
243
+ end
244
+ end
245
+
246
+ # @return [String] the XML representation of the scene graph element.
247
+ def to_xml
248
+ @el.to_xml
249
+ end
250
+
251
+ # @private no need to document this
252
+ def to_s
253
+ "<#{type} #{path}>"
254
+ end
255
+ alias_method :inspect, :to_s
256
+
257
+ # @private no need to document this
258
+ def ==(other)
259
+ (self.class==other.class) && (el==other.el)
260
+ end
261
+ alias_method :eql?, :==
262
+ end
263
+
264
+ attr_reader :by_name
265
+
266
+ HIER = {}
267
+
268
+ %w[Asset Slide Scene].each{ |s| HIER[s] = 'AssetBase' }
269
+ %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
270
+ %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
271
+ %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
272
+
273
+ def initialize(xml)
274
+ @by_name = {'AssetBase'=>AssetBase}
275
+
276
+ doc = Nokogiri.XML(xml)
277
+ hack_in_slide_names!(doc)
278
+
279
+ HIER.each do |class_name,parent_class_name|
280
+ parent_class = @by_name[parent_class_name]
281
+ el = doc.root.at(class_name)
282
+ @by_name[class_name] = create_class(el,parent_class,el.name)
283
+ end
284
+
285
+ # Extend well-known classes with script interfaces after they are created
286
+ @by_name['State'] = @by_name['Slide']
287
+ @by_name['Slide'].instance_eval do
288
+ attr_accessor :index, :name
289
+ define_method :inspect do
290
+ "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
291
+ end
292
+ define_method(:slide?){ true }
293
+ end
294
+
295
+ refmat = @by_name['ReferencedMaterial']
296
+ @by_name['MaterialBase'].instance_eval do
297
+ define_method :replace_with_referenced_material do
298
+ type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
299
+ end
300
+ end
301
+
302
+ @by_name['Path'].instance_eval do
303
+ define_method(:anchors){ find _type:'PathAnchorPoint' }
304
+ end
305
+
306
+ end
307
+
308
+ # Creates a class from MetaData.xml with accessors for the <Property> listed.
309
+ # Instances of the class are associated with a presentation and know how to
310
+ # get/set values in that XML based on value types, slides, defaults.
311
+ # Also used to create classes from effects, materials, and behavior preambles.
312
+ # @param el [Nokogiri::XML::Element] the element in MetaData.xml representing this class.
313
+ # @param parent_class [Class] the asset class to inherit from.
314
+ # @param name [String] the name of this class.
315
+ # @param new_defaults [Hash] hash mapping attribute name to a custom default value (as string) for this class.
316
+ def create_class(el,parent_class,name,new_defaults={})
317
+ Class.new(parent_class) do
318
+ @name = name.to_s
319
+ @properties = Hash[ el.css("Property").map do |e|
320
+ type = e['type'] || (e['list'] ? 'String' : 'Float')
321
+ type = "Float" if type=="float"
322
+ property = UIC::Property.const_get(type).new(e)
323
+ new_defaults.delete(property.name)
324
+ [ property.name, property ]
325
+ end ]
326
+
327
+ new_defaults.each do |name,value|
328
+ if prop=properties[name] # look in ancestor classes
329
+ @properties[name] = prop.dup
330
+ @properties[name].default = value
331
+ end
332
+ end
333
+
334
+ def self.inspect
335
+ @name
336
+ end
337
+ end
338
+ end
339
+
340
+ def new_instance(presentation,el)
341
+ klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
342
+ klass.new(presentation,el)
343
+ end
344
+
345
+ def hack_in_slide_names!(doc)
346
+ doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
347
+ end
348
+ end
349
+
350
+ def UIC.MetaData(metadata_path)
351
+ raise %Q{Cannot find MetaData.xml at "#{metadata_path}"} unless File.exist?(metadata_path)
352
+ UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
353
+ end
354
+
355
+ class UIC::SlideCollection
356
+ include Enumerable
357
+ attr_reader :length
358
+ def initialize(slides)
359
+ @length = slides.length-1
360
+ @slides = slides
361
+ @lookup = {}
362
+ slides.each do |s|
363
+ @lookup[s.index] = s
364
+ @lookup[s.name] = s
365
+ end
366
+ end
367
+ def each
368
+ @slides.each{ |s| yield(s) }
369
+ end
370
+ def [](index_or_name)
371
+ @lookup[ index_or_name ]
372
+ end
373
+ def inspect
374
+ "[ #{@slides.map(&:inspect).join ', '} ]"
375
+ end
376
+ def to_ary
377
+ @slides
378
+ end
379
+ end
380
+
381
+ class UIC::ValuesPerSlide
382
+ def initialize(presentation,asset,property)
383
+ raise unless presentation.is_a?(UIC::Presentation)
384
+
385
+ raise unless asset.is_a?(UIC::MetaData::AssetBase)
386
+ raise unless property.is_a?(UIC::Property)
387
+ @preso = presentation
388
+ @asset = asset
389
+ @el = asset.el
390
+ @property = property
391
+ end
392
+ def value
393
+ values.first
394
+ end
395
+ def [](slide_name_or_index)
396
+ @property.get( @asset, slide_name_or_index )
397
+ end
398
+ def []=(slide_name_or_index,new_value)
399
+ @property.set( @asset, new_value, slide_name_or_index )
400
+ end
401
+ def linked?
402
+ @preso.attribute_linked?( @asset, @property.name )
403
+ end
404
+ def unlink
405
+ @preso.unlink_attribute( @asset, @property.name )
406
+ end
407
+ def link
408
+ @preso.link_attribute( @asset, @property.name )
409
+ end
410
+ def values
411
+ @asset.slides.map{ |s| self[s.name] }
412
+ end
413
+ def inspect
414
+ "<Values of '#{@asset.name}.#{@property.name}' across slides>"
415
+ end
416
+ alias_method :to_s, :inspect
417
+ end
418
+
419
+ class UIC::SlideValues
420
+ attr_reader :asset
421
+ def initialize( asset, slide )
422
+ @asset = asset
423
+ @slide = slide
424
+ end
425
+ def [](attribute_name)
426
+ @asset[attribute_name,@slide]
427
+ end
428
+ def []=( attribute_name, new_value )
429
+ @asset[attribute_name,@slide] = new_value
430
+ end
431
+ def method_missing( name, *args, &blk )
432
+ asset.send(name,*args,&blk)
433
+ end
434
+ def inspect
435
+ "<#{@asset.inspect} on slide #{@slide.inspect}>"
436
+ end
422
437
  end