[Keith Vetter] 2019-10-01 : A spinner, also known as a https://en.wikipedia.org/wiki/Throbber%|%throbber%|%, is an animated graphic element used to show that a computer program is performing an action in the background. It's related to a progress bar except it does not convey how much of the action has been completed. A text spinner is a spinner which just uses characters in its animation. Historically text spinners rotated through the sequence "\" "\" "|" "/", but as this package demonstrates, much fancier ones have been constructed. This package provides an interface to over 50 different text spinners. It lets you add a spinner to widget or a textvar, and it will automatically update the item at an interval set by the spinner type. As usual, I've provided a short demo which displays ten different spinners all animating simultaneously. ====== ##+########################################################################## # # spinners -- Package that provides 50+ different types of text Spinners. # by Keith Vetter 2019-10-01 # # Usage: # pack [label .l -textvar my_var] # [optional] lassign [::Spinner::Info :random:] spinnerName interval frames maxWidth # set id [::Spinner::Start spinnerName my_var] # ::Spinner::Stop $id # # Can also work without Tk--it will periodically update the variable you give it # namespace eval Spinner { # Inspired by https://www.npmjs.com/package/cli-spinners # and https://stackoverflow.com/questions/2685435/cooler-ascii-spinners variable NextID 0 variable ACTIVE variable SPINNERS array set SPINNERS { dots { 80 { ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ }} dots2 { 80 { ⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷ }} dots3 { 80 { ⠋ ⠙ ⠚ ⠞ ⠖ ⠦ ⠴ ⠲ ⠳ ⠓ }} dots4 { 80 { ⠄ ⠆ ⠇ ⠋ ⠙ ⠸ ⠰ ⠠ ⠰ ⠸ ⠙ ⠋ ⠇ ⠆ }} dots5 { 80 { ⠋ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ }} dots6 { 80 { ⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠴ ⠲ ⠒ ⠂ ⠂ ⠒ ⠚ ⠙ ⠉ ⠁ }} dots7 { 80 { ⠈ ⠉ ⠋ ⠓ ⠒ ⠐ ⠐ ⠒ ⠖ ⠦ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈ }} dots8 { 80 { ⠁ ⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈ ⠈ }} dots9 { 80 { ⢹ ⢺ ⢼ ⣸ ⣇ ⡧ ⡗ ⡏ }} dots10 { 80 { ⢄ ⢂ ⢁ ⡁ ⡈ ⡐ ⡠ }} dots11 { 100 { ⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈ }} dots12 { 80 { ⢀⠀ ⡀⠀ ⠄⠀ ⢂⠀ ⡂⠀ ⠅⠀ ⢃⠀ ⡃⠀ ⠍⠀ ⢋⠀ ⡋⠀ ⠍⠁ ⢋⠁ ⡋⠁ ⠍⠉ ⠋⠉ ⠋⠉ ⠉⠙ ⠉⠙ ⠉⠩ ⠈⢙ ⠈⡙ ⢈⠩ ⡀⢙ ⠄⡙ ⢂⠩ ⡂⢘ ⠅⡘ ⢃⠨ ⡃⢐ ⠍⡐ ⢋⠠ ⡋⢀ ⠍⡁ ⢋⠁ ⡋⠁ ⠍⠉ ⠋⠉ ⠋⠉ ⠉⠙ ⠉⠙ ⠉⠩ ⠈⢙ ⠈⡙ ⠈⠩ ⠀⢙ ⠀⡙ ⠀⠩ ⠀⢘ ⠀⡘ ⠀⠨ ⠀⢐ ⠀⡐ ⠀⠠ ⠀⢀ ⠀⡀ }} line { 130 { - \\ | / }} line2 { 100 { ⠂ - – — – - }} pipe { 100 { ┤ ┘ ┴ └ ├ ┌ ┬ ┐ }} simpleDots { 400 { {. } {.. } ... { } }} simpleDotsScrolling { 200 { {. } {.. } ... { ..} { .} { } }} star { 70 { ✶ ✸ ✹ ✺ ✹ ✷ }} star2 { 80 { + x * }} flip { 70 { _ _ _ - ` ` ' ´ - _ _ _ }} hamburger { 100 { ☱ ☲ ☴ }} growVertical { 120 { ▁ ▃ ▄ ▅ ▆ ▇ ▆ ▅ ▄ ▃ }} growHorizontal { 120 { ▏ ▎ ▍ ▌ ▋ ▊ ▉ ▊ ▋ ▌ ▍ ▎ }} balloon { 140 { { } . o O @ * { } }} balloon2 { 120 { . o O ° O o . }} noise { 100 { ▓ ▒ ░ }} bounce { 120 { ⠁ ⠂ ⠄ ⠂ }} boxBounce { 120 { ▖ ▘ ▝ ▗ }} boxBounce2 { 100 { ▌ ▀ ▐ ▄ }} triangle { 50 { ◢ ◣ ◤ ◥ }} arc { 100 { ◜ ◠ ◝ ◞ ◡ ◟ }} circle { 120 { ◡ ⊙ ◠ }} squareCorners { 180 { ◰ ◳ ◲ ◱ }} circleQuarters { 120 { ◴ ◷ ◶ ◵ }} circleHalves { 50 { ◐ ◓ ◑ ◒ }} squish { 100 { ╫ ╪ }} toggle { 250 { ⊶ ⊷ }} toggle2 { 80 { ▫ ▪ }} toggle3 { 120 { □ ■ }} toggle4 { 100 { ■ □ ▪ ▫ }} toggle5 { 100 { ▮ ▯ }} toggle6 { 300 { ဝ ၀ }} toggle7 { 80 { ⦾ ⦿ }} toggle8 { 100 { ◍ ◌ }} toggle9 { 100 { ◉ ◎ }} toggle10 { 100 { ㊂ ㊀ ㊁ }} toggle11 { 50 { ⧇ ⧆ }} toggle12 { 120 { ☗ ☖ }} toggle13 { 80 { = * - }} arrow { 100 { ← ↖ ↑ ↗ → ↘ ↓ ↙ }} arrow2 { 80 { {⬆️ } {↗️ } {➡️ } {↘️ } {⬇️ } {↙️ } {⬅️ } {↖️ } }} arrow3 { 120 { ▹▹▹▹▹ ▸▹▹▹▹ ▹▸▹▹▹ ▹▹▸▹▹ ▹▹▹▸▹ ▹▹▹▹▸ }} bouncingBar { 80 { {[ ]} {[= ]} {[== ]} {[=== ]} {[ ===]} {[ ==]} {[ =]} {[ ]} {[ =]} {[ ==]} {[ ===]} {[====]} {[=== ]} {[== ]} {[= ]} }} bouncingBall { 80 { {( ● )} {( ● )} {( ● )} {( ● )} {( ●)} {( ● )} {( ● )} {( ● )} {( ● )} {(● )} }} pong { 80 { {▐⠂ ▌} {▐⠈ ▌} {▐ ⠂ ▌} {▐ ⠠ ▌} {▐ ⡀ ▌} {▐ ⠠ ▌} {▐ ⠂ ▌} {▐ ⠈ ▌} {▐ ⠂ ▌} {▐ ⠠ ▌} {▐ ⡀ ▌} {▐ ⠠ ▌} {▐ ⠂ ▌} {▐ ⠈ ▌} {▐ ⠂▌} {▐ ⠠▌} {▐ ⡀▌} {▐ ⠠ ▌} {▐ ⠂ ▌} {▐ ⠈ ▌} {▐ ⠂ ▌} {▐ ⠠ ▌} {▐ ⡀ ▌} {▐ ⠠ ▌} {▐ ⠂ ▌} {▐ ⠈ ▌} {▐ ⠂ ▌} {▐ ⠠ ▌} {▐ ⡀ ▌} {▐⠠ ▌} }} shark { 120 { {▐|\____________▌} {▐_|\___________▌} {▐__|\__________▌} {▐___|\_________▌} {▐____|\________▌} {▐_____|\_______▌} {▐______|\______▌} {▐_______|\_____▌} {▐________|\____▌} {▐_________|\___▌} {▐__________|\__▌} {▐___________|\_▌} {▐____________|\▌} ▐____________/|▌ ▐___________/|_▌ ▐__________/|__▌ ▐_________/|___▌ ▐________/|____▌ ▐_______/|_____▌ ▐______/|______▌ ▐_____/|_______▌ ▐____/|________▌ ▐___/|_________▌ ▐__/|__________▌ ▐_/|___________▌ ▐/|____________▌ }} dqpb { 100 { d q p b }} grenade { 80 { {، } {′ } { ´ } { ‾ } { ⸌} { ⸊} { |} { ⁎} { ⁕} { ෴ } { ⁓} { } { } { } }} point { 125 { ∙∙∙ ●∙∙ ∙●∙ ∙∙● ∙∙∙ }} layer { 150 { - = ≡ }} betaWave { 80 { ρββββββ βρβββββ ββρββββ βββρβββ ββββρββ βββββρβ ββββββρ }} } } proc ::Spinner::Info {{spinnerName :random:}} { # Returns info about a spinner, or a random one if name ":random:" is given variable SPINNERS if {$spinnerName eq ":random:"} { set names [array names SPINNERS] set n [expr {int(rand() * [llength $names])}] set spinnerName [lindex $names $n] } lassign $SPINNERS($spinnerName) interval frames set maxWidth [::tcl::mathfunc::max {*}[lmap f $frames {string length $f}]] return [list $spinnerName $interval $frames $maxWidth] } proc ::Spinner::Start {name widget_or_textvar} { variable SPINNERS variable NextID variable ACTIVE set id [incr NextID] if {[info commands winfo] ne "" && [winfo exists $widget_or_textvar]} { set textvar [$widget_or_textvar cget -textvar] } else { set textvar $widget_or_textvar } set ACTIVE($id) go ::Spinner::_Go $id $textvar $name 0 return $id } proc ::Spinner::Stop {{id ""}} { # Stop a single banner or multiple banners variable ACTIVE if {$id eq ""} { array unset ACTIVE } else { array unset ACTIVE $id } } proc ::Spinner::_Go {id textvar spinnerName idx} { # Internal routine to update spinner $id variable ACTIVE variable SPINNERS if {! [info exists ACTIVE($id)]} return lassign $SPINNERS($spinnerName) interval frames set $textvar [lindex $frames $idx] set idx [expr {($idx + 1) % [llength $frames]}] after $interval [list ::Spinner::_Go $id $textvar $spinnerName $idx] } ################################################################ ################################################################ # # Demo code # proc DoDisplay {} { wm title . "Spinner Demo" set CNT 10 for {set i 0} {$i < $CNT} {incr i} { label .title$i -textvar ::spinnerName($i) -font {Helvetical 36} -width 20 label .spin$i -textvar ::spinner($i) -font {Helvetical 36 bold} -width 10 -bd 2 -relief solid grid .title$i .spin$i StartRandom $i } button .newAll -text Random -font {Helvetica 36} \ -command "for {set i 0} {\$i < $CNT} {incr i} { StartRandom \$i }" grid .newAll - -pady .2i } proc StartRandom {idx} { global spinnerName spinnerIds if {[info exists spinnerIds($idx)]} { ::Spinner::Stop $spinnerIds($idx) } lassign [::Spinner::Info :random:] spinnerName($idx) . . . set spinnerIds($idx) [::Spinner::Start $spinnerName($idx) .spin$idx] } DoDisplay return ======