RUIC 0.5.0 → 0.6.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/HISTORY +23 -0
  3. data/README.md +2 -2
  4. data/lib/ruic.rb +11 -3
  5. data/lib/ruic/application.rb +28 -22
  6. data/lib/ruic/attributes.rb +10 -2
  7. data/lib/ruic/behaviors.rb +2 -15
  8. data/lib/ruic/effect.rb +32 -0
  9. data/lib/ruic/interfaces.rb +51 -13
  10. data/lib/ruic/presentation.rb +46 -20
  11. data/lib/ruic/renderplugin.rb +18 -0
  12. data/lib/ruic/ripl.rb +45 -0
  13. data/lib/ruic/statemachine.rb +142 -25
  14. data/lib/ruic/version.rb +1 -1
  15. data/test/customclasses.ruic +0 -1
  16. data/test/filtering.ruic +0 -1
  17. data/test/futureassets.ruic +0 -1
  18. data/test/nonmaster.ruic +0 -1
  19. data/test/paths.ruic +0 -2
  20. data/test/projects/MissingAssets/Existing.uip +87 -0
  21. data/test/projects/MissingAssets/MissingAssets.uia +21 -0
  22. data/test/projects/MissingAssets/MissingAssets.uia-user +14 -0
  23. data/test/projects/MissingAssets/RoundedPlane-1/RoundedPlane-1.import +18 -0
  24. data/test/projects/MissingAssets/RoundedPlane-1/meshes/RoundedPlane.mesh +0 -0
  25. data/test/projects/MissingAssets/effects/effects.txt +3 -0
  26. data/test/projects/MissingAssets/effects/existing.effect +38 -0
  27. data/test/projects/MissingAssets/fonts/Arimo-Regular.ttf +0 -0
  28. data/test/projects/MissingAssets/fonts/Chivo-Black.ttf +0 -0
  29. data/test/projects/MissingAssets/fonts/OFL.txt +92 -0
  30. data/test/projects/MissingAssets/maps/effects/brushnoise.dds +0 -0
  31. data/test/projects/MissingAssets/maps/existing.png +0 -0
  32. data/test/projects/MissingAssets/maps/existing2.png +0 -0
  33. data/test/projects/MissingAssets/maps/maps.txt +2 -0
  34. data/test/projects/MissingAssets/maps/materials/concrete_plain.png +0 -0
  35. data/test/projects/MissingAssets/maps/materials/concrete_plain_bump.png +0 -0
  36. data/test/projects/MissingAssets/maps/materials/spherical_checker.png +0 -0
  37. data/test/projects/MissingAssets/maps/unused.png +0 -0
  38. data/test/projects/MissingAssets/materials/concrete.material +251 -0
  39. data/test/projects/MissingAssets/materials/materials.txt +3 -0
  40. data/test/projects/MissingAssets/scripts/existing1.lua +2 -0
  41. data/test/projects/MissingAssets/scripts/existing2.lua +470 -0
  42. data/test/projects/MissingAssets/states/existing.scxml +4 -0
  43. data/test/projects/MissingAssets/tests/interface-navigation.scxml-test +3 -0
  44. data/test/properties.ruic +0 -2
  45. data/test/referencematerials.ruic +0 -2
  46. data/test/usage.ruic +38 -4
  47. metadata +53 -3
  48. data/lib/ruic/ripl-after-result.rb +0 -25
