#!/usr/bin/wish -f ## ## SCRIPT: make_gradient-on-canvas_externalColorSelector_2buttons.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 GIF or 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. ## ## Also decorations such as bevels could be applied with scripts ## using 'convert' or with interactive image editors such as 'mtpaint'. ## ## 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 DESIGN: ## ## 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 frame of the GUI window), ## in a 'buttons' frame of the GUI window are: ## - an 'Exit' button ## - 2 radiobuttons --- to indicate x or y direction for the ## color gradient ## - 2 buttons which call on an external color-selector GUI, ## to allow the user to select the 2 colors involved. ## ## The the 2 'color' buttons are used 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 using the two radiobuttons and the ## two color buttons: ## 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. ##+####### ## METHOD: ## 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. ## ## The 'DrawGradient' proc is called in the case of 5 'events': ## - button1-release on radiobutton 'x' ## - button1-release on radiobutton 'y' ## - a click on the color1 button ## - a click on the color2 button ## - GUI initialization ## ## The 'create line' commands issued 'across' the button ## (rectangular canvas) proceed fast enough that the canvas ## is colored with the 'gradient-of-colors' within 1 second. ## ##+######### ## REFERENCE (and credit): ## The 'DrawGradient' proc is based on a Tcl-Tk script by B. Oakley and ## Damon Courtney --- published at http://wiki.tcl.tk/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 parameters is provided. ##+###################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name,win-position,color-scheme, ## fonts, widget-geometry-parms, win-size-control). ## 1a) Define ALL frames and sub-frames. ## 1b) Pack ALL frames and sub-frames. ## 2) Define all widgets in the frames. Pack all the widgets for a frame, ## as one finishes defining the widgets in each frame. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen action ## BINDINGS, if needed. ## 4) Define PROCS, if needed. ## 5) Additional GUI INITIALIZATION (typically with one or two of ## the procs), if needed. ## ##---------------------------------- ## The code structure in more detail, 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': ## - 1 button widget ('Exit') ## - 2 radiobuttons (for x or y gradient direction) ## - 2 'color' buttons (to call an external color selector) ## ## - In '.fRcanvas': one 'canvas' widget ## ## 3) Define bindings: ## 2 button1-release bindings -- on the 2 radiobuttons ## ## 4) Define procs: ## - 'DrawGradient' invoked by the 2 bindings and the ## 2 'color' buttons ## ## 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 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+####################################################################### ## MAINTENANCE HISTORY: ## Created by: Blaise Montandon 2012nov02 ## Changed by: ...... ......... 2012 ##+####################################################################### ##+####################################################################### ## Set general window parms (title,position). ##+####################################################################### wm title . "Draw-Color-Gradient in a Canvas" wm iconname . "DrawGradient" wm geometry . +15+30 ##+###################################################### ## Set the color scheme for the window and its widgets --- ## radiobuttons and spinboxes. ##+###################################################### tk_setPalette "#e0e0e0" set radbuttBKGD "#ffffff" ##+######################################################## ## Use a variable-width font for labels for the 6 spinboxes ## and the 2 radiobuttons and the Exit button text. ## Use a fixed-width font for the spinbox entry field. ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -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 font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -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) ##+########################################################### set initCanWidthPx 300 set initCanHeightPx 24 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ## For BUTTONS: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## For LABELS: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 ##+###################################################### ## Set a MINSIZE of the window (roughly) according to the ## approx max WIDTH of the chars in the 'fRbuttons' frame ## --- 1 exit button, 2 radiobuttons, 2 color buttons, ## and 3 labels. ## ## --- and according to the approx HEIGHT of the 2 frames ## --- 'fRbuttons' and 'fRcanvas'. ##+###################################################### set minWinWidthPx [font measure fontTEMP_varwidth \ "Exit Direction: x y Color1 Color2 Color1: #FFFF00 Color2: #FF0000"] ## Add some pixels to account for right-left-size window/widget ## border-widths (about 6 pixels) --- and borderwidths and ## padding for about 6 widgets x 4 pixels/widget. set minWinWidthPx [expr 30 + $minWinWidthPx] ## For MIN-HEIGHT: ## Allow 1 char high for 'fRbuttons', ## 24 pixels high for 'fRcanvas'. set minCharHeightPx [font metrics fontTEMP_SMALL_fixedwidth -linespace] set minWinHeightPx [expr 24 + $minCharHeightPx] ## Add some to account for top-bottom window decoration (about 20 pixels) ## and frame/widget padding (about 3 pixels/frame x 2 frames). set minWinHeightPx [expr 26 + $minWinHeightPx] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## NOTE: ## 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. The user can simply click-release any of the buttons ## (2 radiobuttons or 2 'color' buttons) to re-fill the canvas ## with the user-specified color gradient. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : 'fRbuttons', 'fRcanvas' ## ## Sub-frames: none ##+################################################################ ## FOR TESTING: (to check behavior of frames during window expansion) # set RELIEF_frame raised # set BDwidth_frame 2 set RELIEF_frame flat set BDwidth_frame 0 frame .fRbuttons -relief $RELIEF_frame -borderwidth $BDwidth_frame frame .fRcanvas -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, the frames are defined and packed. ##+##################################### ##+##################################### ## DEFINE-AND-PACK WIDGETS WITHIN FRAMES. ##+##################################### ##+##################################### ##+################################################################ ## IN THE '.fRbuttons' FRAME -- ## DEFINE an 'Exit' BUTTON, 2 RADIOBUTTONS, 2 'color' BUTTONS, ## and 3 LABELS. ##+################################################################ button .fRbuttons.buttEXIT \ -text "Exit" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} ## Define a label and 2 radiobuttons: label .fRbuttons.lab_radbutts \ -text " Gradient direction:" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_button ## We set this widget-variable in the ## GUI-initialization section at the ## bottom of this script. # set curDIRECTION "x" radiobutton .fRbuttons.radbuttX \ -text "x" \ -font fontTEMP_varwidth \ -anchor w \ -variable curDIRECTION \ -value "x" \ -selectcolor "$radbuttBKGD" \ -relief flat \ -bd $BDwidthPx_button radiobutton .fRbuttons.radbuttY \ -text "y" \ -font fontTEMP_varwidth \ -anchor w \ -variable curDIRECTION \ -value "y" \ -selectcolor "$radbuttBKGD" \ -relief flat \ -bd $BDwidthPx_button ## Define the 2 color buttons. button .fRbuttons.buttCOLOR1 \ -text "Gradient Color1" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_color1" button .fRbuttons.buttCOLOR2 \ -text "Gradient Color2" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_color2" ## Define 3 labels for displaying the 2 gradient colors ## --- as hex-values and by setting background and ## foreground color of 2 of the labels. label .fRbuttons.labelCOLORS \ -text "Gradient" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label label .fRbuttons.labelCOLOR1 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label label .fRbuttons.labelCOLOR2 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_label ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.lab_radbutts \ .fRbuttons.radbuttX \ .fRbuttons.radbuttY \ .fRbuttons.buttCOLOR1 \ .fRbuttons.buttCOLOR2 \ .fRbuttons.labelCOLORS \ .fRbuttons.labelCOLOR1 \ .fRbuttons.labelCOLOR2 \ -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.canvas \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 pack .fRcanvas.canvas \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+####################################################### ## END OF DEFINING-and-PACKING ALL WIDGETS and ## END OF DEFINING the GUI. ##+####################################################### ##+####################################################################### ## BINDINGS SECTION: ## - button1-release on each of the 2 radiobuttons ##+####################################################################### bind .fRbuttons.radbuttX "DrawGradient" bind .fRbuttons.radbuttY "DrawGradient" ##+###################################################################### ## PROCS SECTION: ## 'DrawGradient' - to fill the specified canvas according to the ## 7 parms --- x/y r1 g1 b1 r2 g2 b2 ## 'set_color1' - Calls on external color selector to set a ## color1, then call 'DrawGradient'. Also updates ## a color1 label in the GUI. ## 'set_color2' - Calls on external color selector to set a ## color2, then call 'DrawGradient'. Also updates ## a color2 label in the GUI. ## 'update_color1_label' - to update the color1 label widget with ## text and background-foreground colors ## 'update_color2_label' - to update the color2 label widget with ## text and background-foreground colors ##+###################################################################### ##+##################################################################### ## proc DrawGradient ##+##################################################################### ## PURPOSE: This procedure is invoked put a color gradient 'across' ## the canvas --- according to 7 parms --- x or y and ## two RGB triplets. ## ## ARGUMENTS: see the global statement ## ## CALLED BY: the COLOR1 and COLOR2 buttons (actually the 'set_color1' ## and 'set_color2' procs), 2 button1-release bindings ## on the 2 radiobuttons, and called in the GUI initialization ## section at the bottom of this script. ##+#################################################################### proc DrawGradient {} { global curDIRECTION \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex ## Clear the canvas. .fRcanvas.canvas delete all ## Get current canvas width and height. set canWidthPx [winfo width .fRcanvas.canvas] set canHeightPx [winfo height .fRcanvas.canvas] ## Get the distance 'distPx' (in pixels) over which ## the 2 colors are to be gradiated. if {"$curDIRECTION" == "x"} { set distPx $canWidthPx } else { set distPx $canHeightPx } ## Get RGB ranges. set rRange [expr {double($COLOR2r - $COLOR1r)}] set gRange [expr {double($COLOR2g - $COLOR1g)}] set bRange [expr {double($COLOR2b - $COLOR1b)}] ## Calc the ratio of RGB-color-ranges to distance ## 'across' the canvas. set rRatio [expr {$rRange / $distPx}] set gRatio [expr {$gRange / $distPx}] set bRatio [expr {$bRange / $distPx}] ## Increment 'across' the canvas, drawing colored lines ## with canvas-'create line'. for {set i 0} {$i < $distPx} {incr i} { set nR [expr {int( $COLOR1r + ($rRatio * $i) )}] set nG [expr {int( $COLOR1g + ($gRatio * $i) )}] set nB [expr {int( $COLOR1b + ($bRatio * $i) )}] set hexcolor [format "#%02X%02X%02X" $nR $nG $nB] ## FOR TESTING: # puts "hexcolor = $hexcolor" if {"$curDIRECTION" == "x"} { .fRcanvas.canvas create line $i 0 $i $canHeightPx -fill "$hexcolor" } else { .fRcanvas.canvas create line 0 $i $canWidthPx $i -fill "$hexcolor" } } ## END OF for {set i 0} {$i < $distPx} {incr i} } ## END OF proc DrawGradient ##+##################################################################### ## proc 'set_color1' ##+##################################################################### ## PURPOSE: This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a color to be used for the ## 1st color for a color gradient 'across' the canvas. ## ## ARGUMENTS: see the global statement ## ## OUTPUT: in global vars COLOR1r COLOR1g COLOR1b COLOR1hex ## ## CALLED BY: the COLOR1 button ##+##################################################################### proc set_color1 {} { global curDIRECTION \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex # global feDIR_tkguis ## FOR TESTING: # puts "COLOR1r: $COLOR1r" # puts "COLOR1g: $COLOR1g" # puts "COLOR1b: $COLOR1b" set TEMPrgb [ exec \ ./sho_colorvals_via_sliders3rgb.tk \ $COLOR1r $COLOR1g $COLOR1b] # $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \ ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR1hex "#$hexRGB" set COLOR1r $r255 set COLOR1g $g255 set COLOR1b $b255 ## Update the Color1 label. update_color1_label ## Update the color gradient on the canvas. DrawGradient } ## END OF PROC 'set_color1' ##+##################################################################### ## proc 'set_color2' ##+##################################################################### ## PURPOSE: This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a color to be used for the ## 2nd color for a color gradient 'across' the canvas. ## ## ARGUMENTS: see the global statement ## ## OUTPUT: in global vars COLOR2r COLOR2g COLOR2b COLOR2hex ## ## CALLED BY: the COLOR2 button ##+##################################################################### proc set_color2 {} { global curDIRECTION \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex # global feDIR_tkguis ## FOR TESTING: # puts "COLOR2r: $COLOR2r" # puts "COLOR2g: $COLOR2g" # puts "COLOR2b: $COLOR2b" set TEMPrgb [ exec \ ./sho_colorvals_via_sliders3rgb.tk \ $COLOR2r $COLOR2g $COLOR2b] # $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \ ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR2hex "#$hexRGB" set COLOR2r $r255 set COLOR2g $g255 set COLOR2b $b255 ## Update the color2 label. update_color2_label ## Update the color gradient on the canvas. DrawGradient } ## END OF PROC 'set_color2' ##+##################################################################### ## proc 'update_color1_label' ##+##################################################################### ## PURPOSE: This procedure is invoked to update the color1 label ## on the GUI with text and background and foreground colors. ## ## ARGUMENTS: see the global statement ## ## OUTPUT: changed attributes of the colo1 label ## ## CALLED BY: the 'set_color1' proc and in the GUI initialization ## section at the bottom of this script. ##+##################################################################### proc update_color1_label {} { global COLOR1r COLOR1g COLOR1b COLOR1hex .fRbuttons.labelCOLOR1 configure -text "Color1 - $COLOR1hex" .fRbuttons.labelCOLOR1 configure -bg "$COLOR1hex" set RGBsum [expr { $COLOR1r + $COLOR1g + $COLOR1b }] if {$RGBsum > 400} { .fRbuttons.labelCOLOR1 configure -fg "#000000" } else { .fRbuttons.labelCOLOR1 configure -fg "#f0f0f0" } } ## END OF PROC 'update_color1_label' ##+##################################################################### ## proc 'update_color2_label' ##+##################################################################### ## PURPOSE: This procedure is invoked to update the color2 label ## on the GUI with text and background and foreground colors. ## ## ARGUMENTS: see the global statement ## ## OUTPUT: changed attributes of the colo1 label ## ## CALLED BY: the 'set_color2' proc and in the GUI initialization ## section at the bottom of this script. ##+##################################################################### proc update_color2_label {} { global COLOR2r COLOR2g COLOR2b COLOR2hex .fRbuttons.labelCOLOR2 configure -text "Color2 - $COLOR2hex" .fRbuttons.labelCOLOR2 configure -bg "$COLOR2hex" set RGBsum [expr { $COLOR2r + $COLOR2g + $COLOR2b }] if {$RGBsum > 400} { .fRbuttons.labelCOLOR2 configure -fg "#000000" } else { .fRbuttons.labelCOLOR2 configure -fg "#f0f0f0" } } ## END OF PROC 'update_color2_label' ##+############################################################# ## proc ReDraw_if_canvas_resized ## ## PURPOSE: To make sure we are only doing redraws if the ## of the canvas resulted in a change ## in the size of the canvas. ## ## CALLED BY: bind .fRcanvas.canvas ## at bottom of this script. ##+############################################################# proc ReDraw_if_canvas_resized {} { global PREVcanWidthPx PREVcanHeightPx set CURcanWidthPx [winfo width .fRcanvas.canvas] set CURcanHeightPx [winfo height .fRcanvas.canvas] if { $CURcanWidthPx != $PREVcanWidthPx || \ $CURcanHeightPx != $PREVcanHeightPx} { DrawGradient set PREVcanWidthPx $CURcanWidthPx set PREVcanHeightPx $CURcanHeightPx } } ## END OF ReDraw_if_canvas_resized ##+##################################################### ## Additional GUI initialization, if needed (or wanted). ##+##################################################### ## Set initial values for the 'application variables' ## --- but not widget appearance parameters (those ## are set above). ## ## Note these variables are declared global in the ## several procs --- DrawGradient, set_color1, set_color2. ## This avoids some extra memory management for temp-vars ## to pass these variables (their locations) to the procs. set curDIRECTION "x" set COLOR1r 255 set COLOR1g 255 set COLOR1b 0 set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b] set COLOR2r 255 set COLOR2g 0 set COLOR2b 0 set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b] update_color1_label update_color2_label update ## 'update' is needed before DrawGradient so that the ## canvas width and height are implemented. ## DrawGradient uses 'winfo' to get those dimensions. DrawGradient ## After this script drops into the Tk event-handling loop, ## this bind command causes redraws whenever the canvas is resized. set PREVcanWidthPx [winfo width .fRcanvas.canvas] set PREVcanHeightPx [winfo height .fRcanvas.canvas] bind .fRcanvas.canvas "ReDraw_if_canvas_resized"