Changing how mouse cursor is drawn

I’m programming a game where you can scroll the screen by moving the mouse cursor to the edge of the screen, but you can only scroll the screen a certain distance away from the player. When you get too far away, the mouse cursor changes to indicate that you can’t scroll any more.

When scrolling up or to the left, this works fine. But when scrolling down or to the right, the mouse cursor is off the edge of the screen, so you can’t actually see the cursor. What I want is for the cursor image to be displayed so that it’s bottom edge is at the bottom of the screen (or right edge is at the right of the screen), but I don’t want to move the actual cursor over.

Actually, come to think of it, moving the actual cursor over slightly wouldn’t be such a disaster, but I’d still rather avoid doing it. I just want the image to appear moved over so it can be seen.

I tried looking at the source for java.awt.Cursor, but there doesn’t appear to be any sort of paint method. So, is there some way to offset the cursor image somehow?

I’m using Java 6 beta.

I’m assuming you know about Toolkit.createCustomCursor() method which allows
you to create a custom cursor from an image.

If this API for some reason doesn’t work for you, you can always
disable the default cursor (by either setting a null cursor, or a 1x1 transparent image cursor)
and render it yourself.
Just create a mouse motion listener and render whatever you like
at the coordinates of the mouse event.

Or you can use the java.awt.MouseInfo API (added in 5.0) to get the mouse
pointer informaiton and render your custom cursor image as part
of your rendering routine (instead of doing this in mouse motion listener,
which may be expensive).

Thanks,
Dmitri

You will either:

a) need to make the cursor invisible and draw your own, limited to the edge of the window. Note that once the real cursor is outside your application window, it will appear as normal, so you effectively see two cursors in this case. The default cursor and your custom drawn one, limited to your application window. However there is an issue here. When you start a drag from within your application window to outside the window, the default is to retain your custom cursor until the end of the drag, even when this is outside your application window. You will therefore need to change the custom cursor to something non-invisible, when it is outside the application window in this case.

b) Use the Robot class to change the cursor position on the screen. However a word of caution. Using Robot in this way limits the cursor to your window, effectively capturing it. This is probably only really a suitable technique for full screen games. Also you need a sign your application to get this functionality. Finally, Robot functionality is an optional part of the Java Spec, although the set cursor position part is usually available on all platforms.

Alan

I think it can be solved without so much messing with mouse cursor. When mouse comes to the egde of the screen you can hide him and display the scrolling image at the edge of the screen where cursor is. You would have to move the image as mouse position changes.

Do you use Toolkit.getDefaultToolkit().createCustomCursor(Image cursor, Point hotSpot, String name) to create your cursors? Set your hotspot to the right edge of the of the cursor to keep it going off the right of the screen and set it to the bottom to keep it from going off the bottom.

I haven’t tried this, but the hotspot is the part of the cursor where clicks are detected, so it should keep the hotspot on the visible screen at all times.

Thank you. All the suggestions given sound like they will work. Using “createCustomCursor” sounds like it’s by far the simplest approach.

I know I’ve seen the “createCustomCursor” method in the API before, but somehow my brain just didn’t recall that hotSpot parameter. I knew that there had to be something like that somewhere, but, even so, I couldn’t find it.

As for the other suggestions, my game is a full-screen application with no drag-and-drop issues, so the other suggestions would work as well.

I assume that by “it”, you mean the relevant code. The main gist of the code is as follows. To create a new cursor, use:

/**	Creates a Cursor from an Image.
	@param cursorName the name of the Cursor to create
	@param cursorImage the Image to create the Cursor from
	@param xHotSpot the x-coordinate of the cursor's hot spot
	@param yHotSpot the y-coordinate of the cursor's hot spot
	@return a Cursor showing [cursorImage]*/
private static Cursor createCursor(final String cursorName,
	final BufferedImage cursorImage, final int xHotSpot, final int yHotSpot)
{
	ImageUtil.addAlphaChannelTransparency(cursorImage);
	return Toolkit.getDefaultToolkit().createCustomCursor(
		cursorImage, new Point(xHotSpot, yHotSpot), cursorName);
} //end createCursor

