A color-gradient-button-maker GUI with 6 spinboxes

uniquename - 2012aug14

On the page A color-gradient-button-maker GUI, I presented code that can be used to make small or large rectangular image files (like GIF or PNG files) that contain a color gradient, given 2 colors.

That GUI was based on a 'DrawGradient' proc at Drawing Gradients on a Canvas which uses 'create line' commands on a 'canvas' widget to generate the color gradients --- using either horizontal or vertical lines.

I enhanced the code at Drawing Gradients on a Canvas to provide a GUI with an entry widget to enter 7 parameters of the form 'x/y r1 g1 b1 r2 g2 b2' --- along with a 'Draw' button --- to draw the specified gradient into the canvas widget.

I also posted the code for a color selector GUI, at A non-obfuscated color selector GUI. In that GUI, there are three 'scale' (slider-bar) widgets that correspond to RGB colors.

When you use mouse-button-1 to slide any of the sliderbars, a color-swatch (a 'frame' widget) immediately starts changing color, corresponding to the position of the slider in the 'scale' widget.

I realized that I could use a technique, like that in the color-selector GUI, to make the color-gradient-image (in a 'canvas' widget on the GUI), change immediately and smoothly as I used a mouse-button action --- rather than keying color value changes into an 'entry' widget and clicking on a 'Draw' button (or pressing the Enter key) to update the color-gradient in the canvas.

However, I also realized that the 'DrawGradient' proc takes about half a second to redraw the color-gradient in the canvas anytime one clicks on the 'Draw' button.

So, if I used a mouse-button action to change the RGB colors (for two colors that determine the gradient) and if I connected the mouse action directly to ANY change of one of the 6 RGB values, I probably would get a rather 'jumpy' re-drawing of the color-gradient canvas as any one of the 6 RGB values rapidly changed from one value to another.

But, I also realized that if I triggered the re-draw on a mouse-button RELEASE, I could get a pretty pleasing color change on the GUI --- while allowing me to quickly change any of the 6 RGB color values --- with the mouse instead of with the keyboard --- i.e. instead of changing values in an entry widget and then clicking a 'Draw' button (or pressing the Enter key).

In other words, I could experiment quickly with redrawing the color gradient by using mouse-only --- instead of keyboard-and-mouse or keyboard-and-Enter-key.

I thought of using 'scale' widgets --- like on the color-selector GUI, but 6 scales instead of 3 --- in place of the 'entry' widget on the color-gradient-maker GUI. But that would take a lot of space on the GUI.

So I decided to try 6 spinboxes instead. And here is an image of the resulting GUI.

draw-color-gradient-GUI_6spinboxes_screenshot_428x150.png

I present the code for making this GUI below.

It turns out that this spinboxes-implementation is a LITTLE faster for experimenting with color changes than using an 'entry' widget. BUT it is rather slow to change a color by holding down a mouse-button on a spinbox.

To make a BIG color change to one of the six RGB colors (for example, going from 100 to 200 in the 0-to-255 range), it is less frustrating to use the keyboard to enter the big change --- and then use the mouse on a spinbox button to do some 'fine-tuning'.

So I found that, even though it was going to make the color-gradient-maker GUI a lot larger, I needed to try making a GUI using 6 'scale' widgets instead of 6 'spinbox' widgets.

I present the code for the 'scale' implementation (along with a screenshot) on a separate A color-gradient-button-maker GUI with 6 scale widgets page.

______________________________________________________________

As promised above, here is the code for the '6 spinboxes' implementation of the color-gradient-button-maker GUI.

Like with the 'entry widget' version, I have put the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on the pack command for the various frames and widgets. So Tcler's can experiment with these parameters if they want to change the behavior of the GUI when window size is changed.

Alternatively, Tcler's can activate the commented statement

wm resizeable . 0 0

to make the canvas a fixed size and avoid any confusion that might be caused by allowing the window to be resized.

And if you want to see what will happen if you try letting the color-gradient-canvas be redrawn as the spinbox numbers rapidly rotate from one value to another, you can try activating the '-command' statements that appear as comment lines at each of the 6 'spinbox' definition statements in the code below.


 Code for Tk script 'make_gradient-on-canvas_6spinboxes-2radiobuttons.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: make_gradient-on-canvas_6spinboxes-2radiobuttons.tk
