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 [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) 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 . startstop bind . reset bind . switchfullscreen bind . 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 ====== <> GUI | Toys