Skip to content

Commit

Permalink
Add constraints variables as Property traits on CoordinateBox.
Browse files Browse the repository at this point in the history
This means that layout helpers can accept components directly instead of a
reference to the constraints namespace of the component.
  • Loading branch information
jwiggins authored and brett-patterson committed Jun 25, 2014
1 parent 0fe8fcc commit 51d15f2
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 49 deletions.
78 changes: 53 additions & 25 deletions enable/constraints_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,54 @@
#------------------------------------------------------------------------------

# traits imports
from traits.api import Bool, Callable, Dict, Either, Instance, List, Str
from traits.api import Bool, Callable, Dict, Either, Instance, List, \
Property, Str

# local imports
from container import Container
from coordinate_box import get_from_constraints_namespace
from layout.debug_constraints import DebugConstraintsOverlay
from layout.layout_helpers import expand_constraints
from layout.layout_manager import LayoutManager
from layout.utils import add_symbolic_contents_constraints


class ConstraintsContainer(Container):
""" A Container which lays out its child components using a
constraints-based layout solver.
"""
# A read-only symbolic object that represents the left boundary of
# the component
contents_left = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the right boundary
# of the component
contents_right = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the bottom boundary
# of the component
contents_bottom = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the top boundary of
# the component
contents_top = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the width of the
# component
contents_width = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the height of the
# component
contents_height = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the vertical center
# of the component
contents_v_center = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the horizontal
# center of the component
contents_h_center = Property(fget=get_from_constraints_namespace)

# The layout constraints for this container.
# This can either be a list or a callable. If it is a callable, it will be
Expand Down Expand Up @@ -76,15 +110,15 @@ def relayout(self):
constraints modification.
"""
mgr_layout = self._layout_manager.layout
constraints = self.constraints
width_var = constraints.width
height_var = constraints.height
width_var = self.layout_width
height_var = self.layout_height
width, height = self.bounds
def layout():
for component in self._component_map.itervalues():
cns = component.constraints
component.position = (cns.left.value, cns.bottom.value)
component.bounds = (cns.width.value, cns.height.value)
component.position = (component.left.value,
component.bottom.value)
component.bounds = (component.layout_width.value,
component.layout_height.value)
if self._debug_overlay:
layout_mgr = self._layout_manager
self._debug_overlay.update_from_constraints(layout_mgr)
Expand Down Expand Up @@ -114,8 +148,7 @@ def _layout_constraints_changed(self):

# Update the private constraints list. This will trigger the relayout.
expand = expand_constraints
constraints = self.constraints
self._layout_constraints = [cns for cns in expand(constraints, new)]
self._layout_constraints = [cns for cns in expand(self, new)]

def __layout_constraints_changed(self, name, old, new):
""" Invalidate the layout when the private constraints list changes.
Expand Down Expand Up @@ -188,22 +221,17 @@ def _content_box_constraints(self):
container.
"""
cns = self.constraints
contents_left = cns.contents_left
contents_right = cns.contents_right
contents_top = cns.contents_top
contents_bottom = cns.contents_bottom

# Add these to the namespace, but don't use them here
cns.contents_width = contents_right - contents_left
cns.contents_height = contents_top - contents_bottom
cns.contents_v_center = contents_bottom + cns.contents_height / 2.0
cns.contents_h_center = contents_left + cns.contents_width / 2.0

return [contents_left == cns.left,
contents_bottom == cns.bottom,
contents_right == cns.left + cns.width,
contents_top == cns.bottom + cns.height,
add_symbolic_contents_constraints(self._constraints_vars)

contents_left = self.contents_left
contents_right = self.contents_right
contents_top = self.contents_top
contents_bottom = self.contents_bottom

return [contents_left == self.left,
contents_bottom == self.bottom,
contents_right == self.left + self.layout_width,
contents_top == self.bottom + self.layout_height,
]

def _update_fixed_constraints(self):
Expand Down
67 changes: 55 additions & 12 deletions enable/coordinate_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@

# Local, relative imports
from enable_traits import bounds_trait, coordinate_trait
from layout.ab_constrainable import ABConstrainable
from layout.constraints_namespace import ConstraintsNamespace
from layout.utils import add_symbolic_constraints, STRENGTHS


ConstraintPolicyEnum = Enum('ignore', *STRENGTHS)


def get_from_constraints_namespace(self, name):
""" Property getter for all attributes that come from the constraints
namespace.
"""
return getattr(self._constraints_vars, name)


class CoordinateBox(HasTraits):
"""
Represents a box in screen space, and provides convenience properties to
Expand Down Expand Up @@ -52,8 +61,37 @@ class CoordinateBox(HasTraits):
# Constraints-based layout
#------------------------------------------------------------------------

# A namespace containing the constraints for this CoordinateBox
constraints = Instance(ConstraintsNamespace)
# A read-only symbolic object that represents the left boundary of
# the component
left = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the right boundary
# of the component
right = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the bottom boundary
# of the component
bottom = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the top boundary of
# the component
top = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the width of the
# component
layout_width = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the height of the
# component
layout_height = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the vertical center
# of the component
v_center = Property(fget=get_from_constraints_namespace)

