Declarative Markup Language and S-expressions
A markup consists of S-expressions . There are four types of S-expressions, each intended for a specific action:
Call method
( * *
+
)
:= =
(bind isEnabled "$event.enabled" init=false
(event "isEnableChanged")
)
bind -
isEnabled, "$event.enabled" - *
init=false - *
(event "isEnableChanged") - +
Add definition
(def (*) *
+
)
:= : [ = ]
:= =
This definition is essentially a specific instance of an S-expression method. It was introduced only for the special syntax, used for declaring parameters.
(def element TestView(name:str = '', count:number) layout=true
(block
)
)
Set a property value
( = )
(style
(width = 100px)
)
(tf
(text = 'Hello world!')
)
Get a property value
(.
+
)
(.graphics
(lineStyle 1 0xffffdc84 1)
(beginFill "0xff414141" "0")
(drawRect 0 0 450 64)
(endFill)
)
The entire markup is based on these four types of expressions.
Macros
A macro is a named and parametrized markup fragment, which is added to the place where it's called on the parsing stage.
Macros are implemented on the AST(Abstract syntax tree) level.
Definition of a macro:
(def macro (*)
+
)
:= : [ = ]
(def macro StatusesVehicleTypes(width:number=100%, height:number=100%, renderer:str='VehicleTypeItem', name:str='statusesVehicleTypes')
(element List "name" "renderer" "width" "height"
(scope
(containerFlow = "Flow.HORIZONTAL")
(listVscrollPolicy = 'off')
(listHscrollPolicy = 'off')
)
)
)
You can run a macro with the help of the key word — macro.
(macro * *)
(macro StatusesVehicleTypes 160 height=32)
Though it looks like an ordinary method call, it's not. The macro's content is added on the parsing stage.
In the definition of the macro, you can use the macros that were defined before. The resolve operation is done recursively.
(def macro ComponentStateBase (statesDict:expression)
(macro ComponentStateBaseScope "statesDict")
(macro ComponentStateBaseContent)
)
Markup Execution
The engine that executes the markup isn't tied to any specific features. It doesn't keep any knowledge base on sprites, scopes, etc.
A "target object" is the current object, subjected to the operations that are described in the S-expressions. S-expressions are divided into types: method, setter, and getter. On this level, even a definition is simply a method.
The first execution of a markup happens after the file's text has been uploaded to the framework. After the file is parsed, we get a list of S-expressions, contained by the file (as a rule, these are definitions and setups of global constants). A fake object is created to execute this list of S-expressions (though, in fact, these methods don't need it).
Once a definition has been declared, the engine starts executing the list of S-expressions. The engine creates the corresponding object and S-expressions, which are contained in the definition's body and applied to it.
As the S-expressions are being executed, the target object changes. This happens along with the execution of nested S-expressions. For them, the target object will be the object, returned by the parent S-expression. If the S-expression didn't return anything or returned something other than an object, the nested S-expressions won't be executed. Values (including objects) can be returned by methods and getters.
The example below shows how a target object is changing:
Data Types
In markup, variable must be strictly divided into types (arguments of definitions, variables of scope). Types are verified on the execution stage (except for macros — they are verified on the parsing phase).
This helps you identify errors much faster.
Type
Description
Syntax
Comments
number
number
12.34
For numbers, you can specify the unit of measure.
percent
12.34%
pixels
12px
bool
boolean expression
true / false
str
string
'text123'
single quotes
dict
dictionary
{a : 1, b: 2}
array
array
[1, 2, 3]
expression
calculated expression
"a ? 1 : 2"
inside double quotation marks
A constant can't be defined inside this expression.
gfx
указатель на любой GFx::Value
no
a constant cannot be defined in the layout and in the expressions
object
указатель на оперируемый объект
no
a constant cannot be defined in the layout and in the expressions
Note:
An important peculiarity of working with string literals and variables is that you don't need to use single quotation marks, because the parser interprets any value as a string. Consequently, there are two ways of declaring strings.
This peculiarity reveals itself when you use binding structures (bind, bindcall, sync, dispatch). Example:
# Dispatch of the evBtnUpEvent event
(dispatch evBtnUpEvent on=mouseUp)
The first argument of the dispatch function is a string-type variable, but you don't need to use single quotes.
Keep this peculiarity in mind when working with markup.
Enums
In expressions, you can use the enumerations that were set in the core c++ part of Unbound.
Enum name
Properties
Description
Example
Comments
Flow
HORIZONTAL
VERTICAL - by default value
TILE_HORIZONTAL
TILE_VERTICAL
REVERSE_HORIZONTAL
REVERSE_VERTICAL
These properties determine how nested display objects are positioned.
(block
(style
(flow = "Flow.HORISONTAL")
)
)
# This is the equivalent to what's written above:
(hblock
)
For blocks with a specified flow value, you can use the following aliases:
block — vertical block
hblock — horizontal block
vtile — vertical tile block
htile — horizontal tile block
reverse — vertical block with a reverse order of elements in it
hreverse — horizontal block with a reverse order of elements in it
For block property flow = Flow.VERTICAL
ZIndex
FOREGROUND
BACKGROUND
Set the z-index of the display object, and it will determine the object's z-positioning in the parent container.
(block
(style
(zindex = "ZIndex.BACKGROUND")
)
)
Easing
line
elastic_in
elastic_out
bounce_in
bounce_out
back_in
back_out
cubic_in
cubic_out
quint_in
quint_out
expo_in
expo_out
expo_in_out
sine_in
sine_out
sine_in_out
Types of easing for animation.
(controller $Animation
(play duration=0.3 to={alpha:0} easing="Easing.cubic_out")
)