JavaFX: How can I best place a Label centered in a Shape? - javafx

Let's say I already have a Shape on the screen. For example:
Circle circle = new Circle(x, y, radius);
circle.setFill(Color.YELLOW);
root.getChildren().add(circle);
I would like to create a Label "over" that Circle such that the Label is centered in the Circle, the font size is maximized to fit inside the Circle, etc.
I can see how this could be accomplished via binding, but that seems needlessly complicated if the position/size of these things will never change during runtime.
Thank you in advance for your help! I'm very new to JavaFX and not all that experienced at programming in the first place, so I apologize if I should've been able to find this out via my research.

Use a StackPane to automatically center the text on top of the shape.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
// java 8 code.
public class Circular extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
Text text = createText("Xyzzy");
Circle circle = encircle(text);
StackPane layout = new StackPane();
layout.getChildren().addAll(
circle,
text
);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private Text createText(String string) {
Text text = new Text(string);
text.setBoundsType(TextBoundsType.VISUAL);
text.setStyle(
"-fx-font-family: \"Times New Roman\";" +
"-fx-font-style: italic;" +
"-fx-font-size: 48px;"
);
return text;
}
private Circle encircle(Text text) {
Circle circle = new Circle();
circle.setFill(Color.ORCHID);
final double PADDING = 10;
circle.setRadius(getWidth(text) / 2 + PADDING);
return circle;
}
private double getWidth(Text text) {
new Scene(new Group(text));
text.applyCss();
return text.getLayoutBounds().getWidth();
}
}
Related
how to put a text into a circle object to display it from circle's center?
The answer to the related question discusses different bounds types for text (such as Visual bounds), in case you need that.

StackPane stackPane = new StackPane();
Circle circle = new Circle();
Label label = new Label("Hi");
circle.setFill(Color.GOLD);
circle.setStroke(Color.GRAY);
circle.radiusProperty().bind(label.widthProperty());
stackPane.getChildren().addAll(circle, label);

Related

Why is my javafx code centering all my shapes?

I'm trying to teach myself JavaFX and tried to create a simple smiley face image. But for some reason all my shapes wind up centered instead of at the x&y coordinates that I constructed them with. I can't figure out why. Can anyone help me figure it out?
Here's my code:
import javafx.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
public class smiley extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) throws Exception {
Circle head = new Circle(250, 250, 150);
head.setFill(Color.YELLOW);
Circle eyeL = new Circle(200, 175, 25);
eyeL.setFill(Color.BLACK);
Circle eyeR = new Circle(300, 175, 25);
eyeR.setFill(Color.BLACK);
double[] points = { 250.0, 200.0, 250.0, 275.0, 290.0, 275.0 };
Polygon nose = new Polygon(points);
Arc smile = new Arc(275.0, 300.0, 75.0, 50.0, 180.0, 180.0);
smile.setStroke(Color.RED);
smile.setFill(Color.YELLOW);
StackPane smiley = new StackPane();
smiley.getChildren().add(head);
smiley.getChildren().add(eyeL);
smiley.getChildren().add(eyeR);
smiley.getChildren().add(nose);
smiley.getChildren().add(smile);
Scene scene = new Scene(smiley, 500, 500);
primaryStage.setTitle("Smiley");
primaryStage.setScene(scene);
primaryStage.show();
}
}
And this is what I wind up with
StackPane lays out its children on top of each other. Just think of a stack of pancakes. The last one goes on top and the first one is at the bottom. If you set a border/padding, then the children will by place in the center within those insets. Additionally, StackPane will resize all the children to fit the content area, but if that's not possible, it will then place it in the center. I don't think that this is your intent here, so try using the Pane class, which StackPane inherits from.
Pane pane = new Pane();

JavaFX HBox layout binding as Circle

I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm

JavaFX ScrollPane and Scaling of the Content

I would like to show a photo as an ImageView in a ScrollPane with an ZoomIn and ZoomOut Function. But if I reduce by means of scale the imageview, an undesirable empty edge is created in the ScrollPane. How can you make sure that the ScrollPane is always the size of the scaled ImageView?
See the following example. For simplicity, I replaced the ImageView with a rectangle.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneDemo extends Application {
double scale;
Pane contPane = new Pane();
#Override
public void start(Stage primaryStage) {
BorderPane pane = new BorderPane();
ScrollPane sp = new ScrollPane();
sp.setContent(contPane);
sp.setVvalue(0.5);
sp.setHvalue(0.5);
Rectangle rec = new Rectangle(2820, 1240,Color.RED);
scale = 0.2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
contPane.getChildren().add(rec);
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
scale*=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
scale/=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
contPane scaled by using transform don't change its layoutBounds automatically. If you want not to make empty space in contPane, you'd better wrap the node in Group.
See this post. Layout using the transformed bounds
sp.setContent(new Group(contPane));
In addition, if you don't want to make empty space in ScrollPane, limit minimum scale to rate which width or height of the content fits viewport's one.
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
updateScale(scale * 2.0d);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
updateScale(scale / 2.0d);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
updateScale(0.2d);
private void updateScale(double newScale) {
scale = Math.max(newScale, Math.max(sp.getViewportBounds().getWidth() / rec.getWidth(), sp.getViewportBounds().getHeight() / rec.getHeight()));
contPane.setScaleX(scale);
contPane.setScaleY(scale);
}
Consider a case of the image is smaller than ScrollPane's viewport. Because for showing no empty space, this code will stretch contents when it doesn't have enough size.
In a case of huge images, TravisF's comment helps you.

JavaFX css border-radius issue

I am trying to simulate the effect one would get from this css example:
border-radius: 50%;
From searching the API and reading posts on forums including this one, I found that I should be using -fx-background-radius. This however is not giving me the wanted effect.
I setup a picture as the background using -fx-background-image:url(...) and then I want to make it into a circle.
How can I achieve this?
Update
So I see that I was not being too specific so let me try to elaborate:
I created a Pane object, that does extend the Region class from JavaFX.
main.fxml:
...
<Pane styleClass="wrapper">
<Pane layoutX="34.0" layoutY="28.0" styleClass="image" />
</Pane>
For this pane I created the styleclass image as seen above.
main.css:
.list-contact .image {
-fx-alignment:left;
-fx-pref-height:40px;
-fx-pref-width:40px;
-fx-background-radius:50%;
-fx-background-image:url(...);
-fx-background-repeat:no-repeat;
}
The effect I get:
The effect I want:
I hope this explains it better.
This is not possible from CSS alone, since ImageView does not support any of Region's CSS properties.
However you can use a Ellipse as clip for the ImageView:
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Space_Needle_2011-07-04.jpg/304px-Space_Needle_2011-07-04.jpg");
ImageView iv = new ImageView(img);
Ellipse ellipse = new Ellipse(img.getWidth() / 2, img.getHeight() / 2, img.getWidth() / 2, img.getHeight() / 2);
iv.setClip(ellipse);
Text text = new Text("Space Needle, Seattle, Washington, USA");
StackPane.setAlignment(text, Pos.TOP_CENTER);
StackPane root = new StackPane(text, iv);
Scene scene = new Scene(root, img.getWidth(), img.getHeight());
scene.setFill(Color.AQUAMARINE);
primaryStage.setScene(scene);
primaryStage.show();
}
I know it doesn't look good to let the image cover the text. This is only done for the purpose of demonstration.
It looks like a CSS border-radius: 50% should create an elliptical border, and JavaFX CSS does support the % shorthand for either -fx-border-radius or -fx-background-radius. To get the desired effect, however, use Path.subtract() to create an elliptical matte for the image, as shown below.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/38008678/230513
*/
public class Test extends Application {
private final Image IMAGE = new Image("http://i.imgur.com/kxXhIH1.jpg");
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
int w = (int) (IMAGE.getWidth());
int h = (int) (IMAGE.getHeight());
ImageView view = new ImageView(IMAGE);
Rectangle r = new Rectangle(w, h);
Ellipse e = new Ellipse(w / 2, h / 2, w / 2, h / 2);
Shape matte = Path.subtract(r, e);
matte.setFill(Color.SIENNA);
StackPane root = new StackPane();
root.getChildren().addAll(view, matte);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In one Line with Circle as Clip.You can use setClip(any shape).:
imageView.setClip(new Circle(width,height,radius);
The width,height,radius have to be slighty smaller that ImageView size to work.
Inspired by GuiGarage web site.

why is my circle filled Black even though i haven't set it to be filled

I am currently working on an assignment where i must print a circle in the center of the primary stage and 4 buttons on the bottom center area of the primary stage which move the circle, up, down, left, and right when clicked. when i run my code, my circle is filled in with the color black. I have set the stroke of the circle to be black but i have not set the circle to be filled black. I know i can just set my circle to be filled white and somewhat solve the problem, but i am wondering if anyone knows why this is happening. Also, i cannot get the Circle and the buttons to print into the same window. I can get either the circle to print by setting the primaryStage to the scene or print the buttons by setting the scene to hBox and then setting the primaryStage to the scene. How should i best change my code so that the buttons and the circle are both displayed?
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.geometry.Pos;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
public class Btest extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Create a border pane
BorderPane pane = new BorderPane();
// create Hbox, set to bottom center
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.BOTTOM_CENTER);
Button btLeft = new Button("Left");
Button btDown = new Button("Down");
Button btUp = new Button("Up");
Button btRight = new Button("Right");
hBox.getChildren().addAll(btLeft, btDown, btUp, btRight);
// Lambda's
btLeft.setOnAction((e) -> {
System.out.println("Process Left");
});
btDown.setOnAction((e) -> {
System.out.println("Process Down");
});
btUp.setOnAction(e -> {
System.out.println("Process Up");
});
btRight.setOnAction((e) -> {
System.out.println("Process Right");
});
pane.setCenter(new CenteredCircle("Center"));
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 300, 300);
//set stage and display
primaryStage.setTitle("ShowBorderPane"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
Application.launch(args);
}
}
// create custom class for circle
class CenteredCircle extends StackPane {
public CenteredCircle(String title) {
setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
Circle circle = new Circle();
circle.setStroke(Color.BLACK);
circle.setCenterX(50);
circle.setCenterY(50);
circle.setRadius(50);
getChildren().add(circle);
}
}
"Why is my circle filled Black even though i haven't set it to be filled?"
Because the default color is black. See the doc of Shape.setFill() method:
Defines parameters to fill the interior of an Shape using the settings
of the Paint context. The default value is Color.BLACK for all shapes
except Line, Polyline, and Path. The default value is null for those
shapes.
"... Also, i cannot get the Circle and the buttons to print into the same window."
Put the Hbox to the parent BorderPane, for instance into the bottom:
pane.setBottom( hBox );

Resources