# A read-only symbolic object that represents the horizontal
# center of the component
h_center = Property(fget=get_from_constraints_namespace)

# How strongly a layout box hugs it's width hint.
hug_width = ConstraintPolicyEnum('strong')
Expand All @@ -67,6 +105,9 @@ class CoordinateBox(HasTraits):
# How strongly a layout box resists clipping its contents.
resist_height = ConstraintPolicyEnum('strong')

# A namespace containing the constraints for this CoordinateBox
_constraints_vars = Instance(ConstraintsNamespace)

# The list of hard constraints which must be applied to the object.
_hard_constraints = Property

Expand Down Expand Up @@ -172,7 +213,7 @@ def _old_set_y2(self, val):
self.bounds[1] = new_height
return

def _constraints_default(self):
def __constraints_vars_default(self):
obj_name = self.id if hasattr(self, 'id') else ''
cns_names = ConstraintsNamespace(type(self).__name__, obj_name)
add_symbolic_constraints(cns_names)
Expand All @@ -181,11 +222,10 @@ def _constraints_default(self):
def _get__hard_constraints(self):
""" Generate the constraints which must always be applied.
"""
constraints = self.constraints
left = constraints.left
bottom = constraints.bottom
width = constraints.width
height = constraints.height
left = self.left
bottom = self.bottom
width = self.layout_width
height = self.layout_height
cns = [left >= 0, bottom >= 0, width >= 0, height >= 0]
return cns

Expand All @@ -195,9 +235,8 @@ def _get__size_constraints(self):
cns = []
push = cns.append
width_hint, height_hint = self.bounds
constraints = self.constraints
width = constraints.width
height = constraints.height
width = self.layout_width
height = self.layout_height
hug_width, hug_height = self.hug_width, self.hug_height
resist_width, resist_height = self.resist_width, self.resist_height
if width_hint >= 0:
Expand All @@ -218,4 +257,8 @@ def _get__size_constraints(self):
return cns


# EOF
# Register with ABConstrainable so that layout helpers will recognize
# CoordinateBox instances.
ABConstrainable.register(CoordinateBox)

# EOF
5 changes: 2 additions & 3 deletions enable/layout/layout_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,13 @@ def __init__(self, name):
top = property(lambda self: self._namespace.top)
right = property(lambda self: self._namespace.right)
bottom = property(lambda self: self._namespace.bottom)
width = property(lambda self: self._namespace.width)
height = property(lambda self: self._namespace.height)
layout_width = property(lambda self: self._namespace.layout_width)
layout_height = property(lambda self: self._namespace.layout_height)
v_center = property(lambda self: self._namespace.v_center)
h_center = property(lambda self: self._namespace.h_center)


ABConstrainable.register(BoxHelper)
ABConstrainable.register(ConstraintsNamespace)


class LinearBoxHelper(BoxHelper):
Expand Down
20 changes: 18 additions & 2 deletions enable/layout/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@ def add_symbolic_constraints(namespace):
"""
bottom = namespace.bottom
left = namespace.left
width = namespace.width
height = namespace.height
width = namespace.layout_width
height = namespace.layout_height

namespace.right = left + width
namespace.top = bottom + height
namespace.h_center = left + width / 2.0
namespace.v_center = bottom + height / 2.0


def add_symbolic_contents_constraints(namespace):
""" Add constraints to a namespace that are LinearExpressions of basic
constraints.
"""
left = namespace.contents_left
right = namespace.contents_right
top = namespace.contents_top
bottom = namespace.contents_bottom

namespace.contents_width = left - right
namespace.contents_height = top - bottom
namespace.contents_v_center = bottom + namespace.contents_height / 2.0
namespace.contents_h_center = left + namespace.contents_width / 2.0

11 changes: 4 additions & 7 deletions examples/enable/constraints_demo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

from enable.api import Component, ComponentEditor, ConstraintsContainer
from enable.layout.layout_helpers import hbox, vbox, align, grid, vertical
from enable.layout.layout_helpers import hbox, vbox, align, grid, vertical, spacer
from traits.api import HasTraits, Any, Instance, List, Property
from traitsui.api import Item, View, HGroup, TabularEditor
from traitsui.tabular_adapter import TabularAdapter
Expand Down Expand Up @@ -55,12 +55,9 @@ def _canvas_default(self):

parent.add(one, two, three, four)
parent.layout_constraints = [
grid([one.constraints, two.constraints],
[three.constraints, four.constraints]),
align('height', one.constraints, two.constraints,
three.constraints, four.constraints),
align('width', one.constraints, two.constraints,
three.constraints, four.constraints),
grid([one, two], [three, four]),
align('layout_height', one, two, three, four),
align('layout_width', one, two, three, four),
]

return parent
Expand Down

0 comments on commit 51d15f2

Please sign in to comment.