[uniquename] - 2012aug14 On the page [A color-gradient-button-maker GUI] and the page [A color-gradient-button-maker GUI with 6 spinboxes], I presented code that can be used to make small or large rectangular image files (like PNG files) that contain a color gradient, given 2 colors. Those GUI's are 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 --- with either horizontal or vertical lines. As I pointed out on the [A color-gradient-button-maker GUI with 6 spinboxes] page, the re-drawing of the color-gradient, for a change in any of the 6 RGB color parameters involved, cannot be peformed as quickly as I would like --- in using either the entry-widget method or the 6-spinboxes method to provide for changing the 6 RGB values for the 2 colors. As I pointed out on the [A color-gradient-button-maker GUI with 6 spinboxes] page, I should be able to experiment with color parameter changes much more rapidly if I use 6 'scale' widgets, instead of 6 'spinbox' widgets. The main drawback is that the GUI will have to be larger, to accomodate 6 'scale' widgets, instead of 6 'spinbox' widgets. Here is an image of the resulting 6-scales GUI. [draw-color-gradient-GUI_6sliders_screenshot_426x393.png] The 6-scales GUI takes a lot more screen space than the 6-spinbox GUI. (See an image of the 6-spinbox GUI on the [A color-gradient-button-maker GUI with 6 spinboxes] page.) But the speed of changing a color to do a re-draw of the gradient is greatly improved. There is a lot better 'feel' to the 6-scale GUI as one experiments with color changes. I present the code for the 6-scale GUI below. Since the 'DrawGradient' proc takes about half a second to redraw the color-gradient in the canvas, I probably would get a rather 'jumpy' re-drawing of the color-gradient canvas if I tried 'binding' the redraw to ANY change in one of the 6 RGB values, as a slider is moved with the mouse. Like with the 6-spinbox implementation, I 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. And unlike the 6-spinbox implementation, I could rapidly make BIG changes in any of the 6 RGB colors --- by quick drags of the mouse, rather than watching a spinbox relatively slowly roll from one color setting to a quite different setting. And moving a slider is a lot faster than changing an entry in a spinbox with the keyboard. So now I can experiment quickly with redrawing the color gradient by using mouse-only --- quick even for a big color change in any of the 6 RGB values. To do BIG color changes relatively rapidly with the entry-widget or 6-spinboxes implementation of the GUI, I needed to use the keyboard --- either keyboard-and-mouse or keyboard-and-Enter-key. The 6-scales take a lot of space on the GUI, but I could probably use a 'scale' parameter to reduce the height of the horizontal scales somewhat. (I leave that as an exercise for the reader.) _________________________________________________________________ As promised above, here is the code for the '6 scales' implementation of the color-gradient-button-maker GUI. Like with the 'entry widget' version and the '6-spinboxes' 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 scale sliders are rapidly moved from one position to another, you can try activating the '-command' statements that appear as comment lines at each of the 6 'scale' definition statements in the code below. ====== #!/usr/bin/wish -f ## ## SCRIPT: make_gradient-on-canvas_6scales-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. ## ## 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 top frame of the GUI window), ## in bottom frames of the GUI window: ## - one frame for 2 radiobuttons and an 'Exit' button. ## - 6 frames for 6 'scale' (slider) widgets. ## ## The 2 radiobuttons are to set the direction of the gradient --- ## in the x direction or the y direction. ## ## The 6 scales 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 scales: ## 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 scale widget ## which could be used to call the 'DrawGradient' proc to redraw ## the color-gradient in the canvas as any scale (slider) changes. ## ## However, the 'DrawGradient' proc is (probably) not fast enough ## to re-draw all the gradient-lines in a smooth fashion as the ## scale sliders are moved. ## ## So we use a button1-release binding on each scale widget ## to trigger a call to the 'DrawGradient' proc. By the time ## the user mouse-releases one scale and goes to ## move-and-release another scale, the 'DrawGradient' proc ## can complete the re-draw. ## ## Thus we get a 'semi-dynamic' (not quite immediate) GUI-update ## action from the scales, rather than a fully 'dynamic' ## (immediate) GUI-update action. ## ## However, the user/programmer can experiment by commenting ## out the 12 button1-release 'bind ' statements, and ## adding a '-command DrawGradient' parm to the 6 scale ## definition statements. ## ## REFERENCE: ## 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 indicators is provided. ##+###################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (name,position,[size,]color-scheme,fonts, ## widget-geometry-parms,etc.). ## 1) Define ALL frames (and sub-frames). Pack them. ## 2) Define all widgets in the frames. Pack them. ## ## 3) Define keyboard or mouse action BINDINGS, if needed. ## 4) Define PROCS, if needed. ## 5) Additional GUI INITIALIZATION (with procs), if needed. ## ## ## Some detail of the code structure for this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : '.fRcan' and '.fRbuttons' and ## '.fRsliderR1' '.fRsliderG1' '.fRsliderB1' ## '.fRsliderR2' '.fRsliderG2' '.fRsliderB2' ## ## Sub-frames: none ## ## 1b) Pack ALL frames. ## ## 2) Define all widgets in the frames (and pack them): ## ## - In '.fRcan': one 'canvas' widget ## ## - In '.fRbuttons': 1 button widget ('Exit') and ## 2 radiobuttons (for x or y), ## ## - In each '.fRslider' frame: 1 scale widget (with label widget) ## ## 3) Define bindings: six, for the scale widgets ## ## 4) Define procs: ## - 'DrawGradient' invoked by the 6 bindings ## ## 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: ...... ......... 2012 ##+####################################################################### ##+####################################################################### ## Set general window parms (title,position,size,color-scheme,fonts,etc.). ##+####################################################################### wm title . "Draw-Color-Gradient in a Canvas" wm iconname . "DrawGradient" wm geometry . +15+30 ## 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. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+###################################################### ## 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 ## 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) ##+########################################################### set initCanWidthPx 300 set initCanHeightPx 24 set minCanHeightPx 24 set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 set BDwidthPx_scale 2 set scaleLabelChars 10 set scaleLengthPx 200 ##+###################################################### ## Set a minsize of the window according to the ## approx max width of the chars in the 7 bottom frames ## --- and according to the approx height of the 8 frames. ##+###################################################### set minWinWidthPx [font measure fontTEMP_fixedwidth \ "1234567890123456789012345678901234567890123456789012"] ## Add some to account for right-left-size window decoration. set minWinWidthPx [expr 8 + $minWinWidthPx] set minWinHeightPx [font metrics fontTEMP_fixedwidth -linespace] ## Allow 2 characters high for each of the 6 scales, ## and add 1 for the radiobuttons frame. set minWinHeightPx [expr $minCanHeightPx + 13 * $minWinHeightPx] ## Add some to account for top-bottom window decoration (about 20 pixels) ## and frame/widget padding (about 8 pixels/line, 13 lines). set minWinHeightPx [expr 124 + $minWinHeightPx] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : 'fRcan' , 'fRbuttons', ## 'fRsliderR1' , 'fRsliderG1' , 'fRsliderB1' ## 'fRsliderR2' , 'fRsliderG2' , 'fRsliderB2' ## ## Sub-frames: none ##+################################################################ set BDwidth_frame 2 frame .fRcan -relief raised -borderwidth $BDwidth_frame frame .fRbuttons -relief raised -borderwidth $BDwidth_frame frame .fRsliderR1 -relief raised -borderwidth $BDwidth_frame frame .fRsliderG1 -relief raised -borderwidth $BDwidth_frame frame .fRsliderB1 -relief raised -borderwidth $BDwidth_frame frame .fRsliderR2 -relief raised -borderwidth $BDwidth_frame frame .fRsliderG2 -relief raised -borderwidth $BDwidth_frame frame .fRsliderB2 -relief raised -borderwidth $BDwidth_frame ##+############################## ## PACK the 2 top-level FRAMES. ##+############################## pack .fRcan \ -side top \ -anchor nw \ -fill both \ -expand 1 pack .fRbuttons \ .fRsliderR1 \ .fRsliderG1 \ .fRsliderB1 \ .fRsliderR2 \ .fRsliderG2 \ .fRsliderB2 \ -side top \ -anchor nw \ -fill x \ -expand 0 ##+############################### ## DEFINE-and-PACK CANVAS WIDGET: ##+############################### canvas .fRcan.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief raised \ -borderwidth $BDwidthPx_canvas pack .fRcan.can \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+################################################################ ## Set the init values for the 2 radiobuttons and the 6 scales. ##+################################################################ set curDIRECTION "x" set curR1 255 set curG1 255 set curB1 0 set curR2 255 set curG2 0 set curB2 0 ##+################################################################ ## IN THE '.fRbuttons' frame -- DEFINE the 'Exit' button --- also ## 2 radiobuttons (with a label button). ##+################################################################ button .fRbuttons.buttEXIT \ -text "Exit" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} ## Define label and 2 radiobuttons: label .fRbuttons.lab_radbutts \ -text " Gradient direction:" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd $BDwidthPx_button 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 ##+########################################### ## Pack the widgets in the 'fRbuttons1' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.lab_radbutts \ .fRbuttons.radbuttX \ .fRbuttons.radbuttY \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## IN THE FIRST 3 RGB 'slider' frames -- DEFINE A LABEL and ## A SLIDERBAR WIDGET in each one. ##+######################################################## ## RED1 label .fRsliderR1.label \ -text "\ Left/top Color r1:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderR1.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curR1 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderR1.scale set $curR1 ## GREEN1 label .fRsliderG1.label \ -text "\ g1:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderG1.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curG1 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderG1.scale set $curG1 ## BLUE1 label .fRsliderB1.label \ -text "\ b1:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderB1.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curB1 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderB1.scale set $curB1 ##+######################################################## ## IN THE 2nd 3 RGB 'slider' frames -- DEFINE A LABEL and ## A SLIDERBAR WIDGET in each one. ##+######################################################## ## RED2 label .fRsliderR2.label \ -text "\ Right/bottom Color r2:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderR2.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curR2 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderR2.scale set $curR2 ## GREEN2 label .fRsliderG2.label \ -text "\ g2:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderG2.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curG2 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderG2.scale set $curG2 ## BLUE2 label .fRsliderB2.label \ -text "\ b2:" \ -width $scaleLabelChars \ -font fontTEMP_varwidth scale .fRsliderB2.scale \ -orient h \ -digits 0 \ -from 0 -to 255 \ -length $scaleLengthPx \ -variable curB2 # -command {eval DrawGradient .fRcan.can $curDIRECTION \ # $curR1 $curG1 $curB1 $curR2 $curG2$curB2 } # .fRsliderB2.scale set $curB2 ##+########################################################## ## PACK A LABEL & A SCALE IN each of the 6 'fRslider' FRAMEs. ##+########################################################## pack .fRsliderR1.label \ .fRsliderR1.scale \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRsliderG1.label \ .fRsliderG1.scale \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRsliderB1.label \ .fRsliderB1.scale \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRsliderR2.label \ .fRsliderR2.scale \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRsliderG2.label \ .fRsliderG2.scale \ -side left \ -anchor w \ -fill x \ -expand 1 pack .fRsliderB2.label \ .fRsliderB2.scale \ -side left \ -anchor w \ -fill x \ -expand 1 ##+####################################################################### ## 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 return-key on each of the 2 radiobuttons ##+####################################################################### if {1} { bind .fRsliderR1.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRsliderG1.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRsliderB1.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRsliderR2.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRsliderG2.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRsliderB2.scale {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} ## button1 release bindings on 2 radiobuttons: bind .fRbuttons.radbuttX {DrawGradient \ .fRcan.can $curDIRECTION $curR1 $curG1 $curB1 $curR2 $curG2 $curB2} bind .fRbuttons.radbuttY {DrawGradient \ .fRcan.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 {win axis r1 g1 b1 r2 g2 b2} { global ENTRYstring # if {[winfo class $win] != "Canvas"} { # return -code error "$win must be a canvas widget" # } # $win delete gradient 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 { # 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.0 - $r1] set gRange [expr $g2.0 - $g1] set bRange [expr $b2.0 - $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 gradient -fill #${col} } else { $win create line 0 $i $width $i -tags gradient -fill #${col} } } # return $win } ## END OF proc DrawGradient ##+##################################################### ## Additional GUI initialization, if needed (or wanted). ##+##################################################### update ## 'update' is needed before DrawGradient so that the ## canvas width and height are implemented. ## DrawGradient uses 'winfo' to get those dimensions. DrawGradient .fRcan.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. But, hopefully, this ends (at least for a few months) my attempts to make a better color-gradient-maker GUI.