JavaFX Scale and Translate Operation Results in Anomaly - javafx-2

I have a Pane (test_) as the center child of a border pane. The child fills its area and grows when the window is stretched as expected.
Now I scale test_. Normally it would be centered in its area, but I don't want that, so I translate it back to the upper-left corner of its area.
But now when I stretch the widow it pulls test_ away from the upper-left corner of its area. Can anyone explain why this happens? The sample below incorporates a slider that scale's test_.
Thank you.
package fxuicontrols;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ScaleTest
extends Application
implements ChangeListener<Number>
{
private final Pane test_ = new Pane();
public static void main(String[] args)
{
launch();
}
#Override
public void start(Stage stage) throws Exception
{
test_.setStyle( "-fx-background-color: blue;" );
Text text = new Text( "Upper left corner" );
text.setFill( Color.WHITE );
text.relocate( 0, 0 );
test_.getChildren().add( text );
final Slider scaler = new Slider( .25, 1, 1 );
scaler.valueProperty().addListener( this );
test_.scaleXProperty().bind( scaler.valueProperty() );
test_.scaleYProperty().bind( scaler.valueProperty() );
BorderPane root = new BorderPane();
root.setPrefSize( 250, 250 );
root.setCenter( test_ );
root.setBottom( scaler );
stage.setScene( new Scene( root ) );
stage.show();
}
#Override
public void
changed( ObservableValue<? extends Number> oVal,
Number oldNm,
Number newNum
)
{
double scale = newNum.doubleValue();
Bounds bounds = test_.getLayoutBounds();
double width = bounds.getWidth();
double height = bounds.getHeight();
test_.setTranslateX( (scale * width - width) / 2 );
test_.setTranslateY( (scale * height - height) / 2 );
}
}

The reason your solution goes awry when the Scene is resized is because Panes are resizable nodes, so the layoutbounds of the Pane is changing as the Pane is being resized, but you aren't taking that into account in your translation calculations.
The following directly uses Scale and Translate transforms to avoid any resizing related issues. Does this sample code do what you want?
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.transform.*;
import javafx.stage.Stage;
// demonstrates scaling a test pane with content in it.
// slide the slider at the bottom of the scene around to shrink and grow the content.
public class ScaleTest extends Application {
public static void main(String[] args) { launch(); }
#Override public void start(Stage stage) throws Exception {
// create a test pane for scaling.
Pane testPane = new Pane();
// make the test pane background a different color if you want to see the extent of the pane.
testPane.setStyle("-fx-background-color: blue;");
// create some text content to place in the test pane.
Text text = new Text("Upper left corner");
text.setStyle("-fx-font-size: 30px;");
text.setFill(Color.WHITE);
text.setTextOrigin(VPos.TOP);
testPane.getChildren().add(text);
Scale scaleTransform = new Scale();
testPane.getTransforms().addAll(scaleTransform, new Translate(0, 0));
// slider to scale the test pane.
final Slider scaler = new Slider(.25, 3, 1);
scaleTransform.xProperty().bind(scaler.valueProperty());
scaleTransform.yProperty().bind(scaler.valueProperty());
// stackpane added to pad out empty space when testPane is scaled small.
// stackpane also clips the zoomed content when it gets larger than it's standard layout.
final StackPane stack = new StackPane();
stack.getChildren().addAll(testPane);
StackPane.setAlignment(testPane, Pos.TOP_LEFT);
stack.setStyle("-fx-background-color: blue;");
final Rectangle clip = new Rectangle();
testPane.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) {
clip.setWidth(bounds.getWidth());
clip.setHeight(bounds.getHeight());
}
});
stack.setClip(clip);
// layout everything.
VBox layout = new VBox();
layout.setPrefSize(250, 250);
layout.getChildren().setAll(stack, scaler);
VBox.setVgrow(stack, Priority.ALWAYS);
// show the stage.
stage.setScene(new Scene(layout));
stage.show();
}
}

Related

How to dynamically resize an svg in javafx according to the size of the region

