Skip to content

Commit

Permalink
Steal some of Robert's enaml debugging code for visualizing constraints.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwiggins authored and brett-patterson committed Jun 25, 2014
1 parent 79fa596 commit 5be77fd
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 3 deletions.
29 changes: 27 additions & 2 deletions enable/constraints_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#------------------------------------------------------------------------------

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

# local imports
from container import Container
from layout.debug_constraints import DebugConstraintsOverlay
from layout.layout_helpers import expand_constraints
from layout.layout_manager import LayoutManager

Expand Down Expand Up @@ -44,6 +45,25 @@ class ConstraintsContainer(Container):
# The casuarius solver
_layout_manager = Instance(LayoutManager)


#------------------------------------------------------------------------
# Debugging bits
#------------------------------------------------------------------------

# Whether or not debugging info should be shown.
debug = Bool(False)

# The overlay that draws the debugging info
_debug_overlay = Instance(DebugConstraintsOverlay)

def __init__(self, **traits):
super(ConstraintsContainer, self).__init__(**traits)

if self.debug:
dbg = DebugConstraintsOverlay()
self.overlays.append(dbg)
self._debug_overlay = dbg

#------------------------------------------------------------------------
# Public methods
#------------------------------------------------------------------------
Expand All @@ -68,6 +88,9 @@ def layout():
cns = component.constraints
component.position = (cns.left.value, cns.bottom.value)
component.bounds = (cns.width.value, cns.height.value)
if self._debug_overlay:
layout_mgr = self._layout_manager
self._debug_overlay.update_from_constraints(layout_mgr)
mgr_layout(layout, width_var, height_var, (width, height))

self.invalidate_draw()
Expand Down Expand Up @@ -101,6 +124,8 @@ def __layout_constraints_changed(self, name, old, new):
""" Invalidate the layout when the private constraints list changes.
"""
self._layout_manager.replace_constraints(old, new)
if self.debug:
self._debug_overlay.selected_constraints = new
self.relayout()

def __components_items_changed(self, event):
Expand Down Expand Up @@ -130,7 +155,7 @@ def __components_changed(self, new):
def __layout_manager_default(self):
""" Create the layout manager.
"""
lm = LayoutManager()
lm = LayoutManager(debug=self.debug)

constraints = self._hard_constraints + self._content_box_constraints()
lm.initialize(constraints)
Expand Down
117 changes: 117 additions & 0 deletions enable/layout/debug_constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from collections import defaultdict

from enable.abstract_overlay import AbstractOverlay
from enable.colors import ColorTrait
from enable.enable_traits import LineStyle
from traits.api import Any, Bool, Float, HasTraits, Instance, List, Property


class Coords(HasTraits):
""" Simple holder of box-related data.
"""
top = Float()
bottom = Float()
left = Float()
right = Float()
width = Float()
height = Float()

v_center = Property()
_v_center = Any()
def _get_v_center(self):
if self._v_center is None:
return self.bottom + 0.5 * self.height
else:
return self._v_center
def _set_v_center(self, value):
self._v_center = value

h_center = Property()
_h_center = Any()
def _get_h_center(self):
if self._h_center is None:
return self.left + 0.5 * self.width
else:
return self._h_center
def _set_h_center(self, value):
self._h_center = value


class DebugConstraintsOverlay(AbstractOverlay):
""" Highlight the selected constraints on the outline view.
"""

selected_constraints = List()

# Map from box name to Coords.
boxes = Any()

# Style options for the lines.
term_color = ColorTrait('lightblue')
term_line_style = LineStyle('solid')

def update_from_constraints(self, layout_mgr):
""" Update the constraints boxes.
"""
self.boxes = defaultdict(Coords)
if layout_mgr is not None:
for constraint in layout_mgr._constraints:
for expr in (constraint.lhs, constraint.rhs):
for term in expr.terms:
name, attr = self.split_var_name(term.var.name)
setattr(self.boxes[name], attr, term.var.value)
self.request_redraw()

def split_var_name(self, var_name):
class_name, hexid, attr = var_name.rsplit('|', 2)
name = '{}|{}'.format(class_name, hexid)
return name, attr

def overlay(self, other_component, gc, view_bounds=None, mode="normal"):
""" Draws this component overlaid on another component.
"""
if len(self.selected_constraints) == 0:
return
with gc:
gc.set_stroke_color(self.term_color_)
gc.set_line_dash(self.term_line_style_)
gc.set_line_width(3)
term_attrs = set()
for constraint in self.selected_constraints:
for expr in (constraint.lhs, constraint.rhs):
for term in expr.terms:
term_attrs.add(self.split_var_name(term.var.name))
for name, attr in sorted(term_attrs):
box = self.boxes[name]
if attr == 'top':
self.hline(gc, box.left, box.top, box.width)
elif attr == 'left':
self.vline(gc, box.left, box.bottom, box.height)
elif attr == 'width':
self.hline(gc, box.left, box.v_center, box.width)
elif attr == 'height':
self.vline(gc, box.h_center, box.top, box.height)
elif attr == 'bottom':
self.hline(gc, box.left, box.bottom, box.right - box.left)
elif attr == 'right':
self.vline(gc, box.right, box.bottom, box.top - box.bottom)
gc.stroke_path()

def vline(self, gc, x, y0, length):
""" Draw a vertical line.
"""
gc.move_to(x, y0)
gc.line_to(x, y0+length)

def hline(self, gc, x0, y, length):
""" Draw a horizontal line.
"""
gc.move_to(x0, y)
gc.line_to(x0+length, y)

12 changes: 11 additions & 1 deletion enable/layout/layout_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ class LayoutManager(object):
of constraints.
"""
def __init__(self):
def __init__(self, debug=False):
self._solver = Solver(autosolve=False)
self._initialized = False
self._running = False

self._debug = debug
self._constraints = None

def initialize(self, constraints):
""" Initialize the solver with the given constraints.
Expand All @@ -25,6 +28,9 @@ def initialize(self, constraints):
solvers.
"""
if self._debug:
self._constraints = set(constraints)

if self._initialized:
raise RuntimeError('Solver already initialized')
solver = self._solver
Expand All @@ -47,6 +53,10 @@ def replace_constraints(self, old_cns, new_cns):
The list of casuarius constraints to add to the solver.
"""
if self._debug:
self._constraints.difference_update(set(old_cns))
self._constraints.update(set(new_cns))

if not self._initialized:
raise RuntimeError('Solver not yet initialized')
solver = self._solver
Expand Down

0 comments on commit 5be77fd

Please sign in to comment.