class UIC::StateMachine
include UIC::XMLFileBacked
def initialize( scxml )
self.file = scxml
end
def inspect
"<#{self.class} #{file}>"
end
end
def UIC.StateMachine( scxml_path )
UIC::StateMachine.new(File.read(scxml_path,encoding:'utf-8'))
.tap{ |o| o.file = scxml_path }
end
class UIC::Application::StateMachine < UIC::StateMachine
include UIC::ElementBacked
# @!parse extend UIC::ElementBacked::ClassMethods
xmlattribute :id
xmlattribute :src
xmlattribute :datamodel
attr_reader :visual_states
attr_reader :visual_transitions
def initialize(application,el)
self.owner = application
self.el = el
@visuals = app.doc.at( "/xmlns:application/xmlns:statemachine[@ref='##{id}']/xmlns:visual-states" )
@visuals ||= app.doc.root.add_child("")
@visual_states = VisualStates.new( self, @visuals )
@visual_transitions = VisualTransitions.new( self, @visuals )
self.file = application.absolute_path(src)
end
alias_method :app, :owner
def image_usage
(
visual_states.flat_map{ |vs| vs.enter_actions.flat_map{ |a| [a,vs] } } +
visual_states.flat_map{ |vs| vs.exit_actions.flat_map{ |a| [a,vs] } } +
visual_transitions.flat_map{ |vt| vt.actions.flat_map{ |a| [a,vt] } }
).select do |visual_action,owner|
visual_action.is_a?(UIC::Application::StateMachine::VisualAction::SetAttribute) &&
visual_action.value[/\A(['"])[^'"]+\1\Z/] && # ensure that it's a simple string value
visual_action.element.properties[ visual_action.attribute ].is_a?( UIC::Property::Image )
end.group_by do |visual_action,owner|
app.absolute_path( visual_action.value[/\A(['"])([^'"]+)\1\Z/,2] )
end.each do |image_path,array|
array.map!(&:last)
end
end
# @return [Array] all visual actions in the `.uia` for this state machine.
def visual_actions
visual_states.flat_map(&:enter_actions) +
visual_states.flat_map(&:exit_actions) +
visual_transitions.flat_map(&:actions)
end
def referenced_files
visual_actions.map do |action|
if action.is_a?(VisualAction::SetAttribute) && (el=action.element) && (path=action.value[ /\A(['"])([^'"]+)\1\Z/, 2 ])
type=el.properties[ action.attribute ].type
if action.attribute=='sourcepath' || action.attribute=='importfile' || type=='Texture'
app.absolute_path( path.sub(/#.+$/,'') )
elsif type=='Font'
app.absolute_path( File.join( 'fonts', path.sub(/$/,'.ttf') ) )
end
end
end.compact.uniq
end
class UIC::Application::StateMachine::VisualStates
include Enumerable
def initialize(app_machine,visuals_el)
@machine = app_machine
@wrap = visuals_el
@by_el = {}
end
def each
@wrap.xpath('xmlns:state').each{ |el| yield @by_el[el] ||= VisualState.new(el,@machine) }
end
def [](id)
if el=@wrap.at("xmlns:state[@ref='#{id}']")
@by_el[el] ||= VisualState.new(el,@machine)
end
end
def length
@wrap.xpath('count(xmlns:state)').to_i
end
alias_method :count, :length
end
class UIC::Application::StateMachine::VisualTransitions
include Enumerable
def initialize(app_machine,visuals_el)
@machine = app_machine
@wrap = visuals_el
@by_el = {}
end
def each
@wrap.xpath('xmlns:transition').each{ |el| yield @by_el[el] ||= VisualTransition.new(el,@machine) }
end
def [](id)
if el=@wrap.at("xmlns:transition[@ref='#{id}']")
@by_el[el] ||= VisualTransition.new(el,@machine)
end
end
def length
@wrap.xpath('count(xmlns:transition)').to_i
end
alias_method :count, :length
end
class UIC::Application::StateMachine::VisualState
include UIC::ElementBacked
# @!attribute ref
# @return [String] the `id` of the state in the state machine whose enter/exit will trigger the attached visual actions.
xmlattribute :ref
# @return [Nokogiri::XML::Element] the Nokogiri element in the `.uia` representing this visual state.
attr_reader :el
# @return [Application::StateMachine] the state machine containing the referenced state.
attr_reader :machine
def initialize(el,machine)
@el = el
@machine = machine
end
def enter_actions
@el.xpath('xmlns:enter/*').to_a.map{ |el| VisualAction.create(el,self) }
end
def exit_actions
@el.xpath('xmlns:exit/*').to_a.map{ |el| VisualAction.create(el,self) }
end
end
class UIC::Application::StateMachine::VisualTransition
include UIC::ElementBacked
# @!attribute ref
# @return [String] the `uic:id` of the transition that will trigger the attached visual actions.
xmlattribute :ref
# @return [Nokogiri::XML::Element] the Nokogiri element in the `.uia` representing this visual state.
attr_reader :el
# @return [Application::StateMachine] the state machine containing the referenced state.
attr_reader :machine
def initialize(el,machine)
@el = el
@machine = machine
end
def actions
@el.xpath('./*').to_a.map{ |el| VisualAction.create(el,self) }
end
end
class UIC::Application::StateMachine::VisualAction
include UIC::ElementBacked
# @!attribute element
# @return [UIC::MetaData::AssetBase] the element in a UIC presentation affected by this action.
xmlattribute(:element, lambda{ |path,action| (action.machine.app / path) }){ |element| element.path }
# @return [Nokogiri::XML::Element] the Nokogiri element representing this action in the `.uia`.
attr_reader :el
# @return [VisualState,VisualTransition] the visual state or transition wrapping this action.
attr_reader :owner
# @return [Application::StateMachine] the state machine triggering this action.
attr_reader :machine
def self.create(el,owner)
klass = case el.name
when 'goto-slide' then GotoSlide
when 'call' then Call
when 'set-attribute' then SetAttribute
when 'fire-event' then FireEvent
else Generic
end
klass.new(el,owner)
end
def initialize(el,owner)
@el = el
@owner = owner
@machine = owner.machine
end
class UIC::Application::StateMachine::VisualAction::GotoSlide < self
# TODO: xmlattributes
end
class UIC::Application::StateMachine::VisualAction::Call < self
# TODO: xmlattributes
end
class UIC::Application::StateMachine::VisualAction::SetAttribute < self
# @!attribute attribute
# @return [String] the name of the attribute to set.
xmlattribute :attribute
# @!attribute value
# @return [String] the Lua expression to evaluate as the new value.
xmlattribute :value
end
class UIC::Application::StateMachine::VisualAction::FireEvent < self
# TODO: xmlattributes
end
class UIC::Application::StateMachine::VisualAction::Generic
# @return [Nokogiri::XML::Element] the Nokogiri element representing this action in the `.uia`.
attr_reader :el
# @return [VisualState,VisualTransition] the visual state or transition wrapping this action.
attr_reader :owner
# TODO: xmlattributes
def initialize(el,owner)
@el = el
@owner = owner
@machine = owner.machine
end
end
end
end