diff --git a/layout/flex.go b/layout/flex.go index 97cbc8ca..bb68df74 100644 --- a/layout/flex.go +++ b/layout/flex.go @@ -22,6 +22,8 @@ type Flex struct { // size of Flexed children. If WeightSum is zero, the sum // of all Flexed weights is used. WeightSum float32 + // Gap is the space in pixels between children. + Gap int } // FlexChild is the descriptor for a Flex child. @@ -82,6 +84,14 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions { mainMin, mainMax := f.Axis.mainConstraint(cs) crossMin, crossMax := f.Axis.crossConstraint(cs) remaining := mainMax + // Reserve space for gaps between children. + if len(children) > 1 && f.Gap > 0 { + totalGap := f.Gap * (len(children) - 1) + remaining -= totalGap + if remaining < 0 { + remaining = 0 + } + } var totalWeight float32 cgtx := gtx // Note: previously the scratch space was inside FlexChild. @@ -162,6 +172,9 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions { maxBaseline = b } } + if len(children) > 1 && f.Gap > 0 { + size += f.Gap * (len(children) - 1) + } var space int if mainMin > size { space = mainMin - size @@ -199,6 +212,7 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions { trans.Pop() mainSize += f.Axis.Convert(dims.Size).X if i < len(children)-1 { + mainSize += f.Gap switch f.Spacing { case SpaceEvenly: mainSize += space / (1 + len(children)) diff --git a/layout/layout_test.go b/layout/layout_test.go index 54f2e826..90253b0e 100644 --- a/layout/layout_test.go +++ b/layout/layout_test.go @@ -44,6 +44,106 @@ func TestFlex(t *testing.T) { } } +func TestFlexGap(t *testing.T) { + gtx := Context{ + Ops: new(op.Ops), + Constraints: Constraints{ + Max: image.Pt(100, 100), + }, + } + + // Two 20px children with 10px gap = 50px total. + dims := Flex{Gap: 10}.Layout(gtx, + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(20, 10)} + }), + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(20, 10)} + }), + ) + if got, exp := dims.Size.X, 50; got != exp { + t.Errorf("two rigid children with gap: got width %d, expected %d", got, exp) + } + + // Three children: gap added between each pair. + dims = Flex{Gap: 5}.Layout(gtx, + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(10, 10)} + }), + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(10, 10)} + }), + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(10, 10)} + }), + ) + if got, exp := dims.Size.X, 40; got != exp { + t.Errorf("three rigid children with gap: got width %d, expected %d", got, exp) + } + + // Single child: no gap added. + dims = Flex{Gap: 10}.Layout(gtx, + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(20, 10)} + }), + ) + if got, exp := dims.Size.X, 20; got != exp { + t.Errorf("single child with gap: got width %d, expected %d", got, exp) + } + + // Gap with flexed children: gap is reserved from available space. + dims = Flex{Gap: 10}.Layout(gtx, + Flexed(1, func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)} + }), + Flexed(1, func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)} + }), + ) + // 100px max - 10px gap = 90px for flex; 45px each. + if got, exp := dims.Size.X, 100; got != exp { + t.Errorf("flexed children with gap: got width %d, expected %d", got, exp) + } + + // Vertical axis with gap. + dims = Flex{Axis: Vertical, Gap: 15}.Layout(gtx, + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(10, 20)} + }), + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(10, 20)} + }), + ) + if got, exp := dims.Size.Y, 55; got != exp { + t.Errorf("vertical with gap: got height %d, expected %d", got, exp) + } +} + +func TestFlexGapConstraints(t *testing.T) { + gtx := Context{ + Ops: new(op.Ops), + Constraints: Constraints{ + Max: image.Pt(100, 100), + }, + } + + // Verify that flexed children receive constraints with gap accounted for. + var flexMax int + Flex{Gap: 10}.Layout(gtx, + Rigid(func(gtx Context) Dimensions { + return Dimensions{Size: image.Pt(30, 10)} + }), + Flexed(1, func(gtx Context) Dimensions { + flexMax = gtx.Constraints.Max.X + return Dimensions{Size: image.Pt(gtx.Constraints.Max.X, 10)} + }), + ) + // 100 - 10 (gap) - 30 (rigid) = 60 remaining for flex. + if got, exp := flexMax, 60; got != exp { + t.Errorf("flex constraint with gap: got %d, expected %d", got, exp) + } +} + func TestDirection(t *testing.T) { max := image.Pt(100, 100) for _, tc := range []struct {