Skip to content

Conversation

ShahzaibIbrahim
Copy link
Contributor

@ShahzaibIbrahim ShahzaibIbrahim commented Sep 16, 2025

Use OS.CreateIconIndirect for colored cursors with source and mask

Previously, the Cursor constructor that accepted both source and mask used OS.CreateCursor, which only supports monochrome cursors. As a result, any color information in the source was ignored and the cursor always appeared black.

This change updates the constructor to use OS.CreateIconIndirect, allowing full-color cursors while still respecting the mask for transparency. DPI scaling of the source and mask now works correctly with colored cursors.

How to Test

Run the following snippet:

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class Snippet386 {

	private static final int IMAGE_SIZE_IN_POINTS = 16;

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = createShell(display);

		Combo combo = createConstructorCombo(shell);
		Label zoomLabel = createZoomLabel(shell);

		addZoomChangedListener(shell, zoomLabel);
		Group section = new Group(shell, SWT.NONE);
		section.setText("Scale");
		section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		section.setLayout(new FillLayout());
		addPaintTicks(section);

		CursorManager cursorManager = new CursorManager(display, shell, combo);
		combo.addListener(SWT.Selection, e -> cursorManager.updateCursor());
		cursorManager.updateCursor();

		shell.setSize(400, 400);
		shell.open();

		eventLoop(display, shell);
		display.dispose();
	}

	private static Shell createShell(Display display) {
		Shell shell = new Shell(display);
		shell.setText("Snippet 386");
		shell.setLayout(new GridLayout(1, false));
		return shell;
	}

	private static Combo createConstructorCombo(Composite parent) {
		Label label = new Label(parent, SWT.NONE);
		label.setText("Choose Cursor Constructor:");

		Combo combo = new Combo(parent, SWT.READ_ONLY);
		combo.setItems("Cursor(Device, int)", "Cursor(Device, ImageData, ImageData, int, int)",
				"Cursor(Device, ImageData, int, int)", "Cursor(Device, ImageDataProvider, int, int)");
		combo.select(0);
		return combo;
	}

	private static Label createZoomLabel(Shell parent) {
		Label zoomLabel = new Label(parent, SWT.NONE);
		zoomLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		setZoomLabelText(parent, zoomLabel);
		return zoomLabel;
	}

	private static void setZoomLabelText(Shell shell, Label label) {
		int zoom = shell.getMonitor().getZoom();
		int expectedCursorSize = Math.round(IMAGE_SIZE_IN_POINTS * (zoom / 100f));
		label.setText("Current zoom: " + zoom + "% \nExpected Cursor Size = " + expectedCursorSize);
	}

	private static void addZoomChangedListener(Shell shell, Label zoomLabel) {
		shell.addListener(SWT.Resize, event -> {
			setZoomLabelText(shell, zoomLabel);
			shell.layout();
		});
	}

	private static void addPaintTicks(Composite composite) {
		composite.addPaintListener(event -> {
			drawTicks(composite, event.gc);
		});
	}

	private static void drawTicks(Composite shell, GC gc) {
		int deviceZoom = shell.getMonitor().getZoom();
		float devScale = deviceZoom / 100f;
		Point client = shell.getSize();
		int xPos = (int) ((client.x / 2) - (6 * (IMAGE_SIZE_IN_POINTS / devScale)));
		int tickHeight = 10;
		int yPos = 20;

		for (int tickIndex = 0; tickIndex < 6; tickIndex++) {
			xPos += (IMAGE_SIZE_IN_POINTS / devScale);
			int yOffset = (tickIndex % 3 == 1) ? 20 : (tickIndex % 3 == 2) ? 40 : 0;

			gc.drawLine(xPos, yPos, xPos, yPos + tickHeight);
			gc.drawText(Integer.toString(tickIndex * IMAGE_SIZE_IN_POINTS), xPos - 5, yPos + 12 + yOffset);
		}
	}

	private static void eventLoop(Display display, Shell shell) {
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
	}

	public static ImageData createSolidColorImageData(int size, RGB color) {
		PaletteData palette = new PaletteData(0xFF0000, 0x00FF00, 0x0000FF);
		ImageData imageData = new ImageData(size, size, 24, palette);

		int pixel = palette.getPixel(color);
		for (int y = 0; y < size; y++) {
			for (int x = 0; x < size; x++) {
				imageData.setPixel(x, y, pixel);
			}
		}
		return imageData;
	}

	private static class CursorManager {
		private final Display display;
		private final Shell shell;
		private final Combo combo;

		CursorManager(Display display, Shell shell, Combo combo) {
			this.display = display;
			this.shell = shell;
			this.combo = combo;
		}

		void updateCursor() {
			int selection = combo.getSelectionIndex();
			Cursor oldCursor = shell.getCursor();
			if (oldCursor != null && !oldCursor.isDisposed()) {
				oldCursor.dispose();
			}
			Cursor cursor = createCursor(selection);
			if (cursor != null) {
				shell.setCursor(cursor);
			}
		}

		private Cursor createCursor(int selection) {
			switch (selection) {
			case 0:
				return new Cursor(display, SWT.CURSOR_HAND);
			case 1: {
				PaletteData rgbPalette = new PaletteData(0xFF0000, 0x00FF00, 0x0000FF);
				int bluePixel = rgbPalette.getPixel(new RGB(0, 0, 255));
				ImageData source = new ImageData(IMAGE_SIZE_IN_POINTS, IMAGE_SIZE_IN_POINTS, 24, new PaletteData(0xFF0000, 0x00FF00, 0x0000FF));

				for (int x = 0; x < IMAGE_SIZE_IN_POINTS; x++) {
					for (int y = 0; y < IMAGE_SIZE_IN_POINTS; y++) {
						source.setPixel(x, y, bluePixel);
					}
				}

				ImageData mask = new ImageData(IMAGE_SIZE_IN_POINTS, IMAGE_SIZE_IN_POINTS, 1, new PaletteData(new RGB[] {new RGB(0,0,0), new RGB(255,255,255)}));
				for (int x = 0; x < IMAGE_SIZE_IN_POINTS; x++) {
					for (int y = 0; y < IMAGE_SIZE_IN_POINTS; y++) {
						mask.setPixel(x, y, x % 2);
					}
				}

				return new Cursor(display, source, mask, IMAGE_SIZE_IN_POINTS / 2, IMAGE_SIZE_IN_POINTS / 2);
			}
			case 2: {
				RGB red = new RGB(255, 0, 0);
				return new Cursor(display, createSolidColorImageData(IMAGE_SIZE_IN_POINTS, red), 0, 0);
			}
			case 3: {
				RGB green = new RGB(0, 255, 0);
				ImageDataProvider provider = zoom -> {
					return createSolidColorImageData(IMAGE_SIZE_IN_POINTS * zoom / 100, green);
				};
				return new Cursor(display, provider, 0, 0);
			}
			default:
				return null;
			}
		}
	}
}
  • Select the Cursor(Device, ImageData, ImageData, int, int) value from drop down
  • See if the cursor is blue and transparency is also applied.

