RUIC 0.4.1 → 0.4.2
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.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/README.md +279 -270
- data/bin/ruic +0 -0
- data/gui/TODO +2 -2
- data/gui/appattributesmodel.rb +51 -51
- data/gui/appelementsmodel.rb +126 -126
- data/gui/launch.rb +19 -19
- data/gui/makefile +14 -14
- data/gui/resources/style/dark.qss +459 -459
- data/gui/window.rb +90 -90
- data/gui/window.ui +753 -753
- data/lib/ruic.rb +80 -11
- data/lib/ruic/application.rb +68 -13
- data/lib/ruic/assets.rb +289 -287
- data/lib/ruic/attributes.rb +165 -165
- data/lib/ruic/behaviors.rb +0 -0
- data/lib/ruic/interfaces.rb +7 -2
- data/lib/ruic/presentation.rb +175 -40
- data/lib/ruic/ripl-after-result.rb +11 -11
- data/lib/ruic/statemachine.rb +0 -0
- data/lib/ruic/version.rb +2 -2
- data/ruic.gemspec +25 -25
- data/test/MetaData-simple.xml +28 -28
- data/test/MetaData.xml +435 -435
- data/test/customclasses.ruic +21 -21
- data/test/filtering.ruic +43 -43
- data/test/futureassets.ruic +8 -8
- data/test/nonmaster.ruic +0 -0
- data/test/paths.ruic +18 -18
- data/test/projects/CustomClasses/CustomClasses.uia +7 -7
- data/test/projects/CustomClasses/FutureAsset.uip +17 -17
- data/test/projects/SimpleScene/SimpleScene.uia +4 -4
- data/test/projects/SimpleScene/SimpleScene.uip +35 -35
- data/test/properties.ruic +82 -82
- data/test/referencematerials.ruic +52 -52
- data/test/usage.ruic +20 -20
- metadata +6 -26
data/lib/ruic.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#encoding:utf-8
|
2
|
+
|
2
3
|
class RUIC; end
|
3
4
|
module UIC; end
|
4
5
|
|
@@ -12,9 +13,37 @@ require_relative 'ruic/behaviors'
|
|
12
13
|
require_relative 'ruic/statemachine'
|
13
14
|
require_relative 'ruic/presentation'
|
14
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.
|
15
19
|
class RUIC
|
16
20
|
DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
|
17
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]
|
18
47
|
def self.run(opts={})
|
19
48
|
opts = opts
|
20
49
|
ruic = nil
|
@@ -48,47 +77,75 @@ class RUIC
|
|
48
77
|
end
|
49
78
|
end
|
50
79
|
|
80
|
+
# Creates a new environment for executing a RUIC script.
|
81
|
+
# @param metadata [String] Path to the `MetaData.xml` file to use.
|
51
82
|
def initialize( metadata=DEFAULTMETADATA )
|
52
83
|
@metadata = metadata
|
53
84
|
@apps = {}
|
54
85
|
end
|
55
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.
|
56
89
|
def metadata(path)
|
57
90
|
@metadata = path
|
58
91
|
end
|
59
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.
|
60
96
|
def uia(path)
|
61
|
-
meta = UIC.
|
97
|
+
meta = UIC.MetaData @metadata
|
62
98
|
name = @apps.empty? ? :app : :"app#{@apps.length+1}"
|
63
|
-
@apps[name] = UIC.
|
99
|
+
@apps[name] = UIC.Application(meta,path)
|
64
100
|
end
|
65
101
|
|
102
|
+
# @return [Binding] the shared binding used for evaluating the script and REPL
|
66
103
|
def env
|
67
104
|
@env ||= binding
|
68
105
|
end
|
69
106
|
|
70
|
-
|
71
|
-
|
72
|
-
to_s
|
73
|
-
end
|
74
|
-
end
|
107
|
+
# @private used as a one-off
|
108
|
+
module SelfInspecting; def inspect; to_s; end; end
|
75
109
|
|
110
|
+
# Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
|
111
|
+
# @return [UIC::Application] the new application loaded.
|
76
112
|
def method_missing(name,*a)
|
77
113
|
@apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
|
78
114
|
end
|
79
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.
|
80
135
|
def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
|
81
|
-
if block && condition==:CONDITIONNOTSUPPLIED
|
82
|
-
msg =
|
136
|
+
if block && condition==:CONDITIONNOTSUPPLIED
|
137
|
+
msg = yield
|
83
138
|
condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
|
84
139
|
end
|
85
|
-
|
140
|
+
condition || begin
|
86
141
|
file, line, _ = caller.first.split(':')
|
87
|
-
puts "#{
|
142
|
+
puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
|
88
143
|
exit 1
|
89
144
|
end
|
90
145
|
end
|
91
146
|
|
147
|
+
# Nicer name for `puts` to be used in the DSL, printing the
|
148
|
+
# 'nice' string equivalent for all supplied arguments.
|
92
149
|
def show(*a); puts *a.map(&:to_s); end
|
93
150
|
|
94
151
|
def inspect
|
@@ -96,6 +153,18 @@ class RUIC
|
|
96
153
|
end
|
97
154
|
end
|
98
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.
|
99
168
|
def RUIC(opts={},&block)
|
100
169
|
if block
|
101
170
|
Dir.chdir(File.dirname($0)) do
|
data/lib/ruic/application.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# The `Application` represents the root of your UIC application, corresponding to a `.uia` file.
|
1
2
|
class UIC::Application
|
2
3
|
include UIC::FileBacked
|
3
4
|
|
@@ -5,17 +6,27 @@ class UIC::Application
|
|
5
6
|
"<UIC::Application '#{File.basename(file)}'#{:' FILENOTFOUND' unless file_found?}>"
|
6
7
|
end
|
7
8
|
|
9
|
+
# @return [UIC::MetaData] the metadata loaded for this application
|
8
10
|
attr_reader :metadata
|
9
|
-
|
11
|
+
|
12
|
+
# @param metadata [UIC::MetaData] the `MetaData` to use for this application.
|
13
|
+
# @param uia_path [String] path to a `.uia` file to load.
|
14
|
+
# If omitted you will need to later set the `.file = ` for the
|
15
|
+
# instance and then call {#load_from_file}.
|
16
|
+
def initialize(metadata,uia_path=nil)
|
10
17
|
@metadata = metadata
|
11
18
|
self.file = uia_path
|
12
19
|
@assets = {}
|
13
20
|
load_from_file if file_found?
|
14
21
|
end
|
15
22
|
|
23
|
+
# Loads the application from the file. If you pass the path to your `.uia`
|
24
|
+
# to {#initialize} then this method is called automatically.
|
25
|
+
#
|
26
|
+
# @return [nil]
|
16
27
|
def load_from_file
|
17
|
-
self.doc
|
18
|
-
@assets
|
28
|
+
self.doc = Nokogiri.XML(File.read(file,encoding:'utf-8'))
|
29
|
+
@assets = @doc.search('assets *').map do |el|
|
19
30
|
case el.name
|
20
31
|
when 'behavior' then UIC::Application::Behavior
|
21
32
|
when 'statemachine' then UIC::Application::StateMachine
|
@@ -23,16 +34,32 @@ class UIC::Application
|
|
23
34
|
when 'renderplugin' then UIC::Application::RenderPlugin
|
24
35
|
end.new(self,el)
|
25
36
|
end.group_by{ |asset| asset.el.name }
|
37
|
+
nil
|
26
38
|
end
|
27
39
|
|
40
|
+
# @return [Boolean] true if there are any errors with the application.
|
28
41
|
def errors?
|
29
42
|
!errors.empty?
|
30
43
|
end
|
31
44
|
|
45
|
+
# @example
|
46
|
+
# show app.errors if app.errors?
|
47
|
+
# @return [Array<String>] an array (possibly empty) of all errors in this application (including referenced assets).
|
32
48
|
def errors
|
33
49
|
file_found? ? assets.flat_map(&:errors) : ["File not found: '#{file}'"]
|
34
50
|
end
|
35
51
|
|
52
|
+
# Find an asset by `.uia` identifier or path to the asset.
|
53
|
+
# @example
|
54
|
+
# main1 = app['#main']
|
55
|
+
# main2 = app['MyMain.uip']
|
56
|
+
# main3 = app.main_presentation
|
57
|
+
# assert main1==main2 && main2==main3
|
58
|
+
# @param asset_id_or_path [String] an idref like `"#status"` or a relative path to the asset like `"VehicleStatus.uip"` or `"scripts/Main.lua"`.
|
59
|
+
# @return [UIC::Application::Behavior]
|
60
|
+
# @return [UIC::Application::StateMachine]
|
61
|
+
# @return [UIC::Application::Presentation]
|
62
|
+
# @return [UIC::Application::RenderPlugin]
|
36
63
|
def [](asset_id_or_path)
|
37
64
|
all = assets
|
38
65
|
if asset_id_or_path.start_with?('#')
|
@@ -44,10 +71,12 @@ class UIC::Application
|
|
44
71
|
end
|
45
72
|
end
|
46
73
|
|
74
|
+
# @private do not document until working
|
47
75
|
def unused_files
|
48
76
|
referenced_files - directory_files
|
49
77
|
end
|
50
78
|
|
79
|
+
# @private do not document until working
|
51
80
|
def referenced_files
|
52
81
|
# TODO: state machines can reference external scripts
|
53
82
|
# TODO: behaviors can reference external scripts
|
@@ -55,10 +84,15 @@ class UIC::Application
|
|
55
84
|
+ presentations.flat_map{ |pres| pres.presentation.referenced_files }
|
56
85
|
end
|
57
86
|
|
87
|
+
# @return [Array] all assets referenced by the application. Ordered by the order they appear in the `.uia`.
|
58
88
|
def assets
|
59
89
|
@assets.values.inject(:+)
|
60
90
|
end
|
61
91
|
|
92
|
+
# @example
|
93
|
+
# main = app.main
|
94
|
+
# main = app.main_presentation # more-explicit alternative
|
95
|
+
# @return [UIC::Application::Presentation] the main presentation rendered by the application.
|
62
96
|
def main_presentation
|
63
97
|
initial_id = @doc.at('assets')['initial']
|
64
98
|
presos = presentations
|
@@ -66,13 +100,18 @@ class UIC::Application
|
|
66
100
|
end
|
67
101
|
alias_method :main, :main_presentation
|
68
102
|
|
103
|
+
# Change which presentation is rendered for the application.
|
104
|
+
# @param presentation [UIC::Application::Presentation]
|
105
|
+
# @return [UIC::Application::Presentation]
|
69
106
|
def main_presentation=(presentation)
|
70
|
-
# TODO: set to Presentation or
|
107
|
+
# TODO: set to Presentation or Application::Presentation
|
71
108
|
# TODO: create a unique ID if none exists
|
72
109
|
@doc.at('assets')['initial'] = presentation.id
|
73
110
|
end
|
74
111
|
|
112
|
+
# @return [Hash] a mapping of image paths to arrays of elements/assets that reference that image.
|
75
113
|
def image_usage
|
114
|
+
# TODO: this returns the same asset multiple times, with no indication of which property is using it; should switch to Properties.
|
76
115
|
Hash[
|
77
116
|
(presentations + statemachines)
|
78
117
|
.map(&:image_usage)
|
@@ -84,31 +123,48 @@ class UIC::Application
|
|
84
123
|
].tap{ |h| h.extend(UIC::PresentableHash) }
|
85
124
|
end
|
86
125
|
|
126
|
+
# @return [Array<String>] array of all image paths **used** by the application (not just in subfolders).
|
87
127
|
def image_paths
|
88
128
|
image_usage.keys
|
89
129
|
end
|
90
130
|
|
131
|
+
# @return [Array<UIC::Application::Presentation>] all presentations referenced by the application.
|
91
132
|
def presentations
|
92
133
|
@assets['presentation'] ||= []
|
93
134
|
end
|
94
135
|
|
136
|
+
# @return [Array<UIC::Application::Behavior>] all behaviors referenced by the application.
|
95
137
|
def behaviors
|
96
138
|
@assets['behavior'] ||= []
|
97
139
|
end
|
98
140
|
|
141
|
+
# @return [Array<UIC::Application::StateMachine>] all state machines referenced by the application.
|
99
142
|
def statemachines
|
100
143
|
@assets['statemachine'] ||= []
|
101
144
|
end
|
102
145
|
|
146
|
+
# @return [Array<UIC::Application::RenderPlugin>] all render plug-ins referenced by the application.
|
103
147
|
def renderplugins
|
104
148
|
@assets['renderplugin'] ||= []
|
105
149
|
end
|
106
150
|
|
151
|
+
# Save changes to this application and every asset to disk.
|
107
152
|
def save_all!
|
108
|
-
|
153
|
+
save!
|
109
154
|
presentations.each(&:save!)
|
110
|
-
|
111
|
-
|
155
|
+
# TODO: enumerate other assets and save them
|
156
|
+
end
|
157
|
+
|
158
|
+
# Find an element or asset in a presentation by scripting path.
|
159
|
+
# @example
|
160
|
+
# # Four ways to find the same layer
|
161
|
+
# layer1 = app.at "main:Scene.Layer"
|
162
|
+
# layer2 = app/"main:Scene.Layer"
|
163
|
+
# layer3 = app.main.at "Scene.Layer"
|
164
|
+
# layer4 = app.main/"Scene.Layer"
|
165
|
+
#
|
166
|
+
# assert layer1==layer2 && layer2==layer3 && layer3==layer4
|
167
|
+
# @return [UIC::MetaData::Root] The found asset, or `nil` if it cannot be found.
|
112
168
|
def at(path)
|
113
169
|
parts = path.split(':')
|
114
170
|
preso = parts.length==2 ? self["##{parts.first}"] : main_presentation
|
@@ -116,17 +172,16 @@ class UIC::Application
|
|
116
172
|
preso.at(parts.last)
|
117
173
|
end
|
118
174
|
alias_method :/, :at
|
119
|
-
|
120
|
-
def xml
|
121
|
-
@doc.to_xml
|
122
|
-
end
|
123
175
|
end
|
124
176
|
|
125
177
|
class << UIC
|
126
|
-
|
178
|
+
# Create a new {UIC::Application}. Shortcut for `UIC::Application.new(...)`
|
179
|
+
# @param metadata [UIC::MetaData] the MetaData to use for this application.
|
180
|
+
# @param uia_path [String] a path to the .uia to load.
|
181
|
+
# @return [UIC::Application]
|
182
|
+
def Application(metadata,uia_path=nil)
|
127
183
|
UIC::Application.new( metadata, uia_path )
|
128
184
|
end
|
129
|
-
alias_method :App, :Application
|
130
185
|
end
|
131
186
|
|
132
187
|
__END__
|
data/lib/ruic/assets.rb
CHANGED
@@ -1,288 +1,290 @@
|
|
1
|
-
#encoding: utf-8
|
2
|
-
class UIC::
|
3
|
-
class Root
|
4
|
-
@properties = {}
|
5
|
-
class << self
|
6
|
-
attr_reader :name
|
7
|
-
def properties
|
8
|
-
(ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
|
9
|
-
end
|
10
|
-
|
11
|
-
def each
|
12
|
-
(@by_name.values - [self]).each{ |klass| yield klass }
|
13
|
-
end
|
14
|
-
include Enumerable
|
15
|
-
|
16
|
-
def inspect
|
17
|
-
"<#{@name}>"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def properties
|
22
|
-
self.class.properties
|
23
|
-
end
|
24
|
-
|
25
|
-
def at(sub_path)
|
26
|
-
presentation.at(sub_path,@el)
|
27
|
-
end
|
28
|
-
alias_method :/, :at
|
29
|
-
|
30
|
-
attr_accessor :presentation, :el
|
31
|
-
def initialize( presentation, element )
|
32
|
-
@presentation = presentation
|
33
|
-
@el = element
|
34
|
-
end
|
35
|
-
|
36
|
-
def type
|
37
|
-
self.class.name
|
38
|
-
# self.class.name.split('::').last
|
39
|
-
end
|
40
|
-
|
41
|
-
def parent
|
42
|
-
presentation.parent_asset(
|
43
|
-
end
|
44
|
-
|
45
|
-
def children
|
46
|
-
presentation.child_assets(
|
47
|
-
end
|
48
|
-
|
49
|
-
def find(criteria={},&block)
|
50
|
-
criteria[:_under] ||= self
|
51
|
-
presentation.find(criteria,&block)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Find the owning component (even if you are a component)
|
55
|
-
def component
|
56
|
-
presentation.owning_component(
|
57
|
-
end
|
58
|
-
|
59
|
-
def component?
|
60
|
-
@el.name == 'Component'
|
61
|
-
end
|
62
|
-
|
63
|
-
def master?
|
64
|
-
presentation.master?(
|
65
|
-
end
|
66
|
-
|
67
|
-
def slide?
|
68
|
-
false
|
69
|
-
end
|
70
|
-
|
71
|
-
def has_slide?(slide_name_or_index)
|
72
|
-
presentation.has_slide?(
|
73
|
-
end
|
74
|
-
|
75
|
-
def slides
|
76
|
-
presentation.slides_for(
|
77
|
-
end
|
78
|
-
|
79
|
-
def on_slide(slide_name_or_index)
|
80
|
-
if has_slide?(slide_name_or_index)
|
81
|
-
UIC::SlideValues.new( self, slide_name_or_index )
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def path
|
86
|
-
@path ||= @presentation.path_to(
|
87
|
-
end
|
88
|
-
|
89
|
-
def name
|
90
|
-
properties['name'].get( self, presentation.slide_index(
|
91
|
-
end
|
92
|
-
|
93
|
-
def name=( new_name )
|
94
|
-
properties['name'].set( self, new_name, presentation.slide_index(
|
95
|
-
end
|
96
|
-
|
97
|
-
# Get the value(s) of an attribute
|
98
|
-
def [](attribute_name, slide_name_or_index=nil)
|
99
|
-
if property = properties[attribute_name]
|
100
|
-
if slide_name_or_index
|
101
|
-
property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
|
102
|
-
else
|
103
|
-
UIC::ValuesPerSlide.new(@presentation,self,property)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# Set the value of an attribute, either across all slides, or on a particular slide
|
109
|
-
# el['foo'] = 42
|
110
|
-
# el['foo',0] = 42
|
111
|
-
def []=( attribute_name, slide_name_or_index=nil, new_value )
|
112
|
-
if property = properties[attribute_name] then
|
113
|
-
property.set(self,new_value,slide_name_or_index)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_xml
|
118
|
-
@el.to_xml
|
119
|
-
end
|
120
|
-
def inspect
|
121
|
-
"<asset #{@el.name}##{@el['id']}>"
|
122
|
-
end
|
123
|
-
|
124
|
-
def to_s
|
125
|
-
"<#{type} #{path}>"
|
126
|
-
end
|
127
|
-
|
128
|
-
def ==(other)
|
129
|
-
(self.class==other.class) && (el==other.el)
|
130
|
-
end
|
131
|
-
alias_method :eql?, :==
|
132
|
-
end
|
133
|
-
|
134
|
-
attr_reader :by_name
|
135
|
-
|
136
|
-
HIER = {}
|
137
|
-
%w[Asset Slide Scene].each{ |s| HIER[s] = 'Root' }
|
138
|
-
%w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
|
139
|
-
%w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
|
140
|
-
%w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
|
141
|
-
|
142
|
-
def initialize(xml)
|
143
|
-
@by_name = {'Root'=>Root}
|
144
|
-
|
145
|
-
doc = Nokogiri.XML(xml)
|
146
|
-
hack_in_slide_names!(doc)
|
147
|
-
|
148
|
-
HIER.each do |class_name,parent_class_name|
|
149
|
-
parent_class = @by_name[parent_class_name]
|
150
|
-
el = doc.root.at(class_name)
|
151
|
-
@by_name[class_name] = create_class(el,parent_class,el.name)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
@by_name['
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
@
|
185
|
-
|
186
|
-
type =
|
187
|
-
|
188
|
-
property
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
@
|
215
|
-
slides
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
raise unless
|
239
|
-
|
240
|
-
|
241
|
-
@
|
242
|
-
@
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
end
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
1
|
+
#encoding: utf-8
|
2
|
+
class UIC::MetaData
|
3
|
+
class Root
|
4
|
+
@properties = {}
|
5
|
+
class << self
|
6
|
+
attr_reader :name
|
7
|
+
def properties
|
8
|
+
(ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
(@by_name.values - [self]).each{ |klass| yield klass }
|
13
|
+
end
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"<#{@name}>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def properties
|
22
|
+
self.class.properties
|
23
|
+
end
|
24
|
+
|
25
|
+
def at(sub_path)
|
26
|
+
presentation.at(sub_path,@el)
|
27
|
+
end
|
28
|
+
alias_method :/, :at
|
29
|
+
|
30
|
+
attr_accessor :presentation, :el
|
31
|
+
def initialize( presentation, element )
|
32
|
+
@presentation = presentation
|
33
|
+
@el = element
|
34
|
+
end
|
35
|
+
|
36
|
+
def type
|
37
|
+
self.class.name
|
38
|
+
# self.class.name.split('::').last
|
39
|
+
end
|
40
|
+
|
41
|
+
def parent
|
42
|
+
presentation.parent_asset(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def children
|
46
|
+
presentation.child_assets(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def find(criteria={},&block)
|
50
|
+
criteria[:_under] ||= self
|
51
|
+
presentation.find(criteria,&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Find the owning component (even if you are a component)
|
55
|
+
def component
|
56
|
+
presentation.owning_component(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def component?
|
60
|
+
@el.name == 'Component'
|
61
|
+
end
|
62
|
+
|
63
|
+
def master?
|
64
|
+
presentation.master?(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def slide?
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_slide?(slide_name_or_index)
|
72
|
+
presentation.has_slide?(self,slide_name_or_index)
|
73
|
+
end
|
74
|
+
|
75
|
+
def slides
|
76
|
+
presentation.slides_for(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_slide(slide_name_or_index)
|
80
|
+
if has_slide?(slide_name_or_index)
|
81
|
+
UIC::SlideValues.new( self, slide_name_or_index )
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def path
|
86
|
+
@path ||= @presentation.path_to(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
def name
|
90
|
+
properties['name'].get( self, presentation.slide_index(self) )
|
91
|
+
end
|
92
|
+
|
93
|
+
def name=( new_name )
|
94
|
+
properties['name'].set( self, new_name, presentation.slide_index(self) )
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the value(s) of an attribute
|
98
|
+
def [](attribute_name, slide_name_or_index=nil)
|
99
|
+
if property = properties[attribute_name]
|
100
|
+
if slide_name_or_index
|
101
|
+
property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
|
102
|
+
else
|
103
|
+
UIC::ValuesPerSlide.new(@presentation,self,property)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set the value of an attribute, either across all slides, or on a particular slide
|
109
|
+
# el['foo'] = 42
|
110
|
+
# el['foo',0] = 42
|
111
|
+
def []=( attribute_name, slide_name_or_index=nil, new_value )
|
112
|
+
if property = properties[attribute_name] then
|
113
|
+
property.set(self,new_value,slide_name_or_index)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_xml
|
118
|
+
@el.to_xml
|
119
|
+
end
|
120
|
+
def inspect
|
121
|
+
"<asset #{@el.name}##{@el['id']}>"
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_s
|
125
|
+
"<#{type} #{path}>"
|
126
|
+
end
|
127
|
+
|
128
|
+
def ==(other)
|
129
|
+
(self.class==other.class) && (el==other.el)
|
130
|
+
end
|
131
|
+
alias_method :eql?, :==
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_reader :by_name
|
135
|
+
|
136
|
+
HIER = {}
|
137
|
+
%w[Asset Slide Scene].each{ |s| HIER[s] = 'Root' }
|
138
|
+
%w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
|
139
|
+
%w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
|
140
|
+
%w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
|
141
|
+
|
142
|
+
def initialize(xml)
|
143
|
+
@by_name = {'Root'=>Root}
|
144
|
+
|
145
|
+
doc = Nokogiri.XML(xml)
|
146
|
+
hack_in_slide_names!(doc)
|
147
|
+
|
148
|
+
HIER.each do |class_name,parent_class_name|
|
149
|
+
parent_class = @by_name[parent_class_name]
|
150
|
+
el = doc.root.at(class_name)
|
151
|
+
@by_name[class_name] = create_class(el,parent_class,el.name)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Extend well-known classes with script interfaces after they are created
|
155
|
+
@by_name['State'] = @by_name['Slide']
|
156
|
+
@by_name['Slide'].instance_eval do
|
157
|
+
attr_accessor :index, :name
|
158
|
+
define_method :inspect do
|
159
|
+
"<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
|
160
|
+
end
|
161
|
+
define_method(:slide?){ true }
|
162
|
+
end
|
163
|
+
|
164
|
+
refmat = @by_name['ReferencedMaterial']
|
165
|
+
@by_name['MaterialBase'].instance_eval do
|
166
|
+
define_method :replace_with_referenced_material do
|
167
|
+
type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
@by_name['Path'].instance_eval do
|
172
|
+
define_method(:anchors){ find _type:'PathAnchorPoint' }
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
# Creates a class from MetaData.xml with accessors for the <Property> listed.
|
178
|
+
# Instances of the class are associated with a presentation and know how to
|
179
|
+
# get/set values in that XML based on value types, slides, defaults.
|
180
|
+
# Also used to create classes from effects, materials, and behavior preambles.
|
181
|
+
def create_class(el,parent_class,name='CustomAsset')
|
182
|
+
Class.new(parent_class) do
|
183
|
+
@name = name.to_s
|
184
|
+
@properties = Hash[ el.css("Property").map do |e|
|
185
|
+
type = e['type'] || (e['list'] ? 'String' : 'Float')
|
186
|
+
type = "Float" if type=="float"
|
187
|
+
property = UIC::Property.const_get(type).new(e)
|
188
|
+
[ property.name, UIC::Property.const_get(type).new(e) ]
|
189
|
+
end ]
|
190
|
+
def self.inspect
|
191
|
+
@name
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def new_instance(presentation,el)
|
197
|
+
klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
|
198
|
+
klass.new(presentation,el)
|
199
|
+
end
|
200
|
+
|
201
|
+
def hack_in_slide_names!(doc)
|
202
|
+
doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def UIC.MetaData(metadata_path)
|
207
|
+
UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
|
208
|
+
end
|
209
|
+
|
210
|
+
class UIC::SlideCollection
|
211
|
+
include Enumerable
|
212
|
+
attr_reader :length
|
213
|
+
def initialize(slides)
|
214
|
+
@length = slides.length-1
|
215
|
+
@slides = slides
|
216
|
+
@lookup = {}
|
217
|
+
slides.each do |s|
|
218
|
+
@lookup[s.index] = s
|
219
|
+
@lookup[s.name] = s
|
220
|
+
end
|
221
|
+
end
|
222
|
+
def each
|
223
|
+
@slides.each{ |s| yield(s) }
|
224
|
+
end
|
225
|
+
def [](index_or_name)
|
226
|
+
@lookup[ index_or_name ]
|
227
|
+
end
|
228
|
+
def inspect
|
229
|
+
"[ #{@slides.map(&:inspect).join ', '} ]"
|
230
|
+
end
|
231
|
+
def to_ary
|
232
|
+
@slides
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class UIC::ValuesPerSlide
|
237
|
+
def initialize(presentation,asset,property)
|
238
|
+
raise unless presentation.is_a?(UIC::Presentation)
|
239
|
+
raise unless asset.is_a?(UIC::MetaData::Root)
|
240
|
+
raise unless property.is_a?(UIC::Property)
|
241
|
+
@preso = presentation
|
242
|
+
@asset = asset
|
243
|
+
@el = asset.el
|
244
|
+
@property = property
|
245
|
+
end
|
246
|
+
def value
|
247
|
+
values.first
|
248
|
+
end
|
249
|
+
def [](slide_name_or_index)
|
250
|
+
@property.get( @asset, slide_name_or_index )
|
251
|
+
end
|
252
|
+
def []=(slide_name_or_index,new_value)
|
253
|
+
@property.set( @asset, new_value, slide_name_or_index )
|
254
|
+
end
|
255
|
+
def linked?
|
256
|
+
@preso.attribute_linked?( @asset, @property.name )
|
257
|
+
end
|
258
|
+
def unlink
|
259
|
+
@preso.unlink_attribute( @asset, @property.name )
|
260
|
+
end
|
261
|
+
def link
|
262
|
+
@preso.link_attribute( @asset, @property.name )
|
263
|
+
end
|
264
|
+
def values
|
265
|
+
@asset.slides.map{ |s| self[s.name] }
|
266
|
+
end
|
267
|
+
def inspect
|
268
|
+
"<Values of '#{@asset.name}.#{@property.name}' across slides>"
|
269
|
+
end
|
270
|
+
alias_method :to_s, :inspect
|
271
|
+
end
|
272
|
+
|
273
|
+
class UIC::SlideValues
|
274
|
+
def initialize( asset, slide )
|
275
|
+
@asset = asset
|
276
|
+
@slide = slide
|
277
|
+
end
|
278
|
+
def [](attribute_name)
|
279
|
+
@asset[attribute_name,@slide]
|
280
|
+
end
|
281
|
+
def []=( attribute_name, new_value )
|
282
|
+
@asset[attribute_name,@slide] = new_value
|
283
|
+
end
|
284
|
+
def method_missing( name, *args, &blk )
|
285
|
+
asset.send(name,*args,&blk)
|
286
|
+
end
|
287
|
+
def inspect
|
288
|
+
"<#{@asset.inspect} on slide #{@slide.inspect}>"
|
289
|
+
end
|
288
290
|
end
|