##
## PURPOSE:  This TkGUI script facilitates the creation of
##           rectangular color-gradient images that can be used, for example,
##           for the background of 'buttons' in GUIs such as 'toolchests'.
##
##           A screen/window capture utility (like 'gnome-screenshot' on Linux)
##           can be used to capture the image in a PNG file, say.
##
##           Then, if necessary, an image editor (like 'mtpaint' on Linux)
##           can be used to crop the window capture image to get only the
##           rectangular area of the canvas containing the color-gradient
##           --- or some sub-rectangle of that area.
##
##           Furthermore, utilities (such as the ImageMagick 'convert' command
##           on Linux) can be used to 'mirror' or 'flip' a gradient image in
##           an image file (PNG or JPEG or GIF). The 'mirror'  and 'flip'
##           operations can be applied vertically or horizontally ---  and
##           can be applied multiple times, for various visual effects.
##
##           The resulting rectangular color-gradient image can then be used as a
##           background in Tk widgets, such as button or canvas or label widgets
##           in 'toolchests' or other types of GUIs. 
##
########################
## GUI LAYOUT and METHOD:
##           The GUI contains a rectangular canvas widget into which the
##           color gradient is drawn with canvas 'create line' commands,
##           where the lines can be either horizontal (in the x direction)
##           or vertical (in the y direction).
##
##           In addition to the canvas widget (in a bottom frame of the
##           GUI window), in top frames of the GUI window, there are 6 spinboxes,
##           2 radiobuttons, and an 'Exit' button.
##
##           The 2 radiobuttons are to set the direction of the gradient ---
##           in the x direction or the y direction.
##
##           The 6 spinboxes are to set 2 pairs of RGB values --- of the
##           form  r1 g1 b1 r2 g2 b2 --- for the left-color (or top-color)
##           and right-color (bottom-color) of the gradient.
##
##           Examples of 2 settings of the radiobuttons and the 6 spinboxes:
##             x 255 255 0 255 0 0
##             y 255 0 255 0 0 255
##
##           The first example says draw the lines horizontally starting
##           from yellow on the left to red on the right.
##
##           The second example says draw the lines vertically starting
##           from magenta at the top to blue on the bottom.
##
##           The seven parms (x/y r1 g1 b1 r2 g2 b2)
##           are passed into a 'DrawGradient' proc that draws the lines
##           within the canvas, filling the canvas with colored pixels.
##
##           There is a '-command' parameter on the spinbox widget
##           which could be used to call the 'DrawGradient' proc to redraw
##           the color-gradient in the canvas as any spinbox changes.
##
##           However, the 'DrawGradient' proc is (probably) not fast enough
##           to re-draw the gradient in a smooth fashion as the spinbox
##           entries rapidly roll over.
##
##           So we use a button1-release (and return key) binding on each
##           spinbox to trigger a call to the 'DrawGradient' proc. By the time
##           the user mouse-releases/key-returns one spin-box and goes to
##           a release/retrun on another spinbox, the 'DrawGradient' proc
##           can complete the re-draw.
##
##           Thus we get a 'semi-dynamic' (not quite immediate) GUI-update
##           action from the spinboxes, rather than a fully 'dynamic'
##           (immediate) GUI-update action.
##
##           However, the user/programmer can experiment by commenting
##           out the 12 button1-release & key-return 'bind ' statements, and
##           adding a '-command DrawGradient' parm to the 6 spinbox
##           definition statements.
##
#########################################################################
## REFERENCE:
## The 'DrawGradient' proc is based on a Tcl-Tk script by B. Oakley and
## Damon Courtney --- published at https://wiki.tcl-lang.org/6100 -
## 'Drawing Gradients on a Canvas'.  (downloaded 2011sep26)
## That script draws gradients on multiple rectangular canvases, packed
## top to bottom. You need to edit that script to change colors or
## gradient direction. No GUI for entry of those indicators is provided.
##+######################################################################
## 'CANONICAL' STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name,win-position,color-scheme,fonts,
##                 widget-geometry-parms,text-array-for-labels-etc).
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack the frames and sub-frames.
##  2) Define all widgets in the frames, frame-by-frame.
##     After ALL the widgets for a frame are defined, pack the widgets.
##
##  3) Define keyboard and mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or two procs),
##     if needed.
##
##
## Some detail of the code structure for this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level : '.fRcontrols1' '.fRcontrols2' '.fRcontrols3' '.fRcanvas'
##
##      Sub-frames: none
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##       - In '.fRcontrols1': 1 button widget ('Exit') and
##                                   2 radiobuttons (for x or y),
##
##       - In '.fRcontrols2':  3 spinboxes (for r1 g1 b1)
##
##       - In '.fRcontrols3':  3 spinboxes (for r2 g2 b2)
##
##       - In '.fRcanvas': one 'canvas' widget
##
##  3) Define bindings:  six, for button1-release on the spinboxes
##                       six, for return/enter key on the spinboxes
##                       two, for button1-release on the radiobuttons
##
##  4) Define procs:
##     - 'DrawGradient'  invoked by the 14 bindings and the
##                       additional-GUI-initialization section below
##
##  5) Additional GUI initialization:  Execute 'DrawGradient' once with
##                                     an initial, example set of 7 parms
##                                     --- x/y r1 g1 b1 r2 g2 b2 --- to start
##                                     with a color-gradient in the canvas
##                                     rather than a blank canvas.
##+########################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala).
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10.
##+#######################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2012aug10
## Changed by: Blaise Montandon 2012nov18 Added braces to 9 'expr' statements.
##                                        Provided more consistent indenting
##                                        of the code. Touched up the comments
##                                        to match the final code. Added a
##                                        text-array for labels,buttons,etc.
##                                        Improved calc of minsize of window.
##                                        Moved canvas to bottom of GUI.
##+#######################################################################


