Refactor `SplitFixed()` to use SplitCells

* Add basic tests to `SplitFixed()`
* Panic when both `SplitFixed()` and `SplitPercent()` are used
* Refactor `validateOptions()` into two smaller functions
This commit is contained in:
nijynot 2019-05-08 21:48:57 +02:00
parent 4aa60fe8e7
commit 70a5255d5e
7 changed files with 136 additions and 39 deletions

View File

@ -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.

View File

@ -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},

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}