Integrated cassowary with GTK+ backend.
Includes some fixes to the cassowary implementation revealed by testing.
This commit is contained in:
parent
119d2f5346
commit
bbd64d0481
12 changed files with 322 additions and 92 deletions
|
|
@ -1,2 +1,9 @@
|
|||
# Implementation of the Cassowary algorithm
|
||||
# http://www.cs.washington.edu/research/constraints/cassowary/
|
||||
|
||||
from .constraint import Equation, Inequality, StayConstraint, EditConstraint
|
||||
from .expression import Expression
|
||||
from .simplex_solver import SimplexSolver
|
||||
from .strength import REQUIRED, STRONG, MEDIUM, WEAK
|
||||
from .variable import Variable
|
||||
from .utils import approx_equal
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class Inequality(LinearConstraint):
|
|||
if operator == self.LEQ:
|
||||
self.expression.add_expression(param1, -1.0)
|
||||
elif operator == self.GEQ:
|
||||
self.expression.multply(-1)
|
||||
self.expression.multiply(-1)
|
||||
self.expression.add_expression(param1, 1.0)
|
||||
else:
|
||||
raise InternalError("Invalid operator in Inequality constructor")
|
||||
|
|
@ -84,7 +84,15 @@ class Inequality(LinearConstraint):
|
|||
else:
|
||||
raise InternalError("Invalid operator in Inequality constructor")
|
||||
|
||||
# elif isinstance(param2, (float, int)):
|
||||
elif isinstance(param2, (float, int)):
|
||||
super(Inequality, self).__init__(param1.clone(), strength=strength, weight=weight)
|
||||
if operator == self.LEQ:
|
||||
self.expression.multiply(-1.0)
|
||||
self.expression.add_expression(Expression(constant=param2), 1.0)
|
||||
elif operator == self.GEQ:
|
||||
self.expression.add_expression(Expression(constant=param2), -1.0)
|
||||
else:
|
||||
raise InternalError("Invalid operator in Inequality constructor")
|
||||
else:
|
||||
raise InternalError("Invalid parameters to Inequality constructor")
|
||||
|
||||
|
|
@ -142,7 +150,9 @@ class Equation(LinearConstraint):
|
|||
elif isinstance(param2, Variable):
|
||||
super(Equation, self).__init__(param1.clone(), strength=strength, weight=weight)
|
||||
self.expression.add_variable(param2, -1.0)
|
||||
# elif isinstance(param2, (float, int)):
|
||||
elif isinstance(param2, (float, int)):
|
||||
super(Equation, self).__init__(param1.clone(), strength=strength, weight=weight)
|
||||
self.expression.add_expression(Expression(constant=param2), -1.0)
|
||||
else:
|
||||
raise InternalError("Invalid parameters to Equation constructor")
|
||||
|
||||
|
|
@ -153,9 +163,10 @@ class Equation(LinearConstraint):
|
|||
elif isinstance(param2, Variable):
|
||||
super(Equation, self).__init__(Expression(variable=param2), strength=strength, weight=weight)
|
||||
elif isinstance(param2, (float, int)):
|
||||
super(Equation, self).__init__(Expression(value=param2), strength=strength, weight=weight)
|
||||
super(Equation, self).__init__(Expression(constant=param2), strength=strength, weight=weight)
|
||||
self.expression.add_variable(param1, -1.0)
|
||||
else:
|
||||
raise InternalError("Invalid parameters to Equation constructor")
|
||||
|
||||
else:
|
||||
raise InternalError("Invalid parameters to Equation constructor")
|
||||
|
|
|
|||
|
|
@ -7,18 +7,5 @@ class ConstraintNotFound(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class NonExpression(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotEnoughStays(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RequiredFailure(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TooDifficult(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from .variable import Variable, AbstractVariable
|
||||
from .error import NonExpression, InternalError
|
||||
from .error import InternalError
|
||||
from .utils import approx_equal
|
||||
|
||||
|
||||
|
|
@ -43,13 +43,13 @@ class Expression(object):
|
|||
elif x.is_constant:
|
||||
result = self * x.constant
|
||||
else:
|
||||
raise NonExpression()
|
||||
raise TypeError('Cannot multiply expression by non-constant')
|
||||
return result
|
||||
|
||||
def __div__(self, x):
|
||||
if isinstance(x, (float, int)):
|
||||
if approx_equal(x, 0):
|
||||
raise NonExpression()
|
||||
raise ZeroDivisionError()
|
||||
result = Expression(constant=self.constant / x)
|
||||
for clv, value in self.terms.items():
|
||||
result.set_variable(clv, value / x)
|
||||
|
|
@ -57,7 +57,7 @@ class Expression(object):
|
|||
if x.is_constant:
|
||||
result = self / x.constant
|
||||
else:
|
||||
raise NonExpression()
|
||||
raise TypeError('Cannot divide expression by non-constant')
|
||||
return result
|
||||
|
||||
def __add__(self, x):
|
||||
|
|
@ -65,12 +65,20 @@ class Expression(object):
|
|||
return self.clone().add_expression(x, 1.0)
|
||||
elif isinstance(x, Variable):
|
||||
return self.clone().add_variable(x, 1.0)
|
||||
elif isinstance(x, (int, float)):
|
||||
return self.clone().add_expression(Expression(constant=x), 1.0)
|
||||
else:
|
||||
raise TypeError('Cannot add object of type %s to expression' % type(x))
|
||||
|
||||
def __sub__(self, x):
|
||||
if isinstance(x, Expression):
|
||||
return self.clone().add_expression(x, -1.0)
|
||||
elif isinstance(x, Variable):
|
||||
return self.clone().add_variable(x, -1.0)
|
||||
elif isinstance(x, (int, float)):
|
||||
return self.clone().add_expression(Expression(constant=x), -1.0)
|
||||
else:
|
||||
raise TypeError('Cannot subtract object of type %s from expression' % type(x))
|
||||
|
||||
def add_expression(self, expr, n, subject=None, solver=None):
|
||||
if isinstance(expr, AbstractVariable):
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ class SimplexSolver(Tableau):
|
|||
def __init__(self):
|
||||
super(SimplexSolver, self).__init__()
|
||||
|
||||
self.stay_minus_error_vars = []
|
||||
self.stay_plus_error_vars = []
|
||||
self.stay_error_vars = []
|
||||
|
||||
self.error_vars = {}
|
||||
self.marker_vars = {}
|
||||
|
|
@ -34,8 +33,7 @@ class SimplexSolver(Tableau):
|
|||
|
||||
def __repr__(self):
|
||||
parts = []
|
||||
parts.append('stay_plus_error_vars: %s' % self.stay_plus_error_vars)
|
||||
parts.append('stay_minus_error_vars: %s' % self.stay_minus_error_vars)
|
||||
parts.append('stay_error_vars: %s' % self.stay_error_vars)
|
||||
parts.append('edit_var_map: %s' % self.edit_var_map)
|
||||
return super(SimplexSolver, self).__repr__() + '\n' + '\n'.join(parts)
|
||||
|
||||
|
|
@ -96,7 +94,7 @@ class SimplexSolver(Tableau):
|
|||
z_row = self.rows[self.objective]
|
||||
# print "z_row", z_row
|
||||
sw_coeff = cn.strength * cn.weight
|
||||
if sw_coeff == 0:
|
||||
# if sw_coeff == 0:
|
||||
# print "cn ==", cn
|
||||
# print "adding ", eplus, "and", eminus, "with sw_coeff", sw_coeff
|
||||
z_row.set_variable(eplus, sw_coeff)
|
||||
|
|
@ -108,8 +106,7 @@ class SimplexSolver(Tableau):
|
|||
self.insert_error_var(cn, eplus)
|
||||
|
||||
if cn.is_stay_constraint:
|
||||
self.stay_plus_error_vars.append(eplus)
|
||||
self.stay_minus_error_vars.append(eminus)
|
||||
self.stay_error_vars.append((eplus, eminus))
|
||||
elif cn.is_edit_constraint:
|
||||
prev_edit_constant = cn.expression.constant
|
||||
|
||||
|
|
@ -136,6 +133,8 @@ class SimplexSolver(Tableau):
|
|||
self.optimize(self.objective)
|
||||
self.set_external_variables()
|
||||
|
||||
return cn
|
||||
|
||||
def add_constraint_no_exception(self, cn):
|
||||
try:
|
||||
self.add_constraint(cn)
|
||||
|
|
@ -174,7 +173,7 @@ class SimplexSolver(Tableau):
|
|||
assert len(self.edit_var_map) == n
|
||||
|
||||
except ConstraintNotFound:
|
||||
raise InternalError('Constraint not found')
|
||||
raise InternalError('Constraint not found during internal removal')
|
||||
|
||||
def add_point_stays(self, points):
|
||||
# print "add_point_stays", points
|
||||
|
|
@ -201,8 +200,10 @@ class SimplexSolver(Tableau):
|
|||
for cv in e_vars:
|
||||
try:
|
||||
z_row.add_expression(self.rows[cv], -cn.weight * cn.strength, self.objective, self)
|
||||
# print 'add expression', self.rows[cv]
|
||||
except KeyError:
|
||||
z_row.add_variable(cv, -cn.weight * cn.strength, self.objective, self)
|
||||
# print 'add variable', cv
|
||||
|
||||
try:
|
||||
marker = self.marker_vars.pop(cn)
|
||||
|
|
@ -216,59 +217,94 @@ class SimplexSolver(Tableau):
|
|||
exit_var = None
|
||||
min_ratio = 0.0
|
||||
for v in col:
|
||||
# print 'check var', v
|
||||
if v.is_restricted:
|
||||
# print 'var', v, ' is restricted'
|
||||
expr = self.rows[v]
|
||||
coeff = expr.coefficient_for(marker)
|
||||
# print "Marker", marker, "'s coefficient in", expr, "is", coeff
|
||||
if coeff < 0:
|
||||
r = -expr.constant / coeff
|
||||
if exit_var is None or r < min_ratio: # EXTRA BITS IN JS?
|
||||
# print 'set exit var = ',v,r
|
||||
min_ratio = r
|
||||
exit_var = v
|
||||
|
||||
if exit_var is None:
|
||||
# print "exit_var is still None"
|
||||
for v in col:
|
||||
# print 'check var', v
|
||||
if v.is_restricted:
|
||||
# print 'var', v, ' is restricted'
|
||||
expr = self.rows[v]
|
||||
coeff = expr.coefficient_for(marker)
|
||||
# print "Marker", marker, "'s coefficient in", expr, "is", coeff
|
||||
r = expr.constant / coeff
|
||||
if exit_var is None or r < min_ratio:
|
||||
# print 'set exit var = ',v,r
|
||||
min_ratio = r
|
||||
exit_var = v
|
||||
|
||||
if exit_var is None:
|
||||
# print "exit_var is still None (again)"
|
||||
if len(col) == 0:
|
||||
# print 'remove column',marker
|
||||
self.remove_column(marker)
|
||||
else:
|
||||
exit_var = [v for v in col if v != self.objective][-1] # ??
|
||||
# print 'set exit var', exit_var
|
||||
|
||||
if exit_var is not None:
|
||||
# print 'Pivot', marker, exit_var,
|
||||
self.pivot(marker, exit_var)
|
||||
|
||||
if not self.rows.get(marker):
|
||||
if self.rows.get(marker):
|
||||
# print 'remove row', marker
|
||||
expr = self.remove_row(marker)
|
||||
|
||||
if e_vars:
|
||||
# print 'e_vars exist'
|
||||
for v in e_vars:
|
||||
if v != marker:
|
||||
# print 'remove column',v
|
||||
self.remove_column(v)
|
||||
|
||||
if cn.is_stay_constraint:
|
||||
if e_vars:
|
||||
for p_evar, m_evar in zip(self.stay_plus_error_vars):
|
||||
e_vars.remove(p_evar)
|
||||
e_vars.remove(m_evar)
|
||||
# for p_evar, m_evar in self.stay_error_vars:
|
||||
remaining = []
|
||||
while self.stay_error_vars:
|
||||
p_evar, m_evar = self.stay_error_vars.pop()
|
||||
found = False
|
||||
try:
|
||||
# print 'stay constraint - remove plus evar', p_evar
|
||||
e_vars.remove(p_evar)
|
||||
found = True
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
# print 'stay constraint - remove minus evar', m_evar
|
||||
e_vars.remove(m_evar)
|
||||
found = True
|
||||
except KeyError:
|
||||
pass
|
||||
if not found:
|
||||
remaining.append((p_evar, m_evar))
|
||||
self.stay_error_vars = remaining
|
||||
|
||||
elif cn.is_edit_constraint:
|
||||
assert e_vars is not None
|
||||
# print 'edit constraint - remove column', self.edit_var_map[cn.variable].edit_minus
|
||||
self.remove_column(self.edit_var_map[cn.variable].edit_minus)
|
||||
del self.edit_var_map[cn.variable]
|
||||
|
||||
if e_vars:
|
||||
for e_var in e_vars:
|
||||
# print 'Remove error var', e_var
|
||||
del self.error_vars[e_var]
|
||||
|
||||
if self.auto_solve:
|
||||
# print 'final auto solve'
|
||||
self.optimize(self.objective)
|
||||
self.set_external_variables()
|
||||
|
||||
|
|
@ -510,7 +546,7 @@ class SimplexSolver(Tableau):
|
|||
exit_var = v
|
||||
|
||||
if min_ratio == float('inf'):
|
||||
raise InternalError('Objective function is unbounded in optimize')
|
||||
raise RequiredFailure('Objective function is unbounded')
|
||||
|
||||
self.pivot(entry_var, exit_var)
|
||||
|
||||
|
|
@ -530,7 +566,7 @@ class SimplexSolver(Tableau):
|
|||
|
||||
def reset_stay_constants(self):
|
||||
# print "reset_stay_constants"
|
||||
for p_var, m_var in zip(self.stay_plus_error_vars, self.stay_minus_error_vars):
|
||||
for p_var, m_var in self.stay_error_vars:
|
||||
expr = self.rows.get(p_var)
|
||||
if expr is None:
|
||||
expr = self.rows.get(m_var)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
EPSILON = 1e-8
|
||||
|
||||
def approx_equal(a, b):
|
||||
return abs(a - b) < EPSILON
|
||||
return abs(a - b) < EPSILON
|
||||
|
|
|
|||
|
|
@ -29,13 +29,18 @@ class Container(Widget):
|
|||
def __init__(self):
|
||||
super(Container, self).__init__()
|
||||
self._impl = NSView.alloc().init()
|
||||
self.constraints = []
|
||||
self.children = []
|
||||
self.constraints = {}
|
||||
|
||||
def add(self, widget):
|
||||
self.children.append(widget)
|
||||
self._impl.addSubview_(widget._impl)
|
||||
|
||||
def constrain(self, constraint):
|
||||
"Add the given constraint to the widget."
|
||||
if constraint in self.constraints:
|
||||
return
|
||||
|
||||
widget = constraint.attr.widget._impl
|
||||
identifier = constraint.attr.identifier
|
||||
|
||||
|
|
@ -62,4 +67,4 @@ class Container(Widget):
|
|||
|
||||
self._impl.addConstraint_(constraint._impl)
|
||||
|
||||
self.constraints.append(constraint)
|
||||
self.constraints[constraint] = constraint._impl
|
||||
|
|
|
|||
|
|
@ -71,29 +71,6 @@ class Attribute(object):
|
|||
self.multiplier = float(multiplier)
|
||||
self.constant = float(constant)
|
||||
|
||||
@property
|
||||
def update_strategy(self):
|
||||
update_strategies = {
|
||||
Attribute.LEFT: Value.MINIMUM,
|
||||
Attribute.RIGHT: Value.MAXIMUM,
|
||||
Attribute.TOP: Value.MINIMUM,
|
||||
Attribute.BOTTOM: Value.MAXIMUM,
|
||||
Attribute.WIDTH: Value.MAXIMUM,
|
||||
Attribute.HEIGHT: Value.MAXIMUM,
|
||||
Attribute.CENTER_X: Value.AVERAGE,
|
||||
Attribute.CENTER_Y: Value.AVERAGE,
|
||||
# Attribute.BASELINE: Value.AVERAGE,
|
||||
}
|
||||
|
||||
if True: # Check for RTL
|
||||
update_strategies[Attribute.LEADING] = Value.MINIMUM
|
||||
update_strategies[Attribute.TRAILING] = Value.MAXIMUM
|
||||
else:
|
||||
update_strategies[Attribute.LEADING] = Value.MAXIMUM
|
||||
update_strategies[Attribute.TRAILING] = Value.MINIMUM
|
||||
|
||||
return update_strategies[self.identifier]
|
||||
|
||||
@property
|
||||
def identifier_label(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from tailor.widget import WidgetBase
|
||||
from tailor.cassowary import Expression
|
||||
from tailor.constraint import Attribute
|
||||
from tailor.widget import WidgetBase, BoundingBox
|
||||
|
||||
|
||||
def wrapped_handler(widget, handler):
|
||||
|
|
@ -9,11 +11,40 @@ def wrapped_handler(widget, handler):
|
|||
|
||||
|
||||
class Widget(WidgetBase):
|
||||
def __init__(self):
|
||||
super(Widget, self).__init__()
|
||||
self._bounding_box = BoundingBox()
|
||||
self._expand_horizontal = True
|
||||
self._expand_vertical = True
|
||||
|
||||
def _expression(self, identifier):
|
||||
if identifier == Attribute.LEFT:
|
||||
return Expression(variable=self._bounding_box.x)
|
||||
elif identifier == Attribute.RIGHT:
|
||||
return Expression(variable=self._bounding_box.x) + Expression(variable=self._bounding_box.width)
|
||||
elif identifier == Attribute.TOP:
|
||||
return Expression(variable=self._bounding_box.y)
|
||||
elif identifier == Attribute.BOTTOM:
|
||||
return Expression(variable=self._bounding_box.y) + Expression(variable=self._bounding_box.height)
|
||||
elif identifier == Attribute.LEADING:
|
||||
return Expression(variable=self._bounding_box.x)
|
||||
elif identifier == Attribute.TRAILING:
|
||||
return Expression(variable=self._bounding_box.x) + Expression(variable=self._bounding_box.width)
|
||||
elif identifier == Attribute.WIDTH:
|
||||
return Expression(variable=self._bounding_box.width)
|
||||
elif identifier == Attribute.HEIGHT:
|
||||
return Expression(variable=self._bounding_box.height)
|
||||
elif identifier == Attribute.CENTER_X:
|
||||
return Expression(variable=self._bounding_box.x) + (Expression(variable=self._bounding_box.width) / 2)
|
||||
elif identifier == Attribute.CENTER_Y:
|
||||
return Expression(variable=self._bounding_box.y) + (Expression(variable=self._bounding_box.height) / 2)
|
||||
# elif identifier == self.BASELINE:
|
||||
# return ...
|
||||
|
||||
@property
|
||||
def width_hints(self):
|
||||
def _width_hint(self):
|
||||
return self._impl.get_preferred_width()
|
||||
|
||||
@property
|
||||
def height_hints(self):
|
||||
return self._impl.get_preferred_width()
|
||||
def _height_hint(self):
|
||||
return self._impl.get_preferred_height()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ def wrapped_handler(widget, handler):
|
|||
class Button(Widget):
|
||||
def __init__(self, label, on_press=None):
|
||||
super(Button, self).__init__()
|
||||
# Buttons have a fixed drawn height. If their space allocation is
|
||||
# greater than what is provided, center the button vertically.
|
||||
self._expand_vertical = False
|
||||
|
||||
self.on_press = on_press
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +1,218 @@
|
|||
from gi.repository import Gtk, cairo
|
||||
|
||||
from tailor.constraint import LayoutManager
|
||||
from tailor.constraint import Constraint
|
||||
from tailor.cassowary import SimplexSolver, StayConstraint, WEAK, STRONG, REQUIRED, Equation, Inequality, Expression, approx_equal
|
||||
from tailor.gtk.widgets.base import Widget
|
||||
|
||||
|
||||
class LayoutManager(SimplexSolver):
|
||||
def __init__(self, bounding_box):
|
||||
super(LayoutManager, self).__init__()
|
||||
self.bounding_box = bounding_box
|
||||
|
||||
self.children = {}
|
||||
|
||||
# Enforce a hard constraint that the container starts at 0,0
|
||||
self.add_constraint(StayConstraint(self.bounding_box.x, strength=REQUIRED))
|
||||
self.add_constraint(StayConstraint(self.bounding_box.y, strength=REQUIRED))
|
||||
|
||||
# # Add a weak constraint for the bounds of the container.
|
||||
self.width_constraint = StayConstraint(self.bounding_box.width, strength=WEAK)
|
||||
self.height_constraint = StayConstraint(self.bounding_box.height, strength=WEAK)
|
||||
|
||||
self.add_constraint(self.width_constraint)
|
||||
self.add_constraint(self.height_constraint)
|
||||
|
||||
def add_constraint(self, constraint):
|
||||
print constraint
|
||||
return super(LayoutManager, self).add_constraint(constraint)
|
||||
|
||||
def add_widget(self, widget):
|
||||
constraints = set()
|
||||
|
||||
min_width, preferred_width = widget._width_hint
|
||||
min_height, preferred_height = widget._height_hint
|
||||
|
||||
print min_width, preferred_width
|
||||
print min_height, preferred_height
|
||||
# REQUIRED: Widget width must exceed minimum.
|
||||
constraints.add(self.add_constraint(
|
||||
Inequality(
|
||||
Expression(variable=widget._bounding_box.width),
|
||||
Inequality.GEQ,
|
||||
min_width,
|
||||
)
|
||||
))
|
||||
|
||||
# REQUIRED: Widget height must exceed minimum
|
||||
constraints.add(self.add_constraint(
|
||||
Inequality(
|
||||
Expression(variable=widget._bounding_box.height),
|
||||
Inequality.GEQ,
|
||||
min_height,
|
||||
)
|
||||
))
|
||||
|
||||
# STRONG: Adhere to preferred widget width
|
||||
constraints.add(self.add_constraint(
|
||||
Equation(
|
||||
Expression(variable=widget._bounding_box.width),
|
||||
preferred_width,
|
||||
strength=STRONG
|
||||
)
|
||||
))
|
||||
|
||||
# STRONG: Try to adhere to preferred widget height
|
||||
constraints.add(self.add_constraint(
|
||||
Equation(
|
||||
Expression(variable=widget._bounding_box.height),
|
||||
preferred_height,
|
||||
strength=STRONG
|
||||
)
|
||||
))
|
||||
|
||||
print constraints
|
||||
self.children[widget] = constraints
|
||||
|
||||
def enforce(self, width, height):
|
||||
self.remove_constraint(self.width_constraint)
|
||||
self.remove_constraint(self.height_constraint)
|
||||
|
||||
self.bounding_box.width.value = width
|
||||
self.bounding_box.height.value = height
|
||||
|
||||
self.width_constraint = StayConstraint(self.bounding_box.width, strength=REQUIRED)
|
||||
self.height_constraint = StayConstraint(self.bounding_box.height, strength=REQUIRED)
|
||||
|
||||
self.add_constraint(self.width_constraint)
|
||||
self.add_constraint(self.height_constraint)
|
||||
|
||||
def relax(self):
|
||||
self.remove_constraint(self.width_constraint)
|
||||
self.remove_constraint(self.height_constraint)
|
||||
|
||||
self.bounding_box.width.value = 0
|
||||
self.bounding_box.height.value = 0
|
||||
|
||||
self.width_constraint = StayConstraint(self.bounding_box.width, strength=WEAK)
|
||||
self.height_constraint = StayConstraint(self.bounding_box.height, strength=WEAK)
|
||||
|
||||
self.add_constraint(self.width_constraint)
|
||||
self.add_constraint(self.height_constraint)
|
||||
|
||||
|
||||
class TContainer(Gtk.Fixed):
|
||||
def __init__(self, layout_manager):
|
||||
super(TContainer, self).__init__()
|
||||
self.children = []
|
||||
self.layout_manager = layout_manager
|
||||
|
||||
def do_get_preferred_width(self):
|
||||
# Calculate the minimum and natural width of the container.
|
||||
print "PREFERRED WIDTH"
|
||||
return 290, 370
|
||||
# hint = self.layout_manager.layout(None, None)[self.container]
|
||||
# return hint.right.vmin - hint.left.vmin, hint.right.vpref - hint.left.vpref,
|
||||
width = self.layout_manager.bounding_box.width.value
|
||||
# print "PREFERRED WIDTH", width
|
||||
return width, width
|
||||
|
||||
def do_get_preferred_height(self):
|
||||
# Calculate the minimum and natural height of the container.
|
||||
print "PREFERRED HEIGHT"
|
||||
return 100, 150
|
||||
# hint = self.layout_manager.layout(None, None)[self.container]
|
||||
# return hint.bottom.vmin - hint.top.vmin, hint.bottom.vpref - hint.top.vpref,
|
||||
height = self.layout_manager.bounding_box.height.value
|
||||
# print "PREFERRED HEIGHT", height
|
||||
return height, height
|
||||
|
||||
def do_size_allocate(self, allocation):
|
||||
print "Size allocate", allocation.width, 'x', allocation.height, ' @ ', allocation.x, 'x', allocation.y
|
||||
hints = self.layout_manager.layout(allocation.width, allocation.height)
|
||||
|
||||
for widget, hint in hints.items():
|
||||
if widget == self.layout_manager.container:
|
||||
print 'LAYOUT CONTAINER'
|
||||
self.set_allocation(allocation)
|
||||
elif not widget._impl.get_visible():
|
||||
self.set_allocation(allocation)
|
||||
|
||||
# Temporarily enforce a size requirement based on the allocation
|
||||
self.layout_manager.enforce(allocation.width, allocation.height)
|
||||
|
||||
for widget in self.layout_manager.children:
|
||||
print widget, widget._bounding_box
|
||||
if not widget._impl.get_visible():
|
||||
print "CHILD NOT VISIBLE"
|
||||
else:
|
||||
print "CHILD", widget
|
||||
|
||||
min_width, preferred_width = widget._width_hint
|
||||
min_height, preferred_height = widget._height_hint
|
||||
|
||||
x_pos = widget._bounding_box.x.value
|
||||
if widget._expand_horizontal:
|
||||
width = widget._bounding_box.width.value
|
||||
else:
|
||||
x_pos = x_pos + ((widget._bounding_box.width.value - preferred_width) / 2.0)
|
||||
width = preferred_width
|
||||
|
||||
y_pos = widget._bounding_box.y.value
|
||||
if widget._expand_vertical:
|
||||
height = widget._bounding_box.height.value
|
||||
else:
|
||||
y_pos = y_pos + ((widget._bounding_box.height.value - preferred_height) / 2.0)
|
||||
height = preferred_height
|
||||
|
||||
child_allocation = cairo.RectangleInt()
|
||||
child_allocation.x = hint.left.vpref
|
||||
child_allocation.y = hint.top.vpref
|
||||
child_allocation.width = hint.right.vpref - hint.left.vpref
|
||||
child_allocation.height = hint.bottom.vpref - hint.top.vpref
|
||||
child_allocation.x = x_pos
|
||||
child_allocation.y = y_pos
|
||||
child_allocation.width = width
|
||||
child_allocation.height = height
|
||||
|
||||
widget._impl.size_allocate(child_allocation)
|
||||
|
||||
# Restore the unbounded allocation
|
||||
self.layout_manager.relax()
|
||||
|
||||
|
||||
class Container(Widget):
|
||||
_RELATION = {
|
||||
Constraint.LTE: Inequality.LEQ,
|
||||
Constraint.GTE: Inequality.GEQ
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(Container, self).__init__()
|
||||
self._layout_manager = LayoutManager(self)
|
||||
self._layout_manager = LayoutManager(self._bounding_box)
|
||||
self._impl = TContainer(self._layout_manager)
|
||||
self._children = []
|
||||
self._constraints = []
|
||||
|
||||
def add(self, widget):
|
||||
self._layout_manager.reset()
|
||||
self._children.append(widget)
|
||||
self._impl.add(widget._impl)
|
||||
self._layout_manager.add_widget(widget)
|
||||
|
||||
def constrain(self, constraint):
|
||||
"Add the given constraint to the widget."
|
||||
self._layout_manager.reset()
|
||||
self._constraints.append(constraint)
|
||||
widget = constraint.attr.widget
|
||||
identifier = constraint.attr.identifier
|
||||
|
||||
if constraint.related_attr:
|
||||
related_widget = constraint.related_attr.widget
|
||||
related_identifier = constraint.related_attr.identifier
|
||||
|
||||
expr1 = widget._expression(identifier)
|
||||
if not approx_equal(constraint.attr.multiplier, 1.0):
|
||||
expr1 = expr1 * constraint.attr.multiplier
|
||||
if not approx_equal(constraint.attr.constant, 0.0):
|
||||
expr1 = expr1 + constraint.attr.constant
|
||||
|
||||
expr2 = related_widget._expression(related_identifier)
|
||||
if not approx_equal(constraint.related_attr.multiplier, 1.0):
|
||||
expr2 = expr2 * constraint.related_attr.multiplier
|
||||
if not approx_equal(constraint.related_attr.constant, 0.0):
|
||||
expr2 = expr2 + constraint.related_attr.constant
|
||||
|
||||
print 'E1', expr1
|
||||
print 'E2', expr2
|
||||
|
||||
if constraint.relation == Constraint.EQUAL:
|
||||
self._layout_manager.add_constraint(Equation(expr1, expr2))
|
||||
else:
|
||||
self._layout_manager.add_constraint(Inequality(expr1, self._RELATION[constraint.relation], expr2))
|
||||
else:
|
||||
expr = widget._expression(identifier)
|
||||
if not approx_equal(constraint.attr.multiplier, 1.0):
|
||||
expr = expr * constraint.attr.multiplier
|
||||
|
||||
print 'E', expr
|
||||
|
||||
if constraint.relation == Constraint.EQUAL:
|
||||
self._layout_manager.add_constraint(Equation(expr, constraint.attr.constant))
|
||||
else:
|
||||
self._layout_manager.add_constraint(Equation(expr, self._RELATION[constraint.relation], constraint.attr.constant))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
from tailor.cassowary import Variable
|
||||
from tailor.constraint import Attribute
|
||||
|
||||
|
||||
class BoundingBox(object):
|
||||
def __init__(self):
|
||||
self.x = Variable('x', 0.0)
|
||||
self.y = Variable('y', 0.0)
|
||||
self.width = Variable('width', 0.0)
|
||||
self.height = Variable('height', 0.0)
|
||||
|
||||
def __repr__(self):
|
||||
return u'%sx%s @ %s,%s' % (self.width.value, self.height.value, self.x.value, self.y.value)
|
||||
|
||||
|
||||
class WidgetBase(object):
|
||||
def __init__(self):
|
||||
self.LEFT = Attribute(self, Attribute.LEFT)
|
||||
|
|
|
|||
Loading…
Reference in a new issue