JavaFX Circular Cutout


I am developing a JavaFX desktop application and I would like to overlay a circular viewport on another panel.

Here is an very simple image depicting what I want, the white area would be transparent and showing the underlying panel. The black area would be a solid color and prevent mouse events from triggering on the underlying panel.

Example

I have looked into using a Canvas with an event listener attached that ignores all events within the circles radius but I am wondering if there is a more efficient / less hacky method to do this?

Thanks in advance.


Answers:


Set the appropriate Shape on the clip property of the front Node.

Javadoc from the clip property:

Specifies a Node to use to define the the clipping shape for this Node. This clipping Node is not a child of this Node in the scene graph sense. Rather, it is used to define the clip for this Node.

For example, you can use an ImageView Node as a mask to represent the Clip. Or you could use one of the geometric shape Nodes such as Rectangle or Circle. Or you could use a Text node to represent the Clip.

See the class documentation for Node for scene graph structure restrictions on setting the clip. If these restrictions are violated by a change to the clip variable, the change is ignored and the previous value of the clip variable is restored.

Note that this is a conditional feature. See ConditionalFeature.SHAPE_CLIP for more information.

There is a known limitation of mixing Clip with a 3D Transform. Clipping is essentially a 2D image operation. The result of a Clip set on a Group node with 3D transformed children will cause its children to be rendered in order without Z-buffering applied between those children.

Anyway, you could to use a Rectangle that is the same size of the Node. Then, create a Circle that is centered on the Rectangle with the desired radius. From those two shapes create a new shape using Shape.subtract(rectangle, circle);. This will create a "hole" in the middle of the Rectangle that will show any underlying Node.

Code example:

private void setClipViewport(Region region) {
    int width = region.getWidth();
    int height = region.getHeight();

    Rectangle rect = new Rectangle(0, 0, width, height);
    Circle circ = new Circle(width / 2, height / 2, Math.min(width, height) / 2);

    Shape clip = Shape.subtract(rect, circ);

    region.setClip(clip);
}

My example won't resize the clip when necessary, however. So if your UI can resize you'll need to add that behavior.