##+#######################################################################
## Set WINDOW TITLES.
##+#######################################################################

wm title    . "Draw-Color-Gradient in a Canvas"
wm iconname . "DrawGradient"


##+######################################################################
## Set WINDOW POSITION.
##+######################################################################

wm geometry . +15+30


##+######################################################
## Set the COLOR SCHEME for the window and its widgets ---
## radiobuttons and spinboxes.
##+######################################################

tk_setPalette "#e0e0e0"

set radbuttBKGD "#ffffff"
set spinboxBKGD "#ffffff"


##+########################################################
## SET FONT-NAMES.
## We use a variable-width font for labels for the 6 spinboxes
## and the 2 radiobuttons and the Exit button text.
## We use a fixed-width font for the spinbox entry field.
##+########################################################

font create fontTEMP_varwidth \
   -family {comic sans ms} \
   -size -14 \
   -weight bold \
   -slant roman

## Some other possible (similar) variable width fonts:
##  Arial
##  Bitstream Vera Sans
##  DejaVu Sans
##  Droid Sans
##  FreeSans
##  Liberation Sans
##  Nimbus Sans L
##  Trebuchet MS
##  Verdana

font create fontTEMP_fixedwidth  \
   -family {liberation mono} \
   -size -14 \
   -weight bold \
   -slant roman

## Some other possible fixed width fonts (esp. on Linux):
##  Andale Mono
##  Bitstream Vera Sans Mono
##  Courier 10 Pitch
##  DejaVu Sans Mono
##  Droid Sans Mono
##  FreeMono
##  Nimbus Mono L
##  TlwgMono




##+###########################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

## CANVAS parms:

set initCanWidthPx 300
set initCanHeightPx 24
set minCanHeightPx 24
# set BDwidthPx_canvas 2
set BDwidthPx_canvas 0


## BUTTON parms:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL parms:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## SPINBOX parms:

set BDwidthPx_spinbox 2
set initSPINBOXwidthChars 3



##+######################################################
## Set a MINSIZE of the window (roughly)
## according to the approx max WIDTH of the chars in the
## frame that contains the 'Exit' button
## --- and according to the approx HEIGHT of the 4 frames.
##+#################################################################
## We allow the window to be resizable and we pack the canvas with
## '-fill both' so that the canvas can be enlarged by enlarging the
## window. Just click-release any of the spinboxes (or radiobuttons)
## to re-fill the canvas with the user-specified color gradient.
####################################################################