The createCustomCursor is the important method. I had to use different hotspots for different cursors so that certain cursors would be visible at the edge of the screen.

To change which cursor is being displayed, just use Component.setCursor.

What follows is a larger portion of the code (with …'s to denote omitted code as it’s a large file) to demonstrate a little bit about how I was using the cursors. This may be useful to you, but it probably won’t be. The code is long enough to be hard to understand, and the missing sections make it even more confusing. But if you think it’s worthwhile, go ahead and take a look.


/* Note: MainDisplayViewPane extends ViewPane extends JPanel, so GameViewPane is a Component.   This is why it can call setCursor.*/
public class GameViewPane extends MainDisplayViewPane
   ...
{
...

/**	Updates this GameViewPane.*/
public void update() {
	...

	//set the cursor
	//if the user is scrolling too far, output the "no scrolling" cursor
	if(noScrollCursor != null)
		setCursor(noScrollCursor);
	else //else the mouse cursor should be normal, so use the selection mode's cursor
		setCursor(mouseSelectionMode.getCursor());
} //end update

...

/**	Creates a Cursor from an Image.
	@param cursorName the name of the Cursor to create
	@param cursorImage the Image to create the Cursor from
	@return a Cursor showing [cursorImage]*/
private static Cursor createCursor(final String cursorName,
	final BufferedImage cursorImage)
{
	return createCursor(cursorName, cursorImage, 0, 0);
} //end createCursor

/**	Creates a Cursor from an Image.
	@param cursorName the name of the Cursor to create
	@param cursorImage the Image to create the Cursor from
	@param xHotSpot the x-coordinate of the cursor's hot spot
	@param yHotSpot the y-coordinate of the cursor's hot spot
	@return a Cursor showing [cursorImage]*/
private static Cursor createCursor(final String cursorName,
	final BufferedImage cursorImage, final int xHotSpot, final int yHotSpot)
{
	ImageUtil.addAlphaChannelTransparency(cursorImage);
	return Toolkit.getDefaultToolkit().createCustomCursor(
		cursorImage, new Point(xHotSpot, yHotSpot), cursorName);
} //end createCursor

/**	Creates all the "no scroll" cursors.
	@param baseDiagonalImageName the base name of the diagonal image
	@param baseNondiagonalImageName the base name of the nondiagonal image
	@return a 2D array of all the "no scroll" cursors*/
private static Cursor[][] createNoScrollCursors(final String baseDiagonalImageName,
	final String baseNondiagonalImageName)
{
	//create the array
	int numPaneScrollMagnitudes = PaneScrollDirection.values().length;
	Cursor[][] aaNoScrollCursor =
		new Cursor[numPaneScrollMagnitudes][numPaneScrollMagnitudes];

	//indices of the PaneScrollDirections
	int iNegative = PaneScrollDirection.NEGATIVE.ordinal();
	int iNone = PaneScrollDirection.NONE.ordinal();
	int iPositive = PaneScrollDirection.POSITIVE.ordinal();

	int hotSpotOffset = 8;
	BufferedImage upLeftImage = loadCursorImage(baseDiagonalImageName);
	BufferedImage upRightImage = ImageUtil.flipImageHorizontally(upLeftImage);
	BufferedImage downLeftImage = ImageUtil.flipImageVertically(upLeftImage);
	BufferedImage downRightImage = ImageUtil.flipImageVertically(upRightImage);
	aaNoScrollCursor[iNegative][iNegative] =
		createCursor("up-left no scroll", upLeftImage);
	aaNoScrollCursor[iPositive][iNegative] =
		createCursor("up-right no scroll", upRightImage,
			upRightImage.getWidth() + hotSpotOffset, 0);
	aaNoScrollCursor[iNegative][iPositive] =
		createCursor("down-left no scroll", downLeftImage, 0,
			downLeftImage.getHeight() + hotSpotOffset);
	aaNoScrollCursor[iPositive][iPositive] =
		createCursor("down-right no scroll", downRightImage,
			downRightImage.getWidth() + hotSpotOffset,
			downRightImage.getHeight() + hotSpotOffset);

	BufferedImage leftImage = loadCursorImage(baseNondiagonalImageName);
	BufferedImage upImage = ImageUtil.rotate90Degrees(leftImage);
	BufferedImage rightImage = ImageUtil.rotate90Degrees(upImage);
	BufferedImage downImage = ImageUtil.rotate90Degrees(rightImage);
	aaNoScrollCursor[iNegative][iNone] = createCursor("left no scroll", leftImage);
	aaNoScrollCursor[iNone][iNegative] = createCursor("up no scroll", upImage);
	aaNoScrollCursor[iPositive][iNone] = createCursor("right no scroll", rightImage,
		rightImage.getWidth() + hotSpotOffset, 0);
	aaNoScrollCursor[iNone][iPositive] = createCursor("down no scroll", downImage,
		0, downImage.getHeight() + hotSpotOffset);

	return aaNoScrollCursor;
} //end createNoScrollCursors

/**	Loads a cursor from a file in the cursor art directory.
	@param cursorName the name of the cusor to load
	@return Cursor [cursorName] or a default cursor if [cursorName] has no image file*/
private static Cursor loadCursor(final String cursorName) {
	Cursor cursor = null;

	BufferedImage cursorImage = loadCursorImage(cursorName);
	if(cursorImage != null) {
		cursor = createCursor(cursorName, cursorImage);
	} else {
		ErrorLog.output(null,
			RpgEngineErrorList.gameViewPaneFailedToCreateCustomCursor, cursorName);
		cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
	}

	return cursor;
} //end loadCursor

/**	Loads an image from a file in the cursor art directory.
	@param cursorName the name of the cusor to load
	@return the image for Cursor [cursorName] or a null if [cursorName] has no image file
*/
private static BufferedImage loadCursorImage(final String cursorName) {
	BufferedImage cursorImage = null;
	try {
		cursorImage = ImageUtil.loadImage(
			World.cursorDirectory + cursorName + World.defaultImageFilenameExtension);
	} catch(Exception exception) {
		ErrorLog.output(null,
			RpgEngineErrorList.gameViewPaneFailedToCreateCustomCursor, cursorName);
	}

	return cursorImage;
} //end loadCursor

...

/**	<p>Specifies how mouse selection is taking placing
	@author Steven Fletcher
	@since 2005/09/05
	@version 2006/05/09*/
private enum MouseSelectionMode {
	ATTACK("attackcursor", true),
	CHOOSE_ITEM_TARGET("itemcursor", false),
	CHOOSE_SPELL_TARGET("spellcursor", false),
	NORMAL("normalcursor", true);

	/**	Constructor.
		@param cursor the Cursor for this MouseSelectionMode
		@param bIgnoringAlliesIsAllowed whether allies can be ignored in this
	MouseSelectionMode*/
	private MouseSelectionMode(final Cursor cursor,
		final boolean bIgnoringAlliesIsAllowed)
	{
		this.cursor = cursor;
		this.bIgnoringAlliesIsAllowed = bIgnoringAlliesIsAllowed;
	} //end constructor

	/**	Constructor.
		@param cursorName the name of the Cursor for this MouseSelectionMode
		@param bIgnoringAlliesIsAllowed whether allies can be ignored in this
	MouseSelectionMode*/
	private MouseSelectionMode(final String cursorName,
		final boolean bIgnoringAlliesIsAllowed)
	{
		this(loadCursor(cursorName), bIgnoringAlliesIsAllowed);
	} //end constructor

	/**	gets the cursor for this MouseSelectionMode
		@return the cursor for this MouseSelectionMode*/
	private Cursor getCursor() {
		return cursor;
	} //end getCursor

	/**	Determines whether allies should be ignored during combat for this
	MouseSelectionMode.
		@return whether allies should be ignored during combat*/
	private boolean isIgnoringAlliesAllowed() {return bIgnoringAlliesIsAllowed;}

	//VARIABLES
	private final Cursor cursor;
	private final boolean bIgnoringAlliesIsAllowed;
}; //end enum MouseSelectionMode

...

} //end class GameViewPane