From 1aba280365cf81571d80827f392359c2010b1d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lovro=20Ma=C5=BEgon?= Date: Thu, 8 Dec 2022 14:49:38 +0100 Subject: [PATCH] add gauge threshold --- widgets/gauge/gauge.go | 31 +++++++-- widgets/gauge/gauge_test.go | 94 ++++++++++++++++++++++++++++ widgets/gauge/gaugedemo/gaugedemo.go | 2 + widgets/gauge/options.go | 18 ++++++ 4 files changed, 140 insertions(+), 5 deletions(-) diff --git a/widgets/gauge/gauge.go b/widgets/gauge/gauge.go index e850b59..f0f1668 100644 --- a/widgets/gauge/gauge.go +++ b/widgets/gauge/gauge.go @@ -136,10 +136,12 @@ func (g *Gauge) Percent(p int, opts ...Option) error { return nil } -// width determines the required width of the gauge drawn on the provided area -// in order to represent the current progress. -func (g *Gauge) width(ar image.Rectangle) int { - mult := float32(g.current) / float32(g.total) +// width determines the X coordinate that represents point w in rectangle ar. +// This is used to calculate the width of the gauge drawn on the provided area +// in order to represent the current progress or to figure out the coordinate +// for the threshold line. +func (g *Gauge) width(ar image.Rectangle, w int) int { + mult := float32(w) / float32(g.total) width := float32(ar.Dx()) * mult return int(width) } @@ -273,7 +275,7 @@ func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { progress := image.Rect( usable.Min.X, usable.Min.Y, - usable.Min.X+g.width(usable), + usable.Min.X+g.width(usable, g.current), usable.Max.Y, ) if progress.Dx() > 0 { @@ -284,6 +286,25 @@ func (g *Gauge) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { return err } } + if g.opts.threshold > 0 && g.opts.threshold < g.total { + line := draw.HVLine{ + Start: image.Point{ + X: usable.Min.X + g.width(usable, g.opts.threshold), + Y: cvs.Area().Min.Y, + }, + End: image.Point{ + X: usable.Min.X + g.width(usable, g.opts.threshold), + Y: cvs.Area().Max.Y - 1, + }, + } + if err := draw.HVLines(cvs, []draw.HVLine{line}, + draw.HVLineStyle(g.opts.thresholdLineStyle), + draw.HVLineCellOpts(g.opts.thresholdCellOpts...), + ); err != nil { + return err + } + } + return g.drawText(cvs, progress) } diff --git a/widgets/gauge/gauge_test.go b/widgets/gauge/gauge_test.go index cb43297..c182be7 100644 --- a/widgets/gauge/gauge_test.go +++ b/widgets/gauge/gauge_test.go @@ -841,6 +841,100 @@ func TestGauge(t *testing.T) { return ft }, }, + { + desc: "threshold with border percentage", + opts: []Option{ + Char('o'), + Threshold(20, linestyle.Double), + }, + percent: &percentCall{p: 35}, + canvas: image.Rect(0, 0, 10, 3), + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3), + draw.RectChar('o'), + draw.RectCellOpts(cell.BgColor(cell.ColorGreen)), + ) + testdraw.MustText(c, "35%", image.Point{3, 1}) + testdraw.MustHVLines(c, []draw.HVLine{{ + Start: image.Point{X: 2, Y: 0}, + End: image.Point{X: 2, Y: 2}, + }}, draw.HVLineStyle(linestyle.Double)) + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "threshold without border absolute", + opts: []Option{ + Char('o'), + Threshold(3, linestyle.Light, cell.BgColor(cell.ColorRed)), + Border(linestyle.None), + HideTextProgress(), + }, + absolute: &absoluteCall{done: 4, total: 10}, + canvas: image.Rect(0, 0, 10, 3), + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3), + draw.RectChar('o'), + draw.RectCellOpts(cell.BgColor(cell.ColorGreen)), + ) + testdraw.MustHVLines(c, []draw.HVLine{{ + Start: image.Point{X: 3, Y: 0}, + End: image.Point{X: 3, Y: 2}, + }}, draw.HVLineStyle(linestyle.Light), + draw.HVLineCellOpts(cell.BgColor(cell.ColorRed))) + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "threshold outside of bounds (negative)", + opts: []Option{ + Char('o'), + HideTextProgress(), + Threshold(-1, linestyle.Light), // ignored + }, + percent: &percentCall{p: 35}, + canvas: image.Rect(0, 0, 10, 3), + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3), + draw.RectChar('o'), + draw.RectCellOpts(cell.BgColor(cell.ColorGreen)), + ) + testcanvas.MustApply(c, ft) + return ft + }, + }, + { + desc: "threshold outside of bounds (>=max)", + opts: []Option{ + Char('o'), + HideTextProgress(), + Threshold(100, linestyle.Light), // ignored + }, + percent: &percentCall{p: 35}, + canvas: image.Rect(0, 0, 10, 3), + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + c := testcanvas.MustNew(ft.Area()) + + testdraw.MustRectangle(c, image.Rect(0, 0, 3, 3), + draw.RectChar('o'), + draw.RectCellOpts(cell.BgColor(cell.ColorGreen)), + ) + testcanvas.MustApply(c, ft) + return ft + }, + }, } for _, tc := range tests { diff --git a/widgets/gauge/gaugedemo/gaugedemo.go b/widgets/gauge/gaugedemo/gaugedemo.go index 96696e9..0fc6628 100644 --- a/widgets/gauge/gaugedemo/gaugedemo.go +++ b/widgets/gauge/gaugedemo/gaugedemo.go @@ -101,6 +101,7 @@ func main() { gauge.Color(cell.ColorNumber(33)), gauge.Border(linestyle.Light), gauge.BorderTitle("Absolute progress"), + gauge.Threshold(43, linestyle.Light, cell.FgColor(cell.ColorRed)), ) if err != nil { panic(err) @@ -124,6 +125,7 @@ func main() { gauge.Color(cell.ColorRed), gauge.FilledTextColor(cell.ColorBlack), gauge.EmptyTextColor(cell.ColorYellow), + gauge.Threshold(20, linestyle.Double), ) if err != nil { panic(err) diff --git a/widgets/gauge/options.go b/widgets/gauge/options.go index 0540abb..309f7dd 100644 --- a/widgets/gauge/options.go +++ b/widgets/gauge/options.go @@ -47,6 +47,10 @@ type options struct { borderCellOpts []cell.Option borderTitle string borderTitleHAlign align.Horizontal + // If set draws a vertical line representing the threshold. + threshold int + thresholdCellOpts []cell.Option + thresholdLineStyle linestyle.LineStyle } // newOptions returns options with the default values set. @@ -201,3 +205,17 @@ func BorderTitleAlign(h align.Horizontal) Option { opts.borderTitleHAlign = h }) } + +// Threshold configures the Gauge to display a vertical threshold line at value +// t. If the progress is set by a call to Percent(), t represents a percentage, +// e.g. "40" means line is displayed at 40%. If the progress is set by a call to +// Absolute(), the threshold is also considered an absolute number. +// Threshold needs to be configured inside the valid values of a gauge to be +// displayed, if it's set to <=0 or >=total it won't have any effect. +func Threshold(t int, ls linestyle.LineStyle, cOpts ...cell.Option) Option { + return option(func(opts *options) { + opts.threshold = t + opts.thresholdLineStyle = ls + opts.thresholdCellOpts = cOpts + }) +}