A color-gradient-button-maker GUI

uniquename - 2012aug04

I tried out the code at Drawing Gradients on a Canvas and was surprised how fast it fills large rectangular canvas areas with a color gradient.

Here is an image (reduced from original size) from one of the two very similar Tk scripts on that page.

gradientOnCanvas2_wiki6100_screenshot_205x300.jpg

Apparently the trick to get the 'quick draw' is to use 'create line' rather than 'put at x y' --- i.e. make horizontal or vertical lines, rather than poking a color into each x,y location.

This drawing-gradients demo ties in with my wish to make nice backgrounds for buttons in application or utility 'toolchests', as indicated at my request to "Implement '-anchor' (something similar) for compound text on image" at the Tk 9.0 WishList page.

---

You can see from the GUI image above that the Tk scripts on the page Drawing Gradients on a Canvas make a nice 'demo script' but they are definitely not an 'end-user utility' --- unless you expect 'end-users' to edit Tk scripts to change the colors.

Furthermore, I just need to capture one such background at a time. It is not typical that I would want both a horizontal gradient and a vertical gradient at the same time. Besides, the 2nd image takes a lot of room on the canvas that I might want for the 1st image. So ...

I have 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.

Like in the original demo scripts, you can adjust the size of the color-gradient image by changing the size of the window --- including maximizing the size of the window to screen size.

Here is a sample image:

draw-color-gradient-GUI_screenshot_544x108.png

Note: I changed the draw-gradient proc at Drawing Gradients on a Canvas to use RGB values (in decimal form), rather than color names --- so that a much wider range of color choices is available (16 million times 2).

Now I can quickly make color-gradient 'button files' by doing screen/window captures of this GUI --- with a screenshot utility, like 'gnome-screenshot' on Linux.

Below is the code for this 'gradient-button-maker' GUI. It includes plenty of comments to explain the GUI.

One thing to note is that I have put the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on the pack command for the various frames and widgets. This is so that 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.


 Code for the Tk script 'make_colorGradientOnCanvas_entryField7parms.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: make_colorGradientOnCanvas_entryField7parms.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 a top frame of the GUI window, there are a couple of
##           buttons ('Draw' and 'Exit') and an entry field.
##
##           The entry field contains 7 values --- of the format
##               x/y r1 g1 b1 r2 g2 b2
##           Examples:
##             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.
##
##+########################################################################
## REFERENCE:
## The 'DrawGradient' proc is based on a Tcl-Tk script by Damon Courtney
## --- published at https://wiki.tcl-lang.org/6100 .  (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.
##     When the widgets for a frame are defined, pack them.
##
##  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 on the code structure for this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :  '.fRbuttons' and '.fRcanvas'
##
##      Sub-frames: none
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##       - In '.fRbuttons': 2 button widgets ('Draw' and 'Exit') and
##                        an entry widget (for the 7 gradient-drawing parms)
##
##       - In '.fRcanvas': one 'canvas' widget 
##
##  3) Define bindings:  one, for the entry widget
##
##  4) Define procs:
##     - 'DrawGradient'    invoked by the 'Draw' button
##
##  5) Additional GUI initialization:  Execute 'DrawGradient' once
##                                     with an initial, example
##                                     set of 7 parms --- 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,
##   after installing Tcl-Tk 8.5 in place of Tcl-Tk 8.4.
##
##+########################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2012aug01
## 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.
##                                        Added calc of minsize of window.
##                                        Moved canvas to bottom of GUI.
##+########################################################################

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

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


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

wm geometry . +15+30


##+#####################################################################
## Set a COLOR SCHEME for the window and its widgets.
##+#####################################################################

tk_setPalette "#cfcfcf"


##+#####################################################################
## SET FONT-NAMES.
## We use a variable-width font for buttons and labels.
## We use a fixed-width font for the entry field, for easy access
## to narrow characters like i, j, l, and the number 1.
##+#####################################################################

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, padding for Buttons)
##+#######################################################################

## CANVAS parms:

set initCanWidthPx 400
set initCanHeightPx 24
# set BDwidthPx_canvas 2
set BDwidthPx_canvas 0


## BUTTON parms:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## ENTRY parms:

set BDwidthPx_entry 2
set initENTRYwidthChars 30


