mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-28 13:48:51 +08:00
Support addition of gaps.
This commit is contained in:
parent
858ce17ced
commit
e80e1a1134
@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package segmentdisplay
|
package segmentdisplay
|
||||||
|
|
||||||
import "github.com/mum4k/termdash/align"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mum4k/termdash/align"
|
||||||
|
)
|
||||||
|
|
||||||
// options.go contains configurable options for SegmentDisplay.
|
// options.go contains configurable options for SegmentDisplay.
|
||||||
|
|
||||||
@ -37,13 +41,23 @@ type options struct {
|
|||||||
hAlign align.Horizontal
|
hAlign align.Horizontal
|
||||||
vAlign align.Vertical
|
vAlign align.Vertical
|
||||||
maximizeSegSize bool
|
maximizeSegSize bool
|
||||||
|
gapPercent int
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate validates the provided options.
|
||||||
|
func (o *options) validate() error {
|
||||||
|
if min, max := 0, 100; o.gapPercent < min || o.gapPercent > max {
|
||||||
|
return fmt.Errorf("invalid GapPercent %d, must be %d <= value <= %d", o.gapPercent, min, max)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newOptions returns options with the default values set.
|
// newOptions returns options with the default values set.
|
||||||
func newOptions() *options {
|
func newOptions() *options {
|
||||||
return &options{
|
return &options{
|
||||||
hAlign: align.HorizontalCenter,
|
hAlign: align.HorizontalCenter,
|
||||||
vAlign: align.VerticalMiddle,
|
vAlign: align.VerticalMiddle,
|
||||||
|
gapPercent: DefaultGapPercent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,4 +100,13 @@ func MaximizeDisplayedText() Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Spacing between segments in cells.
|
// DefaultGapPercent is the default value for the GapPercent option.
|
||||||
|
const DefaultGapPercent = 20
|
||||||
|
|
||||||
|
// GapPercent sets the size of the horizontal gap between individual segments
|
||||||
|
// (characters) expressed as a percentage of the segment height.
|
||||||
|
func GapPercent(perc int) Option {
|
||||||
|
return option(func(opts *options) {
|
||||||
|
opts.gapPercent = perc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -58,15 +58,18 @@ type SegmentDisplay struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new SegmentDisplay.
|
// New returns a new SegmentDisplay.
|
||||||
func New(opts ...Option) *SegmentDisplay {
|
func New(opts ...Option) (*SegmentDisplay, error) {
|
||||||
opt := newOptions()
|
opt := newOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o.set(opt)
|
o.set(opt)
|
||||||
}
|
}
|
||||||
|
if err := opt.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &SegmentDisplay{
|
return &SegmentDisplay{
|
||||||
wOptsTracker: attrrange.NewTracker(),
|
wOptsTracker: attrrange.NewTracker(),
|
||||||
opts: opt,
|
opts: opt,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextChunk is a part of or the full text that will be displayed.
|
// TextChunk is a part of or the full text that will be displayed.
|
||||||
@ -93,16 +96,28 @@ func NewChunk(text string, wOpts ...WriteOption) *TextChunk {
|
|||||||
//
|
//
|
||||||
// Each of the text chunks can have its own options. At least one chunk must be
|
// Each of the text chunks can have its own options. At least one chunk must be
|
||||||
// specified.
|
// specified.
|
||||||
func (sd *SegmentDisplay) Write(chunks ...*TextChunk) error {
|
//
|
||||||
|
// Any provided options override options given to New.
|
||||||
|
func (sd *SegmentDisplay) Write(chunks []*TextChunk, opts ...Option) error {
|
||||||
sd.mu.Lock()
|
sd.mu.Lock()
|
||||||
defer sd.mu.Unlock()
|
defer sd.mu.Unlock()
|
||||||
|
|
||||||
sd.reset()
|
for _, o := range opts {
|
||||||
|
o.set(sd.opts)
|
||||||
|
}
|
||||||
|
if err := sd.opts.validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(chunks) == 0 {
|
if len(chunks) == 0 {
|
||||||
return errors.New("at least one text chunk must be specified")
|
return errors.New("at least one text chunk must be specified")
|
||||||
}
|
}
|
||||||
|
sd.reset()
|
||||||
|
|
||||||
for i, tc := range chunks {
|
for i, tc := range chunks {
|
||||||
|
if tc.text == "" {
|
||||||
|
return fmt.Errorf("text chunk[%d] is empty, all chunks must contains some text", i)
|
||||||
|
}
|
||||||
if ok, badRunes := sixteen.SupportsChars(tc.text); !ok && tc.wOpts.errOnUnsupported {
|
if ok, badRunes := sixteen.SupportsChars(tc.text); !ok && tc.wOpts.errOnUnsupported {
|
||||||
return fmt.Errorf("text chunk[%d] contains unsupported characters %v, clean the text or provide the WriteSanitize option", i, badRunes)
|
return fmt.Errorf("text chunk[%d] contains unsupported characters %v, clean the text or provide the WriteSanitize option", i, badRunes)
|
||||||
}
|
}
|
||||||
@ -134,70 +149,117 @@ func (sd *SegmentDisplay) reset() {
|
|||||||
sd.wOptsTracker = attrrange.NewTracker()
|
sd.wOptsTracker = attrrange.NewTracker()
|
||||||
}
|
}
|
||||||
|
|
||||||
// segArea given an area available for drawing returns the area required for a
|
// segArea contains information about the area that will contain the segments.
|
||||||
// single segment and the number of segments we can fit.
|
type segArea struct {
|
||||||
func (sd *SegmentDisplay) segArea(ar image.Rectangle) (image.Rectangle, int, error) {
|
// segment is the area for one segment.
|
||||||
|
segment image.Rectangle
|
||||||
|
// canFit is the number of segments we can fit on the canvas.
|
||||||
|
canFit int
|
||||||
|
// gapPixels is the size of gaps between segments in pixels.
|
||||||
|
gapPixels int
|
||||||
|
// gaps is the number of gaps that will be drawn.
|
||||||
|
gaps int
|
||||||
|
}
|
||||||
|
|
||||||
|
// needArea returns the complete area required for all the segments that we can
|
||||||
|
// fit and any gaps.
|
||||||
|
func (sa *segArea) needArea() image.Rectangle {
|
||||||
|
return image.Rect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
sa.segment.Dx()*sa.canFit+sa.gaps*sa.gapPixels,
|
||||||
|
sa.segment.Dy(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// segArea calculates size and number of segments that can fit onto the
|
||||||
|
// specified area.
|
||||||
|
func (sd *SegmentDisplay) segArea(ar image.Rectangle) (*segArea, error) {
|
||||||
segAr, err := sixteen.Required(ar)
|
segAr, err := sixteen.Required(ar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return image.ZR, 0, fmt.Errorf("sixteen.Required => %v", err)
|
return nil, fmt.Errorf("sixteen.Required => %v", err)
|
||||||
}
|
}
|
||||||
|
gapPixels := segAr.Dy() * sd.opts.gapPercent / 100
|
||||||
|
|
||||||
canFit := ar.Dx() / segAr.Dx()
|
var (
|
||||||
return segAr, canFit, nil
|
gaps int
|
||||||
|
canFit int
|
||||||
|
taken int
|
||||||
|
)
|
||||||
|
for i := 0; i < sd.buff.Len(); i++ {
|
||||||
|
taken += segAr.Dx()
|
||||||
|
|
||||||
|
if taken > ar.Dx() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
canFit++
|
||||||
|
|
||||||
|
// Don't insert gaps after the last segment in the text or the last
|
||||||
|
// segment we can fit.
|
||||||
|
if gapPixels == 0 || i == sd.buff.Len()-1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining := ar.Dx() - taken
|
||||||
|
// Only insert gaps if we can still fit one more segment with the gap.
|
||||||
|
if remaining >= gapPixels+segAr.Dx() {
|
||||||
|
taken += gapPixels
|
||||||
|
gaps++
|
||||||
|
} else {
|
||||||
|
// Gap is needed but doesn't fit together with the next segment.
|
||||||
|
// So insert neither.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &segArea{
|
||||||
|
segment: segAr,
|
||||||
|
canFit: canFit,
|
||||||
|
gapPixels: gapPixels,
|
||||||
|
gaps: gaps,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// maximizeFit finds the largest individual segment size that enables us to fit
|
// maximizeFit finds the largest individual segment size that enables us to fit
|
||||||
// the most characters onto a canvas with the provided area. Returns the area
|
// the most characters onto a canvas with the provided area. Returns the area
|
||||||
// required for a single segment and the number of segments we can fit.
|
// required for a single segment and the number of segments we can fit.
|
||||||
func (sd *SegmentDisplay) maximizeFit(ar image.Rectangle) (image.Rectangle, int, error) {
|
func (sd *SegmentDisplay) maximizeFit(ar image.Rectangle) (*segArea, error) {
|
||||||
bestSegAr := image.ZR
|
var bestSegAr *segArea
|
||||||
bestCanFit := 0
|
|
||||||
need := sd.buff.Len()
|
need := sd.buff.Len()
|
||||||
for height := ar.Dy(); height >= sixteen.MinRows; height-- {
|
for height := ar.Dy(); height >= sixteen.MinRows; height-- {
|
||||||
ar := image.Rect(ar.Min.X, ar.Min.Y, ar.Max.X, ar.Min.Y+height)
|
ar := image.Rect(ar.Min.X, ar.Min.Y, ar.Max.X, ar.Min.Y+height)
|
||||||
segAr, canFit, err := sd.segArea(ar)
|
segAr, err := sd.segArea(ar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return image.ZR, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if canFit >= need {
|
if segAr.canFit >= need {
|
||||||
return segAr, canFit, nil
|
return segAr, nil
|
||||||
}
|
}
|
||||||
bestSegAr = segAr
|
bestSegAr = segAr
|
||||||
bestCanFit = canFit
|
|
||||||
}
|
}
|
||||||
|
return bestSegAr, nil
|
||||||
if bestSegAr.Eq(image.ZR) || bestCanFit == 0 {
|
|
||||||
return image.ZR, 0, fmt.Errorf("failed to maximize character fit for area: %v", ar)
|
|
||||||
}
|
|
||||||
return bestSegAr, bestCanFit, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// preprocess determines the size of individual segments maximizing their
|
// preprocess determines the size of individual segments maximizing their
|
||||||
// height or the amount of displayed characters based on the specified options.
|
// height or the amount of displayed characters based on the specified options.
|
||||||
// Returns the area required for a single segment and the text that we can fit.
|
// Returns the area required for a single segment, the text that we can fit and
|
||||||
func (sd *SegmentDisplay) preprocess(cvsAr image.Rectangle) (image.Rectangle, string, error) {
|
// size of gaps between segments in cells.
|
||||||
segAr, canFit, err := sd.segArea(cvsAr)
|
func (sd *SegmentDisplay) preprocess(cvsAr image.Rectangle) (*segArea, error) {
|
||||||
|
segAr, err := sd.segArea(cvsAr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return image.ZR, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
text := sd.buff.String()
|
need := sd.buff.Len()
|
||||||
need := len(text)
|
if need <= segAr.canFit || sd.opts.maximizeSegSize {
|
||||||
|
return segAr, nil
|
||||||
if need <= canFit {
|
|
||||||
return segAr, text, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sd.opts.maximizeSegSize {
|
bestAr, err := sd.maximizeFit(cvsAr)
|
||||||
return segAr, text[:canFit], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bestAr, bestFit, err := sd.maximizeFit(cvsAr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return image.ZR, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bestAr, text[:bestFit], nil
|
return bestAr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws the SegmentDisplay widget onto the canvas.
|
// Draw draws the SegmentDisplay widget onto the canvas.
|
||||||
@ -210,13 +272,13 @@ func (sd *SegmentDisplay) Draw(cvs *canvas.Canvas) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
segAr, text, err := sd.preprocess(cvs.Area())
|
segAr, err := sd.preprocess(cvs.Area())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
needAr := image.Rect(0, 0, segAr.Dx()*len(text), segAr.Dy())
|
text := sd.buff.String()
|
||||||
aligned, err := align.Rectangle(cvs.Area(), needAr, sd.opts.hAlign, sd.opts.vAlign)
|
aligned, err := align.Rectangle(cvs.Area(), segAr.needArea(), sd.opts.hAlign, sd.opts.vAlign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("align.Rectangle => %v", err)
|
return fmt.Errorf("align.Rectangle => %v", err)
|
||||||
}
|
}
|
||||||
@ -226,16 +288,25 @@ func (sd *SegmentDisplay) Draw(cvs *canvas.Canvas) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gaps := segAr.gaps
|
||||||
|
startX := aligned.Min.X
|
||||||
for i, c := range text {
|
for i, c := range text {
|
||||||
|
if i >= segAr.canFit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
disp := sixteen.New()
|
disp := sixteen.New()
|
||||||
if err := disp.SetCharacter(c); err != nil {
|
if err := disp.SetCharacter(c); err != nil {
|
||||||
return fmt.Errorf("disp.SetCharacter => %v", err)
|
return fmt.Errorf("disp.SetCharacter => %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ar := image.Rect(
|
endX := startX + segAr.segment.Dx()
|
||||||
aligned.Min.X+segAr.Dx()*i, aligned.Min.Y,
|
ar := image.Rect(startX, aligned.Min.Y, endX, aligned.Max.Y)
|
||||||
aligned.Min.X+segAr.Dx()*(i+1), aligned.Max.Y,
|
startX = endX
|
||||||
)
|
if gaps > 0 {
|
||||||
|
startX += segAr.gapPixels
|
||||||
|
gaps--
|
||||||
|
}
|
||||||
|
|
||||||
dCvs, err := canvas.New(ar)
|
dCvs, err := canvas.New(ar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,14 +46,53 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
update func(*SegmentDisplay) error // update gets called before drawing of the widget.
|
update func(*SegmentDisplay) error // update gets called before drawing of the widget.
|
||||||
canvas image.Rectangle
|
canvas image.Rectangle
|
||||||
want func(size image.Point) *faketerm.Terminal
|
want func(size image.Point) *faketerm.Terminal
|
||||||
|
wantNewErr bool
|
||||||
wantUpdateErr bool // whether to expect an error on a call to the update function
|
wantUpdateErr bool // whether to expect an error on a call to the update function
|
||||||
wantDrawErr bool
|
wantDrawErr bool
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
desc: "New fails on invalid GapPercent (too low)",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(-1),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
|
wantNewErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "New fails on invalid GapPercent (too high)",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(101),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
|
wantNewErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "write fails on invalid GapPercent (too low)",
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write(
|
||||||
|
[]*TextChunk{NewChunk("1")},
|
||||||
|
GapPercent(-1),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
wantUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "write fails on invalid GapPercent (too high)",
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write(
|
||||||
|
[]*TextChunk{NewChunk("1")},
|
||||||
|
GapPercent(101),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
wantUpdateErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "fails on area too small for a segment",
|
desc: "fails on area too small for a segment",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols-1, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols-1, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
},
|
},
|
||||||
wantDrawErr: true,
|
wantDrawErr: true,
|
||||||
},
|
},
|
||||||
@ -61,7 +100,15 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "write fails without chunks",
|
desc: "write fails without chunks",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write()
|
return sd.Write(nil)
|
||||||
|
},
|
||||||
|
wantUpdateErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "write fails with an empty chunk",
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("")})
|
||||||
},
|
},
|
||||||
wantUpdateErr: true,
|
wantUpdateErr: true,
|
||||||
},
|
},
|
||||||
@ -69,7 +116,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "write fails on unsupported characters when requested",
|
desc: "write fails on unsupported characters when requested",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk(".", WriteErrOnUnsupported()))
|
return sd.Write([]*TextChunk{NewChunk(".", WriteErrOnUnsupported())})
|
||||||
},
|
},
|
||||||
wantUpdateErr: true,
|
wantUpdateErr: true,
|
||||||
},
|
},
|
||||||
@ -78,10 +125,13 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "draws multiple segments, all fit exactly",
|
desc: "draws multiple segments, all fit exactly",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("123"))
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -103,10 +153,13 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "write sanitizes text by default",
|
desc: "write sanitizes text by default",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk(".1"))
|
return sd.Write([]*TextChunk{NewChunk(".1")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -119,10 +172,13 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "write sanitizes text with option",
|
desc: "write sanitizes text with option",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk(".1", WriteSanitize()))
|
return sd.Write([]*TextChunk{NewChunk(".1", WriteSanitize())})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -138,7 +194,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "aligns segment vertical middle by default",
|
desc: "aligns segment vertical middle by default",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -154,10 +210,10 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "subsequent calls to write overwrite previous text",
|
desc: "subsequent calls to write overwrite previous text",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
if err := sd.Write(NewChunk("123")); err != nil {
|
if err := sd.Write([]*TextChunk{NewChunk("123")}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return sd.Write(NewChunk("4"))
|
return sd.Write([]*TextChunk{NewChunk("4")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -170,19 +226,23 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sets cell options per text chunk",
|
desc: "sets cell options per text chunk",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(
|
return sd.Write(
|
||||||
NewChunk("1", WriteCellOpts(
|
[]*TextChunk{
|
||||||
cell.FgColor(cell.ColorRed),
|
NewChunk("1", WriteCellOpts(
|
||||||
cell.BgColor(cell.ColorBlue),
|
cell.FgColor(cell.ColorRed),
|
||||||
)),
|
cell.BgColor(cell.ColorBlue),
|
||||||
NewChunk("2", WriteCellOpts(
|
)),
|
||||||
cell.FgColor(cell.ColorGreen),
|
NewChunk("2", WriteCellOpts(
|
||||||
cell.BgColor(cell.ColorYellow),
|
cell.FgColor(cell.ColorGreen),
|
||||||
)),
|
cell.BgColor(cell.ColorYellow),
|
||||||
)
|
)),
|
||||||
|
})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -213,7 +273,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "reset resets the text content",
|
desc: "reset resets the text content",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
if err := sd.Write(NewChunk("123")); err != nil {
|
if err := sd.Write([]*TextChunk{NewChunk("123")}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sd.Reset()
|
sd.Reset()
|
||||||
@ -225,15 +285,16 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
if err := sd.Write(
|
if err := sd.Write(
|
||||||
NewChunk("1", WriteCellOpts(
|
[]*TextChunk{
|
||||||
cell.FgColor(cell.ColorRed),
|
NewChunk("1", WriteCellOpts(
|
||||||
cell.BgColor(cell.ColorBlue),
|
cell.FgColor(cell.ColorRed),
|
||||||
)),
|
cell.BgColor(cell.ColorBlue),
|
||||||
); err != nil {
|
)),
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sd.Reset()
|
sd.Reset()
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -252,7 +313,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -271,7 +332,29 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
mustDrawChar(cvs, '1', image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows))
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "options given to Write override those given to New so aligns top",
|
||||||
|
opts: []Option{
|
||||||
|
AlignVertical(align.VerticalBottom),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write(
|
||||||
|
[]*TextChunk{NewChunk("1")},
|
||||||
|
AlignVertical(align.VerticalTop),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -290,7 +373,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
canvas: image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows+2),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1"))
|
return sd.Write([]*TextChunk{NewChunk("1")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -306,7 +389,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "aligns segment horizontal center by default",
|
desc: "aligns segment horizontal center by default",
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("8"))
|
return sd.Write([]*TextChunk{NewChunk("8")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -325,7 +408,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("8"))
|
return sd.Write([]*TextChunk{NewChunk("8")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -344,7 +427,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("8"))
|
return sd.Write([]*TextChunk{NewChunk("8")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -363,7 +446,7 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols+2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("8"))
|
return sd.Write([]*TextChunk{NewChunk("8")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -379,10 +462,11 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "draws multiple segments, not enough space, maximizes segment height with option",
|
desc: "draws multiple segments, not enough space, maximizes segment height with option",
|
||||||
opts: []Option{
|
opts: []Option{
|
||||||
MaximizeSegmentHeight(),
|
MaximizeSegmentHeight(),
|
||||||
|
GapPercent(0),
|
||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
canvas: image.Rect(0, 0, sixteen.MinCols*2, sixteen.MinRows),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("123"))
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -403,10 +487,13 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "draws multiple segments, not enough space, maximizes displayed text by default and fits all",
|
desc: "draws multiple segments, not enough space, maximizes displayed text by default and fits all",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("123"))
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -428,10 +515,13 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "draws multiple segments, not enough space, maximizes displayed text but cannot fit all",
|
desc: "draws multiple segments, not enough space, maximizes displayed text but cannot fit all",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(0),
|
||||||
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("1234"))
|
return sd.Write([]*TextChunk{NewChunk("1234")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -456,10 +546,11 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
desc: "draws multiple segments, not enough space, maximizes displayed text with option",
|
desc: "draws multiple segments, not enough space, maximizes displayed text with option",
|
||||||
opts: []Option{
|
opts: []Option{
|
||||||
MaximizeDisplayedText(),
|
MaximizeDisplayedText(),
|
||||||
|
GapPercent(0),
|
||||||
},
|
},
|
||||||
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
canvas: image.Rect(0, 0, sixteen.MinCols*3, sixteen.MinRows*4),
|
||||||
update: func(sd *SegmentDisplay) error {
|
update: func(sd *SegmentDisplay) error {
|
||||||
return sd.Write(NewChunk("123"))
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
},
|
},
|
||||||
want: func(size image.Point) *faketerm.Terminal {
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
ft := faketerm.MustNew(size)
|
ft := faketerm.MustNew(size)
|
||||||
@ -476,6 +567,170 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
mustDrawChar(cvs, tc.char, tc.area)
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a gap by default",
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*3+2, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'1', image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows)},
|
||||||
|
{'2', image.Rect(sixteen.MinCols+1, 0, sixteen.MinCols*2+1, sixteen.MinRows)},
|
||||||
|
{'3', image.Rect(sixteen.MinCols*2+2, 0, sixteen.MinCols*3+2, sixteen.MinRows)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a gap, exact fit",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(20),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*3+2, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'1', image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows)},
|
||||||
|
{'2', image.Rect(sixteen.MinCols+1, 0, sixteen.MinCols*2+1, sixteen.MinRows)},
|
||||||
|
{'3', image.Rect(sixteen.MinCols*2+2, 0, sixteen.MinCols*3+2, sixteen.MinRows)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a larger gap",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(40),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*3+2, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'1', image.Rect(3, 0, 9, 5)},
|
||||||
|
{'2', image.Rect(11, 0, 17, 5)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a gap, not all fit, maximizes displayed text",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(20),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*3+2, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("8888")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'8', image.Rect(0, 0, sixteen.MinCols, sixteen.MinRows)},
|
||||||
|
{'8', image.Rect(sixteen.MinCols+1, 0, sixteen.MinCols*2+1, sixteen.MinRows)},
|
||||||
|
{'8', image.Rect(sixteen.MinCols*2+2, 0, sixteen.MinCols*3+2, sixteen.MinRows)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a gap, not all fit, last segment would fit without a gap",
|
||||||
|
opts: []Option{
|
||||||
|
GapPercent(20),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*4+2, sixteen.MinRows),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("8888")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'8', image.Rect(3, 0, 9, 5)},
|
||||||
|
{'8', image.Rect(10, 0, 16, 5)},
|
||||||
|
{'8', image.Rect(17, 0, 23, 5)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcanvas.MustApply(cvs, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "draws multiple segments with a gap, not enough space, maximizes segment height with option",
|
||||||
|
opts: []Option{
|
||||||
|
MaximizeSegmentHeight(),
|
||||||
|
GapPercent(20),
|
||||||
|
},
|
||||||
|
canvas: image.Rect(0, 0, sixteen.MinCols*5, sixteen.MinRows*2),
|
||||||
|
update: func(sd *SegmentDisplay) error {
|
||||||
|
return sd.Write([]*TextChunk{NewChunk("123")})
|
||||||
|
},
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
cvs := testcanvas.MustNew(ft.Area())
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
char rune
|
||||||
|
area image.Rectangle
|
||||||
|
}{
|
||||||
|
{'1', image.Rect(2, 0, 14, 10)},
|
||||||
|
{'2', image.Rect(16, 0, 28, 10)},
|
||||||
|
} {
|
||||||
|
mustDrawChar(cvs, tc.char, tc.area)
|
||||||
|
}
|
||||||
|
|
||||||
testcanvas.MustApply(cvs, ft)
|
testcanvas.MustApply(cvs, ft)
|
||||||
return ft
|
return ft
|
||||||
},
|
},
|
||||||
@ -484,7 +739,14 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
sd := New(tc.opts...)
|
sd, err := New(tc.opts...)
|
||||||
|
if (err != nil) != tc.wantNewErr {
|
||||||
|
t.Errorf("New => unexpected error: %v, wantNewErr: %v", err, tc.wantNewErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c, err := canvas.New(tc.canvas)
|
c, err := canvas.New(tc.canvas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("canvas.New => unexpected error: %v", err)
|
t.Fatalf("canvas.New => unexpected error: %v", err)
|
||||||
@ -494,7 +756,6 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
err = tc.update(sd)
|
err = tc.update(sd)
|
||||||
if (err != nil) != tc.wantUpdateErr {
|
if (err != nil) != tc.wantUpdateErr {
|
||||||
t.Errorf("update => unexpected error: %v, wantUpdateErr: %v", err, tc.wantUpdateErr)
|
t.Errorf("update => unexpected error: %v, wantUpdateErr: %v", err, tc.wantUpdateErr)
|
||||||
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -533,21 +794,30 @@ func TestSegmentDisplay(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyboard(t *testing.T) {
|
func TestKeyboard(t *testing.T) {
|
||||||
sd := New()
|
sd, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New => unexpected error: %v", err)
|
||||||
|
}
|
||||||
if err := sd.Keyboard(&terminalapi.Keyboard{}); err == nil {
|
if err := sd.Keyboard(&terminalapi.Keyboard{}); err == nil {
|
||||||
t.Errorf("Keyboard => got nil err, wanted one")
|
t.Errorf("Keyboard => got nil err, wanted one")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMouse(t *testing.T) {
|
func TestMouse(t *testing.T) {
|
||||||
sd := New()
|
sd, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New => unexpected error: %v", err)
|
||||||
|
}
|
||||||
if err := sd.Mouse(&terminalapi.Mouse{}); err == nil {
|
if err := sd.Mouse(&terminalapi.Mouse{}); err == nil {
|
||||||
t.Errorf("Mouse => got nil err, wanted one")
|
t.Errorf("Mouse => got nil err, wanted one")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOptions(t *testing.T) {
|
func TestOptions(t *testing.T) {
|
||||||
sd := New()
|
sd, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New => unexpected error: %v", err)
|
||||||
|
}
|
||||||
got := sd.Options()
|
got := sd.Options()
|
||||||
want := widgetapi.Options{
|
want := widgetapi.Options{
|
||||||
MinimumSize: image.Point{sixteen.MinCols, sixteen.MinRows},
|
MinimumSize: image.Point{sixteen.MinCols, sixteen.MinRows},
|
||||||
|
@ -35,8 +35,8 @@ func clock(ctx context.Context, sd *segmentdisplay.SegmentDisplay) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
now := time.Now().Format("150405")
|
now := segmentdisplay.NewChunk(time.Now().Format("150405"))
|
||||||
if err := sd.Write(segmentdisplay.NewChunk(now)); err != nil {
|
if err := sd.Write([]*segmentdisplay.TextChunk{now}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,10 @@ func main() {
|
|||||||
defer t.Close()
|
defer t.Close()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
sd := segmentdisplay.New()
|
sd, err := segmentdisplay.New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
go clock(ctx, sd)
|
go clock(ctx, sd)
|
||||||
|
|
||||||
c, err := container.New(
|
c, err := container.New(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user