From 5b664845a5896d26453cc74ce54b2768ea869a08 Mon Sep 17 00:00:00 2001 From: Matthew Coleman Date: Thu, 7 Mar 2024 18:47:11 -0500 Subject: [PATCH] Reversed split for percentages --- private/area/area.go | 105 +++++++++++++++++------- private/area/area_test.go | 165 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 30 deletions(-) diff --git a/private/area/area.go b/private/area/area.go index 4ee97f3..4ecea7d 100644 --- a/private/area/area.go +++ b/private/area/area.go @@ -22,6 +22,32 @@ import ( "github.com/mum4k/termdash/private/numbers" ) +// TODO +func vSplit(area image.Rectangle, widthPerc int, reversed bool) (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) + } + + width := area.Dx() * widthPerc / 100 + + if reversed { + left = image.Rect(area.Min.X, area.Min.Y, area.Max.X-width, area.Max.Y) + right = image.Rect(area.Max.X-width, area.Min.Y, area.Max.X, area.Max.Y) + } else { + 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 + } + if right.Dx() == 0 { + right = image.ZR + } + + return left, right, nil +} + // TODO func vSplitCells(area image.Rectangle, cells int, reversed bool) (left image.Rectangle, right image.Rectangle, err error) { if min := 0; cells < min { @@ -51,9 +77,36 @@ func vSplitCells(area image.Rectangle, cells int, reversed bool) (left image.Rec left = image.Rect(area.Min.X, area.Min.Y, splitX, area.Max.Y) right = image.Rect(splitX, area.Min.Y, area.Max.X, area.Max.Y) + return left, right, nil } +// TODO +func hSplit(area image.Rectangle, heightPerc int, reversed bool) (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) + } + + height := area.Dy() * heightPerc / 100 + + if reversed { + top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Max.Y-height) + bottom = image.Rect(area.Min.X, area.Max.Y-height, area.Max.X, area.Max.Y) + } else { + 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 + } + if bottom.Dy() == 0 { + bottom = image.ZR + } + + return top, bottom, nil +} + // TODO func hSplitCells(area image.Rectangle, cells int, reversed bool) (top image.Rectangle, bottom image.Rectangle, err error) { if min := 0; cells < min { @@ -104,43 +157,35 @@ func FromSize(size image.Point) (image.Rectangle, error) { } // HSplit returns two new areas created by splitting the provided area at the -// specified percentage of its height. The percentage must be in the range -// 0 <= heightPerc <= 100. +// specified percentage of its height, applying the percentage to the first +// area. The percentage must be in the range 0 <= heightPerc <= 100. // Can return zero size areas. 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) - } - height := area.Dy() * heightPerc / 100 - top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height) - if top.Dy() == 0 { - top = image.ZR - } - bottom = image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y) - if bottom.Dy() == 0 { - bottom = image.ZR - } - return top, bottom, nil + return hSplit(area, heightPerc, false) +} + +// HSplit returns two new areas created by splitting the provided area at the +// specified percentage of its height, applying the percentage to the second +// area. The percentage must be in the range 0 <= heightPerc <= 100. +// Can return zero size areas. +func HSplitReversed(area image.Rectangle, heightPerc int) (top image.Rectangle, bottom image.Rectangle, err error) { + return hSplit(area, heightPerc, true) } // VSplit returns two new areas created by splitting the provided area at the -// specified percentage of its width. The percentage must be in the range -// 0 <= widthPerc <= 100. +// specified percentage of its width, applying the percentage to the first area. +// The percentage must be in the range 0 <= widthPerc <= 100. // Can return zero size areas. 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) - } - width := area.Dx() * widthPerc / 100 - left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y) - if left.Dx() == 0 { - left = image.ZR - } - right = image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y) - if right.Dx() == 0 { - right = image.ZR - } - return left, right, nil + return vSplit(area, widthPerc, false) +} + +// VSplit returns two new areas created by splitting the provided area at the +// specified percentage of its width, applying the percentage to the second area. +// The percentage must be in the range 0 <= widthPerc <= 100. +// Can return zero size areas. +func VSplitReversed(area image.Rectangle, widthPerc int) (left image.Rectangle, right image.Rectangle, err error) { + return vSplit(area, widthPerc, true) } // VSplitCells returns two new areas created by splitting the provided area diff --git a/private/area/area_test.go b/private/area/area_test.go index e324230..fcf2ae4 100644 --- a/private/area/area_test.go +++ b/private/area/area_test.go @@ -198,6 +198,87 @@ func TestHSplit(t *testing.T) { } } +func TestHSplitReversed(t *testing.T) { + tests := []struct { + desc string + area image.Rectangle + heightPerc int + wantTop image.Rectangle + wantBot image.Rectangle + wantErr bool + }{ + { + desc: "fails on heightPerc too small", + area: image.Rect(1, 1, 2, 2), + heightPerc: -1, + wantErr: true, + }, + { + desc: "fails on heightPerc too large", + area: image.Rect(1, 1, 2, 2), + heightPerc: 101, + wantErr: true, + }, + { + desc: "zero area to begin with", + area: image.ZR, + heightPerc: 50, + wantTop: image.ZR, + wantBot: image.ZR, + }, + { + desc: "splitting results in zero height area on the bottom", + area: image.Rect(1, 1, 2, 2), + heightPerc: 0, + wantTop: image.Rect(1, 1, 2, 2), + wantBot: image.ZR, + }, + { + desc: "splitting results in 100 height area on the top", + area: image.Rect(1, 1, 2, 2), + heightPerc: 100, + wantTop: image.ZR, + wantBot: image.Rect(1, 1, 2, 2), + }, + { + desc: "splits area with even height", + area: image.Rect(1, 1, 3, 3), + heightPerc: 50, + wantTop: image.Rect(1, 1, 3, 2), + wantBot: image.Rect(1, 2, 3, 3), + }, + { + desc: "splits area with odd height", + area: image.Rect(1, 1, 4, 4), + heightPerc: 50, + wantTop: image.Rect(1, 1, 4, 3), + wantBot: image.Rect(1, 3, 4, 4), + }, + { + desc: "splits to unequal areas", + area: image.Rect(0, 0, 4, 4), + heightPerc: 25, + wantTop: image.Rect(0, 0, 4, 3), + wantBot: image.Rect(0, 3, 4, 4), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + gotTop, gotBot, err := HSplitReversed(tc.area, tc.heightPerc) + if (err != nil) != tc.wantErr { + t.Errorf("VSplit => unexpected error:%v, wantErr:%v", err, tc.wantErr) + } + if diff := pretty.Compare(tc.wantTop, gotTop); diff != "" { + t.Errorf("HSplit => first value unexpected diff (-want, +got):\n%s", diff) + } + if diff := pretty.Compare(tc.wantBot, gotBot); diff != "" { + t.Errorf("HSplit => second value unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + func TestVSplit(t *testing.T) { tests := []struct { desc string @@ -282,6 +363,90 @@ func TestVSplit(t *testing.T) { } } +func TestVSplitReversed(t *testing.T) { + tests := []struct { + desc string + area image.Rectangle + widthPerc int + wantLeft image.Rectangle + wantRight image.Rectangle + wantErr bool + }{ + { + desc: "fails on widthPerc too small", + area: image.Rect(1, 1, 2, 2), + widthPerc: -1, + wantErr: true, + }, + { + desc: "fails on widthPerc too large", + area: image.Rect(1, 1, 2, 2), + widthPerc: 101, + wantErr: true, + }, + { + desc: "zero area to begin with", + area: image.ZR, + widthPerc: 50, + wantLeft: image.ZR, + wantRight: image.ZR, + }, + { + desc: "splitting results in zero width area on the right", + area: image.Rect(1, 1, 2, 2), + widthPerc: 0, + wantLeft: image.Rect(1, 1, 2, 2), + wantRight: image.ZR, + }, + { + desc: "splitting results in zero width area on the left", + area: image.Rect(1, 1, 2, 2), + widthPerc: 100, + wantLeft: image.ZR, + wantRight: image.Rect(1, 1, 2, 2), + }, + { + desc: "splits area with even width", + area: image.Rect(1, 1, 3, 3), + widthPerc: 50, + wantLeft: image.Rect(1, 1, 2, 3), + wantRight: image.Rect(2, 1, 3, 3), + }, + { + desc: "splits area with odd width", + area: image.Rect(1, 1, 4, 4), + widthPerc: 50, + wantLeft: image.Rect(1, 1, 3, 4), + wantRight: image.Rect(3, 1, 4, 4), + }, + { + desc: "splits to unequal areas", + area: image.Rect(0, 0, 4, 4), + widthPerc: 25, + wantLeft: image.Rect(0, 0, 3, 4), + wantRight: image.Rect(3, 0, 4, 4), + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + gotLeft, gotRight, err := VSplitReversed(tc.area, tc.widthPerc) + if (err != nil) != tc.wantErr { + t.Errorf("VSplit => unexpected error:%v, wantErr:%v", err, tc.wantErr) + } + if err != nil { + return + } + if diff := pretty.Compare(tc.wantLeft, gotLeft); diff != "" { + t.Errorf("VSplit => left value unexpected diff (-want, +got):\n%s", diff) + } + if diff := pretty.Compare(tc.wantRight, gotRight); diff != "" { + t.Errorf("VSplit => right value unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + func TestVSplitCells(t *testing.T) { tests := []struct { desc string