aboutsummaryrefslogtreecommitdiffstats
path: root/lay/strain/solve.go
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2026-03-03 18:59:21 -0500
committerSam Anthony <sam@samanthony.xyz>2026-03-03 18:59:21 -0500
commit9a2c345cfd3697a5de3f5fd799f682ab822e3756 (patch)
tree3ab215fdf51b1ba82a335a432b946605d4a1468a /lay/strain/solve.go
parentb3064e570fab92cb4fa6b4c9265be5a4cbcf80e4 (diff)
downloadgui-9a2c345cfd3697a5de3f5fd799f682ab822e3756.zip
lay/strain: add default constraints to Solver
Diffstat (limited to 'lay/strain/solve.go')
-rw-r--r--lay/strain/solve.go71
1 files changed, 55 insertions, 16 deletions
diff --git a/lay/strain/solve.go b/lay/strain/solve.go
index 89270ed..cfb1f41 100644
--- a/lay/strain/solve.go
+++ b/lay/strain/solve.go
@@ -12,6 +12,11 @@ import (
"github.com/faiface/gui/style"
)
+const (
+ fieldConstraintPriority = casso.Medium
+ layoutConstraintPriority = casso.Medium
+)
+
// Solver uses the Cassowary algorithm to partition a rectangle among
// several layout fields.
//
@@ -80,7 +85,7 @@ func NewSolver(styl *style.Style, constraints []<-chan Constraint) (*Solver, err
solver := casso.NewSolver()
container := NewSymRect()
if err := editRect(solver, container, casso.Strong); err != nil {
- return nil, err
+ return nil, fmt.Errorf("error marking container symbol as editable: %w", err)
}
s := &Solver{
@@ -96,7 +101,9 @@ func NewSolver(styl *style.Style, constraints []<-chan Constraint) (*Solver, err
go func() {
for tc := range fieldConstrs {
constr, fieldIdx := tc.Val, tc.Tag
- err := s.addSizeConstraint(constr, fieldIdx)
+ s.mu.Lock()
+ err := s.addFieldSizeConstraint(constr, fieldIdx)
+ s.mu.Unlock()
if err != nil {
log.Err.Printf("error adding layout constraint %#v from field %d: %v\n",
constr, fieldIdx, err)
@@ -104,19 +111,30 @@ func NewSolver(styl *style.Style, constraints []<-chan Constraint) (*Solver, err
}
}()
+ if err := s.addDefaultConstraints(); err != nil {
+ return nil, fmt.Errorf("error adding default constraint: %w", err)
+ }
+
return s, nil
+}
- // TODO: add some universal constraints:
- // - field size <= container size
- // - field origin >= container origin
+func (s *Solver) addDefaultConstraints() error {
+ for _, field := range s.fields {
+ if err := s.AddConstraintPt(casso.GTE, field.Origin, s.container.Origin); err != nil {
+ return err
+ }
+ if err := s.AddConstraintPt(casso.LTE, field.Size, s.container.Size); err != nil {
+ return err
+ }
+ }
+ return nil
}
// addSizeConstraint adds or modifies a constraint on the size of a
// field, removing mutually exclusive constraints.
-func (s *Solver) addSizeConstraint(constr Constraint, i fieldIndex) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
+//
+// Solver.mu must be held.
+func (s *Solver) addFieldSizeConstraint(constr Constraint, i fieldIndex) error {
fieldSize := s.fields[i].Size
fieldConstrs := &s.fieldSizeConstrs[i]
@@ -127,21 +145,21 @@ func (s *Solver) addSizeConstraint(constr Constraint, i fieldIndex) error {
switch constr.Op {
case casso.EQ:
s.removeConstraints(fieldConstrs.widthEq, fieldConstrs.widthGte, fieldConstrs.widthLte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -width, fieldSize.X.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -width, fieldSize.X.T(1.0))
if err != nil {
return err
}
fieldConstrs.widthEq = &c
case casso.GTE:
s.removeConstraints(fieldConstrs.widthEq, fieldConstrs.widthGte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -width, fieldSize.X.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -width, fieldSize.X.T(1.0))
if err != nil {
return err
}
fieldConstrs.widthGte = &c
case casso.LTE:
s.removeConstraints(fieldConstrs.widthEq, fieldConstrs.widthLte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -width, fieldSize.X.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -width, fieldSize.X.T(1.0))
if err != nil {
return err
}
@@ -154,21 +172,21 @@ func (s *Solver) addSizeConstraint(constr Constraint, i fieldIndex) error {
switch constr.Op {
case casso.EQ:
s.removeConstraints(fieldConstrs.heightEq, fieldConstrs.heightGte, fieldConstrs.heightLte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -height, fieldSize.Y.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -height, fieldSize.Y.T(1.0))
if err != nil {
return err
}
fieldConstrs.heightEq = &c
case casso.GTE:
s.removeConstraints(fieldConstrs.heightEq, fieldConstrs.heightGte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -height, fieldSize.Y.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -height, fieldSize.Y.T(1.0))
if err != nil {
return err
}
fieldConstrs.heightGte = &c
case casso.LTE:
s.removeConstraints(fieldConstrs.heightEq, fieldConstrs.heightLte)
- c, err := s.solver.AddConstraint(casso.NewConstraint(constr.Op, -height, fieldSize.Y.T(1.0)))
+ c, err := s.addFieldConstraint(constr.Op, -height, fieldSize.Y.T(1.0))
if err != nil {
return err
}
@@ -183,6 +201,7 @@ func (s *Solver) addSizeConstraint(constr Constraint, i fieldIndex) error {
return nil
}
+// Solver.mu must be held.
func (s *Solver) removeConstraints(constrs ...*casso.Symbol) error {
for _, constr := range constrs {
if constr != nil {
@@ -194,6 +213,13 @@ func (s *Solver) removeConstraints(constrs ...*casso.Symbol) error {
return nil
}
+// Solver.mu must be held.
+func (s *Solver) addFieldConstraint(op casso.Op, constant float64, terms ...casso.Term) (casso.Symbol, error) {
+ return s.solver.AddConstraintWithPriority(
+ fieldConstraintPriority,
+ casso.NewConstraint(op, constant, terms...))
+}
+
// Container returns the Cassowary symbols representing the layout
// container's position and size.
func (s *Solver) Container() SymRect { return s.container }
@@ -209,10 +235,22 @@ func (s *Solver) Field(i int) SymRect { return s.fields[i] }
func (s *Solver) AddConstraint(op casso.Op, lhs, rhs casso.Symbol) error {
s.mu.Lock()
defer s.mu.Unlock()
- _, err := s.solver.AddConstraint(casso.NewConstraint(op, 0, lhs.T(1.0), rhs.T(-1.0)))
+ _, err := s.solver.AddConstraintWithPriority(layoutConstraintPriority, casso.NewConstraint(op, 0, lhs.T(1.0), rhs.T(-1.0)))
return err
}
+// AddConstraintPt imposes a constraint between two point symbols:
+// (lhs.X op rhs.X) and (lhs.Y op rhs.Y).
+func (s *Solver) AddConstraintPt(op casso.Op, lhs, rhs SymPt) error {
+ if err := s.AddConstraint(op, lhs.X, rhs.X); err != nil {
+ return err
+ }
+ if err := s.AddConstraint(op, lhs.Y, rhs.Y); err != nil {
+ return err
+ }
+ return nil
+}
+
// Solve uses the constraints that the Solver has received so far to
// partition container, dividing it among the fields of the layout.
//
@@ -241,4 +279,5 @@ func (s *Solver) Solve(container image.Rectangle) ([]image.Rectangle, error) {
return fields, nil
}
+// Solver.mu must be held.
func (s *Solver) intVal(sym casso.Symbol) int { return int(s.solver.Val(sym)) }