RUIC 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -3
  3. data/HISTORY +110 -86
  4. data/README.md +220 -220
  5. data/bin/ruic +61 -61
  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 +191 -190
  15. data/lib/ruic/application.rb +25 -3
  16. data/lib/ruic/assets.rb +441 -436
  17. data/lib/ruic/attributes.rb +179 -178
  18. data/lib/ruic/behaviors.rb +0 -0
  19. data/lib/ruic/effect.rb +31 -31
  20. data/lib/ruic/interfaces.rb +0 -0
  21. data/lib/ruic/nicebytes.rb +29 -0
  22. data/lib/ruic/presentation.rb +6 -3
  23. data/lib/ruic/renderplugin.rb +17 -17
  24. data/lib/ruic/ripl.rb +45 -45
  25. data/lib/ruic/statemachine.rb +6 -0
  26. data/lib/ruic/version.rb +3 -3
  27. data/ruic.gemspec +25 -25
  28. data/test/MetaData-simple.xml +28 -28
  29. data/test/MetaData.xml +435 -435
  30. data/test/customclasses.ruic +29 -29
  31. data/test/filtering.ruic +42 -42
  32. data/test/futureassets.ruic +7 -7
  33. data/test/nonmaster.ruic +20 -20
  34. data/test/paths.ruic +16 -16
  35. data/test/projects/CustomClasses/CustomClasses.uia +7 -7
  36. data/test/projects/CustomClasses/CustomClasses.uip +40 -40
  37. data/test/projects/CustomClasses/FutureAsset.uip +17 -17
  38. data/test/projects/MissingAssets/Existing.uip +87 -87
  39. data/test/projects/MissingAssets/MissingAssets.uia +0 -0
  40. data/test/projects/MissingAssets/MissingAssets.uia-user +14 -14
  41. data/test/projects/MissingAssets/RoundedPlane-1/RoundedPlane-1.import +18 -18
  42. data/test/projects/MissingAssets/RoundedPlane-1/meshes/RoundedPlane.mesh +0 -0
  43. data/test/projects/MissingAssets/effects/effects.txt +0 -0
  44. data/test/projects/MissingAssets/effects/existing.effect +0 -0
  45. data/test/projects/MissingAssets/fonts/Arimo-Regular.ttf +0 -0
  46. data/test/projects/MissingAssets/fonts/Chivo-Black.ttf +0 -0
  47. data/test/projects/MissingAssets/fonts/OFL.txt +0 -0
  48. data/test/projects/MissingAssets/maps/effects/brushnoise.dds +0 -0
  49. data/test/projects/MissingAssets/maps/existing.png +0 -0
  50. data/test/projects/MissingAssets/maps/maps.txt +0 -0
  51. data/test/projects/MissingAssets/maps/materials/concrete_plain.png +0 -0
  52. data/test/projects/MissingAssets/maps/materials/concrete_plain_bump.png +0 -0
  53. data/test/projects/MissingAssets/maps/materials/spherical_checker.png +0 -0
  54. data/test/projects/MissingAssets/materials/concrete.material +0 -0
  55. data/test/projects/MissingAssets/materials/materials.txt +0 -0
  56. data/test/projects/MissingAssets/scripts/existing1.lua +0 -0
  57. data/test/projects/MissingAssets/scripts/existing2.lua +0 -0
  58. data/test/projects/MissingAssets/states/existing.scxml +0 -0
  59. data/test/projects/MissingAssets/tests/interface-navigation.scxml-test +0 -0
  60. data/test/projects/Paths/Paths.uia +4 -4
  61. data/test/projects/Paths/Paths.uip +98 -98
  62. data/test/projects/SimpleScene/SimpleScene.uia +4 -4
  63. data/test/projects/SimpleScene/SimpleScene.uip +35 -35
  64. data/test/properties.ruic +80 -80
  65. data/test/referencematerials.ruic +50 -50
  66. data/test/usage.ruic +54 -54
  67. metadata +5 -54
