From 35b05aa476e16a2896d9c1028aa1077463716fef Mon Sep 17 00:00:00 2001 From: Naveen Mahalingam Date: Thu, 14 Apr 2022 10:09:32 -0700 Subject: [PATCH] progress: refactor visibility options (Show*); fixes #196 (#199) --- cmd/demo-progress/demo.go | 49 +++++++++++++++------------ progress/progress.go | 35 ++++++++++++-------- progress/progress_test.go | 28 ++++++++++------ progress/render.go | 32 +++++++++--------- progress/render_test.go | 53 +++++++++++++++++++++++++----- progress/style.go | 69 +++++++++++++++++++++++++++------------ progress/writer.go | 6 ++++ 7 files changed, 185 insertions(+), 87 deletions(-) diff --git a/cmd/demo-progress/demo.go b/cmd/demo-progress/demo.go index 290d00e..bee72ce 100644 --- a/cmd/demo-progress/demo.go +++ b/cmd/demo-progress/demo.go @@ -11,10 +11,16 @@ import ( ) var ( - autoStop = flag.Bool("auto-stop", false, "Auto-stop rendering?") - numTrackers = flag.Int("num-trackers", 13, "Number of Trackers") - randomFail = flag.Bool("rnd-fail", false, "Enable random failures in tracking") - randomLogs = flag.Bool("rnd-logs", false, "Enable random logs in the middle of tracking") + flagAutoStop = flag.Bool("auto-stop", false, "Auto-stop rendering?") + flagHideETA = flag.Bool("hide-eta", false, "Hide the ETA?") + flagHideETAOverall = flag.Bool("hide-eta-overall", false, "Hide the ETA in the overall tracker?") + flagHideOverallTracker = flag.Bool("hide-overall", false, "Hide the Overall Tracker?") + flagHidePercentage = flag.Bool("hide-percentage", false, "Hide the progress percent?") + flagHideTime = flag.Bool("hide-time", false, "Hide the time taken?") + flagHideValue = flag.Bool("hide-value", false, "Hide the tracker value?") + flagNumTrackers = flag.Int("num-trackers", 13, "Number of Trackers") + flagRandomFail = flag.Bool("rnd-fail", false, "Introduce random failures in tracking") + flagRandomLogs = flag.Bool("rnd-logs", false, "Output random logs in the middle of tracking") messageColors = []text.Color{ text.FgRed, @@ -57,12 +63,12 @@ func getUnits(idx int64) *progress.Units { func trackSomething(pw progress.Writer, idx int64, updateMessage bool) { total := idx * idx * idx * 250 - incrementPerCycle := idx * int64(*numTrackers) * 250 + incrementPerCycle := idx * int64(*flagNumTrackers) * 250 units := getUnits(idx) message := getMessage(idx, units) tracker := progress.Tracker{Message: message, Total: total, Units: *units} - if idx == int64(*numTrackers) { + if idx == int64(*flagNumTrackers) { tracker.Total = 0 } @@ -74,9 +80,9 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) { select { case <-ticker: tracker.Increment(incrementPerCycle) - if idx == int64(*numTrackers) && tracker.Value() >= total { + if idx == int64(*flagNumTrackers) && tracker.Value() >= total { tracker.MarkAsDone() - } else if *randomFail && rand.Float64() < 0.1 { + } else if *flagRandomFail && rand.Float64() < 0.1 { tracker.MarkAsErrored() } case <-updateTicker: @@ -93,25 +99,26 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) { func main() { flag.Parse() - fmt.Printf("Tracking Progress of %d trackers ...\n\n", *numTrackers) + fmt.Printf("Tracking Progress of %d trackers ...\n\n", *flagNumTrackers) // instantiate a Progress Writer and set up the options pw := progress.NewWriter() - pw.SetAutoStop(*autoStop) + pw.SetAutoStop(*flagAutoStop) pw.SetTrackerLength(25) - pw.ShowETA(true) - pw.ShowOverallTracker(true) - pw.ShowTime(true) - pw.ShowTracker(true) - pw.ShowValue(true) pw.SetMessageWidth(24) - pw.SetNumTrackersExpected(*numTrackers) + pw.SetNumTrackersExpected(*flagNumTrackers) pw.SetSortBy(progress.SortByPercentDsc) pw.SetStyle(progress.StyleDefault) pw.SetTrackerPosition(progress.PositionRight) pw.SetUpdateFrequency(time.Millisecond * 100) pw.Style().Colors = progress.StyleColorsExample pw.Style().Options.PercentFormat = "%4.1f%%" + pw.Style().Visibility.ETA = !*flagHideETA + pw.Style().Visibility.ETAOverall = !*flagHideETAOverall + pw.Style().Visibility.Percentage = !*flagHidePercentage + pw.Style().Visibility.Time = !*flagHideTime + pw.Style().Visibility.TrackerOverall = !*flagHideOverallTracker + pw.Style().Visibility.Value = !*flagHideValue // call Render() in async mode; yes we don't have any trackers at the moment go pw.Render() @@ -119,14 +126,14 @@ func main() { // add a bunch of trackers with random parameters to demo most of the // features available; do this in async too like a client might do (for ex. // when downloading a bunch of files in parallel) - for idx := int64(1); idx <= int64(*numTrackers); idx++ { - go trackSomething(pw, idx, idx == int64(*numTrackers)) + for idx := int64(1); idx <= int64(*flagNumTrackers); idx++ { + go trackSomething(pw, idx, idx == int64(*flagNumTrackers)) // in auto-stop mode, the Render logic terminates the moment it detects // zero active trackers; but in a manual-stop mode, it keeps waiting and // is a good chance to demo trackers being added dynamically while other // trackers are active or done - if !*autoStop { + if !*flagAutoStop { time.Sleep(time.Millisecond * 100) } } @@ -136,7 +143,7 @@ func main() { time.Sleep(time.Second) messagesLogged := make(map[string]bool) for pw.IsRenderInProgress() { - if *randomLogs && pw.LengthDone()%3 == 0 { + if *flagRandomLogs && pw.LengthDone()%3 == 0 { logMsg := text.Faint.Sprintf("[INFO] done with %d trackers", pw.LengthDone()) if !messagesLogged[logMsg] { pw.Log(logMsg) @@ -145,7 +152,7 @@ func main() { } // for manual-stop mode, stop when there are no more active trackers - if !*autoStop && pw.LengthActive() == 0 { + if !*flagAutoStop && pw.LengthActive() == 0 { pw.Stop() } time.Sleep(time.Millisecond * 100) diff --git a/progress/progress.go b/progress/progress.go index 1b44192..f3a9260 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -7,6 +7,8 @@ import ( "sync" "time" "unicode/utf8" + + "github.com/jedib0t/go-pretty/v6/text" ) var ( @@ -24,11 +26,8 @@ type Progress struct { done chan bool lengthTracker int lengthProgress int + lengthProgressOverall int outputWriter io.Writer - hideTime bool - hideTracker bool - hideValue bool - hidePercentage bool logsToRender []string logsToRenderMutex sync.RWMutex messageWidth int @@ -37,8 +36,6 @@ type Progress struct { overallTrackerMutex sync.RWMutex renderInProgress bool renderInProgressMutex sync.RWMutex - showETA bool - showOverallTracker bool sortBy SortBy style *Style trackerPosition Position @@ -216,33 +213,39 @@ func (p *Progress) SetUpdateFrequency(frequency time.Duration) { } // ShowETA toggles showing the ETA for all individual trackers. +// Deprecated: in favor of Style().Visibility.ETA func (p *Progress) ShowETA(show bool) { - p.showETA = show + p.Style().Visibility.ETA = show } // ShowPercentage toggles showing the Percent complete for each Tracker. +// Deprecated: in favor of Style().Visibility.Percentage func (p *Progress) ShowPercentage(show bool) { - p.hidePercentage = !show + p.Style().Visibility.Percentage = show } // ShowOverallTracker toggles showing the Overall progress tracker with an ETA. +// Deprecated: in favor of Style().Visibility.TrackerOverall func (p *Progress) ShowOverallTracker(show bool) { - p.showOverallTracker = show + p.Style().Visibility.TrackerOverall = show } // ShowTime toggles showing the Time taken by each Tracker. +// Deprecated: in favor of Style().Visibility.Time func (p *Progress) ShowTime(show bool) { - p.hideTime = !show + p.Style().Visibility.Time = show } // ShowTracker toggles showing the Tracker (the progress bar). +// Deprecated: in favor of Style().Visibility.Tracker func (p *Progress) ShowTracker(show bool) { - p.hideTracker = !show + p.Style().Visibility.Tracker = show } // ShowValue toggles showing the actual Value of the Tracker. +// Deprecated: in favor of Style().Visibility.Value func (p *Progress) ShowValue(show bool) { - p.hideValue = !show + p.Style().Visibility.Value = show } // Stop stops the Render() logic that is in progress. @@ -273,11 +276,17 @@ func (p *Progress) initForRender() { p.lengthTracker = DefaultLengthTracker } - // calculate length of the actual progress bar by discount the left/right + // calculate length of the actual progress bar by discounting the left/right // border/box chars p.lengthProgress = p.lengthTracker - utf8.RuneCountInString(p.style.Chars.BoxLeft) - utf8.RuneCountInString(p.style.Chars.BoxRight) + p.lengthProgressOverall = p.messageWidth + + text.RuneCount(p.style.Options.Separator) + + p.lengthProgress + 1 + if p.style.Visibility.Percentage { + p.lengthProgressOverall += text.RuneCount(fmt.Sprintf(p.style.Options.PercentFormat, 0.0)) + } // if not output write has been set, output to STDOUT if p.outputWriter == nil { diff --git a/progress/progress_test.go b/progress/progress_test.go index 3990a0c..e6b9524 100644 --- a/progress/progress_test.go +++ b/progress/progress_test.go @@ -165,44 +165,52 @@ func TestProgress_SetUpdateFrequency(t *testing.T) { assert.Equal(t, time.Duration(time.Second), p.updateFrequency) } +func TestProgress_ShowETA(t *testing.T) { + p := Progress{} + assert.False(t, p.Style().Visibility.ETA) + + p.ShowETA(true) + assert.True(t, p.Style().Visibility.ETA) +} + func TestProgress_ShowOverallTracker(t *testing.T) { p := Progress{} - assert.False(t, p.showOverallTracker) + assert.False(t, p.Style().Visibility.TrackerOverall) p.ShowOverallTracker(true) - assert.True(t, p.showOverallTracker) + assert.True(t, p.Style().Visibility.TrackerOverall) } func TestProgress_ShowPercentage(t *testing.T) { p := Progress{} - assert.False(t, p.hidePercentage) + assert.True(t, p.Style().Visibility.Percentage) p.ShowPercentage(false) - assert.True(t, p.hidePercentage) + assert.False(t, p.Style().Visibility.Percentage) } func TestProgress_ShowTime(t *testing.T) { p := Progress{} - assert.False(t, p.hideTime) + assert.True(t, p.Style().Visibility.Time) p.ShowTime(false) - assert.True(t, p.hideTime) + assert.False(t, p.Style().Visibility.Time) } func TestProgress_ShowTracker(t *testing.T) { p := Progress{} - assert.False(t, p.hideTracker) + assert.True(t, p.Style().Visibility.Tracker) p.ShowTracker(false) - assert.True(t, p.hideTracker) + assert.False(t, p.Style().Visibility.Tracker) } func TestProgress_ShowValue(t *testing.T) { p := Progress{} - assert.False(t, p.hideValue) + assert.True(t, p.Style().Visibility.Value) p.ShowValue(false) - assert.True(t, p.hideValue) + assert.False(t, p.Style().Visibility.Value) } func TestProgress_Stop(t *testing.T) { diff --git a/progress/render.go b/progress/render.go index 78ad5dc..bc1e135 100644 --- a/progress/render.go +++ b/progress/render.go @@ -155,7 +155,7 @@ func (p *Progress) generateTrackerStrIndeterminate(maxLen int) string { func (p *Progress) moveCursorToTheTop(out *strings.Builder) { numLinesToMoveUp := len(p.trackersActive) - if p.showOverallTracker && p.overallTracker != nil && !p.overallTracker.IsDone() { + if p.style.Visibility.TrackerOverall && p.overallTracker != nil && !p.overallTracker.IsDone() { numLinesToMoveUp++ } if numLinesToMoveUp > 0 { @@ -183,18 +183,14 @@ func (p *Progress) renderTracker(out *strings.Builder, t *Tracker, hint renderHi out.WriteString(text.EraseLine.Sprint()) if hint.isOverallTracker { if !t.IsDone() { - trackerLen := p.messageWidth - trackerLen += text.RuneCount(p.style.Options.Separator) - trackerLen += text.RuneCount(p.style.Options.DoneString) - trackerLen += p.lengthProgress + 1 hint := renderHint{hideValue: true, isOverallTracker: true} - p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, trackerLen, hint), hint) + p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, p.lengthProgressOverall, hint), hint) } } else { if t.IsDone() { p.renderTrackerDone(out, t, message) } else { - hint := renderHint{hideTime: p.hideTime, hideValue: p.hideValue} + hint := renderHint{hideTime: !p.style.Visibility.Time, hideValue: !p.style.Visibility.Value} p.renderTrackerProgress(out, t, message, p.generateTrackerStr(t, p.lengthProgress, hint), hint) } } @@ -208,7 +204,7 @@ func (p *Progress) renderTrackerDone(out *strings.Builder, t *Tracker, message s } else { out.WriteString(p.style.Colors.Error.Sprint(p.style.Options.ErrorString)) } - p.renderTrackerStats(out, t, renderHint{hideTime: p.hideTime, hideValue: p.hideValue}) + p.renderTrackerStats(out, t, renderHint{hideTime: !p.style.Visibility.Time, hideValue: !p.style.Visibility.Value}) out.WriteRune('\n') } @@ -221,7 +217,7 @@ func (p *Progress) renderTrackerMessage(out *strings.Builder, t *Tracker, messag } func (p *Progress) renderTrackerPercentage(out *strings.Builder, t *Tracker) { - if !p.hidePercentage { + if p.style.Visibility.Percentage { var percentageStr string if t.IsIndeterminate() { percentageStr = p.style.Options.PercentIndeterminate @@ -241,14 +237,14 @@ func (p *Progress) renderTrackerProgress(out *strings.Builder, t *Tracker, messa p.renderTrackerMessage(out, t, message) out.WriteString(p.style.Colors.Message.Sprint(p.style.Options.Separator)) p.renderTrackerPercentage(out, t) - if !p.hideTracker { + if p.style.Visibility.Tracker { out.WriteString(p.style.Colors.Tracker.Sprint(" " + trackerStr)) } p.renderTrackerStats(out, t, hint) out.WriteRune('\n') } else { p.renderTrackerPercentage(out, t) - if !p.hideTracker { + if p.style.Visibility.Tracker { out.WriteString(p.style.Colors.Tracker.Sprint(" " + trackerStr)) } p.renderTrackerStats(out, t, hint) @@ -276,7 +272,7 @@ func (p *Progress) renderTrackers(lastRenderLength int) int { p.renderTrackersDoneAndActive(&out) // render the overall tracker - if p.showOverallTracker { + if p.style.Visibility.TrackerOverall { p.renderTracker(&out, p.overallTracker, renderHint{isOverallTracker: true}) } @@ -356,12 +352,18 @@ func (p *Progress) renderTrackerStatsTime(outStats *strings.Builder, t *Tracker, tp = p.style.Options.TimeInProgressPrecision } outStats.WriteString(p.style.Colors.Time.Sprint(td.Round(tp))) - if p.showETA || hint.isOverallTracker { - p.renderTrackerStatsETA(outStats, t, hint) - } + + p.renderTrackerStatsETA(outStats, t, hint) } func (p *Progress) renderTrackerStatsETA(out *strings.Builder, t *Tracker, hint renderHint) { + if hint.isOverallTracker && !p.style.Visibility.ETAOverall { + return + } + if !hint.isOverallTracker && !p.style.Visibility.ETA { + return + } + tpETA := p.style.Options.ETAPrecision if eta := t.ETA().Round(tpETA); hint.isOverallTracker || eta > tpETA { out.WriteString("; ") diff --git a/progress/render_test.go b/progress/render_test.go index 1ab1b2a..64a5836 100644 --- a/progress/render_test.go +++ b/progress/render_test.go @@ -32,13 +32,13 @@ func generateWriter() Writer { pw.SetTrackerLength(25) pw.SetTrackerPosition(PositionRight) pw.SetUpdateFrequency(time.Millisecond * 50) - pw.ShowOverallTracker(false) - pw.ShowPercentage(true) - pw.ShowTime(true) - pw.ShowTracker(true) - pw.ShowValue(true) pw.Style().Colors = StyleColors{} pw.Style().Options = StyleOptionsDefault + pw.Style().Visibility.Percentage = true + pw.Style().Visibility.Time = true + pw.Style().Visibility.Tracker = true + pw.Style().Visibility.TrackerOverall = false + pw.Style().Visibility.Value = true return pw } @@ -608,8 +608,8 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker(t *testing.T) { pw := generateWriter() pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) - pw.ShowOverallTracker(true) pw.Style().Options.TimeOverallPrecision = time.Millisecond + pw.Style().Visibility.TrackerOverall = true go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) go func() { pw.Log("some information about something that happened at %s", time.Now().Format(time.RFC3339)) @@ -637,14 +637,51 @@ func TestProgress_RenderSomeTrackers_WithOverallTracker(t *testing.T) { showOutputOnFailure(t, out) } +func TestProgress_RenderSomeTrackers_WithOverallTracker_WithoutETAOverall(t *testing.T) { + renderOutput := outputWriter{} + + pw := generateWriter() + pw.SetOutputWriter(&renderOutput) + pw.SetTrackerPosition(PositionRight) + pw.Style().Options.TimeOverallPrecision = time.Millisecond + pw.Style().Visibility.ETA = true + pw.Style().Visibility.ETAOverall = false + pw.Style().Visibility.TrackerOverall = true + go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) + go func() { + pw.Log("some information about something that happened at %s", time.Now().Format(time.RFC3339)) + }() + go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes}) + go trackSomething(pw, &Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: UnitsCurrencyDollar}) + renderAndWait(pw, false) + + expectedOutPatterns := []*regexp.Regexp{ + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. \d+\.\d+% \[[#.]{23}] \[\$\d+ in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KCalculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KDownloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[KTransferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms]`), + regexp.MustCompile(`\x1b\[K\[[.#]+] \[[\d.ms]+]`), + regexp.MustCompile(`some information about something that happened at \d\d\d\d`), + } + out := renderOutput.String() + for _, expectedOutPattern := range expectedOutPatterns { + if !expectedOutPattern.MatchString(out) { + assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String()) + } + } + showOutputOnFailure(t, out) +} + func TestProgress_RenderSomeTrackers_WithoutOverallTracker_WithETA(t *testing.T) { renderOutput := outputWriter{} pw := generateWriter() pw.SetOutputWriter(&renderOutput) pw.SetTrackerPosition(PositionRight) - pw.ShowETA(true) - pw.ShowOverallTracker(false) + pw.Style().Visibility.ETA = true + pw.Style().Visibility.TrackerOverall = false pw.Style().Options.ETAPrecision = time.Millisecond go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault}) go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes}) diff --git a/progress/style.go b/progress/style.go index 4b424e5..560eddc 100644 --- a/progress/style.go +++ b/progress/style.go @@ -8,43 +8,48 @@ import ( // Style declares how to render the Progress/Trackers. type Style struct { - Name string // name of the Style - Chars StyleChars // characters to use on the progress bar - Colors StyleColors // colors to use on the progress bar - Options StyleOptions // misc. options for the progress bar + Name string // name of the Style + Chars StyleChars // characters to use on the progress bar + Colors StyleColors // colors to use on the progress bar + Options StyleOptions // misc. options for the progress bar + Visibility StyleVisibility // show/hide components of the progress bar(s) } var ( // StyleDefault uses ASCII text to render the Trackers. StyleDefault = Style{ - Name: "StyleDefault", - Chars: StyleCharsDefault, - Colors: StyleColorsDefault, - Options: StyleOptionsDefault, + Name: "StyleDefault", + Chars: StyleCharsDefault, + Colors: StyleColorsDefault, + Options: StyleOptionsDefault, + Visibility: StyleVisibilityDefault, } // StyleBlocks uses UNICODE Block Drawing characters to render the Trackers. StyleBlocks = Style{ - Name: "StyleBlocks", - Chars: StyleCharsBlocks, - Colors: StyleColorsDefault, - Options: StyleOptionsDefault, + Name: "StyleBlocks", + Chars: StyleCharsBlocks, + Colors: StyleColorsDefault, + Options: StyleOptionsDefault, + Visibility: StyleVisibilityDefault, } // StyleCircle uses UNICODE Circle runes to render the Trackers. StyleCircle = Style{ - Name: "StyleCircle", - Chars: StyleCharsCircle, - Colors: StyleColorsDefault, - Options: StyleOptionsDefault, + Name: "StyleCircle", + Chars: StyleCharsCircle, + Colors: StyleColorsDefault, + Options: StyleOptionsDefault, + Visibility: StyleVisibilityDefault, } // StyleRhombus uses UNICODE Rhombus runes to render the Trackers. StyleRhombus = Style{ - Name: "StyleRhombus", - Chars: StyleCharsRhombus, - Colors: StyleColorsDefault, - Options: StyleOptionsDefault, + Name: "StyleRhombus", + Chars: StyleCharsRhombus, + Colors: StyleColorsDefault, + Options: StyleOptionsDefault, + Visibility: StyleVisibilityDefault, } ) @@ -172,3 +177,27 @@ var ( TimeOverallPrecision: time.Second, } ) + +// StyleVisibility controls what gets shown and what gets hidden. +type StyleVisibility struct { + ETA bool // ETA for each tracker + ETAOverall bool // ETA for the overall tracker + Percentage bool // tracker progress percentage value + Time bool // tracker time taken + Tracker bool // tracker ([===========-----------]) + TrackerOverall bool // overall tracker + Value bool // tracker value +} + +var ( + // StyleVisibilityDefault defines sane defaults for the Visibility. + StyleVisibilityDefault = StyleVisibility{ + ETA: false, + ETAOverall: true, + Percentage: true, + Time: true, + Tracker: true, + TrackerOverall: false, + Value: true, + } +) diff --git a/progress/writer.go b/progress/writer.go index 6f85148..cefe50a 100644 --- a/progress/writer.go +++ b/progress/writer.go @@ -24,11 +24,17 @@ type Writer interface { SetStyle(style Style) SetTrackerLength(length int) SetTrackerPosition(position Position) + // Deprecated: in favor of Style().Visibility.ETA ShowETA(show bool) + // Deprecated: in favor of Style().Visibility.TrackerOverall ShowOverallTracker(show bool) + // Deprecated: in favor of Style().Visibility.Percentage ShowPercentage(show bool) + // Deprecated: in favor of Style().Visibility.Time ShowTime(show bool) + // Deprecated: in favor of Style().Visibility.Tracker ShowTracker(show bool) + // Deprecated: in favor of Style().Visibility.Value ShowValue(show bool) SetUpdateFrequency(frequency time.Duration) Stop()