These are the support routines for Canvas Buttons in 3-D.
These routines can also be used to make other things. The corners and sides are all separate, so one could build anything rectangular and 3-D on a canvas. A combobox would be interesting.
Here's a link to the Canvas Buttons in 3-D, that use these routines.
Canvas Buttons in 3-D
proc CreateTopLeftCorner {theCanvas x y height args} {
global tlcorner
set id [$theCanvas create rectangle 0 0 1 1]
set tlcorner($id,theCanvas) $theCanvas
set tlcorner($id,adjustedHeight) [expr round(0.707 * $height)]
# I'd rather deal with integers.
set tlcorner($id,x) $x
set tlcorner($id,y) $y
set tlcorner($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {
set tlcorner($id,color) $value
# No checking at present that color value is valid.
}
-tag -
-tags {
set tags $value
}
-state {
switch -- $value {
normal {
set state normal
}
hidden {
set state hidden
}
default {
tk_messageBox -message "CreateTopLeftCorner: \
Unrecognized state value: $value" -type ok
}
}
}
default {
tk_messageBox -message "CreateTopLeftCorner: Unrecognized\
option: $option" -type ok
}
}
}
set ah $tlcorner($id,adjustedHeight)
set ax [expr $x + $ah] ;# Altered x.
set ay [expr $y + $ah] ;# Altered y.
$theCanvas coords $id $x $y $ax $ay
set c $tlcorner($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
# Both the fill and the outline need to be set, for rectangle
# to match polygon.
return $id
# Hopefully, the caller won't keep the id long, but will assign
# a tag to the assemblage.
}
# Since I don't want to make a separate routine to set the state for each
# piece, and I don't want the rectangle to have to directly twiddle variables
# belonging to a piece, I don't make the state of the canvas item, hidden or
# normal, part of the state of the piece.
proc CreateLeftSide {theCanvas x ty by height args} {
global lside
set id [$theCanvas create rectangle 0 0 1 1]
set lside($id,theCanvas) $theCanvas
set lside($id,adjustedHeight) [expr round(0.707 * $height)]
set lside($id,x) $x
set lside($id,ty) $ty
set lside($id,by) $by
set lside($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set lside($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateLeftSide: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateLeftSide: \
Unrecognized option: $option" -type ok}
}
}
set ah $lside($id,adjustedHeight)
set ax [expr $x + $ah]
set aty [expr $ty + $ah]
set aby [expr $by - $ah]
$theCanvas coords $id $x $aty $ax $aby
set c $lside($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
proc CreateBottomLeftCorner {theCanvas x y height args} {
global blcorner
set id [$theCanvas create polygon 0 0 1 0 1 1]
set blcorner($id,theCanvas) $theCanvas
set blcorner($id,adjustedHeight) [expr round(0.707 * $height)]
set blcorner($id,x) $x
set blcorner($id,y) $y
set blcorner($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set blcorner($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateBottomLeftCorner: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateBottomLeftCorner: \
Unrecognized option: $option" -type ok}
}
}
set ah $blcorner($id,adjustedHeight)
set ax [expr $x + $ah]
set by [expr $y + $ah]
set ty [expr $y - $ah]
$theCanvas coords $id $x $y $ax $by $ax $ty $x $ty
set c $blcorner($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
# We let the corner manage the territory a little way up the side,
# so if we ever decide to allow the customer to change the perspective
# so that he is to the left and below the button, instead of to the left
# and above, we can.
#
# This seems as good a place as any to mention that if the height of the
# button or whatever gets to be an appreciable fraction of the width and
# breadth of the button, these routines will fail.
proc CreateTopSide {theCanvas lx rx y height args} {
global tside
set id [$theCanvas create rectangle 0 0 1 1]
set tside($id,theCanvas) $theCanvas
set tside($id,adjustedHeight) [expr round(0.707 * $height)]
set tside($id,lx) $lx
set tside($id,rx) $rx
set tside($id,y) $y
set tside($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set tside($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateTopSide: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateTopSide: \
Unrecognized option: $option" -type ok}
}
}
set ah $tside($id,adjustedHeight)
set alx [expr $lx + $ah]
set arx [expr $rx - $ah]
set ay [expr $y + $ah]
$theCanvas coords $id $alx $y $arx $ay
set c $tside($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
proc CreateTopRightCorner {theCanvas x y height args} {
global trcorner
set id [$theCanvas create polygon 0 0 1 0 1 1]
set trcorner($id,theCanvas) $theCanvas
set trcorner($id,adjustedHeight) [expr round(0.707 * $height)]
set trcorner($id,x) $x
set trcorner($id,y) $y
set trcorner($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set trcorner($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateTopRightCorner: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateTopRightCorner: \
Unrecognized option: $option" -type ok}
}
}
set ah $trcorner($id,adjustedHeight)
set arx [expr $x + $ah]
set alx [expr $x - $ah]
set ay [expr $y + $ah]
$theCanvas coords $id $x $y $alx $y $alx $ay $arx $ay
set c $trcorner($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
proc CreateBottomSide {theCanvas lx rx y height args} {
global bside
set id [$theCanvas create rectangle 0 0 1 1]
set bside($id,theCanvas) $theCanvas
set bside($id,adjustedHeight) [expr round(0.707 * $height)]
set bside($id,lx) $lx
set bside($id,rx) $rx
set bside($id,y) $y
set bside($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set bside($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateBottomSide: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateBottomSide: \
Unrecognized option: $option" -type ok}
}
}
set ah $bside($id,adjustedHeight)
set arx [expr $rx - $ah]
set ay [expr $y - $ah]
$theCanvas coords $id $lx $y $arx $ay
set c $bside($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
# Logically, the corner owns the territory out an adjustedHeight distance
# from the vertex. But I can't bring myself to make a separate little
# corner when it won't be visible. So I extend this side, and the right
# side, all the way to the vertex.
proc CreateBottomRightCorner {theCanvas x y height args} {
global brcorner
set id [$theCanvas create rectangle 0 0 1 1]
set brcorner($id,theCanvas) $theCanvas
set brcorner($id,adjustedHeight) [expr round(0.707 * $height)]
set brcorner($id,x) $x
set brcorner($id,y) $y
set brcorner($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set brcorner($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateBottomRightCorner: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateBottomRightCorner: \
Unrecognized option: $option" -type ok}
}
}
set ah $brcorner($id,adjustedHeight)
set ax [expr $x - $ah]
set ay [expr $y - $ah]
$theCanvas coords $id $x $y $ax $ay
set c $brcorner($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
proc CreateRightSide {theCanvas x ty by height args} {
global rside
set id [$theCanvas create rectangle 0 0 1 1]
set rside($id,theCanvas) $theCanvas
set rside($id,adjustedHeight) [expr round(0.707 * $height)]
set rside($id,x) $x
set rside($id,ty) $ty
set rside($id,by) $by
set rside($id,color) white
set tags ""
set state normal
foreach {option value} $args {
switch -- $option {
-color {set rside($id,color) $value}
-tag - -tags {set tags $value}
-state {
switch -- $value {
normal {set state normal}
hidden {set state hidden}
default {tk_messageBox -message "CreateRightSide: \
Unrecognized state value: $value" -type ok}
}
}
default {tk_messageBox -message "CreateRightSide: \
Unrecognized option: $option" -type ok}
}
}
set ah $rside($id,adjustedHeight)
set ax [expr $x - $ah]
set aby [expr $by - $ah]
$theCanvas coords $id $x $aby $ax $ty
set c $rside($id,color)
$theCanvas itemconfig $id -fill $c -outline $c -tags $tags -state $state
return $id
}
proc DestroyTopLeftCorner id {
global tlcorner
# I thought about converting from a tag to an id, if the value passed
# is not numeric, but how are you going to get the canvas name?
$tlcorner($id,theCanvas) delete $id
unset tlcorner($id,theCanvas)
unset tlcorner($id,adjustedHeight)
unset tlcorner($id,x)
unset tlcorner($id,y)
unset tlcorner($id,color)
}
# Rather than have a procedure "ShowTopLeftCorner" and one "HideTopLeftCorner"
# I think I'll have the orchestrator configure state using a tag that refers
# to all the parts.
proc DestroyLeftSide id {
global lside
$lside($id,theCanvas) delete $id
unset lside($id,theCanvas)
unset lside($id,adjustedHeight)
unset lside($id,x)
unset lside($id,ty)
unset lside($id,by)
unset lside($id,color)
}
proc DestroyBottomLeftCorner id {
global blcorner
$blcorner($id,theCanvas) delete $id
unset blcorner($id,theCanvas)
unset blcorner($id,adjustedHeight)
unset blcorner($id,x)
unset blcorner($id,y)
unset blcorner($id,color)
}
proc DestroyTopSide id {
global tside
$tside($id,theCanvas) delete $id
unset tside($id,theCanvas)
unset tside($id,adjustedHeight)
unset tside($id,lx)
unset tside($id,rx)
unset tside($id,y)
unset tside($id,color)
}
proc DestroyTopRightCorner id {
global trcorner
$trcorner($id,theCanvas) delete $id
unset trcorner($id,theCanvas)
unset trcorner($id,adjustedHeight)
unset trcorner($id,x)
unset trcorner($id,y)
unset trcorner($id,color)
}
proc DestroyBottomSide id {
global bside
$bside($id,theCanvas) delete $id
unset bside($id,theCanvas)
unset bside($id,adjustedHeight)
unset bside($id,lx)
unset bside($id,rx)
unset bside($id,y)
unset bside($id,color)
}
proc DestroyBottomRightCorner id {
global brcorner
$brcorner($id,theCanvas) delete $id
unset brcorner($id,theCanvas)
unset brcorner($id,adjustedHeight)
unset brcorner($id,x)
unset brcorner($id,y)
unset brcorner($id,color)
}
proc DestroyRightSide id {
global rside
$rside($id,theCanvas) delete $id
unset rside($id,theCanvas)
unset rside($id,adjustedHeight)
unset rside($id,x)
unset rside($id,ty)
unset rside($id,by)
unset rside($id,color)
}
# --------------------- Above are the pieces of a rectangle -------------------
# to be assembled in different ways.
# Some options that might be confusing are:
#
# dx, dy - If you have something written on the surface of your box
# (for example if the box is a button and the top of it says "Quit")
# as the box is raised and recessed, the text or image should be moved
# with a canvas move command. Dx and dy tell you how much to move your
# image over. They are relative to if you had written your image directly
# in the surface, without being raised above it or lowered below it.
# The names of the variables where you would like the values placed are
# passed using these options.
proc Create3DBox {theCanvas lx ty rx by height args} {
global box3d
set id [$theCanvas create rectangle 0 0 1 1]
set box3d($id,theCanvas) $theCanvas
set box3d($id,adjustedHeight) [expr round(0.707 * $height)]
set box3d($id,lx) $lx
set box3d($id,ty) $ty
set box3d($id,rx) $rx
set box3d($id,by) $by
set box3d($id,outSideColor) white
set box3d($id,inSideColor) white
set box3d($id,topFillColor) gray
set box3d($id,topOutlineColor) black
set box3d($id,relief) raised
set dxname ""
set dyname ""
set tags ""
foreach {option value} $args {
switch -- $option {
-outsidecolor {
set box3d($id,outSideColor) $value
# No checking that color value is valid.
}
-insidecolor {
set box3d($id,inSideColor) $value
}
-topfillcolor {
set box3d($id,topFillColor) $value
}
-topoutlinecolor {
set box3d($id,topOutlineColor) $value
}
-relief {
switch -- $value {
raised {
set box3d($id,relief) raised
}
sunken -
recessed {
set box3d($id,relief) recessed
# Tk users are likely to say sunken.
}
flat {
set box3d($id,relief) flat
}
default {
tk_messageBox -message "Create3DBox: \
Unrecognized relief value: $value" -type ok
}
}
}
-dx {
set dxname $value
}
-dy {
set dyname $value
}
-tag -
-tags {
set tags $value
}
default {
tk_messageBox -message "Create3DBox: Unrecognized\
option: $option" -type ok
}
}
}
set osc $box3d($id,outSideColor)
set isc $box3d($id,inSideColor)
set tfc $box3d($id,topFillColor)
set toc $box3d($id,topOutlineColor)
lappend tags box3d$id
set box3d($id,blcid) [CreateBottomLeftCorner $theCanvas $lx $by \
$height -color $osc -tags $tags]
set box3d($id,lsid) [CreateLeftSide $theCanvas $lx $ty $by $height \
-color $osc -tags $tags]
set box3d($id,tlcid) [CreateTopLeftCorner $theCanvas $lx $ty $height \
-color $osc -tags $tags]
set box3d($id,tsid) [CreateTopSide $theCanvas $lx $rx $ty $height \
-color $osc -tags $tags]
set box3d($id,trcid) [CreateTopRightCorner $theCanvas $rx $ty $height \
-color $osc -tags $tags]
set box3d($id,bsid) [CreateBottomSide $theCanvas $lx $rx $by $height \
-color $isc -tags $tags]
set box3d($id,brcid) [CreateBottomRightCorner $theCanvas $rx $by \
$height -color $isc -tags $tags]
set box3d($id,rsid) [CreateRightSide $theCanvas $rx $ty $by $height \
-color $isc -tags $tags]
$theCanvas itemconfig $id -outline $toc -fill $tfc -tags $tags
Draw3DBox $id localdx localdy
if {$dxname != ""} {
upvar $dxname dx
set dx $localdx
}
if {$dyname != ""} {
upvar $dyname dy
set dy $localdy
}
return $id
}
# First we set tags to an empty string, then set it with any list of tags,
# then appended our required tag. Thus we kept it a one level list.
#
# Typically, Create3DBox will be called with only one tag, but it it becomes
# part of a larger assemblage later, this may be useful.
proc Destroy3DBox id {
global box3d
DestroyBottomLeftCorner $box3d($id,blcid)
DestroyLeftSide $box3d($id,lsid)
DestroyTopLeftCorner $box3d($id,tlcid)
DestroyTopSide $box3d($id,tsid)
DestroyTopRightCorner $box3d($id,trcid)
DestroyRightSide $box3d($id,rsid)
DestroyBottomRightCorner $box3d($id,brcid)
DestroyBottomSide $box3d($id,bsid)
$box3d($id,theCanvas) delete $id
unset box3d($id,theCanvas)
unset box3d($id,adjustedHeight)
unset box3d($id,lx)
unset box3d($id,ty)
unset box3d($id,rx)
unset box3d($id,by)
unset box3d($id,outSideColor)
unset box3d($id,inSideColor)
unset box3d($id,topFillColor)
unset box3d($id,topOutlineColor)
unset box3d($id,relief)
unset box3d($id,blcid)
unset box3d($id,lsid)
unset box3d($id,tlcid)
unset box3d($id,tsid)
unset box3d($id,trcid)
unset box3d($id,rsid)
unset box3d($id,brcid)
unset box3d($id,bsid)
}
# Seems best to always have all the parts of a rectangle exists, if the
# rectangle exists. Some of the parts may have the state "hidden".
# Otherwise, we have to create and delete things when the button pops up,
# or create things only when they are first called for, and keep track
# of whether the piece exists yet.
#
# I'll put this here arbitrarily. It seems unfortunate to calculate the
# adjusted height from the passed height of the rectangle in multiple
# different places. What if one calculates it slightly differently?
# Also, it seems silly to put the same code to handle an option like
# -state in each routine. If I were using something like XOTcl, I would
# handle the options the specialized object knows about, and then finish
# my routine in the parent object with the next command.
proc Draw3DBox {id dxname dyname} {
global box3d
set c $box3d($id,theCanvas)
set lx $box3d($id,lx)
set ty $box3d($id,ty)
set rx $box3d($id,rx)
set by $box3d($id,by)
set ah $box3d($id,adjustedHeight)
set osc $box3d($id,outSideColor)
set isc $box3d($id,inSideColor)
set tfc $box3d($id,topFillColor)
set toc $box3d($id,topOutlineColor)
set r $box3d($id,relief)
upvar $dxname dx
upvar $dyname dy
if {$r == "raised"} {
set ulstate normal
set lrstate hidden
} elseif {$r == "recessed"} {
set ulstate hidden
set lrstate normal
} else { ;# Flat
set ulstate hidden
set lrstate hidden
}
$c itemconfig $box3d($id,blcid) -state $ulstate
$c itemconfig $box3d($id,lsid) -state $ulstate
$c itemconfig $box3d($id,tlcid) -state $ulstate
$c itemconfig $box3d($id,tsid) -state $ulstate
$c itemconfig $box3d($id,trcid) -state $ulstate
$c itemconfig $box3d($id,rsid) -state $lrstate
$c itemconfig $box3d($id,brcid) -state $lrstate
$c itemconfig $box3d($id,bsid) -state $lrstate
if {$r == "raised"} {
$c coords $id [expr $lx + $ah] [expr $ty + $ah] [expr $rx + $ah] \
[expr $by + $ah]
$c lower $id box3d$id
# Probably could just lower below everything, but if sometime in
# the future the 3d box is part of a more complicated assemblage,
# it will be good to stay with my group.
#
# Lowered so that outline will only be visible where top meets
# background, not where it meets sides.
set dx $ah
set dy $ah
} elseif {$r == "recessed"} {
$c coords $id $lx $ty [expr $rx - $ah] [expr $by - $ah]
$c lower $id box3d$id
set dx -$ah
set dy -$ah
} else { ;# Flat
$c coords $id $lx $ty $rx $by
set dx 0
set dy 0
}
}
proc Set3DBoxRelief {id relief args} {
global box3d
if {![info exists box3d($id,theCanvas)]} {
tk_messageBox -message "Set3DBoxRelief: no such id: $id" -type ok
}
# I'm not going to check that the relief value is legal each time.
# The user may be waiting for his button to go down.
if {$relief == "sunken"} {set relief recessed}
if {$box3d($id,relief) == $relief} {
set ah $box3d($id,adjustedHeight)
if {$relief == "raised"} {
set localdx $ah
set localdy $ah
} elseif {$relief == "recessed"} {
set localdx -$ah
set localdy -$ah
} else {
set localdx 0
set localdy 0
}
} else {
set box3d($id,relief) $relief
Draw3DBox $id localdx localdy
}
# Set3DBoxRelief really shouldn't know things about drawing, like that
# if the relief is raised, any label on top of the button should be
# moved down and right by adjustedHeight amount. I could always call
# Draw3DBox. I guess I'll regard this knowledge on Set3DBoxRelief's
# part as an optimization.
set dxname ""
set dyname ""
foreach {option value} $args {
switch -- $option {
-dx {set dxname $value}
-dy {set dyname $value}
# Not checking for unrecognized option.
}
}
if {$dxname != ""} {
upvar $dxname dx
set dx $localdx
}
if {$dyname != ""} {
upvar $dyname dy
set dy $localdy
}
}
# It might be desired to bind to the whole box, not to each of its subparts.
# One way to accomplish this might be to put a polygon over the top of the box.
# You can give the polygon a fill color and outline color of the empty string.
proc Get3DBoxPolygon {id} {
global box3d
set coords {}
set ah $box3d($id,adjustedHeight)
set lx $box3d($id,lx)
set ty $box3d($id,ty)
set rx $box3d($id,rx)
set by $box3d($id,by)
lappend coords $lx $ty $rx $ty [expr $rx + $ah] [expr $ty + $ah]
lappend coords [expr $rx + $ah] [expr $by + $ah]
lappend coords [expr $lx + $ah] [expr $by + $ah] $lx $by
return $coords
}