diff --git a/enable/constraints_container.py b/enable/constraints_container.py index fba794253..51e4d7476 100644 --- a/enable/constraints_container.py +++ b/enable/constraints_container.py @@ -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 @@ -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 #------------------------------------------------------------------------ @@ -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() @@ -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): @@ -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) diff --git a/enable/layout/debug_constraints.py b/enable/layout/debug_constraints.py new file mode 100644 index 000000000..152e68955 --- /dev/null +++ b/enable/layout/debug_constraints.py @@ -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) + diff --git a/enable/layout/layout_manager.py b/enable/layout/layout_manager.py index 33778d5d4..bcd81480e 100644 --- a/enable/layout/layout_manager.py +++ b/enable/layout/layout_manager.py @@ -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. @@ -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 @@ -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