Result

Before:
20250916-1528-45 3125627

After:
20250916-1527-37 5966689

Requires

On Windows, the mouse pointer size can be increased in Accessibility
settings. Previously SWT always created cursors at their logical
bitmap size (e.g. 16x16, 32x32), ignoring the accessibility scale.

This change reads `CursorBaseSize` from
`HKCU\Control Panel\Cursors` and uses it as a scale factor when
creating SWT cursors. For example, with scale = 5, a 16px cursor
bitmap is scaled to 80px before being displayed, matching the user’s
configured pointer size.

This aligns SWT custom cursors with the system accessibility setting
and improves usability for users with enlarged cursors.
Previously, the Cursor constructor that accepted both source and mask used
OS.CreateCursor, which only supports monochrome cursors. As a result,
any color information in the source was ignored and the cursor always
appeared black.

This change updates the constructor to use OS.CreateIconIndirect, allowing
full-color cursors while still respecting the mask for transparency.
DPI scaling of the source and mask now works correctly with colored cursors.
Copy link
Contributor

Test Results

   546 files  ±0     546 suites  ±0   39m 37s ⏱️ + 7m 21s
 4 431 tests ±0   4 408 ✅  - 6   17 💤 ±0  6 ❌ +6 
16 764 runs  ±0  16 631 ✅  - 6  127 💤 ±0  6 ❌ +6 

For more details on these failures, see this check.

Results for commit de5bba9. ± Comparison against base commit 508ad4a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unify Cursor Drawing for ImageDataWithMaskCursorHandleProvider and ImageDataCursorHandleProvider
1 participant