custom cursors

IDG Aug 5/08: Now that the -cursor none option works, it would seem that a nice way to have a custom cursor in a canvas would be to draw your own, in response to <Motion> events in the canvas. This sort of works, but there are some peculiarities.

You have to take care not to draw within 2 pixels of the hot spot or strange things happen. This is illustrated in the script below. It displays a rectangle and draws a large hairline cursor. The rectangle changes colour when the cursor enters it.

If you change the variable cursor_style to 1, it no longer works; it looks like the <Enter> and <Leave> events on the rectangle are not generated. The difference in the cursors is that the one that works has a small empty area around the hot spot, the failing one does not.

Is this a bug in Tk, or is there something I'm not understanding?

IDG Jun 18/12: Not a bug, but a feature. If you draw through the hot spot, the thing you draw becomes the topmost item under the cursor, and that's what gets enter/leave events, not the underlying item you had hoped for.

-cut here ----------------------------------------

package require Tk

set cursor_style 0
# space at hot point. Works.
# set cursor_style to 1 for no space, -> doesn't work

proc main {} {

   pack [canvas .c -bg white]
   set r [.c create rectangle 40 40 240 140  -fill cyan]
   
   .c bind $r <Enter> [list .c itemconfigure $r -fill pink]
   .c bind $r <Leave> [list .c itemconfigure $r -fill cyan]
   
   .c configure -cursor "none"
   set ::cursor_items ""

   bind .c <Enter> {draw_cursor %x %y}
   bind .c <Motion> {draw_cursor %x %y}
   bind .c <Leave> erase_cursor
}

proc erase_cursor {} {

   foreach x $::cursor_items {.c delete $x}
}

proc draw_cursor {x y} {

   erase_cursor
   set ::cursor_items ""

   if {$::cursor_style == 0} {set d 2} else {set d 0}
   set s 24
# s sets cursor size, d is central gap size

   set x1 [expr {$x - $s}]
   set x2 [expr {$x + $s}]
   set y1 [expr {$y - $s}]
   set y2 [expr {$y + $s}]

   lappend ::cursor_items [.c create line $x1 $y  [expr {$x - $d}] $y ]
   lappend ::cursor_items [.c create line [expr {$x + $d}] $y $x2 $y ]
   lappend ::cursor_items [.c create line $x  $y1 $x  [expr {$y - $d}]]
   lappend ::cursor_items [.c create line $x  [expr {$y + $d}] $x  $y2]
}

main

TR The -cursor option of many Tk widgets not only allows you to choose from the builtin cursors but also to use your own creations.

Widgets can be configured with any of the following cursor definitions:

name ?fgColor? ?bgColor?

Name is the name of a cursor in the standard X cursor cursor, i.e., any of the names defined in cursorcursor.h, without the XC_. Some example values are X_cursor, hand2, or left_ptr. Appendix B of "The X Window System" by Scheifler & Gettys has illustrations showing what each of these cursors looks like. If fgColor and bgColor are both specified, they give the foreground and background colors to use for the cursor (any of the forms acceptable to Tk_GetColor may be used). If only fgColor is specified, then there will be no background color: the background will be transparent. If no colors are specified, then the cursor will use black for its foreground color and white for its background color.

The Macintosh version of Tk supports all of the X cursors and will also accept any of the standard Mac cursors including ibeam, crosshair, watch, plus, and arrow. In addition, Tk will load Macintosh cursor resources of the types crsr (color) and CURS (black and white) by the name of the of the resource. The application and all its open dynamic library's resource files will be searched for the named cursor. If there are conflicts color cursors will always be loaded in preference to black and white cursors.

@sourceName maskName fgColor bgColor

In this form, sourceName and maskName are the names of files describing cursors for the cursor's source bits and mask. Each file must be in standard X11 or X10 cursor format. FgColor and bgColor indicate the colors to use for the cursor, in any of the forms acceptable to Tk_GetColor. This form of the command will not work on Macintosh or Windows computers.

This form is similar to the one above, except that the source is used as mask also. This means that the cursor's background is transparent. This form of the command will not work on Macintosh or Windows computers.

@sourceName

This form only works on Windows, and will load a Windows system cursor (.ani or .cur) from the file specified in sourceName.


LV Anyone have pointers to a tcl/tk program that would create the .ani or .cur files?


Windows uses a .cur files and unix uses .xbm files.

 set dir /path/to/cursor/files

 switch -- $tcl_platform(platform) {
   unix {
     set file [file join $dir cursorfile.xbm]
     set cursor [list @$file black]
   }
   windows {
     set file [file join $dir cursorfile.cur]
     set cursor [list @$file]
   }
 }
 $widget configure -cursor $cursor

Niagara Falls

To accomplish this, you must specify the file, where the cursor lives:

 # windows:
 $widget configure -cursor "@path/to/cursorfile.cur"
 # unix:
 $widget configure -cursor "@path/to/cursorfile.xbm black"