set minWinWidthPx [font measure fontTEMP_fixedwidth \
   " Exit Gradient direction:   x   y"]

## Add some to account for right-left-side window border-widths
## (about 2x3=6 pixels) and widget border-widths --- about
## 4 widgets x 4 pixels/widget = 16 pixels.

set minWinWidthPx [expr {22 + $minWinWidthPx}]


## For MIN-HEIGHT, allow:
##      1 char    high for frame 'fRcontrols1',
##      1 char    high for frame 'fRcontrols2',
##      1 char    high for frame 'fRcontrols3',
##     24 pixels  high for frame 'fRcanvas'.

set minWinHeightPx [font metrics fontTEMP_fixedwidth -linespace]
set minWinHeightPx [expr {24 + (3 * $minWinHeightPx)}]

## Add some to account for top-bottom window decoration (about 20 pixels)
## and frame/widget padding (about 8 pixels/frame, 4 frames).

set minWinHeightPx [expr {52 + $minWinHeightPx}]

## FOR TESTING:
#   puts "minWinWidthPx = $minWinWidthPx"
#   puts "minWinHeightPx = $minWinHeightPx"

wm minsize . $minWinWidthPx $minWinHeightPx


## If you want to make the window un-resizable, 
## you can use the following statement.

# wm resizable . 0 0


##+################################################
##  Load a TEXT-ARRAY variable with text for
## labels and other GUI widgets --- to facilitate
## 'internationalization' of this script.
##+################################################

## if { "$VARlocale" == "en"}

set aRtext(buttonEXIT) "Exit"
set aRtext(labelDIR)   "  Gradient direction:"

set aRtext(labelSPINGRP1)  "Left/top color"
set aRtext(labelSPINGRP2)  "Right/bottom color"
set longspinLabelChars 15

## END OF  if { "$VARlocale" == "en"}



##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : 'fRcanvas' , 'fRcontrols1' , 'fRcontrols2' , 'fRcontrols3'
##
##   Sub-frames: none
##+################################################################

## FOR TESTING:  (of expansion of frames, esp. during window expansion)
# set RELIEF_frame raised
# set BDwidth_frame 2

set RELIEF_frame flat
set BDwidth_frame 0

frame .fRcontrols1  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRcontrols2  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRcontrols3  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRcanvas     -relief $RELIEF_frame  -borderwidth $BDwidth_frame


##+##############################
## PACK the 2 top-level FRAMES. 
##+##############################

pack .fRcontrols1 \
     .fRcontrols2 \
     .fRcontrols3 \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRcanvas \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1


##+################################################################
## IN THE '.fRcontrols1' frame -
## DEFINE the 'Exit' BUTTON --- and
## 2 radiobuttons (with a label button).
##+################################################################

button .fRcontrols1.buttEXIT \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {exit}

## Define label and 2 radiobuttons:

label .fRcontrols1.labelDIR \
   -text "$aRtext(labelDIR)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button
   
radiobutton  .fRcontrols1.radbuttX \
   -text "x" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curDIRECTION \
   -value "x" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button
   
radiobutton  .fRcontrols1.radbuttY \
   -text "y" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curDIRECTION \
   -value "y" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_button


##+###########################################
## Pack the widgets in the 'fRcontrols1' frame.
##+###########################################

pack .fRcontrols1.buttEXIT \
     .fRcontrols1.labelDIR \
     .fRcontrols1.radbuttX \
     .fRcontrols1.radbuttY \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+################################################################
## IN THE '.fRcontrols2' frame -
##  DEFINE 3 SPINBOXES (with 4 label widgets for them).
##+################################################################

label .fRcontrols2.labSPINGRP1 \
   -text "$aRtext(labelSPINGRP1)" \
   -width $longspinLabelChars \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

## Define label and spinbox for R1:

label .fRcontrols2.lab_spinR1 \
   -text "  r1:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols2.spinR1 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curR1 

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }


## Define label and spinbox for G1:

label .fRcontrols2.lab_spinG1 \
   -text "  g1:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols2.spinG1 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curG1 

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }


## Define label and spinbox for B1:

label .fRcontrols2.lab_spinB1 \
   -text "  b1:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols2.spinB1 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curB1 

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }

## Pack the widgets in the 'fRcontrols2' frame.

pack .fRcontrols2.labSPINGRP1 \
     .fRcontrols2.lab_spinR1 \
     .fRcontrols2.spinR1 \
     .fRcontrols2.lab_spinG1 \
     .fRcontrols2.spinG1 \
     .fRcontrols2.lab_spinB1 \
     .fRcontrols2.spinB1 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+################################################################
## IN THE '.fRcontrols3' frame -
## DEFINE 3 spinboxes (with 4 label widgets for them).
##+################################################################

label .fRcontrols3.labSPINGRP2 \
   -text "$aRtext(labelSPINGRP2)" \
   -width $longspinLabelChars \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

## Define label and spinbox for R2:

label .fRcontrols3.lab_spinR2 \
   -text "  r2:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols3.spinR2 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curR2

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }


## Define label and spinbox for G2:

label .fRcontrols3.lab_spinG2 \
   -text "  g2:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols3.spinG2 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curG2

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }


## Define label and spinbox for B2:

label .fRcontrols3.lab_spinB2 \
   -text "  b2:" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

spinbox .fRcontrols3.spinB2 \
   -from 0 -to 255 \
   -increment 1 \
   -format "%3.0f" \
   -width 3 \
   -font fontTEMP_fixedwith \
   -bg "$spinboxBKGD" \
   -justify center \
   -textvariable curB2 

# -command {eval DrawGradient .fRcanvas.can $curDIRECTION \
#         $curR1 $curG1 $curB1 $curR2 $curG2$curB2 }



## Pack the widgets in the 'fRcontrols3' frame.

pack .fRcontrols3.labSPINGRP2 \
     .fRcontrols3.lab_spinR2 \
     .fRcontrols3.spinR2 \
     .fRcontrols3.lab_spinG2 \
     .fRcontrols3.spinG2 \
     .fRcontrols3.lab_spinB2 \
     .fRcontrols3.spinB2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+###############################
## In the '.fRcanvas' frame -
## DEFINE-and-PACK CANVAS WIDGET.
##+########################################################
## We set highlightthickness & borderwidth of the canvas to
## zero, as suggested on page 558, Chapter 37, 'The Canvas
## Widget', in the 4th edition of the book 'Practical
## Programming in Tcl and Tk'.
##+#######################################################

canvas .fRcanvas.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

pack .fRcanvas.can \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1

## OK. All widgets are defined and packed.
## Now define bindings and procs.


##+#######################################################################
##  BINDINGS SECTION:
##        - one each, for button1-release on each of the 6 spinboxes
##        - one each, for return-key on each of the 6 spinboxes
##        - one each, for button1-release on each of the 2 radiobuttons
##+#######################################################################

if {1} {

bind .fRcontrols2.spinR1 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols2.spinG1 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols2.spinB1 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinR2 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinG2 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinB2 <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

## Return key bindings  on 6 spinboxes:

bind .fRcontrols2.spinR1 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols2.spinG1 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols2.spinB1 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinR2 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinG2 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols3.spinB2 <Return>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

## button1 release bindings  on 2 radiobuttons:

bind .fRcontrols1.radbuttX <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

bind .fRcontrols1.radbuttY <ButtonRelease-1>  {eval DrawGradient \
      .fRcanvas.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2}

}
## END OF 'if {1/0}' SECTION
## To easily disable the bindings.


##+######################################################################
##  PROCS SECTION: 
##    - DrawGradient'   to fill the specified canvas according to the
##                      7 parms --- x/y r1 g1 b1 r2 g2 b2
##+######################################################################

##+#####################################################################
## proc DrawGradient -
##
## PURPOSE:
##     Draws the gradient on the canvas using canvas 'create line'
##     commands. Draws vertical or horizontal lines according to
##     the axis-specification: 'x' or 'y'. Interpolates between
##     2 RGB colors.
##
## CALLED BY:  <Return> and button1-release bindings on the 
##             6 spinboxes,
##             button1-release bindings on the 2 x/y radiobuttons,
##             and in the additional-GUI-initialization section at
##             the bottom of this script.
##+####################################################################

