Placing text in a circle in javafx - java

I am currently attempting to create a series of characters in a circle within an application of Java. Essentially, the phrase "Welcome to Java" will be placed in a circle, starting at the far right with W. As characters move down the circle, they are rotated in a fashion such that the bottoms of the letters are facing inwards, in order to form a circle.
My code seems to rotate the characters appropriately, but they will not appear anywhere other than the center of the pane. So my question is: How do you space the letters away from the center in a pane using javafx utilities? My code is below:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.geometry.Pos;
public class Characters extends Application{
#Override
public void start (Stage primaryStage){
//Create the pane
GridPane pane = new GridPane();
pane.setPrefSize(600, 600);
pane.setAlignment(Pos.CENTER);
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double x = Math.cos(rotation)*100;
double y = Math.sin(rotation)*100;
//Loop
for (int i = 0; i < welcome.length(); i++){
x = Math.cos(Math.toRadians(rotation));
y = Math.sin(Math.toRadians(rotation));
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i)+"");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
public static void main (String [] args){
launch(args);
}
}

This will get you a little bit closer to your desired result. There are several mistakes in there. You should not use a GridPane here because its layout will interfere with your text placement and your formula is wrong too.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Characters extends Application {
#Override
public void start(Stage primaryStage) {
// Create the pane
// GridPane pane = new GridPane();
Pane pane = new Pane();
pane.setPrefSize(600, 600);
// pane.setAlignment(Pos.CENTER);
// Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
// Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double offset = pane.getPrefWidth() / 2;
double radius = 100;
double x = offset + Math.cos(rotation) * radius;
double y = offset + Math.sin(rotation) * radius;
// Loop
for (int i = 0; i < welcome.length(); i++) {
x = offset + Math.cos(Math.toRadians(rotation)) * radius;
y = offset + Math.sin(Math.toRadians(rotation)) * radius;
System.out.println("Y: " + y);
System.out.println("X: " + x);
Text text = new Text(x, y, welcome.charAt(i) + "");
System.out.println("Actual X" + text.getX());
System.out.println("Actual Y" + text.getY());
text.setFont(font);
text.setRotate(rotation);
pane.getChildren().add(text);
rotation += 22.5;
}
// Create the scene for the application
// Scene scene = new Scene(pane, 500, 500);
Scene scene = new Scene(pane);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
// Display
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

GridPane will basically ignore your positioning and decide the position of it's children on it's own. In this case all Text elements are put in the center of the "cell" with column=0; row=0) of the GridPane.
I personally prefer putting the Text nodes in a Group and add the Group to the parent layout instead of placing it in a Parent with a complex layouting algorithm.
The rotation is slightly easier to achieve using a Rotate transform, since this allows you to specify the pivot point of the rotation:
#Override
public void start(Stage primaryStage) {
//Create the pane
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
Group textGroup = new Group();
//Font class instance
Font font = Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 35);
//Welcome to Java string
String welcome = "Welcome to Java";
double rotation = 90;
double radius = 100d;
//Loop
for (char c : welcome.toCharArray()) {
// ignore whitespace, otherwise add rotated char
if (!Character.isWhitespace(c)) {
Text text = new Text(Character.toString(c));
text.setFont(font);
Rotate rotationMatrix = new Rotate(rotation, 0, radius);
text.getTransforms().add(rotationMatrix);
textGroup.getChildren().add(text);
}
rotation += 22.5;
}
pane.getChildren().add(textGroup);
//Create the scene for the application
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("Characters around circle");
primaryStage.setScene(scene);
//Display
primaryStage.show();
}
Result

