The following code produces and infinitely pannable grid and also allows for zooming in and out. The grid is being drawn using css.
My issue is that the css grid doesn't work well when zoomed in very closely or zoomed out a lot. It also is inconsistent and will sometimes display only vertical or only horizontal lines. I'm not sure how to reliably reproduce that, it just kinda happens sometimes.
The perfect solution would be something similar to the grid in blender, where new lines come in to replace the smaller lines that are no longer visible.
Part that creates and sets css:
private void reSetStyle(Pane pane) {
Scene scene = pane.getScene();
double startPointX = pane.getTranslateX()+(scene.getWidth()/2)+(0.5*scale);
double endPointX = pane.getTranslateX()+(scene.getWidth()/2)+(10.5*scale);
double zeroX = pane.getTranslateX()+(scene.getWidth()/2)*scale;
startPointX = Math.round(startPointX * 1000d)/1000d;
endPointX = Math.round(endPointX * 1000d)/1000d;
zeroX = Math.round(zeroX * 1000d)/1000d;
double startPointY = pane.getTranslateY()+(scene.getHeight()/2)+(0.5*scale);
double endPointY = pane.getTranslateY()+(scene.getHeight()/2)+(10.5*scale);
double zeroY = pane.getTranslateY()+(scene.getHeight()/2)*scale;
startPointY = Math.round(startPointY * 1000d)/1000d;
endPointY = Math.round(endPointY * 1000d)/1000d;
zeroY = Math.round(zeroY * 1000d)/1000d;
pane.getParent().setStyle("-fx-background-color: #393939," +
"linear-gradient(from "+startPointX+"px "+zeroX+"px to "+endPointX+"px "+zeroX+"px, repeat, #2f2f2f 5%, transparent 6%)," +
"linear-gradient(from "+zeroY+"px "+startPointY+"px to "+zeroY+"px "+endPointY+"px, repeat, #2f2f2f 5%, transparent 6%);");
}
Full Code:
public class Main extends Application {
private double scale = 1d, minimumScale = 0.2, maximumScale = 2.5;
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
StackPane stackPane = new StackPane();
pane.translateXProperty().addListener((observableValue, number, t1) -> reSetStyle(pane));
pane.translateYProperty().addListener((observableValue, number, t1) -> reSetStyle(pane));
new Pannable(pane, stackPane);
stackPane.getChildren().add(pane);
stackPane.addEventFilter(ScrollEvent.ANY, scrollEvent -> {
scrollEvent.consume();
zoom(scrollEvent, pane);
});
primaryStage.setTitle("broken css");
primaryStage.setScene(new Scene(new BorderPane(stackPane), 640, 480));
primaryStage.show();
reSetStyle(pane);
}
private void zoom(ScrollEvent scrollEvent, Pane pane) {
if (scrollEvent.getDeltaY() == 0) return;
double SCALE_DELTA = 0.1;
double scaleFactor = (scrollEvent.getDeltaY() > 0) ? SCALE_DELTA : -1 * SCALE_DELTA;
double nonZoomedXOffset = pane.getTranslateX() / scale;
double nonZoomedYOffset = pane.getTranslateY() / scale;
//Rounding is needed because java will cause floating point errors otherwise
scale = clamp(minimumScale, Math.round((pane.getScaleX() + scaleFactor) * 1000d)/1000d, maximumScale);
pane.setScaleX(scale);
pane.setScaleY(scale);
pane.setTranslateX(nonZoomedXOffset * scale);
pane.setTranslateY(nonZoomedYOffset * scale);
reSetStyle(pane);
}
private void reSetStyle(Pane pane) {
Scene scene = pane.getScene();
double startPointX = pane.getTranslateX()+(scene.getWidth()/2)+(0.5*scale);
double endPointX = pane.getTranslateX()+(scene.getWidth()/2)+(10.5*scale);
double zeroX = pane.getTranslateX()+(scene.getWidth()/2)*scale;
startPointX = Math.round(startPointX * 1000d)/1000d;
endPointX = Math.round(endPointX * 1000d)/1000d;
zeroX = Math.round(zeroX * 1000d)/1000d;
double startPointY = pane.getTranslateY()+(scene.getHeight()/2)+(0.5*scale);
double endPointY = pane.getTranslateY()+(scene.getHeight()/2)+(10.5*scale);
double zeroY = pane.getTranslateY()+(scene.getHeight()/2)*scale;
startPointY = Math.round(startPointY * 1000d)/1000d;
endPointY = Math.round(endPointY * 1000d)/1000d;
zeroY = Math.round(zeroY * 1000d)/1000d;
pane.getParent().setStyle("-fx-background-color: #393939," +
"linear-gradient(from "+startPointX+"px "+zeroX+"px to "+endPointX+"px "+zeroX+"px, repeat, #2f2f2f 5%, transparent 6%)," +
"linear-gradient(from "+zeroY+"px "+startPointY+"px to "+zeroY+"px "+endPointY+"px, repeat, #2f2f2f 5%, transparent 6%);");
}
public static void main(String[] args) {
launch(args);
}
private static class Pannable implements EventHandler<MouseEvent> {
private double lastMouseX = 0, lastMouseY = 0;
private final Node eventNode;
private final Node dragNode;
public Pannable(final Node dragNode, final Node eventNode) {
this.eventNode = eventNode;
this.dragNode = dragNode;
this.eventNode.addEventHandler(MouseEvent.ANY, this);
}
@Override
public final void handle(final MouseEvent event) {
if(!event.isPrimaryButtonDown() && !event.isMiddleButtonDown()) return;
if (MouseEvent.MOUSE_PRESSED == event.getEventType()) {
if (this.eventNode.contains(event.getX(), event.getY())) {
this.lastMouseX = event.getSceneX();
this.lastMouseY = event.getSceneY();
event.consume();
}
} else if (MouseEvent.MOUSE_DRAGGED == event.getEventType()) {
((Node) event.getSource()).setCursor(Cursor.MOVE);
final double deltaX = (event.getSceneX() - this.lastMouseX);
final double deltaY = (event.getSceneY() - this.lastMouseY);
final double initialTranslateX = dragNode.getTranslateX();
final double initialTranslateY = dragNode.getTranslateY();
dragNode.setTranslateX(initialTranslateX+deltaX);
dragNode.setTranslateY(initialTranslateY+deltaY);
this.lastMouseX = event.getSceneX();
this.lastMouseY = event.getSceneY();
}
}
}
}