Search code examples
javafxscalezoom-sdk

Scale Shapes without moving it apart


I am trying to create 2 similar dimension shapes (rectangles) one below the other with scaling effect. But however when the rectangles are scaled, the 2 shapes overlap on each other. This behavior is not expected.

I would expect the rectangles to be Scaled (Zoomed) and in addition the 2 rectangles should be one below the other without any gaps in-between. How can this be achieved ?

One option is to provide scaling on the group. But it would make the text inside also scaled which is not required. Another option is using VBox, but rather I want to achieve this functionality on a Pane.

Someone suggest me a solution here

The code with which i tried to achieve is below

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.input.ScrollEvent;
import javafx.stage.Stage;


@SuppressWarnings("restriction")
public class TestRectScaling extends Application {

/**
 * @param args
 */
public static void main(final String[] args) {
  launch(args);
}

/**
 * {@inheritDoc}
 */
@Override
public void start(final Stage primaryStage) throws Exception {
  javafx.scene.layout.Pane p = new javafx.scene.layout.Pane();
  javafx.scene.Group g = new javafx.scene.Group(p);
  javafx.scene.control.ScrollPane sp = new javafx.scene.control.ScrollPane(g);

  javafx.scene.layout.StackPane stackPane1 = new javafx.scene.layout.StackPane();

  javafx.scene.shape.Rectangle rect1 = new javafx.scene.shape.Rectangle();
  stackPane1.getChildren().add(rect1);
  rect1.setWidth(100);
  rect1.setHeight(100);
  rect1.setFill(javafx.scene.paint.Color.BLUE);
  javafx.scene.text.Text text1 = new javafx.scene.text.Text("This is sample Text Rect 1");
  text1.setWrappingWidth(30);
  stackPane1.getChildren().add(text1);

  javafx.scene.layout.StackPane stackPane2 = new javafx.scene.layout.StackPane();
  javafx.scene.shape.Rectangle rect2 = new javafx.scene.shape.Rectangle();
  rect2.setWidth(100);
  rect2.setHeight(100);
  rect2.setFill(javafx.scene.paint.Color.BLUEVIOLET);
  javafx.scene.text.Text text2 = new javafx.scene.text.Text("This is sample Text Rect 2");
  text2.setWrappingWidth(30);
  stackPane2.getChildren().add(rect2);
  stackPane2.getChildren().add(text2);

  stackPane1.setLayoutX(30);
  stackPane1.setLayoutY(20);

  stackPane2.setLayoutX(30);
  stackPane2.setLayoutY(120);

  g.getChildren().add(stackPane1);
  g.getChildren().add(stackPane2);


  sp.addEventFilter(javafx.scene.input.ScrollEvent.ANY, new EventHandler<ScrollEvent>() {

    @Override
    public void handle(final ScrollEvent event) {
      double scaleDelta = 1.3d;
      double scaleFactor = 0;
      double deltaY = event.getDeltaY();
      if (deltaY < 0) {
        scaleFactor = 1 / scaleDelta;
      }
      else {
        scaleFactor = scaleDelta;
      }
      rect1.setScaleY(rect1.getScaleY() * scaleFactor);
      rect2.setScaleY(rect2.getScaleY() * scaleFactor);
    }
  });

  javafx.scene.Scene s = new javafx.scene.Scene(sp);
  primaryStage.setScene(s);
  primaryStage.show();
}
}

Let me help with an image

enter image description here

So the main intention is to scale the rectangles without altering the distance between the 2 of them. Which basically means i need a way to calculate the new Y co-ordinates after Scaling them.