proc DrawGradient {win axis r1 g1 b1 r2 g2 b2} {

   global ENTRYstring

   # $win delete TAGgradient

   set width  [winfo width $win]
   set height [winfo height $win]

   switch -- $axis {
      "x" { set max $width; set x 1 }
      "y" { set max $height; set x 0 }
      default {
         ## We could put the error msg on the end of the user-entry
         ## in the entry-field.
         # set ENTRYstring "$ENTRYstring ERR: Invalid 1st parm. Must be x or y."
         # return
         return -code error "Invalid 1st parm: $axis.  Must be x or y"
      }
   }

   if { $r1 > 255 || $r1 < 0 } {
      return -code error "Invalid color value for r1: $r1"
   }


   if { $g1 > 255 || $g1 < 0 } {
      return -code error "Invalid color value for g1: $g1"
   }

   if { $b1 > 255 || $b1 < 0 } {
      return -code error "Invalid color value for b1: $b1"
   }

   if { $r2 > 255 || $r2 < 0 } {
      return -code error "Invalid color value for r2: $r2"
   }

   if { $g2 > 255 || $g2 < 0 } {
      return -code error "Invalid color value for g2: $g2"
   }

   if { $b2 > 255 || $b2 < 0 } {
      return -code error "Invalid color value for b2: $b2"
   }

   set rRange [expr {$r2 - double($r1)}]
   set gRange [expr {$g2 - double($g1)}]
   set bRange [expr {$b2 - double($b1)}]

   set rRatio [expr $rRange / $max]
   set gRatio [expr $gRange / $max]
   set bRatio [expr $bRange / $max]

   for {set i 0} {$i < $max} {incr i} {
      set nR [expr int( $r1 + ($rRatio * $i) )]
      set nG [expr int( $g1 + ($gRatio * $i) )]
      set nB [expr int( $b1 + ($bRatio * $i) )]

      set col [format {%2.2x} $nR]
      append col [format {%2.2x} $nG]
      append col [format {%2.2x} $nB]

      ## FOR TESTING:
      #  puts "col = $col"

      if {$x} {
         $win create line $i 0 $i $height -tags TAGgradient -fill "#$col"
      } else {
         $win create line 0 $i $width $i -tags TAGgradient -fill "#$col"
      }
   }

}
## END OF proc 'DrawGradient'


##+#####################################################
## Additional GUI initialization, if needed (or wanted).
##+#####################################################

##+################################################################
## Set the init values for the 2 radiobuttons and the 6 spinboxes.
##+################################################################

set curDIRECTION "x"
set curR1 255
set curG1 255
set curB1 0
set curR2 255
set curG2 0
set curB2 0


update

## 'update' is needed before DrawGradient so that the
## canvas width and height are implemented.
## DrawGradient uses 'winfo' to get those dimensions.

DrawGradient .fRcanvas.can $curDIRECTION \
      $curR1 $curG1 $curB1 $curR2 $curG2 $curB2


Like I said at the bottom of the A color-gradient-button-maker GUI page:

"Make one enhancement to a Tk script and it leads to other enhancements or completely new scripts. It never ends."

Ousterhout set off a perpetual (Tclers-doing-coding) motion machine.


uniquename 2012nov18 Update:

I am going through some of my old scripts making a few changes based on experiences gained over the past few months. Changes to this script:

  • Added braces to 9 'expr' statements (should increase the draw speed).
  • Provided more consistent indenting of the code (removed white-space at front of many lines).
  • Touched up the comments to match the final code.
  • Added a text-array for text in labels,buttons,etc. --- to facilitate 'internationalization' of the GUI.
  • Improved the calculation of the 'minsize' of the window.
  • Moved the canvas from the top of the GUI to the bottom.

Replaced the code above with the tested script containing these changes.

RLE (2012-11-22): If you are considering internationalization possibilities then you should look at the msgcat command that ships with Tcl. It allows for the configuration of the result strings via external files instead of editing the source.