@@ -1,191 +1,192 @@
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/effect'
14
- require_relative 'ruic/renderplugin'
15
- require_relative 'ruic/statemachine'
16
- require_relative 'ruic/presentation'
17
- require_relative 'ruic/ripl'
18
-
19
- # The `RUIC` class provides the interface for running scripts using the special DSL,
20
- # and for running the interactive REPL.
21
- # See the {file:README.md README} file for description of the DSL.
22
- class RUIC
23
- DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
24
-
25
- # Execute a script and/or launch the interactive REPL.
26
- #
27
- # If you both run a `:script` and then enter the `:repl` all local variables created
28
- # by the script will be available in the REPL.
29
- #
30
- # # Just run a script
31
- # RUIC.run script:'my.ruic'
32
- #
33
- # # Load an application and then enter the REPL
34
- # RUIC.run uia:'my.uia', repl:true
35
- #
36
- # # Run a script and then drop into the REPL
37
- # RUIC.run script:'my.ruic', repl:true
38
- #
39
- # The working directory for scripts is set to the directory of the script.
40
- #
41
- # The working directory for the repl is set to the directory of the first
42
- # loaded application (if any);
43
- # failing that, the directory of the script (if any);
44
- # failing that, the current directory.
45
- #
46
- # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
47
- # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
48
- # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
49
- # @return [nil]
50
- def self.run(opts={})
51
- opts = opts
52
- ruic = nil
53
- if opts[:script]
54
- script = File.read(opts[:script],encoding:'utf-8')
55
- Dir.chdir(File.dirname(opts[:script])) do
56
- ruic = self.new
57
- ruic.metadata opts[:metadata] if opts[:metadata]
58
- ruic.uia opts[:uia] if opts[:uia]
59
- ruic.env.eval(script,opts[:script])
60
- end
61
- end
62
-
63
- if opts[:repl]
64
- location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
65
- Dir.chdir( File.dirname(location) ) do
66
- ruic ||= self.new.tap do |r|
67
- r.metadata opts[:metadata] if opts[:metadata]
68
- r.uia opts[:uia] if opts[:uia]
69
- end
70
- require 'ripl/irb'
71
- require 'ripl/multi_line'
72
- require 'ripl/multi_line/live_error.rb'
73
- Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
74
- Ripl::Shell.include Ripl::MultiLine.engine
75
- Ripl::Shell.include Ripl::AfterResult
76
- Ripl::Shell.include Ripl::FormatResult
77
- Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n", result_line_limit:120, prefix_result_lines:true, skip_nil_results:true
78
- ARGV.clear # So that RIPL doesn't try to interpret the options
79
- puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
80
- ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
81
- puts "" # blank line before first input
82
- Ripl.start binding:ruic.env
83
- end
84
- end
85
- end
86
-
87
- # Creates a new environment for executing a RUIC script.
88
- # @param metadata [String] Path to the `MetaData.xml` file to use.
89
- def initialize( metadata=DEFAULTMETADATA )
90
- @metadata = metadata
91
- @apps = {}
92
- end
93
-
94
- # Set the metadata to use; generally called from the RUIC DSL.
95
- # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
96
- def metadata(path)
97
- @metadata = path
98
- end
99
-
100
- # Load an application, making it available as `app`, `app2`, etc.
101
- # @param path [String] Path to the `*.uia` application file.
102
- # @return [UIC::Application] The new application loaded.
103
- def uia(path)
104
- meta = UIC.MetaData @metadata
105
- name = @apps.empty? ? :app : :"app#{@apps.length+1}"
106
- @apps[name] = UIC.Application(meta,path)
107
- end
108
-
109
- # @return [Binding] the shared binding used for evaluating the script and REPL
110
- def env
111
- @env ||= binding
112
- end
113
-
114
- # @private used as a one-off
115
- module SelfInspecting; def inspect; to_s; end; end
116
-
117
- # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
118
- # @return [UIC::Application] the new application loaded.
119
- def method_missing(name,*a)
120
- @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
121
- end
122
-
123
- # Simple assertion mechanism to be used within scripts.
124
- #
125
- # @example 1) simple call syntax
126
- # # Provides a generic failure message
127
- # assert a==b
128
- # #=> assertion failed (my.ruic line 17)
129
- #
130
- # # Provides a custom failure message
131
- # assert a==b, "a should equal b"
132
- # #=> a should equal b : assertion failed (my.ruic line 17)
133
- #
134
- # @example 2) block with string syntax
135
- # # The code in the string to eval is also the failure message
136
- # assert{ "a==b" }
137
- # #=> a==b : assertion failed (my.ruic line 17)
138
- #
139
- # @param condition [Boolean] the value to evaluate.
140
- # @param msg [String] the nice error message to display.
141
- # @yieldreturn [String] the code to evaluate as a condition.
142
- def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
143
- if block && condition==:CONDITIONNOTSUPPLIED
144
- msg = yield
145
- condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
146
- end
147
- condition || begin
148
- file, line, _ = caller.first.split(':')
149
- puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
150
- exit 1
151
- end
152
- end
153
-
154
- # Nicer name for `puts` to be used in the DSL, printing the
155
- # 'nice' string equivalent for all supplied arguments.
156
- def show(*a)
157
- a=a.first if a.length==1 && a.first.is_a?(Array)
158
- opts = { result_prompt:'# ', result_line_limit:120, prefix_result_lines:true, to_s:true }
159
- a.each{ |x| puts Ripl::FormatResult.format_result(x,opts) }
160
- nil # so that Ripl won't show the result
161
- end
162
-
163
- def inspect
164
- "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
165
- end
166
- end
167
-
168
- # Run a series of commands inside the RUIC DSL.
169
- #
170
- # @example
171
- # require 'ruic'
172
- # RUIC do
173
- # uia 'test/MyProject/MyProject.uia'
174
- # show app
175
- # #=>UIC::Application 'MyProject.uia'>
176
- # end
177
- #
178
- # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
179
- # @option opts [String] :uia Optionally load an application before running the script.
180
- def RUIC(opts={},&block)
181
- if block
182
- Dir.chdir(File.dirname($0)) do
183
- RUIC.new.tap do |r|
184
- r.metadata opts[:metadata] if opts[:metadata]
185
- r.uia opts[:uia] if opts[:uia]
186
- end.instance_eval(&block)
187
- end
188
- else
189
- RUIC.run(opts)
190
- 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/effect'
14
+ require_relative 'ruic/renderplugin'
15
+ require_relative 'ruic/statemachine'
16
+ require_relative 'ruic/presentation'
17
+ require_relative 'ruic/ripl'
18
+ require_relative 'ruic/nicebytes'
19
+
20
+ # The `RUIC` class provides the interface for running scripts using the special DSL,
21
+ # and for running the interactive REPL.
22
+ # See the {file:README.md README} file for description of the DSL.
23
+ class RUIC
24
+ DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
25
+
26
+ # Execute a script and/or launch the interactive REPL.
27
+ #
28
+ # If you both run a `:script` and then enter the `:repl` all local variables created
29
+ # by the script will be available in the REPL.
30
+ #
31
+ # # Just run a script
32
+ # RUIC.run script:'my.ruic'
33
+ #
34
+ # # Load an application and then enter the REPL
35
+ # RUIC.run uia:'my.uia', repl:true
36
+ #
37
+ # # Run a script and then drop into the REPL
38
+ # RUIC.run script:'my.ruic', repl:true
39
+ #
40
+ # The working directory for scripts is set to the directory of the script.
41
+ #
42
+ # The working directory for the repl is set to the directory of the first
43
+ # loaded application (if any);
44
+ # failing that, the directory of the script (if any);
45
+ # failing that, the current directory.
46
+ #
47
+ # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
48
+ # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
49
+ # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
50
+ # @return [nil]
51
+ def self.run(opts={})
52
+ opts = opts
53
+ ruic = nil
54
+ if opts[:script]
55
+ script = File.read(opts[:script],encoding:'utf-8')
56
+ Dir.chdir(File.dirname(opts[:script])) do
57
+ ruic = self.new
58
+ ruic.metadata opts[:metadata] if opts[:metadata]
59
+ ruic.uia opts[:uia] if opts[:uia]
60
+ ruic.env.eval(script,opts[:script])
61
+ end
62
+ end
63
+
64
+ if opts[:repl]
65
+ location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
66
+ Dir.chdir( File.dirname(location) ) do
67
+ ruic ||= self.new.tap do |r|
68
+ r.metadata opts[:metadata] if opts[:metadata]
69
+ r.uia opts[:uia] if opts[:uia]
70
+ end
71
+ require 'ripl/irb'
72
+ require 'ripl/multi_line'
73
+ require 'ripl/multi_line/live_error.rb'
74
+ Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
75
+ Ripl::Shell.include Ripl::MultiLine.engine
76
+ Ripl::Shell.include Ripl::AfterResult
77
+ Ripl::Shell.include Ripl::FormatResult
78
+ Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n", result_line_limit:200, prefix_result_lines:true, skip_nil_results:true
79
+ ARGV.clear # So that RIPL doesn't try to interpret the options
80
+ puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
81
+ ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
82
+ puts "" # blank line before first input
83
+ Ripl.start binding:ruic.env
84
+ end
85
+ end
86
+ end
87
+
88
+ # Creates a new environment for executing a RUIC script.
89
+ # @param metadata [String] Path to the `MetaData.xml` file to use.
90
+ def initialize( metadata=DEFAULTMETADATA )
91
+ @metadata = metadata
92
+ @apps = {}
93
+ end
94
+
95
+ # Set the metadata to use; generally called from the RUIC DSL.
96
+ # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
97
+ def metadata(path)
98
+ @metadata = path
99
+ end
100
+
101
+ # Load an application, making it available as `app`, `app2`, etc.
102
+ # @param path [String] Path to the `*.uia` application file.
103
+ # @return [UIC::Application] The new application loaded.
104
+ def uia(path)
105
+ meta = UIC.MetaData @metadata
106
+ name = @apps.empty? ? :app : :"app#{@apps.length+1}"
107
+ @apps[name] = UIC.Application(meta,path)
108
+ end
109
+
110
+ # @return [Binding] the shared binding used for evaluating the script and REPL
111
+ def env
112
+ @env ||= binding
113
+ end
114
+
115
+ # @private used as a one-off
116
+ module SelfInspecting; def inspect; to_s; end; end
117
+
118
+ # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
119
+ # @return [UIC::Application] the new application loaded.
120
+ def method_missing(name,*a)
121
+ @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
122
+ end
123
+
124
+ # Simple assertion mechanism to be used within scripts.
125
+ #
126
+ # @example 1) simple call syntax
127
+ # # Provides a generic failure message
128
+ # assert a==b
129
+ # #=> assertion failed (my.ruic line 17)
130
+ #
131
+ # # Provides a custom failure message
132
+ # assert a==b, "a should equal b"
133
+ # #=> a should equal b : assertion failed (my.ruic line 17)
134
+ #
135
+ # @example 2) block with string syntax
136
+ # # The code in the string to eval is also the failure message
137
+ # assert{ "a==b" }
138
+ # #=> a==b : assertion failed (my.ruic line 17)
139
+ #
140
+ # @param condition [Boolean] the value to evaluate.
141
+ # @param msg [String] the nice error message to display.
142
+ # @yieldreturn [String] the code to evaluate as a condition.
143
+ def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
144
+ if block && condition==:CONDITIONNOTSUPPLIED
145
+ msg = yield
146
+ condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
147
+ end
148
+ condition || begin
149
+ file, line, _ = caller.first.split(':')
150
+ puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
151
+ exit 1
152
+ end
153
+ end
154
+
155
+ # Nicer name for `puts` to be used in the DSL, printing the
156
+ # 'nice' string equivalent for all supplied arguments.
157
+ def show(*a)
158
+ a=a.first if a.length==1 && a.first.is_a?(Array)
159
+ opts = { result_prompt:'# ', result_line_limit:200, prefix_result_lines:true, to_s:true }
160
+ a.each{ |x| puts Ripl::FormatResult.format_result(x,opts) }
161
+ nil # so that Ripl won't show the result
162
+ end
163
+
164
+ def inspect
165
+ "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
166
+ end
167
+ end
168
+
169
+ # Run a series of commands inside the RUIC DSL.
170
+ #
171
+ # @example
172
+ # require 'ruic'
173
+ # RUIC do
174
+ # uia 'test/MyProject/MyProject.uia'
175
+ # show app
176
+ # #=>UIC::Application 'MyProject.uia'>
177
+ # end
178
+ #
179
+ # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
180
+ # @option opts [String] :uia Optionally load an application before running the script.
181
+ def RUIC(opts={},&block)
182
+ if block
183
+ Dir.chdir(File.dirname($0)) do
184
+ RUIC.new.tap do |r|
185
+ r.metadata opts[:metadata] if opts[:metadata]
186
+ r.uia opts[:uia] if opts[:uia]
187
+ end.instance_eval(&block)
188
+ end
189
+ else
190
+ RUIC.run(opts)
191
+ end
191
192
  end