You can do it with a PathTransition, but there is no kerning for the Text. So the characters won't look pretty well placed, also I had to insert some whitespace. Maybe some other one will make it a bit nicer.
But as a nice add-on, if you comment the line timer.start();, then the text is rotating around the circle.
import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BezierTextPlotter extends Application {
private static final String CIRCLE_TEXT = " Welcome to Java ";
#Override
public void start(final Stage stage) throws Exception {
final Text text = new Text(CIRCLE_TEXT);
text.setStyle("-fx-font-size: 40px");
Circle circle = new Circle(200, 200, 100);
final ObservableList<Text> parts = FXCollections.observableArrayList();
final ObservableList<PathTransition> transitions
= FXCollections.observableArrayList();
for (char character : text.textProperty().get().toCharArray()) {
Text c = new Text(character + "");
c.setEffect(text.getEffect());
c.setStyle(text.getStyle());
parts.add(c);
transitions.add(createPathTransition(circle, c));
}
AnchorPane ap = new AnchorPane();
ap.getChildren().addAll(parts);
for (int i = 0; i < parts.size(); i++) {
final Transition t = transitions.get(i);
t.stop();
t.jumpTo(Duration.seconds(10).multiply((i + 0.5) * 1.0 / parts.size()));
AnimationTimer timer = new AnimationTimer() {
int frameCounter = 0;
#Override
public void handle(long l) {
frameCounter++;
if (frameCounter == 1) {
t.stop();
stop();
}
}
};
timer.start();
t.play();
}
stage.setTitle("Circle Text Sample");
stage.setScene(new Scene(ap, 400, 400, Color.ALICEBLUE));
stage.show();
}
private PathTransition createPathTransition(Shape shape, Text text) {
final PathTransition trans
= new PathTransition(Duration.seconds(10), shape, text);
trans.setAutoReverse(false);
trans.setCycleCount(PathTransition.INDEFINITE);
trans.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
trans.setInterpolator(Interpolator.LINEAR);
return trans;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
And with the line timer.start() commented:

Related

How to create hexagon of hexagons as buttons in JavaFX

I want to create a hexagon of hexagons as buttons in JavaFX, I use an image and try to position some buttons to the position of each hexagon but I cannot change the position of them in a grid pane. Here is my code:
package sample;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
public class GameBoard extends GridPane {
public GameBoard(){
this.setAlignment(Pos.CENTER);
ImageView image = new ImageView();
Image hexagonImg = new Image("hexagon.jpg");
image.setFitWidth(500);
image.setFitHeight(500);
image.setImage(hexagonImg);
this.add(image,0,0);
GridPane button1Pane = new GridPane();
this.add(button1Pane,0,0);
Button button1 = new Button();
button1Pane.add(button1,1,0);
}
}
polygon hexagons
Since you need to use as buttons ; I added mouse click event wich changes stage's tittle . If you want to place hexagons side to side you may need to concider radius for x axis and apothem for y axis .
this is afunctional single class javafx app you can try
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
double HexagonRadius = 100;
Hexagon hexagon1 = new Hexagon(HexagonRadius, Color.CADETBLUE);
Hexagon hexagon2 = new Hexagon(HexagonRadius, Color.MEDIUMPURPLE);
hexagon2.setTranslateY(hexagon1.getOffsetY() * 2);
Hexagon hexagon3 = new Hexagon(HexagonRadius, Color.MEDIUMSEAGREEN);
hexagon3.setTranslateY(-hexagon1.getOffsetY() * 2);
Hexagon hexagon4 = new Hexagon(HexagonRadius, Color.CORNFLOWERBLUE);
hexagon4.setTranslateY(-hexagon1.getOffsetY());
hexagon4.setTranslateX(hexagon1.getOffsetX());
Hexagon hexagon5 = new Hexagon(HexagonRadius, Color.YELLOW);
hexagon5.setTranslateY(-hexagon1.getOffsetY());
hexagon5.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon6 = new Hexagon(HexagonRadius, Color.ORANGE);
hexagon6.setTranslateY(hexagon1.getOffsetY());
hexagon6.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon7 = new Hexagon(HexagonRadius, Color.SKYBLUE);
hexagon7.setTranslateY(hexagon1.getOffsetY());
hexagon7.setTranslateX(hexagon1.getOffsetX());
Group hexagonsGroup = new Group(hexagon1, hexagon2, hexagon3, hexagon4, hexagon5, hexagon6, hexagon7);
StackPane stackPane = new StackPane(hexagonsGroup);
var scene = new Scene(stackPane, 640, 480);
scene.setFill(Color.ANTIQUEWHITE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public class Hexagon extends Group {
private Polygon polygon;
private double radius;
private double radianStep = (2 * Math.PI) / 6;
private double offsetY;
private double offsetX;
public Hexagon(double radius, Paint color) {
this.radius = radius;
makeHexagon(radius, color);
offsetY = calculateApothem();
offsetX = radius * 1.5;
changeTittle();
}
private void makeHexagon(double radius, Paint color) {
polygon = new Polygon();
this.getChildren().add(polygon);
polygon.setFill(color);
polygon.setStroke(Color.WHITESMOKE);
polygon.setEffect(new DropShadow(10, Color.BLACK));
polygon.setStrokeWidth(10);
polygon.setStrokeType(StrokeType.INSIDE);
for (int i = 0; i < 6; i++) {
double angle = radianStep * i;
polygon.getPoints().add(Math.cos(angle) * radius / 1.1);
polygon.getPoints().add(Math.sin(angle) * radius / 1.1);
}
}
public void changeTittle() {
polygon.setOnMouseClicked(e -> {
Stage stage = (Stage) this.getScene().getWindow();
stage.setTitle(polygon.getFill().toString());
});
}
public double getOffsetY() {
return offsetY;
}
public double getOffsetX() {
return offsetX;
}
private double calculateApothem() {
return (Math.tan(radianStep) * radius) / 2;
}
}
}
I would just create a Path for each Hexagon and put them in Group which you can then place in the middle of some Pane. Dealing with images and a GridPane only complicates things. Just doing some graphics directly is much easier.

JavaFX: How to move 2 Rectangles at the same time?

I have 2 rectangles representing the paddles for my Pong game. I use W/S for one rectangle and UP/DOWN for the second rectangle. When I press W to move one rectangle and then press UP to move the second rectangle, the first rectangle will stop moving and then the second rectangle will move. How do I make it so both rectangles can move simultaneously?
GraphicsContext gc;
Rectangle player11;
Rectangle player22;
Circle ball;
private int y1;
private int p1y = 381;
private int y2;
private int p2y = 381;
AnimateObjects animate;
Canvas canvas;
private AnimationTimer timer = new AnimationTimer() {
public void handle(long now) {
// update paddle positions
p1y += y1;
p2y += y2;
if (p1y < 0) {
p1y = 0;
}
if (p2y < 0) {
p2y = 0;
}
player11.setY(p1y);
player22.setY(p2y);
}
};
public EventHandler<KeyEvent> keyReleased = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
// set movement to 0, if the released key was responsible for the paddle
switch (event.getCode()) {
case W:
case S:
y1 = 0;
break;
case UP:
case DOWN:
y2 = 0;
break;
}
}
};
private EventHandler<KeyEvent> keyPressed = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
// start movement according to key pressed
switch (event.getCode()) {
case W:
y1 = -6;
break;
case S:
y1 = 6;
break;
case UP:
y2 = -6;
break;
case DOWN:
y2 = 6;
break;
}
}
};
public static void main(String[] args) {
launch();
}//main
public void start(Stage stage) {
stage.setTitle("Pong");
Group root = new Group();
canvas = new Canvas(1000, 800);
root.getChildren().add(canvas);
Scene scene = new Scene(root, Color.GREEN);
stage.setScene(scene);
player11 = new Rectangle(30, p1y, 20, 70);
player11.setFill(Color.RED);
player22 = new Rectangle(750, p2y, 20, 70);
player22.setFill(Color.BLUE);
root.getChildren().add(player11);
root.getChildren().add(player22);
scene.setOnKeyPressed(keyPressed);
scene.setOnKeyReleased(keyReleased);
ball = new Circle(10, Color.DARKSLATEBLUE);
root.getChildren().add(ball);
ball.relocate(500,350);
gc = canvas.getGraphicsContext2D();
animate = new AnimateObjects();
animate.start();
stage.show();
}//start
public class AnimateObjects extends AnimationTimer {
public void handle(long now) {
}//handle method in AnimateObjects class
}//AnimateObjects class
}//pong class
You need to capture KeyCodes. Use up, down, z, and x to control paddles. The right paddle will not move beyond the upper and lower bounds of the gameboard. The left paddle will. More comments in the code! Here is a working example.
import java.util.HashSet;
import java.util.Set;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class App extends Application
{
Rectangle leftPaddle, rightPaddle;
AnimationTimer gameLoop;
Set<KeyCode> input;
Pane gameBoard;
#Override
public void start(Stage primaryStage)
{
leftPaddle = new Rectangle(7, 100, Color.BLACK);
leftPaddle.setX(3);
leftPaddle.setY(0);
rightPaddle = new Rectangle(7, 100, Color.BLACK);
rightPaddle.setX(500 - 10);
rightPaddle.setY(0);
input = new HashSet(); //This set is used to keep up with keys that are currently being pressed.
gameBoard = new Pane(leftPaddle, rightPaddle);
VBox.setVgrow(gameBoard, Priority.ALWAYS);
gameBoard.setOnKeyPressed(event -> input.add(event.getCode()));//add keys that are currently being pressed to the set
gameBoard.setOnKeyReleased(event -> input.remove(event.getCode()));//remove keys from the set after they are released
gameLoop = new AnimationTimer()
{
#Override
public void handle(long l)
{
movePaddle();//Call method to move paddle based on certain keys in the set
//System.out.println("playing");
}
};
Button btnStartGame = new Button("Play");
btnStartGame.setMaxWidth(Double.MAX_VALUE);
btnStartGame.setOnAction((event) -> {
gameBoard.requestFocus();//Request gameboard focus to capture keyevents
gameLoop.start();
btnStartGame.setDisable(true);
});
VBox root = new VBox(gameBoard, btnStartGame);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
//Use to move paddles based on keycodes in set
private void movePaddle()
{
if (input.contains(KeyCode.UP)) {
rightPaddle.setY(rightPaddle.getY() - 10);
if (rightPaddle.getY() < 0) {
rightPaddle.setY(0);
}
}
else if (input.contains(KeyCode.DOWN)) {
rightPaddle.setY(rightPaddle.getY() + 10);
if (rightPaddle.getY() + rightPaddle.getHeight() > gameBoard.getHeight()) {
rightPaddle.setY(gameBoard.getHeight() - rightPaddle.getHeight());
}
}
if (input.contains(KeyCode.Z)) {
leftPaddle.setY(leftPaddle.getY() - 10);
}
else if (input.contains(KeyCode.X)) {
leftPaddle.setY(leftPaddle.getY() + 10);
}
}
}

JAVAFX zoom, scroll in ScrollPane

I have JAVAFX application with zoom and scale as described here:
Scale at pivot point in an already scaled node
What I need to achieve is to place the image to right pane and keep the image on the left as depicted in the figure below.
Question
How can I embed this application into SPlitPane, where on left will be another panel.
SplitPane splitPane = new SplitPane();
splitPane.getItems().add(new Label("Left Panel"));
splitPane.getItems().add(group);
Scene scene = new Scene(splitPane, 1024, 768);
Unfortunately the code results in wrong coordinates,
For zooming inside a ScrollPane, you need to adjust the scroll positions. This is a bit more complex than just appending a transformation , which could be done, if no ScrollPane was used.
Basically you need to check the current scroll position before scaling the node and after scaling modify the scroll positions to prevent the pivot point from changing it's position because of the changed scale factor.
The following example lets you move a circle around "in" a grid and move around the grid when not clicking on a circle (pannable ScrollPane) in addition to allowing you to adjust the zoom when moving the mouse wheel while holding down the CTRL key.
import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class PivotZoom extends Application {
public static Region createContent() {
double width = 1000;
double height = 1000;
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.LIGHTGREY);
gc.fillRect(0, 0, width, height);
gc.setStroke(Color.BLUE);
gc.beginPath();
for (int i = 50; i < width; i += 50) {
gc.moveTo(i, 0);
gc.lineTo(i, height);
}
for (int i = 50; i < height; i += 50) {
gc.moveTo(0, i);
gc.lineTo(width, i);
}
gc.stroke();
Pane content = new Pane(
new Circle(50, 50, 20),
new Circle(120, 90, 20, Color.RED),
new Circle(200, 70, 20, Color.GREEN)
);
StackPane result = new StackPane(canvas, content);
result.setAlignment(Pos.TOP_LEFT);
class DragData {
double startX;
double startY;
double startLayoutX;
double startLayoutY;
Node dragTarget;
}
DragData dragData = new DragData();
content.setOnMousePressed(evt -> {
if (evt.getTarget() != content) {
// initiate drag gesture, if a child of content receives the
// event to prevent ScrollPane from panning.
evt.consume();
evt.setDragDetect(true);
}
});
content.setOnDragDetected(evt -> {
Node n = (Node) evt.getTarget();
if (n != content) {
// set start paremeters
while (n.getParent() != content) {
n = n.getParent();
}
dragData.startX = evt.getX();
dragData.startY = evt.getY();
dragData.startLayoutX = n.getLayoutX();
dragData.startLayoutY = n.getLayoutY();
dragData.dragTarget = n;
n.startFullDrag();
evt.consume();
}
});
// stop dragging when mouse is released
content.setOnMouseReleased(evt -> dragData.dragTarget = null);
content.setOnMouseDragged(evt -> {
if (dragData.dragTarget != null) {
// move dragged node
dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX);
dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY);
Point2D p = new Point2D(evt.getX(), evt.getY());
evt.consume();
}
});
return result;
}
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Region zoomTarget = createContent();
zoomTarget.setPrefSize(1000, 1000);
zoomTarget.setOnDragDetected(evt -> {
Node target = (Node) evt.getTarget();
while (target != zoomTarget && target != null) {
target = target.getParent();
}
if (target != null) {
target.startFullDrag();
}
});
Group group = new Group(zoomTarget);
// stackpane for centering the content, in case the ScrollPane viewport
// is larget than zoomTarget
StackPane content = new StackPane(group);
group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// keep it at least as large as the content
content.setMinWidth(newBounds.getWidth());
content.setMinHeight(newBounds.getHeight());
});
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPannable(true);
scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// use vieport size, if not too small for zoomTarget
content.setPrefSize(newBounds.getWidth(), newBounds.getHeight());
});
content.setOnScroll(evt -> {
if (evt.isControlDown()) {
evt.consume();
final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2;
Bounds groupBounds = group.getLayoutBounds();
final Bounds viewportBounds = scrollPane.getViewportBounds();
// calculate pixel offsets from [0, 1] range
double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
// convert content coordinates to zoomTarget coordinates
Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY())));
// calculate adjustment of scroll position (pixels)
Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
// do the resizing
zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX());
zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY());
// refresh ScrollPane scroll positions & content bounds
scrollPane.layout();
// convert back to [0, 1] range
// (too large/small values are automatically corrected by ScrollPane)
groupBounds = group.getLayoutBounds();
scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
}
});
StackPane left = new StackPane(new Label("Left Menu"));
SplitPane root = new SplitPane(left, scrollPane);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Try adding what it is you wish to zoom and scroll to a group layout. Then add the group to a scrollpane. This will keep the node you are zooming on the left position of the scrollpane. Hope this solves your problem!
Node you are zooming inside group,
Group inside scrollpane,
Scrollpane on the rightside of the
Splitpane!!

