1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 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
|
.. _layout:
==================
Constraints Layout
==================
Enaml widgets come in two basic types: Containers and Controls. Controls
are conceptually single UI elements with no other Enaml widgets inside them,
such as labels, fields, and buttons. Containers are widgets which contain
other widgets, usually including information about how to layout the widgets
that they contain. Examples of containers include top-level windows, scroll
areas and forms.
Enaml uses constraints-based layout implemented by the Cassowary layout system.
Constraints are specified as a system of linear inequalities together with an
error function which is minimized according to a modified version of the
Simplex method. The error function is specified via assigning weights to the
various inequalities. The default weights exposed in Enaml are ``'weak'``,
``'medium'``, ``'strong'``, ``'required'``, and ``'ignored'``, but other values
are possible within the system, if needed. While a developer writing Enaml
code could specify all constraints directly, in practice they will use a set of
helper classes, functions and attributes to help specify the set of constraints
in a more understandable way.
Every widget knows its preferred size, usually by querying the underlying
toolkit, and can express how closely it adheres to the preferred size via its
``hug_width``, ``hug_height``, ``resist_width`` and ``resist_height``,
``limit_width`` and ``limit_height`` attribute which take one of the previously
mentioned weights. These are set to reasonable defaults for most widgets, but
they can be overriden. The ``hug`` attributes specify how strongly the widget
resists deformation by adding a constraint of the appropriate weight that
specifies that the dimension be equal to the preferred value, while the
``resist`` attributes specify how strongly the widget resists compression by
adding a constraint that specifies that the dimension be greater than or equal
to the preferred value. The ``limit`` attributes specify how strongly the
widget resists expansion by adding a constraint that specifies that the
dimension be smaller than or equal to the preferred value
Containers can specify additional constraints that relate their child widgets.
By default a container simply lays out its children as a vertical list and
tries to expand them to use the full width and height that the container has
available. Layout containers, like Form, specify different default constraints
that give automatic layout of their children, and may provide additional hooks
for other widgets to use to align with their significant features.
Additional constraints are specified via the ``constraints`` attribute on the
container. The simplest way to specify a constraint is with a simple equality
or inequality. Inequalities can be specified in terms of symbols provided
by the components, which at least default to the symbols for a basic box model:
``top``, ``bottom``, ``left``, ``right``, ``v_center``, ``h_center``, ``width``
and ``height``. Other components may expose other symbols: for example the
``Form`` widget exposes ``midline`` for aligning the fields of multiple forms
along the same line, and a ``Container`` exposes various ``contents`` symbols
to account for padding around the boundaries of its children.
.. code-block:: enaml
enamldef Main(Window):
Container:
constraints = [
# Pin the first push button to the top contents anchor.
pb1.top == contents_top,
# Relate the left side of the push button to the width
# of the container.
pb1.left == 0.3 * width,
# Relate the width of the push button to the width of
# the container
pb1.width == 0.5 * width,
# Pin the second push button to the left contents anchor.
pb2.left == contents_left,
# Relate the top of the push button to width of the first
# push button.
pb2.top == 0.3 * pb1.width + 10
]
PushButton: pb1:
text = 'Horizontal'
PushButton: pb2:
text = 'Long Name Foo'
However, this can get tedious, and so there are some helpers that are
available to simplify specifying layout. These are:
``spacer``
A singleton spacer that represents a flexible space in a layout
with a minimum value of the default space. Additional restrictions
on the space can be specified using ``==``, ``<=`` and ``>=`` with
an integer value.
``spacer.flex()``
A flexible spacer that has a hard minimum but also a weaker preference
to be no larger than that minimum.
``horizontal(*items)`` or ``hbox(*items)``
``vertical(*items)`` or ``vbox(*items)``
These four functions take a list of symbols, widgets and spacers and
create a series of constraints that specify a sequential horizontal
or vertical layout where the sides of each object in sequence abut
against each other.
``align(variable, *items)``
Align the given string variable name on each of the specified items.
``grid(*rows, **config)``
A function which takes a variable number of iterable rows and
arranges the items in a grid according to the configuration
parameters.
``factory(func, *args, **kwargs)``
A function which takes a function which should return the set of
constraints to use. The factory function is called each time the layout
can change (widget addition, deletion, etc).The arguments are passed
are passed to function.
By using appropriate combinations of these objects you can specify complex
layouts quickly and clearly.
.. code-block:: enaml
enamldef Main(Window):
Container:
constraints = [
# Arrange the Html Frame above the horizontal row of butttons
vbox(
html_frame,
hbox(
add_button, remove_button, spacer,
change_mode_button, spacer, share_button,
),
),
# Weakly align the centers of the Html frame and the center
# button. Declaring this constraint as 'weak' is what allows
# the button to ignore the constraint as he window is resized
# too small to allow it to be centered.
align('h_center', html_frame, change_mode_button) | 'weak',
# Set a sensible minimum height for the frame
html_frame.height >= 150,
]
Html: html_frame:
source = '<center><h1>Hello Enaml!</h1></center>'
PushButton: add_button:
text = 'Add'
PushButton: remove_button:
text = 'Remove'
clicked :: print('removed')
PushButton: change_mode_button:
text = 'Change Mode'
PushButton: share_button:
text = 'Share...'
Alternatively one can override the ``layout_constraints`` function in the
enaml definition.
.. code-block:: enaml
enamldef Main(Window):
title = 'Custom Constraints'
Container:
layout_constraints => ():
rows = []
widgets = self.visible_widgets()
row_iters = (iter(widgets),) * 2
rows = list(zip_longest(*row_iters))
return [grid(*rows)] + [align('v_center', *row) for row in rows]
Label:
text = 'Name'
Field:
pass
Label:
text = 'Surname'
Field:
pass
PushButton:
text = 'Click me'
|