For a party at work with our colleagues, we needed a little stop watch to do a game show. We projected the little thing below onto a big canvas, and the party was big fun:) The stop watch runs with music and is heavily borrowed from this wiki, therefore I decided to contribute back.
Put the music as a set of MP3 files in a folder music/ in the same directory as the script. The following key bindings are defined:
- ESC: Toggle fullscreen mode
- Space: Start/Stop watch
- Return: Reset watch
- M: Toggle music. The music will start/stop together with the watch, and smoothly fade in and out
One can wrap the script and music into a starpack, and thanks to snack, this also plays directly from a single file.
#!/usr/bin/wish
set basedir [file normalize [file dirname [info script]]]
lappend auto_path [file join $basedir lib]
package require snit
package require snack
package require Tk
snit::widgetadaptor flexiclock {
option -bgcolor -default {#909090} -configuremethod Configured
option -fgcolor -default white -configuremethod Configured
option -inner_div -default 10 -configuremethod Configured
option -outer_div -default 60 -configuremethod Configured
option -inner_every -default 1 -configuremethod Configured
option -outer_every -default 1 -configuremethod Configured
option -ihandpos -default 0 -configuremethod Configured
option -ohandpos -default 0 -configuremethod Configured
option -borderfrac -default 0.05 -configuremethod Configured
option -font -default Helvetica -configuremethod Configured
option -otik1 -default 0.85 -configuremethod Configured
option -otik2 -default 0.9 -configuremethod Configured
option -itik1 -default 0.75 -configuremethod Configured
option -itik2 -default 0.8 -configuremethod Configured
option -onpos -default 0.95 -configuremethod Configured
option -ofnt_size -default 0.05 -configuremethod Configured
option -ofigcolor -default black -configuremethod Configured
option -inpos -default 0.71 -configuremethod Configured
option -ifnt_size -default 0.04 -configuremethod Configured
option -ifigcolor -default grey50 -configuremethod Configured
option -ohandlength -default 0.83 -configuremethod Configured
option -ohandthickness -default 0.05 -configuremethod Configured
option -ohandcolor -default black -configuremethod Configured
option -ihandlength -default 0.6 -configuremethod Configured
option -ihandthickness -default 0.04 -configuremethod Configured
option -ihandcolor -default red -configuremethod Configured
option -shadowcolor -default grey70 -configuremethod Configured
option -sdist -default 0.02 -configuremethod Configured
variable xsize
variable ysize
variable radius
variable xm
variable ym
variable redrawid
variable dirty [dict create hands 0 face 0]
constructor {args} {
# create nice clock of size width x width
# inner division 10
# outer division 60
installhull using canvas -width 400 -height 400 -highlightthickness 0
$hull create rectangle 0 0 400 400 -fill $options(-bgcolor) -tag clockbg
$hull create oval 10 10 380 380 -fill $options(-fgcolor) -outline black -tag clockfg
# create hands and shadow
$hull create line {210 210 210 20} -tag ohandshadow -fill $options(-shadowcolor)
$hull create line {200 200 200 10} -tag ohand
$hull create line {210 210 390 210} -tag ihand
$hull create line {200 200 380 200} -tag ihandshadow -fill $options(-shadowcolor)
$self configurelist $args
# $self invalidate face
bind $win <Configure> [mymethod resize]
}
method invalidate {what} {
dict set dirty $what 1
if {![info exists redrawid]} {
set redrawid [after idle [mymethod redraw]]
}
}
method redraw {} {
if {[info exists redrawid]} { unset redrawid }
if {[dict get $dirty face]} {
$self updateface
}
if {[dict get $dirty hands]} {
$self updatehands
}
}
method resize {} {
$self invalidate face
}
method Configured {option value} {
set options($option) $value
if {[dict exists {
-ihandpos 0 -ihandlength 0 -ihandcolor 0 -ihandthickness 0
-ohandpos 0 -ohandlength 0 -ohandcolor 0 -ohandthickness 0} $option]} {
$self invalidate hands
} else {
$self invalidate face
}
}
method updateface {} {
set xsize [winfo width $win]
set ysize [winfo height $win]
set size [expr {min($xsize, $ysize)}]
set radius [expr {0.5*$size*(1.0-$options(-borderfrac))}]
set xm [expr {$xsize/2}]
set ym [expr {$ysize/2}]
# move background
$hull coords clockbg 0 0 $xsize $ysize
$hull itemconfigure clockbg -fill $options(-bgcolor)
# move foreground
$hull coords clockfg [expr {$xm-$radius}] [expr {$ym-$radius}] [expr {$xm+$radius}] [expr {$ym+$radius}]
$hull itemconfigure clockfg -fill $options(-fgcolor)
# redraw figures
$hull delete iticks
$hull delete oticks
$hull delete innerfigures
$hull delete outerfigures
set pi 3.14159265358979
for {set i 1} {$i <= $options(-outer_div)} {incr i} {
set np [expr {double($i) / $options(-outer_div)}]
#draw the outer ticks
set x1 [expr {$xm + ($radius * $options(-otik1)) * sin($np * $pi * 2)}]
set y1 [expr {$ym - ($radius * $options(-otik1)) * cos($np * $pi * 2)}]
set x2 [expr {$xm + ($radius * $options(-otik2)) * sin($np * $pi * 2)}]
set y2 [expr {$ym - ($radius * $options(-otik2)) * cos($np * $pi * 2)}]
$hull create line [list $x1 $y1 $x2 $y2] -tag oticks -width 2 -fill $options(-ofigcolor)
if {$i % $options(-outer_every) == 0} {
#draw outer set of numbers
set x1 [expr $xm + ($radius * $options(-onpos)) * sin($np * $pi * 2)]
set y1 [expr $ym - ($radius * $options(-onpos)) * cos($np * $pi * 2)]
$hull create text $x1 $y1 -text $i -tags outerfigures \
-fill $options(-ofigcolor) \
-font "$options(-font) [expr {-round($options(-ofnt_size) * $radius)}]"
}
}
for {set i 1} {$i <= $options(-inner_div)} {incr i} {
set np [expr {double($i) / $options(-inner_div)}]
#draw the inner ticks
set x1 [expr {$xm + ($radius * $options(-itik1)) * sin($np * $pi * 2)}]
set y1 [expr {$ym - ($radius * $options(-itik1)) * cos($np * $pi * 2)}]
set x2 [expr {$xm + ($radius * $options(-itik2)) * sin($np * $pi * 2)}]
set y2 [expr {$ym - ($radius * $options(-itik2)) * cos($np * $pi * 2)}]
$hull create line [list $x1 $y1 $x2 $y2] -tag iticks -fill $options(-ifigcolor)
if {$i % $options(-inner_every) == 0} {
#draw inner set of numbers
set x1 [expr $xm + ($radius * $options(-inpos)) * sin($np * $pi * 2)]
set y1 [expr $ym - ($radius * $options(-inpos)) * cos($np * $pi * 2)]
$hull create text $x1 $y1 -text $i -tags innerfigures \
-fill $options(-ifigcolor) \
-font "$options(-font) [expr {-round($options(-ifnt_size) * $radius)}]"
}
}
$hull raise ihandshadow
$hull raise ohandshadow
$hull raise ihand
$hull raise ohand
dict set dirty face 0
dict set dirty hands 1
}
method getc {} { return $hull }
method updatehands {} {
# configure hands to be at correct positions
set pi 3.14159265358979
set np [expr {double($options(-ihandpos)) / $options(-inner_div)}]
set x2 [expr {$xm + ($radius * $options(-ihandlength)) * sin($np * $pi * 2)}]
set y2 [expr {$ym - ($radius * $options(-ihandlength)) * cos($np * $pi * 2)}]
# shadow distance
set sd [expr {$radius*$options(-sdist)}]
$hull coords ihand [list $xm $ym $x2 $y2]
$hull coords ihandshadow [list [expr {$xm+$sd}] [expr {$ym+$sd}] [expr {$x2+$sd}] [expr {$y2+$sd}]]
$hull itemconfigure ihand -width [expr {$options(-ihandthickness)*$radius}] -fill $options(-ihandcolor)
$hull itemconfigure ihandshadow -width [expr {$options(-ihandthickness)*$radius}]
set np [expr {double($options(-ohandpos)) / $options(-outer_div)}]
set x2 [expr {$xm + ($radius * $options(-ohandlength)) * sin($np * $pi * 2)}]
set y2 [expr {$ym - ($radius * $options(-ohandlength)) * cos($np * $pi * 2)}]
# shadow distance
set sd [expr {$radius*$options(-sdist)}]
$hull coords ohand [list $xm $ym $x2 $y2]
$hull coords ohandshadow [list [expr {$xm+$sd}] [expr {$ym+$sd}] [expr {$x2+$sd}] [expr {$y2+$sd}]]
$hull itemconfigure ohand -width [expr {$options(-ohandthickness)*$radius}] -fill $options(-ohandcolor)
$hull itemconfigure ohandshadow -width [expr {$options(-ohandthickness)*$radius}]
dict set dirty hands 0
}
}
proc init {} {
variable w
variable basedir
variable fadetime 2000
set w(clock) [flexiclock .c \
-outer_every 5 -ofnt_size 0.1 -onpos 0.92 \
-otik1 0.8 -otik2 0.85 \
-ifnt_size 0.06 -inpos 0.65 \
-itik1 0.7 -itik2 0.75]
set w(disp) [label .l -text "00:00.00" -textvariable digitime -font "Helvetica -20" -bg black -fg red]
bind $w(disp) <Configure> resizetime
place $w(disp) -relx 0 -rely 0 -relwidth 1 -relheight 0.2
place $w(clock) -relx 0 -rely 0.2 -relwidth 1 -relheight 0.8
bind . <space> startstop
bind . <Return> reset
bind . <Escape> switchfullscreen
bind . <m> switchmusic
variable splittime 0
variable running 0
showtime
# read music files
variable musicfiles [glob [file join $basedir music *.mp3]]
variable musicindex 0
readmusic
# create fading filters
variable ifade [snack::filter fade in logarithmic $fadetime]
variable ofade [snack::filter fade out exponential $fadetime]
}
proc switchfullscreen {} {
if {[wm attributes . -fullscreen]} {
wm attributes . -fullscreen 0
} else {
wm attributes . -fullscreen 1
}
}
proc resizetime {} {
variable w
set pt [winfo height $w(disp)]
$w(disp) configure -font "Helvetica [expr {-round($pt*0.75)}]"
}
proc startstop {} {
variable running
variable splittime
variable starttime
set now [clock clicks -milliseconds]
if {$running} {
incr splittime [expr {$now-$starttime}]
set running false
stopmusic
} else {
set starttime $now
set running true
startmusic
}
showtime
}
proc reset {} {
variable running
if {!$running} {
variable splittime 0
showtime
}
}
proc showtime {} {
variable starttime
variable running
variable splittime
variable afterid
variable w
variable digitime
variable musicon
if {[info exists afterid]} {
after cancel $afterid
unset afterid
}
if {$running} {
set time [expr {$splittime+[clock clicks -milliseconds]-$starttime}]
} else {
set time $splittime
}
# compute time in minutes, seconds and hundreth
set ms [expr {$time % 1000}]
set s [expr {$time / 1000}]
set min [expr {$s/60}]
set s [expr {$s % 60}]
set frac [expr {double($ms/10)*0.1}]
$w(clock) configure -ihandpos $frac -ohandpos $s
# digital display
if {$musicon} {
set fmtstring "\u25c0 %02d:%02d.%02d \u25b6"
} else {
set fmtstring "%02d:%02d.%02d"
}
set digitime [format $fmtstring $min $s [expr {$ms/10}]]
if {$running} {
set afterid [after 20 showtime]
}
}
proc readmusic {} {
variable musicindex
variable musicfiles
if {$musicindex >= [llength $musicfiles]} {
set musicindex 0
}
set mp3 [lindex $musicfiles $musicindex]
snack::sound player -file $mp3
variable musicposition 0
incr musicindex
}
proc startmusic {} {
variable ifade
variable musicposition
variable musicon
if {!$musicon} return
player play -start $musicposition -filter $ifade -command nextmusic
}
proc stopmusic {} {
variable ofade
variable fadetime
variable musicposition
variable musicon
if {!$musicon} return
lassign [player info] length rate
set musicposition [player current_position]
set endpos [expr {$musicposition+round($rate*$fadetime/1000.0)}]
player stop
player play -start $musicposition -filter $ofade -end $endpos
set musicposition $endpos
}
proc nextmusic {} {
# music stopped. read next file and start
player destroy
readmusic
set musicposition 0
player play -command nextmusic
}
variable musicon 0
proc switchmusic {} {
variable musicon
variable running
if {$musicon} {
if {$running} { stopmusic }
set musicon 0
} else {
set musicon 1
if {$running} { startmusic }
}
showtime ;# display the indicator
}
init