.. _constraints-layout:

Enable Constraints Layout
=========================

This document describes the constraints-based layout system that is being
proposed as the new layout model going forward. Familiarity with Enaml_ and
its layout system is helpful but not required.


Using Constraints
-----------------

:class:`~.ConstraintsContainer` is a :class:`~.Container` subclass which uses the
Kiwisolver_ constraint solver to determine the layout of its child
:class:`~.Component` instances. This is achieved by adding constraint variables
to the :class:`~.Component` class which define a simple box model:

* :attr:`layout_height`: The height of the component.
* :attr:`layout_width`: The width of the component.
* :attr:`left`: The left edge of the component.
* :attr:`right`: The right edge of the component.
* :attr:`top`: The top edge of the component.
* :attr:`bottom`: The bottom edge of the component.
* :attr:`h_center`: The vertical center line between the left and right edges
* :attr:`v_center`: The  horizontal center line between the top and bottom edges

Additionally, there are some constraints which only exist on
:class:`~.ConstraintsContainer`:

* :attr:`contents_height`: The height of the container.
* :attr:`contents_width`: The width of the container.
* :attr:`contents_left`: The left edge of the container.
* :attr:`contents_right`: The right edge of the container.
* :attr:`contents_top`: The top edge of the container.
* :attr:`contents_bottom`: The bottom edge of the container.
* :attr:`contents_h_center`: The vertical center line of the container.
* :attr:`contents_v_center`: The  horizontal center line of the container.

These variables can be used in linear inequality expressions which make up the
layout constraints of a container:

::

  def build_hierarchy():
    container = ConstraintsContainer()
    one = Component()
    two = Component()
    container.add(one, two)
    container.layout_constraints = [
        one.layout_width == two.layout_width * 2.0,
        one.layout_height == two.layout_height,
        # ... and so on ...
    ]

    return container

For more complicated layouts, the :attr:`layout_constraints` trait on a
:class:`~.ConstraintsContainer` can be a :class:`callable`. The function is
passed a reference to the container and should return a list of
:class:`~.LinearContraints` objects or layout helper instances (as described below).

::

   def create_container(self):
    self.container = ConstraintsContainer()
    self.container.add(self.bar)
    self.container.layout_constraints = self.my_layout_constraints

  def my_layout_constraints(self, container):
    cns = []

    if self.foo:
      cns.append(self.foo.layout_height <= 300)
      cns.append(hbox(self.foo, self.bar))
    cns.append(self.bar.layout_width == 250)

    return cns

If :attr:`layout_constraints` is callable, it will be invoked each time a
component is added to the container or whenever the :attr:`layout_size_hint`
trait changes on a child component.

Layout Helpers
--------------

In practice, it's too tedious to specify all the constraints for a rich UI
layout. To aid in the generation of layouts, the layout helpers from Enaml_ are
also available in Enable. The layout helpers are:

:data:`spacer`: Creates space between two adjacent components.

.. function:: hbox(*components[, spacing=10, margins=...])

    Takes a list of components and lines them up using their left and right 
    edges and ensures that the components' heights match that of their
    container.

   :param components: A sequence of :class:`~.Component` or :class:`~.spacer` objects.
   :param spacing: How many pixels of inter-element spacing to use
   :type spacing: integer >= 0
   :param margins: An ``int``, ``tuple`` of ints, or :class:`enable.layout.geometry.Box`
                   of ints >= 0 which indicate how many pixels of margin to add around
                   the bounds of the box. The default is 0.

.. function:: vbox(*components[, spacing=10, margins=...])

   Takes a list of components and lines them up using their top and bottom 
   edges and ensures that the components' widths match each other.

   :param components: A sequence of :class:`~.Component` or :class:`~.spacer` objects.
   :param spacing: How many pixels of inter-element spacing to use
   :type spacing: integer >= 0
   :param margins: An ``int``, ``tuple`` of ints, or :class:`enable.layout.geometry.Box`
                   of ints >= 0 which indicate how many pixels of margin to add around
                   the bounds of the box. The default is 0.

