Created
August 17, 2019 11:52
-
-
Save io7m/cde1044a30caf9f8c8bdf69a0af22318 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javafx.application.Application; | |
import javafx.scene.Node; | |
import javafx.scene.Scene; | |
import javafx.scene.control.Button; | |
import javafx.scene.layout.AnchorPane; | |
import javafx.scene.layout.Border; | |
import javafx.scene.layout.BorderStroke; | |
import javafx.scene.layout.BorderStrokeStyle; | |
import javafx.scene.layout.BorderWidths; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.transform.Affine; | |
import javafx.scene.transform.Transform; | |
import javafx.stage.Stage; | |
public final class ExampleScales extends Application | |
{ | |
public ExampleScales() | |
{ | |
} | |
private static final class Translation { | |
double x = 0.0; | |
double y = 0.0; | |
} | |
private static final class Scale { | |
double x = 128.0; | |
double y = 128.0; | |
} | |
@Override | |
public void start(final Stage stage) | |
{ | |
final var translate = new Translation(); | |
final var scale = new Scale(); | |
/* | |
* A container pane for the entire view. | |
*/ | |
final var containerPane = new AnchorPane(); | |
containerPane.setStyle("-fx-background-color: #000000;"); | |
containerPane.setPrefSize(640.0, 480.0); | |
/* | |
* A group that contains all objects in the container. | |
*/ | |
final var containerGroup = new Pane(); | |
containerPane.getChildren().add(containerGroup); | |
updateGroupTransform(translate, scale, containerGroup); | |
/* | |
* Some individual objects within the scene. | |
*/ | |
createOneObject(containerGroup, 1.0, 1.0); | |
createOneObject(containerGroup, 3.0, 1.0); | |
createOneObject(containerGroup, 2.0, 2.5); | |
/* | |
* Buttons to control the "camera". | |
*/ | |
makeCameraButtons(translate, scale, containerPane, containerGroup); | |
stage.setScene(new Scene(containerPane)); | |
stage.titleProperty().setValue("Example"); | |
stage.show(); | |
} | |
private static void makeCameraButtons( | |
final Translation translate, | |
final Scale scale, | |
final AnchorPane containerPane, | |
final Pane containerGroup) | |
{ | |
final var leftButton = new Button("LEFT"); | |
leftButton.setPrefSize(64.0, 32.0); | |
final var rightButton = new Button("RIGHT"); | |
rightButton.setPrefSize(64.0, 32.0); | |
final var upButton = new Button("UP"); | |
upButton.setPrefSize(64.0, 32.0); | |
final var downButton = new Button("DOWN"); | |
downButton.setPrefSize(64.0, 32.0); | |
AnchorPane.setLeftAnchor(rightButton, Double.valueOf(64.0)); | |
AnchorPane.setTopAnchor(upButton, Double.valueOf(32.0)); | |
AnchorPane.setTopAnchor(downButton, Double.valueOf(32.0)); | |
AnchorPane.setLeftAnchor(downButton, Double.valueOf(64.0)); | |
leftButton.setOnAction(event -> { | |
translate.x -= 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
rightButton.setOnAction(event -> { | |
translate.x += 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
upButton.setOnAction(event -> { | |
translate.y -= 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
downButton.setOnAction(event -> { | |
translate.y += 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
containerPane.getChildren().add(leftButton); | |
containerPane.getChildren().add(rightButton); | |
containerPane.getChildren().add(upButton); | |
containerPane.getChildren().add(downButton); | |
final var xZoomMButton = new Button("X-"); | |
xZoomMButton.setPrefSize(64.0, 32.0); | |
final var xZoomPButton = new Button("X+"); | |
xZoomPButton.setPrefSize(64.0, 32.0); | |
final var yZoomMButton = new Button("Y-"); | |
yZoomMButton.setPrefSize(64.0, 32.0); | |
final var yZoomPButton = new Button("Y+"); | |
yZoomPButton.setPrefSize(64.0, 32.0); | |
AnchorPane.setTopAnchor(xZoomMButton, Double.valueOf(64.0)); | |
AnchorPane.setTopAnchor(xZoomPButton, Double.valueOf(64.0)); | |
AnchorPane.setTopAnchor(yZoomMButton, Double.valueOf(96.0)); | |
AnchorPane.setTopAnchor(yZoomPButton, Double.valueOf(96.0)); | |
AnchorPane.setLeftAnchor(xZoomMButton, Double.valueOf(64.0)); | |
AnchorPane.setLeftAnchor(yZoomPButton, Double.valueOf(64.0)); | |
xZoomMButton.setOnAction(event -> { | |
scale.x -= 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
xZoomPButton.setOnAction(event -> { | |
scale.x += 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
yZoomMButton.setOnAction(event -> { | |
scale.y -= 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
yZoomPButton.setOnAction(event -> { | |
scale.y += 0.5; | |
updateGroupTransform(translate, scale, containerGroup); | |
}); | |
containerPane.getChildren().add(xZoomMButton); | |
containerPane.getChildren().add(xZoomPButton); | |
containerPane.getChildren().add(yZoomMButton); | |
containerPane.getChildren().add(yZoomPButton); | |
} | |
private static void createOneObject( | |
final Pane containerGroup, | |
final double x, | |
final double y) | |
{ | |
/* | |
* A single object container. | |
*/ | |
final var objectTransformTranslate = Affine.translate(x, y); | |
final var objectExternalPane = new Pane(); | |
objectExternalPane.getTransforms().add(objectTransformTranslate); | |
objectExternalPane.setPrefSize(1.0, 1.0); | |
objectExternalPane.setStyle("-fx-background-color: #aaaaaa;"); | |
objectExternalPane.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, null, new BorderWidths(1.0 / 128.0)))); | |
containerGroup.getChildren().add(objectExternalPane); | |
/* | |
* Configure the internal group such that the scaling transform of the external | |
* pane is inverted. This allows us to work with "scene" pixel positions inside | |
* the group (so that things like labels can actually render correctly). | |
*/ | |
final var objectInternalPane = new Pane(); | |
objectExternalPane.getChildren().add(objectInternalPane); | |
containerGroup.localToParentTransformProperty() | |
.addListener((observable, oldValue, newValue) -> determineInverseScale(objectInternalPane, newValue)); | |
determineInverseScale(objectInternalPane, containerGroup.getLocalToParentTransform()); | |
/* | |
* Add a listener so that the internal pane size is set to that of the external | |
* pane, but expressed in scene pixels. | |
* | |
* XXX: This may not be correct. What we actually want is the size expressed in | |
* the coordinate system of the container of the containerGroup. | |
*/ | |
objectExternalPane.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> { | |
final var sceneBounds = objectExternalPane.localToScene(newValue); | |
objectInternalPane.setPrefSize(sceneBounds.getWidth(), sceneBounds.getHeight()); | |
}); | |
final var button = new Button("Button"); | |
objectInternalPane.getChildren().add(button); | |
} | |
private static void updateGroupTransform( | |
final Translation translate, | |
final Scale scale, | |
final Node containerGroup) | |
{ | |
final var transforms = containerGroup.getTransforms(); | |
transforms.clear(); | |
transforms.add(Affine.scale(scale.x, scale.y)); | |
transforms.add(Transform.translate(translate.x, translate.y)); | |
} | |
private static void determineInverseScale( | |
final Pane pane, | |
final Transform newValue) | |
{ | |
final var scaleX = newValue.getMxx(); | |
final var scaleY = newValue.getMyy(); | |
final var scale = Affine.scale(1.0 / scaleX, 1.0 / scaleY); | |
final var transforms = pane.getTransforms(); | |
transforms.clear(); | |
transforms.add(scale); | |
} | |
public static void main(final String[] args) | |
{ | |
Application.launch(ExampleScales.class, args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment