|
| 1 | +/******************************************************************************* |
| 2 | + * Copyright (c) 2025 Patrick Ziegler and others. |
| 3 | + * |
| 4 | + * This program and the accompanying materials are made available under the |
| 5 | + * terms of the Eclipse Public License 2.0 which is available at |
| 6 | + * http://www.eclipse.org/legal/epl-2.0. |
| 7 | + * |
| 8 | + * SPDX-License-Identifier: EPL-2.0 |
| 9 | + * |
| 10 | + * Contributors: |
| 11 | + * Patrick Ziegler - initial API and implementation |
| 12 | + *******************************************************************************/ |
| 13 | + |
| 14 | +package org.eclipse.gef.internal; |
| 15 | + |
| 16 | +import java.util.HashMap; |
| 17 | +import java.util.Map; |
| 18 | +import java.util.Objects; |
| 19 | + |
| 20 | +import org.eclipse.swt.SWT; |
| 21 | +import org.eclipse.swt.graphics.GC; |
| 22 | +import org.eclipse.swt.graphics.Image; |
| 23 | +import org.eclipse.swt.graphics.ImageData; |
| 24 | +import org.eclipse.swt.graphics.PaletteData; |
| 25 | +import org.eclipse.swt.graphics.Path; |
| 26 | +import org.eclipse.swt.widgets.Display; |
| 27 | + |
| 28 | +import org.eclipse.jface.resource.ImageDescriptor; |
| 29 | + |
| 30 | +import org.eclipse.draw2d.ColorConstants; |
| 31 | + |
| 32 | +/** |
| 33 | + * This class defines the shape of the default GEF-cursor used for the plug/tree |
| 34 | + * images. The cursor can't be part of the SVG itself, as it is a) |
| 35 | + * platform-specific and b) because SWT doesn't respect the |
| 36 | + * {@code non-scaling-stroke} vector effect, which ensures that the cursor |
| 37 | + * always has a stroke-width of 1px. |
| 38 | + */ |
| 39 | +public class InternalCursor { |
| 40 | + /** |
| 41 | + * Defines the shape of the cursor at 100% zoom. |
| 42 | + */ |
| 43 | + //@formatter:off |
| 44 | + private static final float[] CURSOR_POINTS = { |
| 45 | + 0f, 0f, |
| 46 | + 0f, 17f, |
| 47 | + 4f, 13f, |
| 48 | + 7f, 19f, |
| 49 | + 9f, 18f, |
| 50 | + 7f, 13f, |
| 51 | + 7f, 12f, |
| 52 | + 12f, 12f, |
| 53 | + 0f, 0f |
| 54 | + }; |
| 55 | + //@formatter:on |
| 56 | + /** |
| 57 | + * Local cache to store the cursor data for each zoom level. |
| 58 | + */ |
| 59 | + private static final Map<Integer, ImageData> CURSOR_AT_ZOOM = new HashMap<>(); |
| 60 | + /** |
| 61 | + * The default cursor that is constructed using {link {@link #CURSOR_POINTS}. |
| 62 | + * May be replaced with a custom cursor by calling |
| 63 | + * {@link #setCursorDescriptor(ImageDescriptor)}. |
| 64 | + */ |
| 65 | + private static ImageDescriptor CURRENT_CURSOR_DESCRIPTOR = ImageDescriptor |
| 66 | + .createFromImageDataProvider(zoom -> CURSOR_AT_ZOOM.computeIfAbsent(zoom, InternalCursor::getCursorAtZoom)); |
| 67 | + |
| 68 | + /** |
| 69 | + * This method generates the image data for the cursor at the given zoom level. |
| 70 | + * The points defined with {@link #CURSOR_POINTS} are scaled by the given zoom |
| 71 | + * and painted onto an image. |
| 72 | + * |
| 73 | + * @param zoom The zoom level. e.g. 100, 125, 200 |
| 74 | + * @return The cursor image data at the given zoom level. |
| 75 | + */ |
| 76 | + private static ImageData getCursorAtZoom(int zoom) { |
| 77 | + float maxWidth = 0f; |
| 78 | + float maxHeight = 0f; |
| 79 | + |
| 80 | + for (int i = 0; i < CURSOR_POINTS.length; i += 2) { |
| 81 | + maxWidth = Math.max(maxWidth, CURSOR_POINTS[i]); |
| 82 | + maxHeight = Math.max(maxHeight, CURSOR_POINTS[i + 1]); |
| 83 | + } |
| 84 | + |
| 85 | + float zoomFactor = zoom / 100.0f; |
| 86 | + |
| 87 | + int width = 1 + (int) Math.ceil(zoomFactor * maxWidth); |
| 88 | + int height = 1 + (int) Math.ceil(zoomFactor * maxHeight); |
| 89 | + |
| 90 | + // |
| 91 | + Display display = Display.getDefault(); |
| 92 | + // Construct path |
| 93 | + Path path = new Path(display); |
| 94 | + for (int i = 0; i < CURSOR_POINTS.length; i += 2) { |
| 95 | + float x = zoomFactor * CURSOR_POINTS[i]; |
| 96 | + float y = zoomFactor * CURSOR_POINTS[i + 1]; |
| 97 | + if (i == 0) { |
| 98 | + path.moveTo(x, y); |
| 99 | + } else { |
| 100 | + path.lineTo(x, y); |
| 101 | + } |
| 102 | + } |
| 103 | + // Construct image |
| 104 | + ImageData imageData = new ImageData(width, height, 32, new PaletteData(0xFF0000, 0x00FF00, 0x0000FF)); |
| 105 | + imageData.alphaData = new byte[width * height]; |
| 106 | + Image image = new Image(display, imageData); |
| 107 | + GC gc = new GC(image); |
| 108 | + gc.setAlpha(0); |
| 109 | + gc.fillRectangle(0, 0, width, height); |
| 110 | + gc.setAlpha(255); |
| 111 | + gc.setAntialias(SWT.ON); |
| 112 | + gc.setLineWidth(1); |
| 113 | + gc.setBackground(ColorConstants.white); |
| 114 | + gc.fillPath(path); |
| 115 | + gc.setBackground(ColorConstants.black); |
| 116 | + gc.drawPath(path); |
| 117 | + gc.dispose(); |
| 118 | + path.dispose(); |
| 119 | + // Image is already scaled to expected zoom level |
| 120 | + imageData = image.getImageData(100); |
| 121 | + image.dispose(); |
| 122 | + return imageData; |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Returns the image descriptor for the GEF cursor. Never {@code null}. |
| 127 | + * |
| 128 | + * @return Either the default or a custom cursor descriptor. |
| 129 | + */ |
| 130 | + public static ImageDescriptor getCursorDescriptor() { |
| 131 | + return CURRENT_CURSOR_DESCRIPTOR; |
| 132 | + } |
| 133 | + |
| 134 | + /** |
| 135 | + * Convenience method to allow replacing the default cursor descriptor. An |
| 136 | + * exception is thrown if {@code cursorDescriptor} is {@code null}. |
| 137 | + * |
| 138 | + * @param cursorDescriptor The new cursor descriptor. |
| 139 | + */ |
| 140 | + public static void setCursorDescriptor(ImageDescriptor cursorDescriptor) { |
| 141 | + Objects.requireNonNull(cursorDescriptor, "The new cursor descriptor must not be null!"); //$NON-NLS-1$ |
| 142 | + CURRENT_CURSOR_DESCRIPTOR = cursorDescriptor; |
| 143 | + } |
| 144 | +} |
0 commit comments