I have been working on a javafx program using FXML. I am trying to dynamically create a TilePane from information pulled in from one scene, and I can't seem to get the TilePane to show up. I've tried just about everything I can think of, and I am stumped. I am posting the relevant code below. Thank you for your help!
EDIT: I've simplified the code below for easier reading. If this isn't enough, I'll make an MCVE.
Main Controller
#FXML private ScrollPane gridScroll;
private TilePane tPane;
//Method to build each cell for the tilepane
private StackPane buildCell(Stitch stitch){
StackPane pane = new StackPane();
pane.setPrefSize(5, 5);
// add labels to stackpane
Label stitchLabel = new Label(stitch.getStitchType().toString());
Label strandLabel = new Label(stitch.getNumStrands().toString());
//create rectangle to color stackpane
Rectangle rect = new Rectangle (5, 5); //set rectangle to same size as stackpane
rect.setFill(stitch.getDisplayColor()); //Color the rectangle
rect.setStroke(Color.BLACK); //border the rectangle in black
pane.getChildren().addAll(stitchLabel, strandLabel, rect);
return pane;
}
protected void createTiles(Stitch[][] stitchArray, int width, int height){
tPane = new TilePane(); //Create a new tilepane
gridScroll.setContent(tPane); //add tilepane to existing scrollpane
tPane.setPrefColumns(width); //set prefcolumns to the array width
//add cells to tilepane
for (int i=0; i<width; i++){
for (int j=0; j<height; j++){
StackPane cell = buildCell(stitchArray[i][j]);
tPane.getChildren().add(cell);
}
}
}
Code from the secondary controller that calls the tilepane to be created
#FXML void finish(){
//create data to populate tilepane with
Stitch[][] stitchArray = floss.ImageConversion.createStitchArray(posterizedImage);
int width = (int) currentPicWidth; //cast double to int
int height = (int) currentPicHeight; //cast double to int
//get the main Controller
FXMLLoader loader = new FXMLLoader(getClass().getResource("InStitchesFXML.fxml"));
try {
loader.load();
} catch (IOException e) {e.printStackTrace();}
InStitchesController isCtrl = loader.getController();
//create the tiles
isCtrl.createTiles(stitchArray, width, height);
//close the stage
importStage.close();
}
I hope this helps clarify things.
Fixed it. I rewrote my code based on this tutorial.
Then, I created a BooleanProperty object and added a change listener to it. When the BooleanProperty was set to true, the code to create tiles was executed. Then, I set the BooleanProperty to true when I exited the tile creation dialog.
If anyone has any questions, I can post code snippets.
Related
Im trying to create a scrolling background effect with two imageViews where one picture is on top of another picture and out of the window; then i try to scroll both down the window to create a scrolling effect by changing their y coordinates. I made a loop to do so and put a thread.sleep so it wouldnt do it too quickly. Then i reset the picutres positions and do the loop again. However, when i try to run the program, the window will never open. Taking out the loop obviously properly shows the window with the picutre.
public class TestBackground extends Application{
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("DRIFT STAGE");
Pane game = new Pane();
Scene gameScene = new Scene(game, 956, 740);
ImageView background = new ImageView(getClass().getResource("bg.png").toExternalForm());
game.getChildren().add(background);
ImageView background2 = new ImageView(getClass().getResource("bg.png").toExternalForm());
game.getChildren().add(background2);
background2.setY(-740);
//loop to scroll background vertically
for (int j = 0; j < 20; j++) {
for (double i = 1.0; i < 741.0; i++) {
background.setY(background.getY() + i);
background2.setY(background2.getY() + i);
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
background.setY(0.0);
background2.setY(-740.0);
}
stage.setScene(gameScene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Your loop is not the right thing to do. Use a Transition Animation on each ImageView. You want a TranslateTransition.
Something like this:
// Animation to scroll background vertically
TranslateTransition trans1 = new TranslateTransition(Duration.minutes(1), background);
trans1.setFromY(0);
trans1.setToY(740);
trans1.setCycleCount(20);
TranslateTransition trans2 = new TranslateTransition(Duration.minutes(1), background2);
trans2.setFromY(-740);
trans2.setToY(0);
trans2.setCycleCount(20);
ParallelTransition parTrans = new ParallelTransition(trans1, trans2);
parTrans.play();
If your intention is to have these images as a parallax background that scrolls "forever", set the transitions to cycle indefinitely
trans1.setCycleCount(Animation.INDEFINITE);
and use slightly different durations for each.
If you are using the same image, don't load it twice:
Image bgImg = new Image(getClass().getResource("bg.png").toExternalForm());
ImageView background = new ImageView(bgImg);
game.getChildren().add(background);
ImageView background2 = new ImageView(bgImg);
game.getChildren().add(background2);
Here's the whole start method with an added speed slider, just for fun:
#Override
public void start(Stage stage) {
stage.setTitle("DRIFT STAGE");
Pane game = new Pane();
Scene gameScene = new Scene(game, 956, 740);
Image bgImg = new Image(getClass().getResource("bg.png").toExternalForm());
ImageView background = new ImageView(bgImg);
ImageView background2 = new ImageView(bgImg);
Slider speedSlider = new Slider(0, 5, 1);
game.getChildren().addAll(background, background2, speedSlider);
// Animation to scroll background vertically
TranslateTransition trans1 = new TranslateTransition(Duration.seconds(10), background);
trans1.setFromY(0);
trans1.setToY(740);
trans1.setInterpolator(Interpolator.LINEAR);
trans1.setCycleCount(Animation.INDEFINITE);
TranslateTransition trans2 = new TranslateTransition(Duration.seconds(10), background2);
trans2.setFromY(-740);
trans2.setToY(0);
trans2.setCycleCount(Animation.INDEFINITE);
trans2.setInterpolator(Interpolator.LINEAR);
ParallelTransition parTrans = new ParallelTransition(trans1, trans2);
parTrans.rateProperty().bind(speedSlider.valueProperty());
parTrans.play();
stage.setScene(gameScene);
stage.show();
}
So I have a StackPane. Its height is set to 300. Inside the stackpane is an imageview containing an image and a label. I set the image height to 250 and the alignment to top-center. I set the label alignment to bottom center of the pane. and yet every time I run the code the image is pushed to the bottom of the frame and the label text is on top of it so it is difficult to read. It does not matter if the stackpane's height is 500, the image is 100 Pos.TOP-CENTER, and the label is baseline-center, the text will not be displayed under the image like it is supposed to. instead it is displayed on the bottom of the image. can someone tell me why this is happening?
public class VBoxProductPane extends StackPane {
private MainController mainController;
private String name;
private Image image;
private String fileName;
private double price;
private int quantity;
private Button button = new Button();
private Product product;
public VBoxProductPane(Product product){
setPrefSize(250, 275);
this.name = product.getName();
this.price = product.getPrice();
this.quantity = product.getQuantity();
this.fileName = product.getFileName();
this.product = new Product(this.name, this.price, this.quantity, this.fileName);
setImage();
setButton();
setLabel();
setOnMouseEntered(e -> {
button.setVisible(true);
});
setOnMouseExited(e -> {
button.setVisible(false);
});
}
private void setButton(){
button.setText("Explore");
getChildren().add(button);
button.setVisible(false);
button.setOnAction(e -> {
button.setVisible(true);
});
button.setOnAction(e -> {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/ProductLayout.fxml"));
Stage secondaryStage = new Stage();
loader.setController(new ProductPage(mainController, product));
secondaryStage.setTitle(product.getName());
secondaryStage.setHeight(450);
secondaryStage.setWidth(600);
Scene scene = new Scene(loader.load());
secondaryStage.setScene(scene);
secondaryStage.show();
} catch (IOException ex) {
ex.printStackTrace();
}
});
}
private void setLabel(){
Label label = new Label(getLabelText());
setAlignment(label, Pos.BOTTOM_CENTER);
// this does nothing
label.setLayoutY(250);
label.setWrapText(true);
label.setTextAlignment(TextAlignment.CENTER);
getChildren().add(label);
setAlignment(label, Pos.BOTTOM_CENTER);
}
private String getLabelText(){
if (this.product.getName() == null){
System.out.println("name is null");
}
return this.product.getName();
}
private Image getImage(){
Image image = this.product.getImage();
return image;
}
private void setImage() {
ImageView imageViews = new ImageView();
imageViews.setImage(this.product.getImage());
setAlignment(imageViews, Pos.TOP_CENTER);
// this does nothing
imageViews.setFitHeight(150);
// does not matter what this height is set to
// image is always displayed at the bottom with text over top
imageViews.setFitWidth(250);
imageViews.setY(0);
getChildren().add(imageViews);
}
}
ImageView is a very simple type of Node. It is not resizable (it doesn't have min/max/pref values). This makes it (unfortunately) really akward to use in layouts.
The StackPane for example cannot make the correct decisions on what to do with the ImageView (as it doesn't have a preferred size, or even a maximum size) and just assigns it as much space as possible.
Things you can do to solve this:
1) Wrap the ImageView in a container and set its sizes (setting the maximum sizes to be the same as the fit sizes should work).
2) Use a VBox or BorderPane so you can place the Label at the bottom correctly.
3) Use setGraphic of Label to integrate the Image directly with the Label control.
I am trying to create a Java application that uses JavaFX to allow the user to create shapes by selecting radio button options and then clicking and dragging in a BorderPane area to create their shapes.
I am on the right track so far. The problem I'm having is with getting them positioned correctly. I'm hoping that someone can help me figure out why the shapes aren't being created in the place that I expect them to.
Currently, the first shape I create gets placed in the upper left hand corner of the HBox that I have in the Center section of the BorderPane, regardless of where I click to start creating. Dragging the mouse seems to accurately size the box in accordance with the cursor.
Subsequent attempts to create shapes results in shapes created off-location of the cursor, and dragging will resize, but also not in correlation with the cursor.
Here is my code. I've taken out parts that aren't relevant to the issue at hand to hopefully make it more readable:
public class Main extends Application{
public static String shapeType = "";
public static String color = "";
static Rectangle customRectangle = null;
public void start(Stage mainStage) throws Exception{
Group root = new Group();
Scene scene = new Scene(root, 600, 400);
BorderPane borderPane = new BorderPane();
.....
HBox canvas = new HBox();
Group canvasGroup = new Group();
canvas.getChildren().add(canvasGroup);
canvas.setStyle("-fx-background-color: yellow");
borderPane.setTop(shapeOptions);
borderPane.setLeft(colorOptions);
borderPane.setCenter(canvas);
canvas.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getEventType() == MouseEvent.MOUSE_PRESSED && shapeType != ""){
switch(shapeType){
case "rectangle":
createCustomRectangle(event, color, canvasGroup);
}
}
if(event.getEventType() == MouseEvent.MOUSE_DRAGGED){
switch (shapeType){
case "rectangle":
editCustomRectangle(event);
}
}
}
});
root.getChildren().add(borderPane);
mainStage.setScene(scene);
mainStage.show();
}
public static void createCustomRectangle(MouseEvent event, String color, Group canvasGroup){
customRectangle = new Rectangle(0, 0, 10,10);
customRectangle.relocate(event.getX(), event.getY());
customRectangle.setFill(Color.RED);
canvasGroup.getChildren().add(customRectangle);
}
public static void editCustomRectangle(MouseEvent event){
customRectangle.setWidth(event.getX() - customRectangle.getTranslateX());
customRectangle.setHeight(event.getY() - customRectangle.getTranslateY());
}
public static void main(String[] args){
launch(args);
}
}
I also wanted to attach a couple of images to make my issue more clear. Here is attempting to create the first shape:
Clicking and dragging to create first shape
And here is trying to create a subsequent shape:
Clicking and dragging to create another shape
Hopefully the description, code, and images are enough to convey what's going on. Any help would be greatly appreciated.
Thanks!
Let's start fixing each problem at a time. First of all a friendly advice, try to name your variables in a way that helps the reader understand their meaning and their identity ( when I first saw the canvas variable I thought it was an actual Canvas ).
Now your layout is something like this :
BorderPane
TOP
- Something
CENTER
- HBox
- Group
- Rectangle
- Rectangle
- ...
Left
- Something
Bottom
- Something
The HBox takes all the available height and calculates it's width depending on its children. So in order to take all the
available space inside the BorderPane you need to actually specify it or bind its preferredWidthProperty with the
widthProperty of the BorderPane.
From the documentation of the Group class you can see that :
Any transform, effect, or state applied to a Group will be applied to
all children of that group. Such transforms and effects will NOT be
included in this Group's layout bounds, however, if transforms and
effects are set directly on children of this Group, those will be
included in this Group's layout bounds.
So when you relocate the Node ( the actual rectangle ) the method relocate() just set the translateX and translateY values and that transformation is applied to the Group's layout bounds as well. To fix that you could change the Group to an AnchorPane.
The way you resize the rectangle is not correct. You need to take the first mouse click coordinates when the first click event takes place and then on a drag event you will take the new coordinates, calculate the delta value of X and Y and just add that value to the width and height for the rectangle finally update the firstX and firstY variable on drag event listener :
deltaX = event.getX() - firstX;
deltaY = event.getY() - firstY;
customRectangle.setWidth(customRectangle.getWidth + deltaX);
customRectangle.setHeight(customRectangle.getHeight + deltaY);
Here is an example of the above :
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
public static String shapeType = "";
public static String color = "";
private static Rectangle customRectangle = null;
private double firstX = 0;
private double firstY = 0;
public void start(Stage mainStage) throws Exception {
BorderPane mainPane = new BorderPane();
HBox centerPane = new HBox();
centerPane.prefWidthProperty().bind(mainPane.widthProperty());
centerPane.prefHeightProperty().bind(mainPane.heightProperty());
AnchorPane anchorPane = new AnchorPane();
anchorPane.prefWidthProperty().bind(centerPane.widthProperty());
anchorPane.prefHeightProperty().bind(centerPane.heightProperty());
centerPane.getChildren().add(anchorPane);
centerPane.setStyle("-fx-background-color: yellow");
shapeType = "rectangle";
mainPane.setCenter(centerPane);
centerPane.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_PRESSED && shapeType != "") {
switch (shapeType) {
case "rectangle":
firstX = event.getX();
firstY = event.getY();
createCustomRectangle(event, color, anchorPane);
}
}
if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
switch (shapeType) {
case "rectangle":
editCustomRectangle(event);
firstX = event.getX();
firstY = event.getY();
}
}
}
});
Scene scene = new Scene(mainPane, 600, 400);
mainStage.setScene(scene);
mainStage.show();
}
public void createCustomRectangle(MouseEvent event, String color, AnchorPane canvasGroup) {
customRectangle = new Rectangle(0, 0, 10, 10); // or just set the actual X and Y from the start
customRectangle.relocate(event.getX(), event.getY());
customRectangle.setFill(Color.RED);
canvasGroup.getChildren().add(customRectangle);
}
public void editCustomRectangle(MouseEvent event) {
double deltaX = event.getX() - firstX;
double deltaY = event.getY() - firstY;
double width = customRectangle.getWidth() + deltaX;
double height = customRectangle.getHeight() + deltaY;
customRectangle.setWidth(width);
customRectangle.setHeight(height);
}
public static void main(String[] args) {
launch(args);
}
}
Well, first off, customRectangle.relocate(event.getX(), event.getY()); clearly isn't doing what it's supposed to do. It might be better to create the rectangle at the appropriate spot in the first place.
Instead of:
customRectangle = new Rectangle(0, 0, 10,10);
customRectangle.relocate(event.getX(), event.getY());
try:
customRectangle = new Rectangle(event.getX(), event.getY(), 10,10);
Secondly, it looks like customRectangle.getTranslateX() and customRectangle.getTranslateY() always return 0, so I'd take a look at those methods and see what's going on there as well. Good luck, hope this helped.
Edit:
instead of relocate maybe try using setTranslateX() and setTranslateY().
Thanks everyone for the suggestions! I found what I think is the most straight forward solution to my problem. One large part was that Group was not the kind of node that would allow my intended action. It appended every newly added Node/Object to the right of the previously created one. So, I switched to a StackPane and got better results. The problem there is that it creates everything on the center of the StackPane. My solution to that was to set the alignment for the shape as it's created. Largely, everything else remained the same.
Here is the pertinent code segments for anyone looking to perform a similar operation (my original post has more complete code):
StackPane stackCanvas = new StackPane();
stackCanvas.setStyle("-fx-background-color: yellow");
borderPane.setTop(shapeOptions);
borderPane.setLeft(colorOptions);
borderPane.setCenter(stackCanvas);
public static void createCustomRectangle(MouseEvent event, String color, StackPane stackCanvas){
customRectangle = new Rectangle();
StackPane.setAlignment(customRectangle, Pos.TOP_LEFT);
stackCanvas.getChildren().add(customRectangle);
customRectangle.setTranslateX(event.getX());
customRectangle.setTranslateY(event.getY());
customRectangle.setFill(Color.RED);
}
I use libgdx's scene2d built-in UI library to make an UI in my game. I'm interesting how can I draw 2 images(or actors) in a table one on another? I'm looking to the similar like LayerDrawable in Android. Is there any exists stuff to make it?
There is a Stack widget in libGDX UI library for these purposes.
UPDATE: if you want to place one widget on top of another widget and these widgets have different size then you should wrap the smaller widget. Something like:
//First add actual content
stack.add(content);
//Second add wrapped overlay object
Table overlay = new Table();
overlay.add(overlayWidget).expand().fillX().bottom().left();
stack.add(overlay);
stack variable is a Stack instance
It's an actual excerpt from my project. You should tune it for your case.
You can use somethings like this :
//create stage
stage = new Stage(new StretchViewport(game.width, game.height));
//create table
Table table = new Table();
table.setSize(game.width,game.height); //fullscreen
//add Image 1
table.add(new Image(game.skin.getDrawable("image1")));
//add Image 2 (create new col)
table.add(new Image(game.skin.getDrawable("image2"))).width(xx).height(xx);
//add new row
table.row();
//add Image 3 - padding to draw over image1/2 && colspan
table.add(new Image(game.skin.getDrawable("image3"))).width(xx).height(xx).padTop(-50f).colspan(2);
//add table to stage
stage.addActor(table);
/*
* Add action
*
*/
#Override
public void render(float delta) {
...
//don't forget to draw ur stage
stage.act(delta); // or stage.act(Gdx.graphics.getDeltaTime())
stage.draw();
}
Also you can add action to stage/table or image etc ... :
stage.addAction(Actions.sequence(
Actions.fadeOut(0.0001f),
Actions.fadeIn(0.5f),
Actions.delay(0.1f),
Actions.run(new Runnable() {
#Override
public void run() {
//mmmm new screen
dispose();
game.setScreen(new BoobsScreen(game));
}
})));
Of course you can draw more than image : button, textbutton, label etc or add scrollPane for Table but before take a look at Skin on libgdx
For exemple a programmatically ninepatch textbutton :
private NinePatch processNinePatchFile(String fname) {
final Texture t = new Texture(Gdx.files.internal(fname));
final int width = t.getWidth() - 2;
final int height = t.getHeight() - 2;
return new NinePatch(new TextureRegion(t, 1, 1, width, height), 3, 3, 3, 3);
}
//create skin
skin = new Skin();
...
//create font
...
//create ninepatch button from android/assets/style/button
NinePatch ninepatch = processNinePatchFile("style/button.9.png");
skin.add("button.9", ninepatch);
//create textbuttonstyle
TextButton.TextButtonStyle textButtonStyle = new TextButton.TextButtonStyle();
textButtonStyle.font = skin.getFont("font_black_38");
textButtonStyle.fontColor = Color.DARK_GRAY;
textButtonStyle.up = skin.getDrawable("button.9");
skin.add("buttonTextStyle", textButtonStyle);
//now you can create textbutton
TextButton btn = new TextButton("Hello", game.skin.get("buttonTextStyle", TextButton.TextButtonStyle.class));
//add a clicklistener
btn.addListener(new ClickListener(){
#Override
public void clicked(InputEvent event, float x, float y) {
//mmmmmm add action for exemple
stage.addAction(Actions.sequence(Actions.fadeOut(0.5f), Actions.run(new Runnable() {
#Override
public void run() {
//dispose and load screen
dispose();
game.setScreen(new MoreBoobsScreen(game));
}
})));
}
});
table.add(btn);
To create bitmapfont take a look at Gdx freetype, it's really easy to use.
Trying to add multiple items to a scrollpane I quickly found that all "addActor" functions are Unsupported. So, I went with adding a table with all the items I wanted (this code misses a image that I still want to add) to make a scrollable credits screen... but this approach (currently) doesn't allow for overflow, rendering the ScrollPane useless. (My text is only shown up to what the screen's height allows, and isn't scrollable).
What's the way to make a scrollable pane with multiple widgets in LibGDX? (I only care about Android and Win/Lin/Mac platforms at the moment.
pane = new ScrollPane(null, skin);
pane.setFillParent(true);
paneContent = new Table(skin);
paneContent.setFillParent(true);
Label temp = new Label("", skin);
temp.setAlignment(Align.left, Align.center);
temp.setText( Gdx.files.internal("licenses/credits.txt").readString("UTF-8") );
paneContent.addActor(temp);
pane.setWidget(paneContent);
stage.addActor(pane);
If you want to put multiple items into the ScrollPane you simply need to put a table into it and call add() for each widget you want to put into the ScrollPane.
Below is an example of how to make your credits scrollable:
public class ScrollTest implements ApplicationListener {
private Stage stage;
private static final String reallyLongString = "This\nIs\nA\nReally\nLong\nString\nThat\nHas\nLots\nOf\nLines\nAnd\nRepeats.\n"
+ "This\nIs\nA\nReally\nLong\nString\nThat\nHas\nLots\nOf\nLines\nAnd\nRepeats.\n"
+ "This\nIs\nA\nReally\nLong\nString\nThat\nHas\nLots\nOf\nLines\nAnd\nRepeats.\n";
#Override public void create() {
this.stage = new Stage();
Gdx.input.setInputProcessor(this.stage);
final Skin skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
final Label text = new Label(reallyLongString, skin);
text.setAlignment(Align.center);
text.setWrap(true);
final Label text2 = new Label("This is a short string!", skin);
text2.setAlignment(Align.center);
text2.setWrap(true);
final Label text3 = new Label(reallyLongString, skin);
text3.setAlignment(Align.center);
text3.setWrap(true);
final Table scrollTable = new Table();
scrollTable.add(text);
scrollTable.row();
scrollTable.add(text2);
scrollTable.row();
scrollTable.add(text3);
final ScrollPane scroller = new ScrollPane(scrollTable);
final Table table = new Table();
table.setFillParent(true);
table.add(scroller).fill().expand();
this.stage.addActor(table);
}
#Override public void render() {
this.stage.act();
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
this.stage.draw();
}
#Override public void resize(final int width, final int height) {}
#Override public void pause() {}
#Override public void resume() {}
#Override public void dispose() {}
}
Edit: Added code on setting up a table inside of a ScrollPane.