.. function:: horizontal(*components[, spacing=10])

   Like :func:`hbox`, but does not ensure that the heights of components match
   each other.

   Takes a list of components and lines them up using their left and right
   edges.

   :param components: A sequence of :class:`~.Component` or :class:`~.spacer` objects.
   :param spacing: How many pixels of inter-element spacing to use
   :type spacing: integer >= 0

.. function:: vertical(*components[, spacing=10])

   Like :func:`vbox`, but does not ensure that the widths of components match
   each other.

   Takes a list of components and lines them up using their top and bottom 
   edges.

   :param components: A sequence of :class:`~.Component` or :class:`~.spacer` objects.
   :param spacing: How many pixels of inter-element spacing to use
   :type spacing: integer >= 0

.. function:: align(anchor, *components[, spacing=10])

   Aligns a single constraint across multiple components.

   :param anchor: The name of a constraint variable that exists on all of the
                  ``components``.
   :param components: A sequence of :class:`~.Component` objects. Spacers are not allowed.
   :param spacing: How many pixels of inter-element spacing to use
   :type spacing: integer >= 0

.. function:: grid(*rows[, row_align='', row_spacing=10, column_align='', column_spacing=10, margins=...])

   Creates an NxM grid of components. Components may span multiple columns or rows.

   :param rows: A sequence of sequences of :class:`~.Component` objects
   :param row_align: The name of a constraint variable on an item. If given,
                     it is used to add constraints on the alignment of items
                     in a row. The constraints will only be applied to items
                     that do not span rows.
   :type row_align: string
   :param row_spacing: Indicates how many pixels of space should be placed
                       between rows in the grid. The default is 10.
   :type row_spacing: integer >= 0

   :param column_align: The name of a constraint variable on an item. If given,
                        it is used to add constraints on the alignment of items
                        in a column. The constraints will only be applied to
                        items that do not span columns.
   :type column_align: string
   :param column_spacing: Indicates how many pixels of space should be placed
                          between columns in the grid. The default is 10.
   :type column_spacing: integer >= 0
   :param margins: An ``int``, ``tuple`` of ints, or :class:`enable.layout.geometry.Box`
                   of ints >= 0 which indicate how many pixels of margin to add around
                   the bounds of the box. The default is 0.


Fine Tuning Layouts
-------------------

:class:`~.Component` defines a :class:`~.Tuple` trait :attr:`layout_size_hint` which
controls the minimum size of a component when it's part of a contraints layout.
Additionally, :class:`~.Component` defines some strength traits that can be used
to fine tune the behavior of a component instance during layout. They are:

* :attr:`hug_height`: How strongly a component prefers the height of its size hint when it could grow.
* :attr:`hug_width`: How strongly a component prefers the width of its size hint when it could grow.
* :attr:`resist_height`: How strongly a component resists its height being made smaller than its size hint.
* :attr:`resist_width`: How strongly a component resists its width being made smaller than its size hint.

The allow values for these strengths are: ``'required'``, ``'strong'``, ``'medium'``,
and ``'weak'``.

Contrained Layout Pitfalls
--------------------------

* The :attr:`auto_size` trait of :class:`~.Container` is *completely ignored* by
  constrained layout. Just ignore it.
* The :attr:`bounds` trait of a :class:`~.Component` which is a child of a
  :class:`~.ConstraintsContainer` is *not considered* when generating a layout.
  One should instead specify a minimum size with :attr:`layout_size_hint` and/or
  add constraints which reference the component's :attr:`layout_height` or
  :attr:`layout_width` traits.
* Similarly, the :attr:`position` trait of a :class:`~.Component` which is a
  child of a :class:`~.ConstraintsContainer` is overwritten by the constraint
  solver and not considered. Add constraints which reference the component's
  :attr:`left` or :attr:`top` traits if you want to explicitly control the
  final value of :attr:`position` (also :attr:`right`, :attr:`top`,
  :attr:`v_center`, and :attr:`h_center` can influence the layout position)
* If a child :class:`~.Component` has zero :attr:`width` or :attr:`height`
  after the container's :py:meth:`refresh` is called, that usually means the
  layout is not sufficiently constrained. In that case, you need to add more
  constraints to the container's :attr:`layout_constraints`.


.. _Kiwisolver: https://kiwisolver.readthedocs.io/en/latest/
.. _Enaml: https://enaml.readthedocs.io/en/latest/
