diff --git a/container/container.go b/container/container.go index 0d065c8..89f0d16 100644 --- a/container/container.go +++ b/container/container.go @@ -171,10 +171,17 @@ func (c *Container) split() (image.Rectangle, image.Rectangle, error) { if err != nil { return image.ZR, image.ZR, err } - if c.opts.split == splitTypeVertical { - return area.VSplit(ar, c.opts.splitPercent, c.opts.splitFixed) + if c.opts.splitFixed > -1 { + if c.opts.split == splitTypeVertical { + return area.VSplitCells(ar, c.opts.splitFixed) + } + return area.HSplitCells(ar, c.opts.splitFixed) } - return area.HSplit(ar, c.opts.splitPercent, c.opts.splitFixed) + + if c.opts.split == splitTypeVertical { + return area.VSplit(ar, c.opts.splitPercent) + } + return area.HSplit(ar, c.opts.splitPercent) } // createFirst creates and returns the first sub container of this container. diff --git a/container/container_test.go b/container/container_test.go index 63f704b..b4e094d 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -490,6 +490,26 @@ func TestNew(t *testing.T) { }, wantContainerErr: true, }, + { + desc: "fails when both SplitFixed and SplitPercent are specified", + termSize: image.Point{10, 10}, + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitHorizontal( + Top( + Border(linestyle.Light), + ), + Bottom( + Border(linestyle.Light), + ), + SplitFixed(4), + SplitPercent(20), + ), + ) + }, + wantContainerErr: true, + }, { desc: "empty container", termSize: image.Point{10, 10}, @@ -616,6 +636,32 @@ func TestNew(t *testing.T) { return ft }, }, + { + desc: "horizontal unequal split", + termSize: image.Point{10, 20}, + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitHorizontal( + Top( + Border(linestyle.Light), + ), + Bottom( + Border(linestyle.Light), + ), + SplitFixed(4), + ), + ) + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + cvs := testcanvas.MustNew(ft.Area()) + testdraw.MustBorder(cvs, image.Rect(0, 0, 10, 4)) + testdraw.MustBorder(cvs, image.Rect(0, 4, 10, 20)) + testcanvas.MustApply(cvs, ft) + return ft + }, + }, { desc: "horizontal split, parent and children have borders", termSize: image.Point{10, 10}, @@ -742,6 +788,32 @@ func TestNew(t *testing.T) { return ft }, }, + { + desc: "vertical fixed splits", + termSize: image.Point{20, 10}, + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left( + Border(linestyle.Light), + ), + Right( + Border(linestyle.Light), + ), + SplitFixed(4), + ), + ) + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + cvs := testcanvas.MustNew(ft.Area()) + testdraw.MustBorder(cvs, image.Rect(0, 0, 4, 10)) + testdraw.MustBorder(cvs, image.Rect(4, 0, 20, 10)) + testcanvas.MustApply(cvs, ft) + return ft + }, + }, { desc: "vertical split, parent and children have borders", termSize: image.Point{10, 10}, diff --git a/container/grid/grid_test.go b/container/grid/grid_test.go index 85ca390..01f6b29 100644 --- a/container/grid/grid_test.go +++ b/container/grid/grid_test.go @@ -127,7 +127,7 @@ func mirror() *fakewidget.Mirror { // mustHSplit splits the area or panics. func mustHSplit(ar image.Rectangle, heightPerc int) (top image.Rectangle, bottom image.Rectangle) { - t, b, err := area.HSplit(ar, heightPerc, -1) + t, b, err := area.HSplit(ar, heightPerc) if err != nil { panic(err) } @@ -136,7 +136,7 @@ func mustHSplit(ar image.Rectangle, heightPerc int) (top image.Rectangle, bottom // mustVSplit splits the area or panics. func mustVSplit(ar image.Rectangle, widthPerc int) (left image.Rectangle, right image.Rectangle) { - l, r, err := area.VSplit(ar, widthPerc, -1) + l, r, err := area.VSplit(ar, widthPerc) if err != nil { panic(err) } diff --git a/container/options.go b/container/options.go index 49793ec..46a56b2 100644 --- a/container/options.go +++ b/container/options.go @@ -38,9 +38,8 @@ func applyOptions(c *Container, opts ...Option) error { return nil } -// validateOptions validates options set in the container tree. -func validateOptions(c *Container) error { - // ensure all the container identifiers are either empty or unique. +// ensure all the container identifiers are either empty or unique. +func validateIds(c *Container) error { var errStr string seenID := map[string]bool{} preOrder(c, &errStr, func(c *Container) error { @@ -60,6 +59,43 @@ func validateOptions(c *Container) error { return nil } +// ensure all the container only have one split modifier. +func validateSplits(c *Container) error { + var errStr string + preOrder(c, &errStr, func(c *Container) error { + if c.opts.splitFixed > -1 && c.opts.splitPercent != 50 { + return fmt.Errorf( + "only one of splitFixed `%v` and splitPercent `%v` is allowed to be set per container", + c.opts.splitFixed, + c.opts.splitPercent, + ) + } + + return nil + }) + if errStr != "" { + return errors.New(errStr) + } + return nil +} + +// validateOptions validates options set in the container tree. +func validateOptions(c *Container) error { + // ensure all the container identifiers are either empty or unique. + errIds := validateIds(c) + if errIds != nil { + return errIds + } + + // ensure that max one split modifier is used per container + errSplits := validateSplits(c) + if errSplits != nil { + return errSplits + } + + return nil +} + // Option is used to provide options to a container. type Option interface { // set sets the provided option. @@ -218,8 +254,8 @@ func SplitPercent(p int) SplitOption { }) } -// SplitFixed sets the heights of the top and the bottom containers. -// The first container will be fixed and the second one will fill the rest of the space. +// SplitFixed sets the size as a fixed value of the available space. +// FIXME: More comments // Allows values greater or equal to zero. func SplitFixed(cells int) SplitOption { return splitOption(func(opts *options) error { diff --git a/internal/area/area.go b/internal/area/area.go index 4c6c2bd..3c2bcca 100644 --- a/internal/area/area.go +++ b/internal/area/area.go @@ -45,23 +45,14 @@ func FromSize(size image.Point) (image.Rectangle, error) { // // The fixed value must by in the range -1 <= heightFixed. // If fixed value is -1 (default value), heightPerc is used instead. -func HSplit(area image.Rectangle, heightPerc int, heightFixed int) (top image.Rectangle, bottom image.Rectangle, err error) { +func HSplit(area image.Rectangle, heightPerc int) (top image.Rectangle, bottom image.Rectangle, err error) { if min, max := 0, 100; heightPerc < min || heightPerc > max { return image.ZR, image.ZR, fmt.Errorf("invalid heightPerc %d, must be in range %d <= heightPerc <= %d", heightPerc, min, max) } - if heightFixed < -1 { - return image.ZR, image.ZR, fmt.Errorf("invalid heightFixed %d, must be in range %d <= heightFixed", heightFixed, -1) - } - // Prioritize `SplitFixed()` over `SplitPercent()`. - if heightFixed >= 0 { - top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+heightFixed) - bottom = image.Rect(area.Min.X, area.Min.Y+heightFixed, area.Max.X, area.Max.Y) - } else { - height := area.Dy() * heightPerc / 100 - top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height) - bottom = image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y) - } + height := area.Dy() * heightPerc / 100 + top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height) + bottom = image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y) if top.Dy() == 0 { top = image.ZR } @@ -78,23 +69,14 @@ func HSplit(area image.Rectangle, heightPerc int, heightFixed int) (top image.Re // // The fixed value must by in the range -1 <= heightFixed. // If fixed value is -1 (default value), heightPerc is used instead. -func VSplit(area image.Rectangle, widthPerc int, widthFixed int) (left image.Rectangle, right image.Rectangle, err error) { +func VSplit(area image.Rectangle, widthPerc int) (left image.Rectangle, right image.Rectangle, err error) { if min, max := 0, 100; widthPerc < min || widthPerc > max { return image.ZR, image.ZR, fmt.Errorf("invalid widthPerc %d, must be in range %d <= widthPerc <= %d", widthPerc, min, max) } - if widthFixed < -1 { - return image.ZR, image.ZR, fmt.Errorf("invalid widthFixed %d, must be in range %d <= widthFixed", widthFixed, -1) - } - // Prioritize `SplitFixed()` over `SplitPercent()`. - if widthFixed >= 0 { - left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+widthFixed, area.Max.Y) - right = image.Rect(area.Min.X+widthFixed, area.Min.Y, area.Max.X, area.Max.Y) - } else { - width := area.Dx() * widthPerc / 100 - left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y) - right = image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y) - } + width := area.Dx() * widthPerc / 100 + left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y) + right = image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y) if left.Dx() == 0 { left = image.ZR } diff --git a/internal/area/area_test.go b/internal/area/area_test.go index 54dba59..4acd496 100644 --- a/internal/area/area_test.go +++ b/internal/area/area_test.go @@ -184,7 +184,7 @@ func TestHSplit(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - gotTop, gotBot, err := HSplit(tc.area, tc.heightPerc, -1) + gotTop, gotBot, err := HSplit(tc.area, tc.heightPerc) if (err != nil) != tc.wantErr { t.Errorf("VSplit => unexpected error:%v, wantErr:%v", err, tc.wantErr) } @@ -265,7 +265,7 @@ func TestVSplit(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - gotLeft, gotRight, err := VSplit(tc.area, tc.widthPerc, -1) + gotLeft, gotRight, err := VSplit(tc.area, tc.widthPerc) if (err != nil) != tc.wantErr { t.Errorf("VSplit => unexpected error:%v, wantErr:%v", err, tc.wantErr) } diff --git a/widgets/textinput/textinput.go b/widgets/textinput/textinput.go index d58343c..cc2de60 100644 --- a/widgets/textinput/textinput.go +++ b/widgets/textinput/textinput.go @@ -330,7 +330,7 @@ func split(cvsAr image.Rectangle, label string, widthPerc *int) (labelAr, textAr switch { case widthPerc != nil: splitP := 100 - *widthPerc - labelAr, textAr, err := area.VSplit(cvsAr, splitP, -1) + labelAr, textAr, err := area.VSplit(cvsAr, splitP) if err != nil { return image.ZR, image.ZR, err }