[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 # package require Tk proc DoDisplay {} { wm title . "Spinner Demo" set font {Helvetica 36} set fontBold {Courier 36 bold} set numColumns 3 set widgets {} set row -1 set names [lsort -dictionary [array names ::Spinner::SPINNERS]] set n [lsearch $names "shark"] set names [concat [lreplace $names $n $n] shark] foreach spinnerName $names { incr row label .title$row -text $spinnerName -font $font label .spin$row -textvar ::spinner($row) -font $fontBold \ -width 10 -bd 2 -relief solid ::Spinner::Start $spinnerName .spin$row lassign [::Spinner::Info $spinnerName] . . . maxWidth lappend widths $maxWidth lappend widgets .title$row .spin$row if {[llength $widgets] >= 2*$numColumns} { grid {*}$widgets set widgets {} } } if {$widgets ne {}} { grid {*}$widgets } # The shark spinner needs extra space grid config .spin$row -columnspan 2 -sticky w .spin$row config -width 16 } DoDisplay return ====== ---- '''[arjen] - 2019-10-02 07:20:23''' Very nice! Two remarks though: when I ran it on my laptop, the window was larger than the screen and the font did not have all the glyphs, it seems as some spinners only showed a single question mark.