@@ -60,8 +60,31 @@ class UIC::Application
60
60
  # Files in the application directory not used by the application.
61
61
  #
62
62
  # @return [Array<String>] absolute paths of files in the directory not used by the application.
63
- def unused_files
64
- (directory_files - referenced_files).sort
63
+ def unused_files( hierarchy=false )
64
+ unused = (directory_files - referenced_files).sort
65
+ if hierarchy
66
+ root = File.dirname(file)
67
+ UIC.tree_hierarchy(root) do |dir|
68
+ File.directory?(dir) ? Dir.chdir(dir){ Dir['*'].map{ |f| File.expand_path(f) } } : []
69
+ end.map do |prefix,file|
70
+ if file
71
+ all = unused.select{ |path| path[/^#{file}/] }
72
+ unless all.empty?
73
+ size = NiceBytes.nice_bytes(all.map{ |f| File.size(f) }.inject(:+))
74
+ partial = file.sub(/^#{root}\//o,'')
75
+ if File.directory?(file)
76
+ "%s %s (%d files, %s)" % [prefix,partial,all.length,size]
77
+ else
78
+ "%s %s (%s)" % [prefix,partial,size]
79
+ end
80
+ end
81
+ else
82
+ prefix
83
+ end
84
+ end.compact.join("\n")
85
+ else
86
+ unused
87
+ end
65
88
  end
66
89
 
67
90
  # Files referenced by the application but not present in the directory.
@@ -71,7 +94,6 @@ class UIC::Application
71
94
  (referenced_files - directory_files).sort
72
95
  end
73
96
 
74
-
75
97
  # @return [Array<String>] absolute paths of files referenced by the application.
76
98
  def referenced_files
77
99
  # TODO: state machines can reference external scripts
@@ -1,437 +1,442 @@
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
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 = begin
323
+ UIC::Property.const_get(type).new(e)
324
+ rescue NameError
325
+ warn "WARNING: Unsupported property type '#{type}' on\n#{e}\nTreating this as a String."
326
+ UIC::Property::String.new(e)
327
+ end
328
+ new_defaults.delete(property.name)
329
+ [ property.name, property ]
330
+ end ]
331
+
332
+ new_defaults.each do |name,value|
333
+ if prop=properties[name] # look in ancestor classes
334
+ @properties[name] = prop.dup
335
+ @properties[name].default = value
336
+ end
337
+ end
338
+
339
+ def self.inspect
340
+ @name
341
+ end
342
+ end
343
+ end
344
+
345
+ def new_instance(presentation,el)
346
+ klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
347
+ klass.new(presentation,el)
348
+ end
349
+
350
+ def hack_in_slide_names!(doc)
351
+ doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
352
+ end
353
+ end
354
+
355
+ def UIC.MetaData(metadata_path)
356
+ raise %Q{Cannot find MetaData.xml at "#{metadata_path}"} unless File.exist?(metadata_path)
357
+ UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
358
+ end
359
+
360
+ class UIC::SlideCollection
361
+ include Enumerable
362
+ attr_reader :length
363
+ def initialize(slides)
364
+ @length = slides.length-1
365
+ @slides = slides
366
+ @lookup = {}
367
+ slides.each do |s|
368
+ @lookup[s.index] = s
369
+ @lookup[s.name] = s
370
+ end
371
+ end
372
+ def each
373
+ @slides.each{ |s| yield(s) }
374
+ end
375
+ def [](index_or_name)
376
+ @lookup[ index_or_name ]
377
+ end
378
+ def inspect
379
+ "[ #{@slides.map(&:inspect).join ', '} ]"
380
+ end
381
+ def to_ary
382
+ @slides
383
+ end
384
+ end
385
+
386
+ class UIC::ValuesPerSlide
387
+ def initialize(presentation,asset,property)
388
+ raise unless presentation.is_a?(UIC::Presentation)
389
+
390
+ raise unless asset.is_a?(UIC::MetaData::AssetBase)
391
+ raise unless property.is_a?(UIC::Property)
392
+ @preso = presentation
393
+ @asset = asset
394
+ @el = asset.el
395
+ @property = property
396
+ end
397
+ def value
398
+ values.first
399
+ end
400
+ def [](slide_name_or_index)
401
+ @property.get( @asset, slide_name_or_index )
402
+ end
403
+ def []=(slide_name_or_index,new_value)
404
+ @property.set( @asset, new_value, slide_name_or_index )
405
+ end
406
+ def linked?
407
+ @preso.attribute_linked?( @asset, @property.name )
408
+ end
409
+ def unlink
410
+ @preso.unlink_attribute( @asset, @property.name )
411
+ end
412
+ def link
413
+ @preso.link_attribute( @asset, @property.name )
414
+ end
415
+ def values
416
+ @asset.slides.map{ |s| self[s.name] }
417
+ end
418
+ def inspect
419
+ "<Values of '#{@asset.name}.#{@property.name}' across slides>"
420
+ end
421
+ alias_method :to_s, :inspect
422
+ end
423
+
424
+ class UIC::SlideValues
425
+ attr_reader :asset
426
+ def initialize( asset, slide )
427
+ @asset = asset
428
+ @slide = slide
429
+ end
430
+ def [](attribute_name)
431
+ @asset[attribute_name,@slide]
432
+ end
433
+ def []=( attribute_name, new_value )
434
+ @asset[attribute_name,@slide] = new_value
435
+ end
436
+ def method_missing( name, *args, &blk )
437
+ asset.send(name,*args,&blk)
438
+ end
439
+ def inspect
440
+ "<#{@asset.inspect} on slide #{@slide.inspect}>"
441
+ end
437
442
  end