.. _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/