the cursor will be displayed in the size it was defined.


Here is a 16x16 pixel cursor named hand.xbm:

 #define hand_width 16
 #define hand_height 16
 #define hand_x_hot 6
 #define hand_y_hot 0
 static unsigned char panPointer_bits[] = {
    0x00, 0x00, 0x80, 0x01, 0x58, 0x0e, 0x64, 0x12, 0x64, 0x52, 0x48, 0xb2,
    0x48, 0x92, 0x16, 0x90, 0x19, 0x80, 0x11, 0x40, 0x02, 0x40, 0x04, 0x40,
    0x04, 0x20, 0x08, 0x20, 0x10, 0x10, 0x20, 0x10 };

This file has two extra lines compared to "normal" xbm files that define where the so-called "hot spot" is. This is the point in the bitmap where a click event appears to come from. The coordinates count from left to right (x) and from top to bottom (y), just like the canvas coordinate system.


On Windows, there is a nice cursor named "no", which is unfortunately unavailable on Unix. Therefore here is a little xbm file which can be used as such:

 #define no_width 17
 #define no_height 17
 #define no_x_hot 8
 #define no_y_hot 8
 static unsigned char no_bits[] = {
   0x80, 0x03, 0x00, 0xf0, 0x1f, 0x00, 0xf8, 0x3e, 0x00, 0x3c, 0x70, 0x00,
   0x7e, 0xe0, 0x00, 0xfe, 0xc0, 0x00, 0xf6, 0xc1, 0x01, 0xe7, 0xc3, 0x01,
   0xc3, 0x87, 0x01, 0x87, 0xcf, 0x00, 0x06, 0xdf, 0x00, 0x06, 0xfe, 0x00,
   0x0e, 0xfc, 0x00, 0x1c, 0x78, 0x00, 0xf8, 0x3c, 0x00, 0xf0, 0x1f, 0x00,
   0x00, 0x05, 0x00 };

Save this text in a file named "no.xbm" and use

 .mywidget configure -cursor @path/to/no.xbm

to set the cursor. I wasn't able to figure out how to define a cursor "no", so that the setting of the cursor could be identical to using the "no" cursor on Windows.


On Windows you should always create your cursor files with a size of 32x32 pixels even if you do not need so much space. If you created a 16x16 pixel cursor (as with unix above) it would get enlarged by a factor of two when displayed. A windows cursor file can easily be created from a bmp or gif file by the free Windows program IconShop [L1 ] (yes, it can also create windows icons). The only problem is, you cannot specify your own hotspot. But since the cur file format is known, we can do that with Tcl. Thus:

 set cur [open myCursorFile.cur r+]
 fconfigure $cur -encoding binary -translation binary
 # this is the file position of the x coord of the hot spot:
 seek $cur 10
 # set x=6
 puts -nonewline $cur [binary format c 6]
 # dto. for the y coord of the hot spot:
 seek $cur 12
 # set y=0
 puts -nonewline $cur [binary format c 0]
 close $cur

Now you have a nice cursor for your Unix and Windows programs.


Who can tell what to do on MacOS?


Beware of an issue relating to many Windows OS's that have spaces in the path name of default installation locations. If a software package happens to be installed in such a place, the following will fail:

 set filename "C:/Program Files/MyApp/linkcursor.cur"
 . configure -cursor @$filename

As Jeff Hobbs pointed out in c.l.t. on 25-Jan-2004, the canonical mouse cursor specification is a proper Tcl list. Therefore, the following invocation must be used to succesfully load a mouse cursor from such places:

 set filename "C:/Program Files/MyApp/linkcursor.cur"
 . configure -cursor [list @$filename]

PD - I couldnt get any of that to work under Windows myself, but the following seemed to handle the "space in path" problem OK. The $install variable here has been set to something like "C:/Program Files/MyApp" ...

 set filename [file attributes "$install/cursors/busy.xbm" -shortname]
 set maskname [file attributes "$install/cursors/busy-mask.xbm" -shortname]
 . configure -cursor [list @$filename $maskname red white]

What would be the proper way to set filename in the previous example, making use of file join ?

LV I don't use Windows, but would think that something like:

 set filename [file join "C:/Program Files" MyApp linkcursor.cur]

would be close. I have been unsuccessful, to date, in finding a way to join the drive designator and first level directory without hard coding a slash.

MG would guess that's correct since, using [file split], the trailing / remains on the drive designation, even when all the unnecessary slashes are automatically stripped:

 % file split [file join c://// "program files"]
 c:/ {program files}

spinning beach-ball


What about the new .pcf format cursors?


IgorNovikov - 2009-08-28 15:55:32

It seems the best way for custom ARGB/animated cursor implementation under X.org could be native extension to use Xcursor library. Sorry, our sK1 application is a Python driven app, so we have resolved this issue for Tkinter:

tkXcursor package: http://sk1project.org/viewpage.php?page_id=20

But actually there is no problem to add this functionality into tcl/tk or create according tk extension.