I'm keen on svg and would like to put many of them in my User Interface. But I have a problem with the size of svg. I would like to load any svg I retrieve as a parameter and resize it dynamically to the size of the control.
All the examples I found are resize thanks to the "rescale" method (as found in the following article JavaFX: How to resize button containing svg image.
But since I have no idea of the size of the original svg I don't know what factor to apply in the rescale method.
So, my question is how do I generify the following code:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.shape.SVGPath;
import javafx.stage.Stage;
public class Main extends Application{
private final int MIN_BUTTON_SIZE = 10;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
HBox root = new HBox();
root.setAlignment(Pos.CENTER);
SVGPath svg = new SVGPath();
svg.setContent("M87.5,50.002C87.5,29.293,70.712,12.5,50,12.5c-20.712,0-37.5,16.793-37.5,37.502C12.5,70.712,29.288,87.5,50,87.5" +
"c6.668,0,12.918-1.756,18.342-4.809c0.61-0.22,1.049-0.799,1.049-1.486c0-0.622-0.361-1.153-0.882-1.413l0.003-0.004l-6.529-4.002" +
"L61.98,75.79c-0.274-0.227-0.621-0.369-1.005-0.369c-0.238,0-0.461,0.056-0.663,0.149l-0.014-0.012" +
"C57.115,76.847,53.64,77.561,50,77.561c-15.199,0-27.56-12.362-27.56-27.559c0-15.195,12.362-27.562,27.56-27.562" +
"c14.322,0,26.121,10.984,27.434,24.967C77.428,57.419,73.059,63,69.631,63c-1.847,0-3.254-1.23-3.254-3.957" +
"c0-0.527,0.176-1.672,0.264-2.111l4.163-19.918l-0.018,0c0.012-0.071,0.042-0.136,0.042-0.21c0-0.734-0.596-1.33-1.33-1.33h-7.23" +
"c-0.657,0-1.178,0.485-1.286,1.112l-0.025-0.001l-0.737,3.549c-1.847-3.342-5.629-5.893-10.994-5.893" +
"c-10.202,0-19.877,9.764-19.877,21.549c0,8.531,5.101,14.775,13.632,14.775c4.75,0,9.587-2.727,12.665-7.035l0.088,0.527" +
"c0.615,3.342,9.843,7.576,15.121,7.576c7.651,0,16.617-5.156,16.617-19.932l-0.022-0.009C87.477,51.13,87.5,50.569,87.5,50.002z" +
"M56.615,56.844c-1.935,2.727-5.101,5.805-9.763,5.805c-4.486,0-7.212-3.166-7.212-7.738c0-6.422,5.013-12.754,12.049-12.754" +
"c3.958,0,6.245,2.551,7.124,4.486L56.615,56.844z");
Button buttonWithGraphics = new Button();
buttonWithGraphics.setGraphic(svg);
// Bind the Image scale property to the buttons size
svg.scaleXProperty().bind(buttonWithGraphics.widthProperty().divide(100));
svg.scaleYProperty().bind(buttonWithGraphics.heightProperty().divide(100));
// Declare a minimum size for the button
buttonWithGraphics.setMinSize(MIN_BUTTON_SIZE, MIN_BUTTON_SIZE);
root.getChildren().addAll(buttonWithGraphics);
root.layoutBoundsProperty().addListener((observableValue, oldBounds, newBounds) -> {
double size = Math.max(MIN_BUTTON_SIZE, Math.min(newBounds.getWidth(), newBounds.getHeight()));
buttonWithGraphics.setPrefSize(size, size);
}
);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I guess this is linked with the following lines:
svg.scaleXProperty().bind(buttonWithGraphics.widthProperty().divide(100));
create a resizable canvas, you can find details here
canvas can be scaled, and also graphic context can also be scale
var gc = canvas.getGraphicsContext2D();
gc.scale(0.1, 0.1);
use gc to draw the svg path, line, circle, etc.
sample code here:
var canvas = new ResizableCanvas() {
#Override
public void draw() {
var gc = getGraphicsContext2D();
gc.save();//make sure you save the status here and restore after all operations are finished
//System.out.print(getWidth()+" ");
//System.out.println(getHeight());
var width = getWidth();
var height = getHeight();
gc.clearRect(0,0,width, height);
gc.scale(width/512.002, height/512.002);
gc.beginPath();
gc.setFill(Color.web("#2D4961"));
gc.appendSVGPath("""
M399.994,0H112.008C94.337,0,80.009,14.327,80.009,31.999v342.624
c0.08,16.159,8.288,31.191,21.839,39.998l145.433,94.796c5.304,3.448,12.135,3.448,17.439,0l145.433-94.556
c13.551-8.808,21.759-23.839,21.839-39.998V31.999C431.993,14.327,417.665,0,399.994,0z M399.994,68.477
c-6.872-6.24-15.399-10.352-24.559-11.839c-1.496-9.2-5.64-17.759-11.919-24.639h36.478V68.477z M148.486,31.999
c-6.264,6.864-10.408,15.391-11.919,24.559c-9.168,1.512-17.695,5.656-24.559,11.919V31.999H148.486z M256.001,476.858
l-26.479-17.199c11.047-4.6,20.327-12.623,26.479-22.879c6.152,10.256,15.431,18.279,26.479,22.879L256.001,476.858z
M399.994,374.622c0.008,5.424-2.728,10.48-7.28,13.439l-91.756,59.917c-20.895-1.592-37.022-19.039-36.958-39.998
c0-4.416-3.584-8-8-8s-8,3.584-8,8c0.064,20.959-16.063,38.406-36.958,39.998l-91.756-59.917c-4.552-2.96-7.288-8.016-7.28-13.439
V103.995c0-17.671,14.327-31.998,31.998-31.998c4.416,0,8-3.584,8-8c0-17.671,14.327-31.999,31.999-31.999h143.993
c17.671,0,31.999,14.327,31.999,31.999c0,4.416,3.584,8,8,8c17.671,0,31.999,14.327,31.999,31.998L399.994,374.622L399.994,374.622z""");
gc.fill();
gc.setFill(Color.web("#44637F"));
gc.beginPath();
gc.appendSVGPath("""
M80.009,31.999v271.987l0,0c8.84,0,15.999-7.16,15.999-15.999V31.999
c0-8.84,7.16-15.999,15.999-15.999h271.987c8.84,0,15.999-7.16,15.999-15.999H112.008C94.329,0,80.009,14.327,80.009,31.999z""");
gc.fill();
gc.setFill(Color.web("#123247"));
gc.beginPath();
gc.appendSVGPath("""
M410.154,414.861c13.551-8.808,21.759-23.839,21.839-39.998V55.997l0,0
c-8.84,0-15.999,7.16-15.999,15.999v304.466c0,8.04-4.024,15.551-10.719,19.999L269.28,487.097
c-8.304,5.56-13.287,14.887-13.279,24.879l0,0c3.096,0.008,6.12-0.88,8.72-2.56L410.154,414.861z""");
gc.fill();
gc.restore();
}
};
canvas.widthProperty().bind(canvas.heightProperty());
canvas.heightProperty().bind(stage.getScene().heightProperty().multiply(0.1));
The solution above tries to do it dynamically, although (as you said) fails to do it in respect to the original SvgPath size.
I would recommend doing it like that:
double size = 30;
svg.setScaleX(size / svg.boundsInLocalProperty().get().getWidth());
svg.setScaleY(size / svg.boundsInLocalProperty().get().getHeight());
This will scale your SvgPath to size.

How to reduce with of vertical Text / Label

I want to place a vertical (90 degree rotated) Label on the left side of a HBox. A grid pane shall fill out the entire remaining space. If I decrease the with of the rotated label to reduce its requirred horizontal space, the text length is shrinked. Otherwise if i reduce the Height manually, nothing happens.
How can i reduce the used with of the rotated Label?
Using Scenebuilder and Windows 7.
Rotate the Label and wrap it in a Group. The layout bounds of the group will be computed after applying the transformation to the label:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class RotatedLabelTest extends Application {
#Override
public void start(Stage primaryStage) {
Label hello = new Label("Hello");
Label world = new Label("World");
hello.setRotate(90);
world.setRotate(90);
HBox root = new HBox(5, new Group(hello), new Group(world));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to add space between menus in javafx

I am trying to implement a menu. This is my code :
Menu menuFile1 = new Menu("ADD");
Menu menuFile2 = new Menu("EDIT");
Menu menuFile3 = new Menu("VIEW");
Menu menuFile4 = new Menu("HELP");
How can I put some space between each menu (that is between ADD,EDIT,VIEW and HELP) ?
Answer
Space around menus is controlled by padding (see the Region css guide).
For example:
menu.setStyle("-fx-padding: 5 10 8 10;");
sets the padding around the menu to 5 pixels on the top, 10 pixels on the right, 8 pixels on the bottom and 10 pixels on the left.
Sample
The following is a bit overcomplicated for a code sample to demonstrate this effect, but you could run it to see the effect of varying padding values.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringExpression;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SpacedOut extends Application {
#Override
public void start(final Stage stage) {
MenuBar menuBar = createMenuBar();
VBox controlPane = createControlPane(menuBar);
VBox layout = new VBox(10,
menuBar,
controlPane
);
VBox.setVgrow(controlPane, Priority.ALWAYS);
stage.setScene(new Scene(layout, 400, 200));
stage.show();
}
private MenuBar createMenuBar() {
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(
new Menu("ADD"),
new Menu("EDIT"),
new Menu("VIEW"),
new Menu("HELP")
);
return menuBar;
}
private VBox createControlPane(MenuBar menuBar) {
CheckBox useCustomPadding = new CheckBox("Use Custom Padding");
useCustomPadding.setSelected(false);
Slider padAmount = new Slider(0, 30, 15);
padAmount.setShowTickMarks(true);
padAmount.setShowTickLabels(true);
padAmount.setMajorTickUnit(10);
padAmount.setMaxWidth(200);
padAmount.disableProperty().bind(
useCustomPadding.selectedProperty().not()
);
VBox contentPane = new VBox(10,
useCustomPadding,
padAmount
);
contentPane.setPadding(new Insets(10));
StringExpression paddingExpression = Bindings.concat(
"-fx-padding: ", padAmount.valueProperty(), "px;"
);
menuBar.getMenus().forEach(
menu -> menu.styleProperty().bind(
Bindings
.when(useCustomPadding.selectedProperty())
.then(paddingExpression)
.otherwise("")
)
);
return contentPane;
}
public static void main(String[] args) {
launch(args);
}
}
With the setStyle() Method you can pass one or more css styles in one string.
Like menuFile1.setStyle("-fx-border-color: red; -fx-effect: dropshadow( one-pass-box , red , 10,0.5,0,0 );");
Alternatively you could put your style information inside a css file and add it to the Scene through.
Scene somescene = new Scene(root)
somescene.getStylesheets().add("your.css");
See the css reference of Java FX 2 or this tutorial.

JavaFx 2.x : Stage within a TabPane

I need to display one or more stage(s) within a TabPane by clicking on a button, such as the picture below
My target is to have a situation similar to JInternalFrame in Swing: how to accomplish this?
I am not able to add stage as children to the tab pane.
If this is not possible, what could be other solutions? I would like to have SplitPanes on the stage.
Thanks
PS I am using Win7, NetBeans 7.4 Beta (Build 201307092200), SceneBuilder 1.1
Edit: here is how it looks after some VFXWindows css changes
There's one thing worth notice: I have had to add a node ( in my case an HBox with prefSize(0,0), otherwise I can't move o resize the first window plotted, only the first one.
As last, I can't find a way to set windows full screen (maximize).
Here I put an example of windows from jfxtras inside of Tabs, I just modify the example.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
import jfxtras.labs.scene.control.window.CloseIcon;
import jfxtras.labs.scene.control.window.MinimizeIcon;
import jfxtras.labs.scene.control.window.Window;
public class WindowInTab extends Application {
private static int counter = 1;
private void init(Stage primaryStage) {
TabPane tabPane = new TabPane();
Tab tab = generateTab("Windows...");
Tab anotherTab = generateTab("More Windows");
tabPane.getTabs().addAll(tab, anotherTab);
primaryStage.setResizable(true);
primaryStage.setScene(new Scene(tabPane, 600, 500));
}
private Tab generateTab(String tabName) {
Tab tab = new Tab(tabName);
final Group root = new Group();
tab.setContent(root);
Button button = new Button("Add more windows");
root.getChildren().addAll(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// create a window with title "My Window"
Window w = new Window("My Window#"+counter);
// set the window position to 10,10 (coordinates inside canvas)
w.setLayoutX(10);
w.setLayoutY(10);
// define the initial window size
w.setPrefSize(300, 200);
// either to the left
w.getLeftIcons().add(new CloseIcon(w));
// .. or to the right
w.getRightIcons().add(new MinimizeIcon(w));
// add some content
w.getContentPane().getChildren().add(new Label("Content... \nof the window#"+counter++));
// add the window to the canvas
root.getChildren().add(w);
}
});
return tab;
}
public double getSampleWidth() {return 600;}
public double getSampleHeight() {return 500;}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {launch(args);}
}
I don't know if this was exactly what you were looking for. Hope it helps!

Center text on Canvas?

Can someone please give me an example on how to center text on a JavaFX 2 Canvas?
GraphicsContext has some functions like setTextAlign, but I am not sure on how to use all those methods and which of them I really need. I want to center my text vertically and horizontally.
Set the text align to center.
Set the text baseline to center.
Draw the text in the center of your canvas (by positioning it at half the canvas width and height).
Here is a sample:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.layout.StackPane;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
public class TextCanvas extends Application {
#Override public void start(Stage primaryStage) {
Canvas canvas = new Canvas(175, 40);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER);
gc.fillText(
"Text centered on your Canvas",
Math.round(canvas.getWidth() / 2),
Math.round(canvas.getHeight() / 2)
);
StackPane layout = new StackPane();
layout.getChildren().addAll(canvas);
primaryStage.setScene(new Scene(layout));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
you wanna use
control.setAlignment(Pos.CENTER); and
control.setStyle("-fx-alignment: CENTER;");
see here for some downloadable sample code of this in action.

Resources