From 1fbf19a35ad015b552ff860a8e7c97f1ee066ab8 Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Thu, 1 Sep 2022 16:46:28 +0100 Subject: [PATCH] Add default spinner speeds Prior to this commit spinners would use the default speed of 250 milliseconds set in the managers constructor method. This commit introduces a new concept of animation properties that allow each spinner to have it's own default speed. The speeds used come from https://wiki.tcl-lang.org/page/Text+Spinner. It is still possible to globally override the speed via the managers WithFrameDuration option. --- examples/spinners/main.go | 24 ++++++++++++ manager.go | 9 +++-- manager_test.go | 2 +- pkg/animations/animations.go | 62 ++++++++++++++++++++++--------- pkg/animations/animations_test.go | 60 ++++++++++++++++++++---------- spinner_test.go | 2 +- 6 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 examples/spinners/main.go diff --git a/examples/spinners/main.go b/examples/spinners/main.go new file mode 100644 index 0000000..7ff4855 --- /dev/null +++ b/examples/spinners/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "time" + + "github.com/chelnak/ysmrr" + "github.com/chelnak/ysmrr/pkg/animations" +) + +func main() { + availableAnimations := animations.GetAnimations() + + for _, animation := range availableAnimations { + manager := ysmrr.NewSpinnerManager( + ysmrr.WithAnimation(animation), + ) + + _ = manager.AddSpinner(fmt.Sprintf("This is spinner %d...", animation)) + manager.Start() + time.Sleep(2 * time.Second) + manager.Stop() + } +} diff --git a/manager.go b/manager.go index 7493f08..79013f0 100644 --- a/manager.go +++ b/manager.go @@ -216,9 +216,10 @@ func (sm *spinnerManager) setNextFrame() { // WithSpinnerColor(colors.Red), // ) func NewSpinnerManager(options ...managerOption) SpinnerManager { + animationSpeed, animationChars := animations.GetAnimation(animations.Dots) sm := &spinnerManager{ - chars: animations.GetAnimation(animations.Dots), - frameDuration: 100 * time.Millisecond, + chars: animationChars, + frameDuration: animationSpeed, spinnerColor: colors.FgHiGreen, errorColor: colors.FgHiRed, completeColor: colors.FgHiGreen, @@ -257,7 +258,9 @@ type managerOption func(*spinnerManager) // The default spinner animation is the Dots. func WithAnimation(a animations.Animation) managerOption { return func(sm *spinnerManager) { - sm.chars = animations.GetAnimation(a) + animationSpeed, animationChars := animations.GetAnimation(a) + sm.chars = animationChars + sm.frameDuration = animationSpeed } } diff --git a/manager_test.go b/manager_test.go index 5baa2aa..b7a54fa 100644 --- a/manager_test.go +++ b/manager_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -var arrows = animations.GetAnimation(animations.Arrow) +var _, arrows = animations.GetAnimation(animations.Arrow) func TestNewSpinnerManager(t *testing.T) { spinnerManager := ysmrr.NewSpinnerManager() diff --git a/pkg/animations/animations.go b/pkg/animations/animations.go index 7958070..6292491 100644 --- a/pkg/animations/animations.go +++ b/pkg/animations/animations.go @@ -4,6 +4,10 @@ // * https://stackoverflow.com/questions/2685435/cooler-ascii-spinners package animations +import ( + "time" +) + type Animation int const ( @@ -23,24 +27,48 @@ const ( SquareCorners ) -var lookup = map[Animation][]string{ - Arc: {"◜", "◠", "◝", "◞", "◡", "◟"}, - Arrow: {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"}, - Baloon: {".", "o", "O", "@", "*"}, - Baloon2: {".", "o", "O", "°", "O", "o", "."}, - Circle: {"◡", "⊙", "◠"}, - CircleHalves: {"◐", "◓", "◑", "◒"}, - CircleQuarters: {"◴", "◷", "◶", "◵"}, - Dots: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, - Hamburger: {"☱", "☲", "☴"}, - Layer: {"-", "=", "≡"}, - Pipe: {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"}, - Point: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}, - Star: {"✶", "✸", "✹", "✺", "✹", "✷"}, - SquareCorners: {"◰", "◳", "◲", "◱"}, +type Properties struct { + Speed time.Duration + Characters []string +} + +func (p Properties) GetSpeed() time.Duration { + return p.Speed * time.Millisecond +} + +func (p Properties) GetCharacters() []string { + return p.Characters +} + +var lookup = map[Animation]Properties{ + Arc: {100, []string{"◜", "◠", "◝", "◞", "◡", "◟"}}, + Arrow: {100, []string{"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"}}, + Baloon: {140, []string{".", "o", "O", "@", "*"}}, + Baloon2: {120, []string{".", "o", "O", "°", "O", "o", "."}}, + Circle: {120, []string{"◡", "⊙", "◠"}}, + CircleHalves: {50, []string{"◐", "◓", "◑", "◒"}}, + CircleQuarters: {120, []string{"◴", "◷", "◶", "◵"}}, + Dots: {80, []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}}, + Hamburger: {100, []string{"☱", "☲", "☴"}}, + Layer: {150, []string{"-", "=", "≡"}}, + Pipe: {100, []string{"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"}}, + Point: {125, []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"}}, + Star: {70, []string{"✶", "✸", "✹", "✺", "✹", "✷"}}, + SquareCorners: {180, []string{"◰", "◳", "◲", "◱"}}, } // GetAnimation retirms a slice of strings for the given type. -func GetAnimation(a Animation) []string { - return lookup[a] +func GetAnimation(a Animation) (time.Duration, []string) { + return lookup[a].GetSpeed(), lookup[a].GetCharacters() +} + +// GetAnimations returns an unsorted slice of all available animations ids. +func GetAnimations() []Animation { + keys := make([]Animation, len(lookup)) + i := 0 + for k := range lookup { + keys[i] = k + i++ + } + return keys } diff --git a/pkg/animations/animations_test.go b/pkg/animations/animations_test.go index 0e78288..ad477d3 100644 --- a/pkg/animations/animations_test.go +++ b/pkg/animations/animations_test.go @@ -2,6 +2,7 @@ package animations_test import ( "testing" + "time" "github.com/chelnak/ysmrr/pkg/animations" "github.com/stretchr/testify/assert" @@ -26,30 +27,51 @@ var ( func TestAnimations(t *testing.T) { tests := []struct { - name string - s animations.Animation - want []string + name string + s animations.Animation + wantChars []string + wantSpeed time.Duration }{ - {name: "Arc", s: animations.Arc, want: Arc}, - {name: "Arrow", s: animations.Arrow, want: Arrow}, - {name: "Baloon", s: animations.Baloon, want: Baloon}, - {name: "Baloon2", s: animations.Baloon2, want: Baloon2}, - {name: "Circle", s: animations.Circle, want: Circle}, - {name: "CircleHalves", s: animations.CircleHalves, want: CircleHalves}, - {name: "CircleQuarters", s: animations.CircleQuarters, want: CircleQuarters}, - {name: "Dots", s: animations.Dots, want: Dots}, - {name: "Hamburger", s: animations.Hamburger, want: Hamburger}, - {name: "Layer", s: animations.Layer, want: Layer}, - {name: "Pipe", s: animations.Pipe, want: Pipe}, - {name: "Point", s: animations.Point, want: Point}, - {name: "Star", s: animations.Star, want: Star}, - {name: "SquareCorners", s: animations.SquareCorners, want: SquareCorners}, + {name: "Arc", s: animations.Arc, wantChars: Arc, wantSpeed: 100}, + {name: "Arrow", s: animations.Arrow, wantChars: Arrow, wantSpeed: 100}, + {name: "Baloon", s: animations.Baloon, wantChars: Baloon, wantSpeed: 140}, + {name: "Baloon2", s: animations.Baloon2, wantChars: Baloon2, wantSpeed: 120}, + {name: "Circle", s: animations.Circle, wantChars: Circle, wantSpeed: 120}, + {name: "CircleHalves", s: animations.CircleHalves, wantChars: CircleHalves, wantSpeed: 50}, + {name: "CircleQuarters", s: animations.CircleQuarters, wantChars: CircleQuarters, wantSpeed: 120}, + {name: "Dots", s: animations.Dots, wantChars: Dots, wantSpeed: 80}, + {name: "Hamburger", s: animations.Hamburger, wantChars: Hamburger, wantSpeed: 100}, + {name: "Layer", s: animations.Layer, wantChars: Layer, wantSpeed: 150}, + {name: "Pipe", s: animations.Pipe, wantChars: Pipe, wantSpeed: 100}, + {name: "Point", s: animations.Point, wantChars: Point, wantSpeed: 125}, + {name: "Star", s: animations.Star, wantChars: Star, wantSpeed: 70}, + {name: "SquareCorners", s: animations.SquareCorners, wantChars: SquareCorners, wantSpeed: 180}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := animations.GetAnimation(tt.s) - assert.Equal(t, tt.want, got) + gotSpeed, gotChars := animations.GetAnimation(tt.s) + assert.Equal(t, tt.wantChars, gotChars) + assert.Equal(t, tt.wantSpeed*time.Millisecond, gotSpeed) }) } } + +var properties = animations.Properties{ + Speed: 100, + Characters: []string{"a", "b", "c"}, +} + +func TestProperties_GetSpeed(t *testing.T) { + assert.Equal(t, 100*time.Millisecond, properties.GetSpeed()) +} + +func TestProperties_GetCharacters(t *testing.T) { + assert.Equal(t, []string{"a", "b", "c"}, properties.GetCharacters()) +} + +func TestGetAnimations(t *testing.T) { + got := animations.GetAnimations() + assert.IsType(t, []animations.Animation{}, got) + assert.Equal(t, 14, len(got)) +} diff --git a/spinner_test.go b/spinner_test.go index 873c6a7..d293b79 100644 --- a/spinner_test.go +++ b/spinner_test.go @@ -80,7 +80,7 @@ func TestPrint(t *testing.T) { spinner := ysmrr.NewSpinner(opts) var buf bytes.Buffer - dots := animations.GetAnimation(animations.Dots) + _, dots := animations.GetAnimation(animations.Dots) spinner.Print(&buf, dots[0]) want := fmt.Sprintf("%s %s\r\n", dots[0], initialMessage)