Button Ring Example

An example layout which is impossible with typical layout systems.

This example creates a ring of 50 PushButton widgets with a Label in the center. The constraints shown here are generally silly in that the resulting layout is more-or-less useless. Nevertheless, it serves well to demonstrate the power and flexibility of constraints-based layout.

Note that the ‘gen_constraints’ function is only called once, not on every resize as may be expected when laying out widgets manually.

Note that this example also demonstrates that constraints may be defined on any subclass of ConstraintsWidget. They need not be confined to a Container.

Requires numpy to be installed.

Tip

To see this example in action, download it from button_ring and run:

$ enaml-run button_ring.enaml

Screenshot

../_images/ex_button_ring.png

Example Enaml Code

#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------
""" An example layout which is impossible with typical layout systems.

This example creates a ring of 50 `PushButton` widgets with a `Label` in
the center. The constraints shown here are generally silly in that the
resulting layout is more-or-less useless. Nevertheless, it serves well to
demonstrate the power and flexibility of constraints-based layout.

Note that the 'gen_constraints' function is only called once, not on
every resize as may be expected when laying out widgets manually.

Note that this example also demonstrates that constraints may be defined
on any subclass of `ConstraintsWidget`. They need not be confined to a
`Container`.

Requires numpy to be installed.

<< autodoc-me >>
"""
from __future__ import print_function
import numpy as np
from enaml.widgets.api import MainWindow, Container, PushButton, Label
from enaml.core.include import Include


def gen_constraints(comps, top, left, width, height):
    """ A helper function which generates radial constraints.

    Parameters
    ----------
    comps : list
        A list of ConstraintWidget instances which should be arrange
        radially over an intervale of 2-Pi.

    top : ConstraintVariable
        The variable representing the top of the layout area.

    left : ConstraintVariable
        The variable representing the left of the layout area.

    width : ConstraintVariable
        The variable representing the width of the layout area.

    height : ConstraintVariable
        The variable representing the height of the layout area.

    Returns
    -------
    result : list
        The list of primitive constraints which arrange the given
        components radially in the layout area.

    """
    res = []
    nitems = len(comps)
    rads = np.linspace(0, 2 * np.pi, nitems)
    x_coeffs = (np.cos(rads) + 1.0) / 2.0
    y_coeffs = (np.sin(rads) + 1.0) / 2.0
    for comp, x_coeff, y_coeff in zip(comps, x_coeffs, y_coeffs):
        res.extend([
            comp.left == (left + x_coeff * (width - comp.width)),
            comp.top == (top + y_coeff * (height - comp.height)),
        ])
    return res


enamldef SimpleButton(PushButton):
    """ A custom `PushButton` for use in the button ring example.

    The width and height of this `PushButton` is fixed to its size hint.
    This is required since no other constraints are placed on the width
    of the button in this example. If these constraints were not added,
    then the constraints solver would error with an unbounded system,
    indicating an underconstrained system.

    """
    hug_width = 'required'
    hug_height = 'required'
    clicked :: print('%s clicked' % text)


enamldef Main(MainWindow):
    Container: cntr:
        constraints << gen_constraints(
            inc.objects, top, left, width, height,
        ) + [width >= 200, height >= 200]
        Include: inc:
            objects = [SimpleButton(text=str(i)) for i in range(50)]
        Label: lbl:
            # Constraints can also be directly defined on a component.
            # This is provided as a convenience where it makes the code
            # clearer and easier to understand. It makes no difference
            # if the constraints are defined here, or on the Container.
            constraints = [
                v_center == cntr.v_center,
                h_center == cntr.h_center,
            ]
            text = 'Button Ring'
            font = 'italic small-caps bold expanded 12pt arial'