diff --git a/layout/layout.go b/layout/layout.go index 0b91a21b..3e9da75f 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -102,6 +102,36 @@ func (c Constraints) Constrain(size image.Point) image.Point { return size } +// AddMin returns a copy of Constraints with the Min constraint enlarged by up to delta +// while still fitting within the Max constraint. The Max is unchanged, and the Min constraint +// will not go negative. +func (c Constraints) AddMin(delta image.Point) Constraints { + c.Min = c.Min.Add(delta) + if c.Min.X < 0 { + c.Min.X = 0 + } + if c.Min.Y < 0 { + c.Min.Y = 0 + } + c.Min = c.Constrain(c.Min) + return c +} + +// SubMax returns a copy of Constraints with the Max constraint shrunk by up to delta +// while not going negative. The values of delta are expected to be positive. +// The Min constraint is adjusted to fit within the new Max constraint. +func (c Constraints) SubMax(delta image.Point) Constraints { + c.Max = c.Max.Sub(delta) + if c.Max.X < 0 { + c.Max.X = 0 + } + if c.Max.Y < 0 { + c.Max.Y = 0 + } + c.Min = c.Constrain(c.Min) + return c +} + // Inset adds space around a widget by decreasing its maximum // constraints. The minimum constraints will be adjusted to ensure // they do not exceed the maximum. diff --git a/layout/layout_test.go b/layout/layout_test.go index c6434b2c..54f2e826 100644 --- a/layout/layout_test.go +++ b/layout/layout_test.go @@ -76,3 +76,69 @@ func TestDirection(t *testing.T) { }) } } + +func TestConstraints(t *testing.T) { + type testcase struct { + name string + in Constraints + subMax image.Point + addMin image.Point + expected Constraints + } + for _, tc := range []testcase{ + { + name: "no-op", + in: Constraints{Max: image.Pt(100, 100)}, + expected: Constraints{Max: image.Pt(100, 100)}, + }, + { + name: "shrink max", + in: Constraints{Max: image.Pt(100, 100)}, + subMax: image.Pt(25, 25), + expected: Constraints{Max: image.Pt(75, 75)}, + }, + { + name: "shrink max below min", + in: Constraints{Max: image.Pt(100, 100), Min: image.Pt(50, 50)}, + subMax: image.Pt(75, 75), + expected: Constraints{Max: image.Pt(25, 25), Min: image.Pt(25, 25)}, + }, + { + name: "shrink max below zero", + in: Constraints{Max: image.Pt(100, 100), Min: image.Pt(50, 50)}, + subMax: image.Pt(125, 125), + expected: Constraints{Max: image.Pt(0, 0), Min: image.Pt(0, 0)}, + }, + { + name: "enlarge min", + in: Constraints{Max: image.Pt(100, 100)}, + addMin: image.Pt(25, 25), + expected: Constraints{Max: image.Pt(100, 100), Min: image.Pt(25, 25)}, + }, + { + name: "enlarge min beyond max", + in: Constraints{Max: image.Pt(100, 100)}, + addMin: image.Pt(125, 125), + expected: Constraints{Max: image.Pt(100, 100), Min: image.Pt(100, 100)}, + }, + { + name: "decrease min below zero", + in: Constraints{Max: image.Pt(100, 100), Min: image.Pt(50, 50)}, + addMin: image.Pt(-125, -125), + expected: Constraints{Max: image.Pt(100, 100), Min: image.Pt(0, 0)}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + start := tc.in + if tc.subMax != (image.Point{}) { + start = start.SubMax(tc.subMax) + } + if tc.addMin != (image.Point{}) { + start = start.AddMin(tc.addMin) + } + if start != tc.expected { + t.Errorf("expected %#+v, got %#+v", tc.expected, start) + } + }) + } +}