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

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

  • layout_height: The height of the component.

  • layout_width: The width of the component.

  • left: The left edge of the component.

  • right: The right edge of the component.

  • top: The top edge of the component.

  • bottom: The bottom edge of the component.

  • h_center: The vertical center line between the left and right edges

  • v_center: The horizontal center line between the top and bottom edges

Additionally, there are some constraints which only exist on ConstraintsContainer:

  • contents_height: The height of the container.

  • contents_width: The width of the container.

  • contents_left: The left edge of the container.

  • contents_right: The right edge of the container.

  • contents_top: The top edge of the container.

  • contents_bottom: The bottom edge of the container.

  • contents_h_center: The vertical center line of the container.

  • 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 layout_constraints trait on a ConstraintsContainer can be a callable. The function is passed a reference to the container and should return a list of 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 layout_constraints is callable, it will be invoked each time a component is added to the container or whenever the 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:

spacer: Creates space between two adjacent components.

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.

Parameters
  • components – A sequence of Component or spacer objects.

  • spacing (integer >= 0) – How many pixels of inter-element spacing to use

  • margins – An int, tuple of ints, or 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.

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.

Parameters
  • components – A sequence of Component or spacer objects.

  • spacing (integer >= 0) – How many pixels of inter-element spacing to use

  • margins – An int, tuple of ints, or 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.

horizontal(*components[, spacing=10])

Like 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.

Parameters
  • components – A sequence of Component or spacer objects.

  • spacing (integer >= 0) – How many pixels of inter-element spacing to use

vertical(*components[, spacing=10])

Like 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.

Parameters
  • components – A sequence of Component or spacer objects.

  • spacing (integer >= 0) – How many pixels of inter-element spacing to use

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

Aligns a single constraint across multiple components.

Parameters
  • anchor – The name of a constraint variable that exists on all of the components.

  • components – A sequence of Component objects. Spacers are not allowed.

  • spacing (integer >= 0) – How many pixels of inter-element spacing to use

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.

Parameters
  • rows – A sequence of sequences of Component objects

  • row_align (string) – 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.

  • row_spacing (integer >= 0) – Indicates how many pixels of space should be placed between rows in the grid. The default is 10.

  • column_align (string) – 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.

  • column_spacing (integer >= 0) – Indicates how many pixels of space should be placed between columns in the grid. The default is 10.

  • margins – An int, tuple of ints, or 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

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

  • hug_height: How strongly a component prefers the height of its size hint when it could grow.

  • hug_width: How strongly a component prefers the width of its size hint when it could grow.

  • resist_height: How strongly a component resists its height being made smaller than its size hint.

  • 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 auto_size trait of Container is completely ignored by constrained layout. Just ignore it.

  • The bounds trait of a Component which is a child of a ConstraintsContainer is not considered when generating a layout. One should instead specify a minimum size with layout_size_hint and/or add constraints which reference the component’s layout_height or layout_width traits.

  • Similarly, the position trait of a Component which is a child of a ConstraintsContainer is overwritten by the constraint solver and not considered. Add constraints which reference the component’s left or top traits if you want to explicitly control the final value of position (also right, top, v_center, and h_center can influence the layout position)

  • If a child Component has zero width or height after the container’s 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 layout_constraints.