Solution

  • Irrespective of your discussion about the implementation, if you are still looking for a solution with your current approach, you need to include the below listeners to your rectangles boundsInParent property.

    The idea is, when you scale a node, the boundsInParent get updated. And if you listen to that property, you can update the heights of the stackPanes and layoutY of second stackPane.

    double gap = 15.0;
    rect1.boundsInParentProperty().addListener((obs, old, bounds) -> {
        stackPane1.setPrefHeight(bounds.getHeight());
        stackPane2.setLayoutY(stackPane1.getLayoutY() + bounds.getHeight() + gap);
    });
    
    rect2.boundsInParentProperty().addListener((obs, old, val) -> {
        stackPane2.setPrefHeight(val.getHeight());
    });
    

    Update::

    Below is the demo to limit the scaling for the max/min heights.

    import javafx.application.Application;
    import javafx.stage.Stage;
    
    public class TestRectScaling extends Application {
    
        double defaultHeight = 100.0;
        double maxHeight = 400.0;
        double gap = 15.0;
    
        public static void main(final String[] args) {
            launch(args);
        }
    
        @Override
        public void start(final Stage primaryStage) throws Exception {
            javafx.scene.layout.Pane p = new javafx.scene.layout.Pane();
            javafx.scene.Group g = new javafx.scene.Group(p);
            javafx.scene.control.ScrollPane sp = new javafx.scene.control.ScrollPane(g);
    
            javafx.scene.layout.StackPane stackPane1 = new javafx.scene.layout.StackPane();
    
            javafx.scene.shape.Rectangle rect1 = new javafx.scene.shape.Rectangle();
            stackPane1.getChildren().add(rect1);
            rect1.setWidth(100);
            rect1.setHeight(defaultHeight);
            rect1.setFill(javafx.scene.paint.Color.BLUE);
            javafx.scene.text.Text text1 = new javafx.scene.text.Text("This is sample Text Rect 1");
            text1.setWrappingWidth(30);
            stackPane1.getChildren().add(text1);
    
            javafx.scene.layout.StackPane stackPane2 = new javafx.scene.layout.StackPane();
            javafx.scene.shape.Rectangle rect2 = new javafx.scene.shape.Rectangle();
            rect2.setWidth(100);
            rect2.setHeight(defaultHeight);
            rect2.setFill(javafx.scene.paint.Color.BLUEVIOLET);
            javafx.scene.text.Text text2 = new javafx.scene.text.Text("This is sample Text Rect 2");
            text2.setWrappingWidth(30);
            stackPane2.getChildren().add(rect2);
            stackPane2.getChildren().add(text2);
    
            rect1.boundsInParentProperty().addListener((obs, old, bounds) -> {
                stackPane1.setPrefHeight(bounds.getHeight());
                stackPane2.setLayoutY(stackPane1.getLayoutY() + bounds.getHeight() + gap);
            });
    
            rect2.boundsInParentProperty().addListener((obs, old, val) -> {
                stackPane2.setPrefHeight(val.getHeight());
            });
    
            stackPane1.setLayoutX(30);
            stackPane1.setLayoutY(20);
    
            stackPane2.setLayoutX(30);
            stackPane2.setLayoutY(120);
    
            g.getChildren().add(stackPane1);
            g.getChildren().add(stackPane2);
    
            sp.addEventFilter(javafx.scene.input.ScrollEvent.ANY, event -> {
                double scaleDelta = 1.3d;
                double scaleFactor = 0;
                double deltaY = event.getDeltaY();
                if (deltaY < 0) {
                    scaleFactor = 1 / scaleDelta;
                } else {
                    scaleFactor = scaleDelta;
                }
    
                double scale = rect1.getScaleY() * scaleFactor;
    
                // Determine the resultant height of this scale.
                double scaledHeight = scale * defaultHeight;
                if(scaledHeight > maxHeight){
                    // If the target height is > max, then set the scale to max height.
                    scale = maxHeight/defaultHeight;
                } else if(scaledHeight < defaultHeight){
                    // If the target height is < default, then set the scale to default height.
                    scale = 1;
                }
                rect1.setScaleY(scale);
                rect2.setScaleY(scale);
            });
    
            javafx.scene.Scene s = new javafx.scene.Scene(sp);
            primaryStage.setScene(s);
            primaryStage.show();
        }
    }