The layout DSL (for Domain Specific Language) is designed to help with common use-case layout definitions. Here's an example:
import nimx / [ window, button, layout ]
let w = newWindow(newRect(50, 50, 400, 200))
let margin = 5.0
w.makeLayout:
- Button:
leading == super.leading + margin
trailing == super.trailing - margin
top == super.top + margin
bottom == super.bottom - margin
title: "Hello"
onAction:
echo "Action!"
makeLayout
macro accepts the View
as a first argument. This view is where
the layout should happen. The second argument is the body of the DSL, that
has the following syntax.
DSL ::= ViewConfiguration
ViewConfiguration ::= ViewConfigurationStatement*
ViewConfigurationStatement ::=
"discard"
| SubviewDefinition
| PropertyDefinition
| ConstraintDefinition
SubviewDefinition ::= "-" ( ViewType | ViewCreationExpression ) ( "as" Identifier )? ":" ViewConfiguration
PropertyDefinition ::= PropertyName ":" PropertyValue
ConstraintDefinition ::= ConstraintExpression (ConstraintPriority)?
-
In the example above the
- Button:
and everything that follows is theSubviewDefinition
.Button
is theViewType
. Note that the types have to be valid symbols in the scope of layout definition, that's why the sample code importsbutton
. -
title: "Hello"
is the property definition of the button. If this button was bound to a variablemyButton
, this would be equivalent tomyButton.title = "Hello"
. -
onAction: ...
is also a property, but with a special rule. Properties starting with "on" and uppercased third letter are treated as callback properties. So this would expand to roughly:
myButton.onAction = proc() =
echo "Action!"
If your callback takes arguments or returns something, you can use the do-notation:
- MyControl:
onSomeEvent do(e: EventData) -> EventDataResult:
echo "hi"
leading == super + margin
is a constraint definition, consisting of constraint expression, and default priority. Constraint expression should always be one of the following comparison expressions:<=
,==
, or>=
. Constraint expression can refer to the following "variables":width
,height
,left
,right
,x
,y
,top
,bottom
,leading
,trailing
,centerX
,centerY
,origin
,center
,size
.
Some examples:
mySubView
should completely fill themainView
:
mainView.makeLayout:
- View as mySubView:
origin == super.origin
size == super.size
mySubView
should be of size 20 by 20, and be centered within themainView
:
mainView.makeLayout:
- View as mySubView:
center == super.center
width == 20
height == 20
Note there's a special word super
in the examples above. This is a placeholder
designating the superview of the currently defined view. There are other placeholders:
self
- the currently defined viewprev
- the previous sibling of the currently defined viewnext
- the next sibling of the currently defined view
Note the pattern of smth == super.smth
is pretty common, so there's a special
case to make it shorter:
mainView.makeLayout:
- View:
center == super
This works because the left side of the constraint expression consist of exactly
one identifier which is treated as subject. When super
, self
, prev
or next
is met in the expression and it is not within the dot-expression, it is treated
as a dot expression with the subject.
If the left side of expression is not a single identifier then there is no subject and thus such shortcut would not work:
mainView.makeLayout:
- View:
width + 20 == super # Will not compile
width + 20 == super.width # Will compile
width == super - 20 # Will compile