JavaFX removing more than one circle while creating bounding rectangle

I am making a javafx application that creates a bounded rectangle around the circles the user creates when primary mouse clicking. The user can also remove a circle with the secondary mouse button and the bounding rectangle should react accordingly and use the remaining circles as its upper and lower limits. The problem with my program is that it does not work when I try to remove more than one circle in a row (it only removes and resizes for the last circle)
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collections;
public class BoundRectangle extends Application {
double minX, maxX, minY, maxY;
Pane pane = new Pane();
Rectangle rectangle;
ArrayList<Circle> allCircles = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
VBox mainBox = new VBox();
Pane mainPane = new Pane(mainBox);
mainPane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().addAll(mainPane);
mainBox.setLayoutX(10);
mainBox.setLayoutY(10);
pane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
Circle circle = new Circle(e.getX(), e.getY(), 10);
allCircles.add(circle);
pane.getChildren().add(drawRectangle());
pane.getChildren().add(circle);
System.out.println("maxX " + maxX);
System.out.println("maxY " + maxY);
System.out.println("minX " + minX);
System.out.println("minY " + minY);
circle.setOnMouseClicked(evt -> {
if (evt.getButton() == MouseButton.SECONDARY) {
pane.getChildren().remove(circle);
allCircles.remove(circle);
pane.getChildren().add(drawRectangle());
}
});
circle.setStroke(Color.BURLYWOOD);
circle.setStrokeWidth(3);
circle.setFill(Color.TRANSPARENT);
}
});
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.setTitle("click circles, make rectangle");
primaryStage.show();
}
public Rectangle drawRectangle() {
refresh();
getMinMax();
if (pane.getChildren().size() == 1)
{
Rectangle rect0 = new Rectangle(0, 0, 0, 0);
return rect0;
}
Rectangle boundingRect = new Rectangle();
boundingRect.setX(minX - 10 - 2);
boundingRect.setY(minY - 10 - 2);
boundingRect.setWidth(maxX - minX + 2.0 * 10 + 2);
boundingRect.setHeight(maxY - minY + 2.0 * 10 + 2);
boundingRect.setStroke(Color.BLACK);
boundingRect.setStrokeWidth(2);
boundingRect.setFill(Color.TRANSPARENT);
return boundingRect;
}
public void getMinMax() {
maxY = allCircles.get(0).getCenterY();
minY = allCircles.get(0).getCenterY();
maxX = allCircles.get(0).getCenterX();
minX = allCircles.get(0).getCenterX();
for (Circle c : allCircles) {
if (c.getCenterX() < minX)
minX = c.getCenterX();
if (c.getCenterX() > maxX)
maxX = c.getCenterX();
if (c.getCenterY() < minY)
minY = c.getCenterY();
if (c.getCenterY() > maxY)
maxY = c.getCenterY();
}
}
private void refresh() {
ObservableList<Node> list = pane.getChildren();
for (Node c : list) {
if (c instanceof Rectangle) {
pane.getChildren().remove(c);
break;
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
The last circle you add appears on top of the rectangle; however the rectangle appears on top of all the other circles. So only the last circle added will get the mouse clicks (clicks on other circles are targeted to the rectangle).
The quickest fix is to make the rectangle mouse transparent:
boundingRect.setMouseTransparent(true);
A better solution overall might be just to create one rectangle and update its x, y, width and height properties. That way you can just ensure the rectangle is added once and remains at the bottom of the stack.

How show specific part of an image in javafx

i have this picture(all of these effects are in one .png file ) i want to display for example second picture how can i use Image and ImageView in javafx to display specific part of this image ? thanks
This answer is overkill. But with a nice set of images like you have in your question, maybe overkill is what is called for :-)
The fundamental design is the same as Uluk's, it just adjusts the Viewport of the ImageView rather than setting a clip, but the concept is the same.
Beware => Java 8
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
class ExploadableImageView extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Duration FRAME_TIME = Duration.seconds(.5);
public ExploadableImageView(Image explosionImage, int numCells) {
this.numCells = numCells;
double cellWidth = explosionImage.getWidth() / numCells;
double cellHeight = explosionImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(explosionImage);
setViewport(cellClips[0]);
}
public void explode(EventHandler<ActionEvent> onFinished) {
final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
Timeline kaboom = new Timeline(
new KeyFrame(FRAME_TIME, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
kaboom.setCycleCount(numCells);
kaboom.setOnFinished(onFinished);
kaboom.play();
}
}
class ExplodableItem extends StackPane {
public ExplodableItem(Image objectImage, Image explosionImage, int numCells) {
ImageView objectView = new ImageView(objectImage);
ExploadableImageView explosionView = new ExploadableImageView(
explosionImage, numCells
);
setMinSize(
Math.max(
objectImage.getWidth(),
explosionView.getViewport().getWidth()
),
Math.max(
objectImage.getHeight(),
explosionView.getViewport().getHeight()
)
);
objectView.setPickOnBounds(false);
objectView.setOnMouseClicked(event -> {
getChildren().setAll(explosionView);
explosionView.explode(complete -> getChildren().setAll(objectView));
});
DropShadow drop = new DropShadow(10, Color.GOLD);
drop.setInput(new Glow());
objectView.setOnMouseEntered(event -> objectView.setEffect(drop));
objectView.setOnMouseExited(event -> objectView.setEffect(null));
getChildren().setAll(objectView);
}
}
public class CatWhack extends Application {
public static void main(String[] args) {
launch(args);
}
private static final int NUM_CELLS_PER_EXPLOSION = 6;
#Override
public void start(Stage stage) {
Image objectImage = new Image("http://icons.iconarchive.com/icons/iconka/meow/96/cat-box-icon.png"); // cat icon linkware: backlink to http://www.iconka.com required
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(CatWhack.class.getResourceAsStream("QMqbQ.png"));
Image explosionImage = new Image("http://i.stack.imgur.com/QMqbQ.png");
TilePane tiles = new TilePane();
tiles.setPrefColumns(4);
for (int i = 0; i <16; i++) {
tiles.getChildren().add(
new ExplodableItem(objectImage, explosionImage, NUM_CELLS_PER_EXPLOSION)
);
}
tiles.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stage.setTitle("Cat Whack - Click a cat to whack it!");
stage.setScene(new Scene(tiles));
stage.show();
}
}
Simpler example
Here is the same concept as demonstrated in the above game, but just with a simpler system of an animated image which can be controlled via method calls rather than user mouse clicks on the image.
The animated image is similar to a Sprite. The code below is not meant to be a production quality Sprite system (probably a true Sprite system for a game would have more functions and features), it just demonstrates very simple display of an animated image based upon a Viewport.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
class Sprite extends ImageView {
private final Rectangle2D[] cellClips;
private int numCells;
private final Timeline timeline;
private final IntegerProperty frameCounter = new SimpleIntegerProperty(0);
public Sprite(Image animationImage, int numCells, Duration frameTime) {
this.numCells = numCells;
double cellWidth = animationImage.getWidth() / numCells;
double cellHeight = animationImage.getHeight();
cellClips = new Rectangle2D[numCells];
for (int i = 0; i < numCells; i++) {
cellClips[i] = new Rectangle2D(
i * cellWidth, 0,
cellWidth, cellHeight
);
}
setImage(animationImage);
setViewport(cellClips[0]);
timeline = new Timeline(
new KeyFrame(frameTime, event -> {
frameCounter.set((frameCounter.get() + 1) % numCells);
setViewport(cellClips[frameCounter.get()]);
})
);
}
public void playOnce() {
frameCounter.set(0);
timeline.setCycleCount(numCells);
timeline.stop();
timeline.playFromStart();
}
public void playContinuously() {
frameCounter.set(0);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.stop();
timeline.playFromStart();
}
public void stop() {
frameCounter.set(0);
setViewport(cellClips[frameCounter.get()]);
timeline.stop();
}
}
public class SpriteSample extends Application {
private static final int NUM_CELLS_PER_ANIMATION = 6;
private static final Duration FRAME_TIME = Duration.seconds(.5);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// looks likes imgur may have blocked direct access to following png from a Java app (somehow).
// but you can still download the QMqbQ.png from that location
// and save it locally in the same directory as the CatWhack program
// then pick it up by replacing the new Image call with:
// new Image(Sprite.class.getResourceAsStream("QMqbQ.png"));
Image tilesheetImage = new Image(SpriteSample.class.getResourceAsStream("QMqbQ.png"));
Sprite sprite = new Sprite(tilesheetImage, NUM_CELLS_PER_ANIMATION, FRAME_TIME);
ToggleButton animationControl = new ToggleButton("Animate");
animationControl.setOnAction(event -> {
if (animationControl.isSelected()) {
animationControl.setText("Stop");
sprite.playContinuously();
} else {
animationControl.setText("Animate");
sprite.stop();
}
});
VBox layout = new VBox(10, sprite, animationControl);
layout.setPadding(new Insets(10));
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}
You can utilize the clip property of the Node, along with x property of the ImageView. Below is a demo showing the part of image in timeline, like a gif animated picture:
#Override
public void start(Stage stage) {
Group root = new Group();
Image image = new Image(this.getClass().getResource("your.png").toExternalForm());
final int numberOfFrames = 6; // in image
double frameWidth = image.getWidth() / numberOfFrames;
Scene scene = new Scene(root, frameWidth, image.getHeight());
final ImageView view = new ImageView(image);
Rectangle mask = new Rectangle(frameWidth, image.getHeight());
view.setClip(mask);
Timeline timeline = new Timeline();
for (int i = 0; i <= numberOfFrames; i++) {
KeyFrame kf = new KeyFrame(Duration.seconds(i), new KeyValue(view.xProperty(), -frameWidth * i, Interpolator.DISCRETE));
timeline.getKeyFrames().add(kf);
}
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(view);
stage.setScene(scene);
stage.show();
}
As someone who is new to JavaFX, I had the same question. The question asks for much less than the above answers provide. We just want to manually select and display a portion of the image in an ImageView without animation. Here is the simplest solution wherein the user enters the image number and presses a button to display the desired image part.
package com.pakzaban;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class Controller {
#FXML
public ImageView imageView;
public TextField numberField;
private final int IMAGE_WIDTH = 50;
private final int IMAGE_HEIGHT = 80;
private int imageNumber = 1;
public void onSetImagePressed(){
try {
imageNumber = Integer.parseInt(numberField.getText());
//SET THE WHOLE IMAGE INTO IMAGEVIEW
Image wholeImage = new Image("com/pakzaban/wholePicture.png");
imageView.setImage(wholeImage);
//SET THE VIEWPORT TO DESIRED PART OF THE IMAGE
Rectangle2D imagePart = new Rectangle2D((imageNumber - 1) * IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
imageView.setViewport(imagePart);
}
catch (Exception e){
e.printStackTrace();
}
}
}

Categories