0

I have an resizable ImageView and some parts of the image should be clickable. My idea was to create a GridPane that would act as an overlay and position several transparent buttons inside it so that when a user clicks some part of the image, he would actually trigger a hidden button over the image:

<!-- parent that resizes from time to time -->
<StackPane>

    <StackPane>

        <!-- background image, preserves ration -->
        <ImageView/>

        <!-- overlay with transparent buttons -->
        <!-- should be positionion exactly the same as Image inside ImageView -->
        <GridPane>
            <!- buttons here, columns == rows -->
        </GridPane>

    </StackPane>

</StackPane>

Code looks likes this:

    StackPane stackPane_wrapper = new StackPane();
    stackPane_wrapper.setAlignment(Pos.CENTER);

    //menu wheel
    WrappedImageView imageView_wheel = new WrappedImageView();
    imageView_wheel.setImage(new Image("images/menu-wheel.png"));
    imageView_wheel.setPreserveRatio(true);
    stackPane_wrapper.getChildren().add(imageView_wheel);

    GridPane gridPane_overlay = new GridPane();
    stackPane_wrapper.getChildren().add(gridPane_overlay);

    int size = 20;

    for (int i = 0; i < size; ++i)
    {
        ColumnConstraints columnConstraints = new ColumnConstraints();
        columnConstraints.setPercentWidth(100.0d / size);
        gridPane_overlay.getColumnConstraints().add(columnConstraints);

        RowConstraints rowConstraints = new RowConstraints();
        rowConstraints.setPercentHeight(100.0d / size);
        gridPane_overlay.getRowConstraints().add(rowConstraints);
    }

    Button button = new Button();
    button.setText("test");
    //... set style to make this button transparent
    gridPane_overlay.add(button, 3, 5, 2, 4);

My problem is that I am unable to place gridPane_overlay on the same position as the Image inside ImageView. Image inside imageView_wheel keeps resizing and changing it's position, which is totally fine, but I do not know how assign it's size and position to gridPane_overlay.

I've tries adding listeners to various x/y and width/height properties and was able to achieve some results but it stopped working one the stage became maximized and kept setting completely invalid coordinates.

Update:

Seems like getParentInParent.getWidth() and getParentInParent.getHeight() return correct size of the Image inside WrappedImageView, now I need to get it's position and assign both size and position to the grid.

Update 2:

Based on the comments, I've made the following solution;

/**
 *
 * @param bounds bounds of image - result of {@link WrappedImageView#localToScene(Bounds)} from {@link WrappedImageView#getBoundsInLocal()}
 * @param x click X - {@link MouseEvent#getSceneX()} minus {@link Bounds#getMinX()}
 * @param y click Y - {@link MouseEvent#getSceneY()} minus {@link Bounds#getMinY()}
 * @return
 */
private int determineClick(Bounds bounds, double x, double y)
{
    double centerX = bounds.getWidth() / 2;
    double centerY = bounds.getHeight() / 2;

    double angle = Math.toDegrees(Math.atan2(x - centerX, y - centerY));

    Point2D center = new Point2D(centerX, centerY);
    Point2D click = new Point2D(x, y);
    double distance = center.distance(click);

    double diameter = centerX;
    boolean isInner = distance < diameter * 0.6;

    //-90 -> -135
    if (angle <= -90 && angle > -135)
    {
        if (isInner)
        {
            return 10;
        }

        return 0;
    }

    //-135 -> -180/180
    if (angle <= -135 && angle > -180)
    {
        if (isInner)
        {
            return 11;
        }

        return 1;
    }

    //-180/180 -> 135
    if (angle <= 180 && angle > 135)
    {
        if (isInner)
        {
            return 12;
        }

        return 2;
    }

    //135 -> 90
    if (angle <= 135 && angle > 90)
    {
        if (isInner)
        {
            return 13;
        }

        return 3;
    }

    //90 -> 45
    if (angle <= 90 && angle > 45)
    {
        if (isInner)
        {
            return 14;
        }

        return 4;
    }

    //45 -> -0/0
    if (angle <= 45 && angle > 0)
    {
        if (isInner)
        {
            return 15;
        }

        return 5;
    }

    //-0/0 -> -45
    if (angle <= 0 && angle > -45)
    {
        if (isInner)
        {
            return 16;
        }

        return 6;
    }

    //-45 -> -90
    if (angle <= -45 && angle > -90)
    {
        if (isInner)
        {
            return 17;
        }

        return 7;
    }

    throw new RuntimeException("Unable to determine point coordinates");
}
CorellianAle
  • 645
  • 8
  • 16
  • Firstly, you wouldn't want the `GridPane` to affect the actual size of the whole thing, so you shouldn't add any texts which would do that. Instead, the `GridPane` should resize itself to fit whatever space its parent could give it. Secondly, as far as I know, all transparent nodes are excluded from mouse events, so making it *fully* transparent is probably not going to work. – Jai Dec 03 '18 at 02:06
  • It might be easier to add a mouse click event at the `StackPane`, and use that to calculate where the click occurred, then do whatever you need based on that. – Jai Dec 03 '18 at 02:08

1 Answers1

0

I am also thinking as exactly what @Jai mentioned in the comment. You can include a mouse clicked event on the imageview itself to determine at what point of its bounds is clicked. And then you can determine the range of click position and perform your logic.

Something like...

imageView_wheel.setOnMouseClicked(e -> {
    Bounds bounds = imageView_wheel.localToScene(imageView_wheel.getBoundsInLocal());
    double x = e.getSceneX()-bounds.getMinX();
    double y = e.getSceneY()-bounds.getMinY();
    System.out.println("Clicked at :: "+x+","+y);
    // Now you know at what point in the image bounds is clicked, compute your logic as per your needs..
});
Sai Dandem
  • 8,229
  • 11
  • 26