##+##################################################################
## Set a MINSIZE of the window (roughly),
## according to the approx WIDTH of the widgets in the
## 'fRbuttons' frame --- 2 buttons, 1 label, 1 entry.
##
## --- and according to the approx HEIGHT of the 2 frames
## --- 'fRbuttons', 'fRcanvas'.
##+##################################################################
## We allow the window to be resizable. We pack the canvas with
## '-fill both' so that the canvas can be enlarged by enlarging the
## window. The 'Draw' proc can be used to re-fill the canvas with
## the user-specified color gradient.
##+#################################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   " Exit  Draw  Draw-Color-Gradient parms: x 255 255 0 255 0 0"]

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

set minWinWidthPx [expr {22 + $minWinWidthPx}]


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

set minCharHeightPx [font metrics fontTEMP_fixedwidth -linespace]

set minWinHeightPx [expr { 24 + $minCharHeightPx}]

## Add some to account for top-bottom window decoration (about 23 pixels)
## and frame/widget padding/borders (about
## 4 frames/widgets x 4 pixels/frame-widget = 16 pixels).

set minWinHeightPx [expr {39 + $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(buttonDRAW)   "Draw"
set aRtext(labelENTRY)   "\
Draw-Color-Gradient parms:
     (x/y r1 g1 b1 r2 g2 b2)"


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


##+####################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level :  '.fRbuttons' and '.fRcanvas'
##               
##   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 .fRcanvas   -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRbuttons  -relief $RELIEF_frame  -borderwidth $BDwidth_frame


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

pack .fRbuttons \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

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

## OK. All frames are defined and packed.
## Now define the widgets within the frames.


##+#######################################################################
## IN THE '.fRbuttons' frame -
## DEFINE the 'Draw' and 'Exit' buttons
## --- and a pair of label and entry widgets.
##+#######################################################################

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

button .fRbuttons.buttDRAW \
   -text "$aRtext(buttonDRAW)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {eval DrawGradient .fRcanvas.can $ENTRYstring}

label .fRbuttons.lab \
   -text "$aRtext(labelENTRY)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

set ENTRYstring "x 255 255 0 255 0 0"

entry .fRbuttons.ent \
   -textvariable ENTRYstring \
   -bg "#f0f0f0" \
   -font fontTEMP_fixedwidth \
   -width $initENTRYwidthChars \
   -relief sunken \
   -bd $BDwidthPx_entry


##+##############################################
## Pack ALL the widgets in the .fRbuttons' frame.
##+##############################################

pack .fRbuttons.buttEXIT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.buttDRAW \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.lab \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.ent \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+#############################
## 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, for Enter key in the entry field.
##+#######################################################################

bind .fRbuttons.ent <Return>  {eval DrawGradient .fRcanvas.can $ENTRYstring}


##+#######################################################################
##  PROCS SECTION: 
##    - DrawGradient'   to fill the specified canvas according to the
##                      7 parms from the ENTRYstring variable
##+#######################################################################

##+#####################################################################
## 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> binding on .fRbuttons.ent
##             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).
##
## We draw a gradient on the canvas, rather than letting the
## GUI come up with an empty canvas.
##+###############################################################

update

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

eval DrawGradient .fRcanvas.can $ENTRYstring


Now I have a tool to make nice color-gradient buttons for my Freedom Environment toolchests (see [L1 ]) --- in future releases.

I may try using some of the ideas in the Functional imaging page (thanks, Suchenwirth, for your many contributions) to make more complex color gradients than simple x-direction or y-direction gradients. But it will take a careful weeding out of the many functions presented there, to find functions that provide nice backgrounds for toolchest buttons (nice subtle gradients that do not distract from the text on the 'toolchest drawer' buttons).

For now, I can use the ImageMagick commands 'convert' and 'composite' (as in the 'IMAGEtools' scripts of the 'feNautilusScripts' subsystem of the Freedom Environment software) to combine x and y gradient buttons (and other buttons) to make some additional types of gradient buttons.


uniquename 2012aug13 Update: After using this GUI based on an 'entry' widget, I have found that it might be easier/faster to enter the 6 RGB values via a 'spinbox' or a 'scale' widget.

So I have added a couple of new pages on this wiki titled A color-gradient-button-maker GUI with 6 spinboxes and A color-gradient-button-maker GUI with 6 scale widgets.

Make one enhancement to a Tk script and it leads to other enhancements or ideas for completely new scripts. It never ends. Darn you, Ousterhout, for starting all this. :-)


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 some).
  • Provided more consistent indenting of the code (removed some whitespace from the 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.
  • Added calculation of 'minsize' of window.
  • Moved canvas from top of GUI to bottom.

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