diff --git a/label.go b/label.go index 79ca9ff..a2599f2 100644 --- a/label.go +++ b/label.go @@ -29,6 +29,14 @@ func NewLabel(text string) *Label { } } +// Resize changes the size of the Widget. +func (l *Label) Resize(size image.Point) { + if l.Size() != size { + l.cacheSizeHint = nil + } + l.WidgetBase.Resize(size) +} + // Draw draws the label. func (l *Label) Draw(p *Painter) { lines := strings.Split(l.text, "\n") diff --git a/label_sizehint_test.go b/label_sizehint_test.go new file mode 100644 index 0000000..bc8202e --- /dev/null +++ b/label_sizehint_test.go @@ -0,0 +1,151 @@ +package tui + +import ( + "image" + "testing" +) + +var TailBoxTests = []struct { + Test string + Setup func() Widget + Want string +} { + { + Test: "draw small labels", + Setup: func() Widget { + return NewTailBox( + NewLabel("hello mom"), + NewLabel("hello dad"), + ) + }, + Want: ` + + + +hello mom +hello dad +`, + }, + { + Test: "draw unwrapped labels", + Setup: func() Widget { + l1, l2 := NewLabel("hello muddah"), NewLabel("hello faddah") + return NewTailBox(l1, l2) + }, + Want: ` + + + +hello mudd +hello fadd +`, + }, + { + Test: "draw wrapped labels", + Setup: func() Widget { + l1, l2 := NewLabel("hello muddah"), NewLabel("hello faddah") + l1.SetWordWrap(true) + l2.SetWordWrap(true) + return NewTailBox(l1, l2) + }, + Want: ` + +hello +muddah +hello +faddah +`, + }, + + +} + +func TestTailBox(t *testing.T) { + for _, tt := range TailBoxTests { + tt := tt + t.Run(tt.Test, func(t *testing.T) { + surface := NewTestSurface(10, 5) + p := NewPainter(surface, NewTheme()) + p.Repaint(tt.Setup()) + + if surface.String() != tt.Want { + t.Errorf("unexpected contents: got = \n%s\nwant = \n%s", surface.String(), tt.Want) + } + }) + } +} + + +// TailBox is a container Widget that may not show all its +// While Box attempts to show every contained Widget - sometimes shrinking +// those Widgets to do so- TailBox prioritizes completely displaying its last +// Widget, then the next-to-last widget, etc. +// It is vertically-aligned, i.e. all the contained Widgets have the same width. +type TailBox struct { + WidgetBase + sz image.Point + contents []Widget +} + +var _ Widget = &TailBox{} + +func NewTailBox(w ...Widget) *TailBox { + return &TailBox{ + contents: w, + } +} + +func (t *TailBox) Append(w Widget) { + t.contents = append(t.contents, w) + t.doLayout(t.Size()) +} + +func (t *TailBox) SetContents(w ...Widget) { + t.contents = w + t.doLayout(t.Size()) +} + +func (t *TailBox) Draw(p *Painter) { + p.WithMask(image.Rect(0, 0, t.sz.X, t.sz.Y), func(p *Painter) { + // Draw background + p.FillRect(0, 0, t.sz.X, t.sz.Y) + + // Draw from the bottom up. + space := t.sz.Y + p.Translate(0, space) + defer p.Restore() + for i := len(t.contents) - 1; i >= 0 && space > 0; i-- { + w := t.contents[i] + space -= w.Size().Y + p.Translate(0, -w.Size().Y) + defer p.Restore() + w.Draw(p) + } + }) +} + +// Resize recalculates the layout of the box's contents. +func (t *TailBox) Resize(size image.Point) { + t.WidgetBase.Resize(size) + defer func() { + t.sz = size + }() + + // If it's just a height change, Draw should do the right thing already. + if size.X != t.sz.X { + t.doLayout(size) + } +} + +func (t *TailBox) doLayout(size image.Point) { + for _, w := range t.contents { + hint := w.SizeHint() + // Set the width to the container width, and height to the requested height + w.Resize(image.Pt(size.X, hint.Y)) + // ...and then resize again, now that the Y-hint has been refreshed by the X-value. + hint = w.SizeHint() + w.Resize(image.Pt(size.X, hint.Y)) + } +} + +