@@ -0,0 +1,18 @@
1
+ class UIC::RenderPlugin
2
+ include UIC::FileBacked
3
+ def initialize( file )
4
+ self.file = file
5
+ end
6
+ end
7
+
8
+ class UIC::Application::RenderPlugin < UIC::RenderPlugin
9
+ include UIC::ElementBacked
10
+ # @!parse extend UIC::ElementBacked::ClassMethods
11
+ xmlattribute :id
12
+ xmlattribute :src
13
+ def initialize(application,el)
14
+ self.owner = application
15
+ self.el = el
16
+ super( application.absolute_path(src) )
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ module Ripl; end
2
+
3
+ # Allows [Ripl](https://github.com/cldwalker/ripl) to execute code or print a message after every result.
4
+ #
5
+ # @example 1) Printing a blank line after each result
6
+ # require_relative 'ripl-after-result'
7
+ # Ripl::Shell.include Ripl::AfterResult
8
+ # Ripl.config.merge! after_result:"\n"
9
+ #
10
+ # @example 2) Executing arbitrary code with the result
11
+ # results = []
12
+ # require_relative 'ripl-after-result'
13
+ # Ripl::Shell.include Ripl::AfterResult
14
+ # Ripl.config.merge! after_result:proc{ |result| results << result }
15
+ module Ripl::AfterResult
16
+ # @private no need to document the method
17
+ def print_result(result)
18
+ super unless result.nil? && config[:skip_nil_results]
19
+ if after=config[:after_result]
20
+ if after.respond_to?(:call)
21
+ after.call(result)
22
+ else
23
+ puts after
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Allows [Ripl](https://github.com/cldwalker/ripl) to wrap lines of output
30
+ # and/or prefix each line of the result with the `result_prompt`.
31
+ module Ripl::FormatResult
32
+ # @private no need to document the method
33
+ def format_result(result,conf={})
34
+ conf=config if respond_to? :config
35
+ result = conf[:to_s] ? result.to_s : result.inspect
36
+ result = result.dup if result.frozen?
37
+ if limit=conf[:result_line_limit]
38
+ match = /^.{#{limit}}.+/o
39
+ :go while result.sub!(match){ |line| line.sub /^(?:(.{,#{limit}})[ \t]+|(.{#{limit}}))/o, "\\1\\2\n" }
40
+ end
41
+
42
+ result.gsub( conf[:prefix_result_lines] ? /^/ : /\A/, conf[:result_prompt] )
43
+ end
44
+ module_function :format_result
45
+ end
@@ -1,15 +1,10 @@
1
1
  class UIC::StateMachine
2
- include UIC::FileBacked
3
- def initialize( xml )
4
- @doc = Nokogiri.XML( xml )
2
+ include UIC::XMLFileBacked
3
+ def initialize( scxml )
4
+ self.file = scxml
5
5
  end
6
-
7
- def errors?
8
- !errors.empty?
9
- end
10
-
11
- def errors
12
- file_found? ? [] : ["File not found: '#{file}'"]
6
+ def inspect
7
+ "<#{self.class} #{file}>"
13
8
  end
14
9
  end
15
10
 
@@ -29,12 +24,13 @@ class UIC::Application::StateMachine < UIC::StateMachine
29
24
  def initialize(application,el)
30
25
  self.owner = application
31
26
  self.el = el
32
- self.file = application.resolve_file_path(src)
33
- super( File.read( file, encoding:'utf-8' ) )
34
- @visuals = @doc.at( "/application/statemachine[@ref='##{id}']/visual-states" )
35
- @visuals ||= @doc.root.add_child("<statemachine ref='##{id}'><visual-states/></statemachine>")
27
+
28
+ @visuals = app.doc.at( "/xmlns:application/xmlns:statemachine[@ref='##{id}']/xmlns:visual-states" )
29
+ @visuals ||= app.doc.root.add_child("<statemachine ref='##{id}'><visual-states/></statemachine>")
36
30
  @visual_states = VisualStates.new( self, @visuals )
37
31
  @visual_transitions = VisualTransitions.new( self, @visuals )
32
+
33
+ self.file = application.absolute_path(src)
38
34
  end
39
35
  alias_method :app, :owner
40
36
 
@@ -48,12 +44,32 @@ class UIC::Application::StateMachine < UIC::StateMachine
48
44
  visual_action.value[/\A(['"])[^'"]+\1\Z/] && # ensure that it's a simple string value
49
45
  visual_action.element.properties[ visual_action.attribute ].is_a?( UIC::Property::Image )
50
46
  end.group_by do |visual_action,owner|
51
- visual_action.value[/\A(['"])([^'"]+)\1\Z/,2]
47
+ app.absolute_path( visual_action.value[/\A(['"])([^'"]+)\1\Z/,2] )
52
48
  end.each do |image_path,array|
53
49
  array.map!(&:last)
54
50
  end
55
51
  end
56
52
 
53
+ # @return [Array<VisualAction>] all visual actions in the `.uia` for this state machine.
54
+ def visual_actions
55
+ visual_states.flat_map(&:enter_actions) +
56
+ visual_states.flat_map(&:exit_actions) +
57
+ visual_transitions.flat_map(&:actions)
58
+ end
59
+
60
+ def referenced_files
61
+ visual_actions.map do |action|
62
+ if action.is_a?(VisualAction::SetAttribute) && (el=action.element) && (path=action.value[ /\A(['"])([^'"]+)\1\Z/, 2 ])
63
+ type=el.properties[ action.attribute ].type
64
+ if action.attribute=='sourcepath' || action.attribute=='importfile' || type=='Texture'
65
+ app.absolute_path( path.sub(/#.+$/,'') )
66
+ elsif type=='Font'
67
+ app.absolute_path( File.join( 'fonts', path.sub(/$/,'.ttf') ) )
68
+ end
69
+ end
70
+ end.compact.uniq
71
+ end
72
+
57
73
  class UIC::Application::StateMachine::VisualStates
58
74
  include Enumerable
59
75
  def initialize(app_machine,visuals_el)
@@ -62,15 +78,15 @@ class UIC::Application::StateMachine < UIC::StateMachine
62
78
  @by_el = {}
63
79
  end
64
80
  def each
65
- @wrap.xpath('state').each{ |el| yield @by_el[el] ||= VisualState.new(el) }
81
+ @wrap.xpath('xmlns:state').each{ |el| yield @by_el[el] ||= VisualState.new(el,@machine) }
66
82
  end
67
83
  def [](id)
68
- if el=@wrap.at("state[@ref='#{id}']")
69
- @by_el[el] ||= VisualState.new(el)
84
+ if el=@wrap.at("xmlns:state[@ref='#{id}']")
85
+ @by_el[el] ||= VisualState.new(el,@machine)
70
86
  end
71
87
  end
72
88
  def length
73
- @wrap.xpath('count(state)').to_i
89
+ @wrap.xpath('count(xmlns:state)').to_i
74
90
  end
75
91
  alias_method :count, :length
76
92
  end
@@ -83,30 +99,131 @@ class UIC::Application::StateMachine < UIC::StateMachine
83
99
  @by_el = {}
84
100
  end
85
101
  def each
86
- @wrap.xpath('transition').each{ |el| yield @by_el[el] ||= VisualState.new(el) }
102
+ @wrap.xpath('xmlns:transition').each{ |el| yield @by_el[el] ||= VisualTransition.new(el,@machine) }
87
103
  end
88
104
  def [](id)
89
- if el=@wrap.at("transition[@ref='#{id}']")
90
- @by_el[el] ||= VisualTransition.new(el)
105
+ if el=@wrap.at("xmlns:transition[@ref='#{id}']")
106
+ @by_el[el] ||= VisualTransition.new(el,@machine)
91
107
  end
92
108
  end
93
109
  def length
94
- @wrap.xpath('count(transition)').to_i
110
+ @wrap.xpath('count(xmlns:transition)').to_i
95
111
  end
96
112
  alias_method :count, :length
97
113
  end
98
114
 
99
115
  class UIC::Application::StateMachine::VisualState
100
- def initialize(el)
116
+ include UIC::ElementBacked
117
+
118
+ # @!attribute ref
119
+ # @return [String] the `id` of the state in the state machine whose enter/exit will trigger the attached visual actions.
120
+ xmlattribute :ref
121
+
122
+ # @return [Nokogiri::XML::Element] the Nokogiri element in the `.uia` representing this visual state.
123
+ attr_reader :el
124
+
125
+ # @return [Application::StateMachine] the state machine containing the referenced state.
126
+ attr_reader :machine
127
+
128
+ def initialize(el,machine)
101
129
  @el = el
130
+ @machine = machine
131
+ end
132
+
133
+ def enter_actions
134
+ @el.xpath('xmlns:enter/*').to_a.map{ |el| VisualAction.create(el,self) }
135
+ end
136
+
137
+ def exit_actions
138
+ @el.xpath('xmlns:exit/*').to_a.map{ |el| VisualAction.create(el,self) }
102
139
  end
103
140
  end
104
141
 
105
142
  class UIC::Application::StateMachine::VisualTransition
106
- def initialize(el)
143
+ include UIC::ElementBacked
144
+
145
+ # @!attribute ref
146
+ # @return [String] the `uic:id` of the transition that will trigger the attached visual actions.
147
+ xmlattribute :ref
148
+
149
+ def initialize(el,machine)
107
150
  @el = el
151
+ @machine = machine
152
+ end
153
+ def actions
154
+ @el.xpath('./*').to_a.map{ |el| VisualAction.create(el,self) }
108
155
  end
109
156
  end
110
157
 
158
+ class UIC::Application::StateMachine::VisualAction
159
+ include UIC::ElementBacked
160
+
161
+ # @!attribute element
162
+ # @return [UIC::MetaData::AssetBase] the element in a UIC presentation affected by this action.
163
+ xmlattribute(:element, lambda{ |path,action| (action.machine.app / path) }){ |element| element.path }
164
+
165
+ # @return [Nokogiri::XML::Element] the Nokogiri element representing this action in the `.uia`.
166
+ attr_reader :el
111
167
 
168
+ # @return [VisualState,VisualTransition] the visual state or transition wrapping this action.
169
+ attr_reader :owner
170
+
171
+ # @return [Application::StateMachine] the state machine triggering this action.
172
+ attr_reader :machine
173
+
174
+ def self.create(el,owner)
175
+ klass = case el.name
176
+ when 'goto-slide' then GotoSlide
177
+ when 'call' then Call
178
+ when 'set-attribute' then SetAttribute
179
+ when 'fire-event' then FireEvent
180
+ else Generic
181
+ end
182
+ klass.new(el,owner)
183
+ end
184
+
185
+ def initialize(el,owner)
186
+ @el = el
187
+ @owner = owner
188
+ @machine = owner.machine
189
+ end
190
+
191
+ class UIC::Application::StateMachine::VisualAction::GotoSlide < self
192
+ # TODO: xmlattributes
193
+ end
194
+
195
+ class UIC::Application::StateMachine::VisualAction::Call < self
196
+ # TODO: xmlattributes
197
+ end
198
+
199
+ class UIC::Application::StateMachine::VisualAction::SetAttribute < self
200
+ # @!attribute attribute
201
+ # @return [String] the name of the attribute to set.
202
+ xmlattribute :attribute
203
+
204
+ # @!attribute value
205
+ # @return [String] the Lua expression to evaluate as the new value.
206
+ xmlattribute :value
207
+ end
208
+
209
+ class UIC::Application::StateMachine::VisualAction::FireEvent < self
210
+ # TODO: xmlattributes
211
+ end
212
+
213
+ class UIC::Application::StateMachine::VisualAction::Generic
214
+ # @return [Nokogiri::XML::Element] the Nokogiri element representing this action in the `.uia`.
215
+ attr_reader :el
216
+
217
+ # @return [VisualState,VisualTransition] the visual state or transition wrapping this action.
218
+ attr_reader :owner
219
+
220
+ # TODO: xmlattributes
221
+
222
+ def initialize(el,owner)
223
+ @el = el
224
+ @owner = owner
225
+ @machine = owner.machine
226
+ end
227
+ end
228
+ end
112
229
  end
@@ -1,3 +1,3 @@
1
1
  class RUIC
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruic
2
2
  metadata 'MetaData.xml' # optional; also may be set via -m flag
3
3
  uia 'projects/CustomClasses/CustomClasses.uia' # required before other commands
4
- show app.errors if app.errors?
5
4
 
6
5
  main = app.main_presentation
7
6
 
@@ -1,7 +1,6 @@
1
1
  metadata 'MetaData.xml'
2
2
 
3
3
  uia 'projects/SimpleScene/SimpleScene.uia'
4
- show app.errors if app.errors?
5
4
 
6
5
  main = app.main_presentation
7
6
 
@@ -1,6 +1,5 @@
1
1
  metadata 'MetaData-simple.xml'
2
2
  uia 'projects/CustomClasses/CustomClasses.uia' # required before other commands
3
- show app.errors if app.errors?
4
3
 
5
4
  future = app["#future"]
6
5
 
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruic
2
2
  metadata 'MetaData.xml' # optional; also may be set via -m flag
3
3
  uia 'projects/CustomClasses/CustomClasses.uia' # required before other commands
4
- show app.errors if app.errors?
5
4
 
6
5
  main = app.main_presentation
7
6
 
@@ -2,8 +2,6 @@
2
2
  metadata 'MetaData.xml' # optional; also may be set via -m flag
3
3
  uia 'projects/Paths/Paths.uia' # required before other commands
4
4
 
5
- show "Uh oh!", app.errors if app.errors?
6
-
7
5
  main = app.main
8
6
  paths = main.find _type:'Path'
9
7
  assert paths.length == 5
@@ -0,0 +1,87 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <UIP version="3" >
3
+ <Project >
4
+ <ProjectSettings author="" company="" presentationWidth="800" presentationHeight="480" maintainAspect="False" />
5
+ <Classes >
6
+ <Effect id="existing_001" name="existing" sourcepath=".\effects\existing.effect" />
7
+ <Effect id="missing_001" name="missing" sourcepath=".\effects\missing.effect" />
8
+ <CustomMaterial id="concrete" name="concrete" sourcepath=".\materials\concrete.material" />
9
+ <CustomMaterial id="missingcustom" name="missingcustom" sourcepath=".\materials\missing.material" />
10
+ <Behavior id="existing1" name="existing1" sourcepath=".\scripts\existing1.lua" />
11
+ <Behavior id="missing1" name="missing1" sourcepath=".\scripts\missing1.lua" />
12
+ </Classes>
13
+ <Graph >
14
+ <Scene id="Scene" >
15
+ <Behavior id="existing1_001" class="#existing1" />
16
+ <Behavior id="missing1_001" class="#missing1" />
17
+ <Layer id="Layer" >
18
+ <Camera id="Camera" />
19
+ <Light id="Light" />
20
+ <Text id="Text" />
21
+ <Text id="Text2" />
22
+ <Model id="existing" >
23
+ <Material id="Material" >
24
+ <Image id="Material_diffusemap" />
25
+ </Material>
26
+ </Model>
27
+ <Model id="missing" >
28
+ <Material id="Material_001" >
29
+ <Image id="Material_001_diffusemap" />
30
+ </Material>
31
+ </Model>
32
+ <Effect id="existing2" class="#existing_001" />
33
+ <Effect id="existing3" class="#existing_001" />
34
+ <Effect id="missing2" class="#missing_001" />
35
+ <Model id="Cube" >
36
+ <CustomMaterial id="Material_002" class="#concrete" />
37
+ </Model>
38
+ <Model id="Rectangle" >
39
+ <CustomMaterial id="concrete_001" class="#missingcustom" />
40
+ </Model>
41
+ <Group id="RoundedPlane-1" importid="__import__root__" >
42
+ <Model id="RoundedPlane" name="RoundedPlane" importid="RoundedPlane" orientation="Right Handed" position="0 0 -0" rotation="0 -0 0" rotationorder="XYZr" scale="1 1 1" sourcepath=".\RoundedPlane-1\meshes\RoundedPlane.mesh#1" >
43
+ <Material id="lambert2" name="lambert2" blendmode="Normal" diffuse="1 1 1" emissivepower="0" importid="lambert2" opacity="100" specularamount="0" />
44
+ </Model>
45
+ </Group>
46
+ <Group id="MissingImport" importid="__import__root__" >
47
+ <Model id="MissingMesh" name="RoundedPlane" importid="RoundedPlane" orientation="Right Handed" position="0 0 -0" rotation="0 -0 0" rotationorder="XYZr" scale="1 1 1" sourcepath=".\models\meshes\missing.mesh#1" >
48
+ <Material id="lambert2_001" name="lambert2" blendmode="Normal" diffuse="1 1 1" emissivepower="0" importid="lambert2" opacity="100" specularamount="0" />
49
+ </Model>
50
+ </Group>
51
+ </Layer>
52
+ </Scene>
53
+ </Graph>
54
+ <Logic >
55
+ <State name="Master Slide" component="#Scene" >
56
+ <Add ref="#Layer" />
57
+ <Add ref="#Camera" />
58
+ <Add ref="#Light" />
59
+ <State id="Scene-Intro" name="Intro" >
60
+ <Add ref="#existing1_001" name="existing1" />
61
+ <Add ref="#missing1_001" name="missing1" />
62
+ <Add ref="#Text" name="WelcomeMessage" font="Arimo-Regular" position="0 90 0" size="36" textstring="Hello World!&#10;This is the Intro Slide." />
63
+ <Add ref="#Text2" name="MissingText" font="Missing" />
64
+ <Add ref="#existing" name="existing" position="-353.627 -164.545 0" scale="2.7328 2.7328 0.53375" sourcepath="#Rectangle" />
65
+ <Add ref="#Material" diffusemap="#Material_diffusemap" />
66
+ <Add ref="#Material_diffusemap" sourcepath=".\maps\existing.png" />
67
+ <Add ref="#missing" name="missing" position="291.562 -170.318 0" scale="2.432 2.432 0.475" sourcepath="#Rectangle" />
68
+ <Add ref="#Material_001" diffusemap="#Material_001_diffusemap" />
69
+ <Add ref="#Material_001_diffusemap" sourcepath=".\maps\missing.png" />
70
+ <Add ref="#existing2" name="existing2" />
71
+ <Add ref="#existing3" name="existing3" NoiseSamp=".\maps\missing2.png" />
72
+ <Add ref="#missing2" name="missing2" />
73
+ <Add ref="#Cube" name="Cube" position="373.834 191.969 0" rotation="-41.4279 82.585 -28.6931" sourcepath="#Cube" />
74
+ <Add ref="#Material_002" name="Material" bump_texture=".\maps\missing.png" diffuse_texture=".\maps\existing.png" />
75
+ <Add ref="#Rectangle" name="Rectangle" position="-375.278 223.723 0" sourcepath="#Rectangle" />
76
+ <Add ref="#concrete_001" name="MissingCustom" />
77
+ <Add ref="#RoundedPlane-1" name="RoundedPlane-1" importfile=".\RoundedPlane-1\RoundedPlane-1.import" sourcepath=".\RoundedPlane-1\RoundedPlane-1.import" />
78
+ <Add ref="#RoundedPlane" importfile=".\RoundedPlane-1\RoundedPlane-1.import" />
79
+ <Add ref="#lambert2" importfile=".\RoundedPlane-1\RoundedPlane-1.import" />
80
+ <Add ref="#MissingImport" name="MissingImport" importfile=".\RoundedPlane-1\RoundedPlane-1.import" position="20 0 0" sourcepath=".\RoundedPlane-1\RoundedPlane-1.import" />
81
+ <Add ref="#MissingMesh" name="MissingMesh" importfile=".\RoundedPlane-1\RoundedPlane-1.import" />
82
+ <Add ref="#lambert2_001" importfile=".\RoundedPlane-1\RoundedPlane-1.import" />
83
+ </State>
84
+ </State>
85
+ </Logic>
86
+ </Project>
87
+ </UIP>
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <application scalemode="center" watermark-location="1.0 1.0" xmlns="http://nvidia.com/uicomposer">
3
+ <assets initial="main">
4
+ <behavior src="scripts/existing1.lua" />
5
+ <behavior src="scripts/missing1.lua" />
6
+ <statemachine id="s1" src="states/existing.scxml" />
7
+ <statemachine id="s2" src="states/missing.scxml" />
8
+ <presentation id="main" src="Existing.uip" />
9
+ <presentation id="sub" src="Missing.uip" />
10
+ <renderplugin id="rp" src="scripts/missing.so" />
11
+ </assets>
12
+ <statemachine ref="#s1">
13
+ <visual-states>
14
+ <state ref="Initial"><enter>
15
+ <set-attribute element="main:Scene.Layer.existing.Material.Image" attribute="sourcepath" value="'./maps/existing2.png'" />
16
+ </enter><exit>
17
+ <set-attribute element="main:Scene.Layer.existing.Material.Image" attribute="sourcepath" value="'.\\maps\\missing3.png'" />
18
+ </exit></state>
19
+ </visual-states>
20
+ </statemachine>
21
+ </application>