aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lay/strain/solve.go40
-rw-r--r--lay/strain/solve_test.go105
-rw-r--r--lay/strain/sym.go11
3 files changed, 131 insertions, 25 deletions
diff --git a/lay/strain/solve.go b/lay/strain/solve.go
index 7fd3c03..71e427b 100644
--- a/lay/strain/solve.go
+++ b/lay/strain/solve.go
@@ -26,8 +26,7 @@ type Solver struct {
// External symbols
container SymRect // position and size of container
- fieldOrigins []SymPt // top-left corner position of each field
- fieldSizes []SymPt // width and height of each field
+ fields []SymRect // position and size of each field
fieldSizeConstrs []sizeConstraint
@@ -35,7 +34,7 @@ type Solver struct {
layoutConstrs chan constrainRequest // constraints from the layout via AddConstraint()
solveReqs chan solveRequest
- style style.Style
+ style *style.Style
ctx context.Context
cancel func()
@@ -75,24 +74,21 @@ type solveResponse struct {
//
// The Solver should be closed after use, but not before all of the
// constraint channels have been closed.
-func NewSolver(styl style.Style, constraints []<-chan Constraint) (*Solver, error) {
+func NewSolver(styl *style.Style, constraints []<-chan Constraint) (*Solver, error) {
nfields := len(constraints)
fieldConstrs := make(chan tag.Tagged[Constraint, int])
- fieldOrigins := make([]SymPt, nfields)
- fieldSizes := make([]SymPt, nfields)
+ fields := make([]SymRect, nfields)
for i, cs := range constraints {
go tag.Tag(fieldConstrs, cs, func(c Constraint) int { return i })
- fieldOrigins[i] = NewSymPt()
- fieldSizes[i] = NewSymPt()
+ fields[i] = NewSymRect()
}
ctx, cancel := context.WithCancel(context.Background())
solver := &Solver{
casso.NewSolver(),
NewSymRect(),
- fieldOrigins,
- fieldSizes,
+ fields,
make([]sizeConstraint, nfields),
fieldConstrs,
make(chan constrainRequest),
@@ -101,7 +97,7 @@ func NewSolver(styl style.Style, constraints []<-chan Constraint) (*Solver, erro
ctx,
cancel,
}
- if err := editRect(solver.solver, solver.container, casso.Required); err != nil {
+ if err := editRect(solver.solver, solver.container, casso.Strong); err != nil {
return nil, err
}
@@ -142,7 +138,7 @@ func (s *Solver) run() {
// addSizeConstraint adds or modifies a constraint on the size of a
// field, removing mutually exclusive constraints.
func (s *Solver) addSizeConstraint(constr Constraint, fieldIdx int) error {
- fieldSize := s.fieldSizes[fieldIdx]
+ fieldSize := s.fields[fieldIdx].Size
fieldConstrs := &s.fieldSizeConstrs[fieldIdx]
// Clear mutually exclusive constraints and replace with new one
@@ -223,16 +219,22 @@ func (s *Solver) solve(container image.Rectangle) (fields []image.Rectangle, err
if err := suggestRect(s.solver, s.container, container); err != nil {
return nil, err
}
- fields = make([]image.Rectangle, len(s.fieldOrigins))
+ fields = make([]image.Rectangle, len(s.fields))
for i := range fields {
- origin, size := s.fieldOrigins[i], s.fieldSizes[i]
- min := image.Pt(int(s.solver.Val(origin.X)), int(s.solver.Val(origin.Y)))
- max := min.Add(image.Pt(int(s.solver.Val(size.X)), int(s.solver.Val(size.Y))))
+ field := s.fields[i]
+ min := image.Pt(
+ s.intVal(field.Origin.X),
+ s.intVal(field.Origin.Y))
+ max := min.Add(image.Pt(
+ s.intVal(field.Size.X),
+ s.intVal(field.Size.Y)))
fields[i] = image.Rectangle{min, max}
}
return fields, nil
}
+func (s *Solver) intVal(sym casso.Symbol) int { return int(s.solver.Val(sym)) }
+
// Close destroys the solver. You must first close all the Constraint
// channels that were passed to NewSolver() before calling Close.
func (s *Solver) Close() { s.cancel() }
@@ -242,10 +244,8 @@ func (s *Solver) Close() { s.cancel() }
func (s *Solver) Container() SymRect { return s.container }
// Field returns the Cassowary symbols representing the i'th field's
-// position and size. origin represents the position of the top-left
-// corner of the field. size represents the width and height of the
-// field.
-func (s *Solver) Field(i int) (origin, size SymPt) { return s.fieldOrigins[i], s.fieldSizes[i] }
+// position and size.
+func (s *Solver) Field(i int) SymRect { return s.fields[i] }
// AddConstraint imposes a constraint between two symbols. The symbols
// may be aspects of the Container() or Field()s.
diff --git a/lay/strain/solve_test.go b/lay/strain/solve_test.go
new file mode 100644
index 0000000..61ab7e6
--- /dev/null
+++ b/lay/strain/solve_test.go
@@ -0,0 +1,105 @@
+package strain_test
+
+import (
+ "testing"
+ "image"
+ "slices"
+
+ "github.com/lithdew/casso"
+
+ "github.com/faiface/gui/style"
+ "github.com/faiface/gui/lay/strain"
+)
+
+type solverTest struct {
+ t *testing.T
+ *style.Style
+ *strain.Solver
+}
+
+func newSolverTest(t *testing.T, constraints []<-chan strain.Constraint) solverTest {
+ styl, err := style.New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ solver, err := strain.NewSolver(styl, constraints)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return solverTest{t, styl, solver}
+}
+
+func (st solverTest) Close() {
+ st.Solver.Close()
+ if err := st.Style.Close(); err != nil {
+ st.t.Error(err)
+ }
+}
+
+func (st solverTest) addConstraint(op casso.Op, lhs, rhs casso.Symbol) {
+ if err := st.Solver.AddConstraint(op, lhs, rhs); err != nil {
+ st.t.Error(err)
+ }
+}
+
+func (st solverTest) solve(container image.Rectangle, wantFields []image.Rectangle) {
+ fields, err := st.Solver.Solve(container)
+ if err != nil {
+ st.t.Errorf("Solve(%v): %v; want %v", container, err, wantFields)
+ }
+ if !slices.Equal(fields, wantFields) {
+ st.t.Errorf("Solve(%v) = %v; want %v", container, fields, wantFields)
+ }
+}
+
+// No constraints and zero-sized container.
+func TestTrivial(t *testing.T) {
+ t.Parallel()
+ st := newSolverTest(t, nil)
+ defer st.Close()
+ fields, err := st.Solver.Solve(image.ZR)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(fields) != 0 {
+ t.Errorf("expected 0 fields; got %d", len(fields))
+ }
+}
+
+// One field that occupies the whole container.
+func TestSingleField(t *testing.T) {
+ // Setup
+ t.Parallel()
+ constraints := make(chan strain.Constraint)
+ st := newSolverTest(t, []<-chan strain.Constraint{constraints})
+ defer st.Close()
+ defer close(constraints)
+
+ // Add constraints
+ container := st.Solver.Container()
+ field := st.Solver.Field(0)
+ st.addConstraint(casso.EQ, field.Origin.X, container.Origin.X)
+ st.addConstraint(casso.EQ, field.Origin.Y, container.Origin.Y)
+ st.addConstraint(casso.EQ, field.Size.X, container.Size.X)
+ st.addConstraint(casso.EQ, field.Size.Y, container.Size.Y)
+
+ // Solve
+ for _, container := range []image.Rectangle{
+ image.ZR,
+ image.Rectangle{image.ZP, image.Pt(800, 600)},
+ image.Rectangle{image.Pt(12, 34), image.Pt(123, 456)},
+ } {
+ // field == container
+ st.solve(container, []image.Rectangle{container})
+ }
+}
+
+// Solver with only layout constaints, no field constraints.
+func TestLayConstrs(t *testing.T) {
+ t.Parallel()
+
+ st := newSolverTest(t, nil)
+ defer st.Close()
+
+ t.Fail() // TODO
+}
diff --git a/lay/strain/sym.go b/lay/strain/sym.go
index cf8777b..9d2a0bf 100644
--- a/lay/strain/sym.go
+++ b/lay/strain/sym.go
@@ -14,7 +14,8 @@ type SymPt struct {
// SymRect is a set of Cassowary symbols representing an
// image.Rectangle.
type SymRect struct {
- Min, Max SymPt
+ Origin SymPt // top-left corner position
+ Size SymPt // Dx() and Dy()
}
func NewSymPt() SymPt {
@@ -28,10 +29,10 @@ func NewSymRect() SymRect {
// editRect marks all symbols of a rectangle as editable with a
// certain precedence.
func editRect(solver *casso.Solver, sr SymRect, p casso.Priority) error {
- if err := editPt(solver, sr.Min, p); err != nil {
+ if err := editPt(solver, sr.Origin, p); err != nil {
return err
}
- if err := editPt(solver, sr.Max, p); err != nil {
+ if err := editPt(solver, sr.Size, p); err != nil {
return err
}
return nil
@@ -50,10 +51,10 @@ func editPt(solver *casso.Solver, sp SymPt, p casso.Priority) error {
}
func suggestRect(solver *casso.Solver, sr SymRect, r image.Rectangle) error {
- if err := suggestPt(solver, sr.Min, r.Min); err != nil {
+ if err := suggestPt(solver, sr.Origin, r.Min); err != nil {
return err
}
- if err := suggestPt(solver, sr.Max, r.Max); err != nil {
+ if err := suggestPt(solver, sr.Size, image.Pt(r.Dx(), r.Dy())); err != nil {
return err
}
return nil