RUIC 0.4.6 → 0.5.0

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 (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