I've made myself a convenient little method here that I use to spawn a floating piece of text at a given coordinate in my GUI.
public static void spawnLabel(final String text, final Color color, final double x, final double y) {
final Popup pc = new Popup();
final Label label = new Label(text);
pc.getScene().setRoot(new AnchorPane(label));// Add label to Popup
/* Style label */
label.setTextFill(color);
label.setBackground(null);
label.setStyle("-fx-font-weight: bold;");
/* Set Popup positions */
pc.setX(0);
pc.setY(0);
final TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(2), label);
translateTransition.setFromY(y);
translateTransition.setFromX(x);
translateTransition.setToX(x);
translateTransition.setToY(y - 25);
translateTransition.setInterpolator(Interpolator.EASE_OUT);
final FadeTransition opacityTransition = new FadeTransition(Duration.seconds(2), label);
opacityTransition.setFromValue(0.7);
opacityTransition.setToValue(0.0);
pc.show(stage);// Uses the Stage of my app.
translateTransition.play();
opacityTransition.play();
opacityTransition.setOnFinished(event -> pc.hide());
}
It simply takes in some text, a color, and a location and spawns a piece of text wherever the location is. The text will have the given color and will say whatever is passed into this method. This all works fine and uses a Popup object as a window and a Label object as the text node. The text will slowly move upwards and fade away.
Here's an image of the text that shows up:
The problem I'm getting is that when the method is called multiple times, the program begins to "lag". (Usually 4-5 calls is enough to make the "lag" heavy.) What I mean by "lag" is that certain functions, like pressing buttons or hovering over some nodes, is greatly delayed.
For production, I plan to use this method quite intensively, so I can't afford to have a slight delay in common functions. I don't know what causes this lag nor how to get rid of it and I haven't found anything on google, so I've come here.
Edit 1: After repeatedly calling my method via a Button node in my GUI, the following exception occurs at random AND my hovering text stops showing up:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at javafx.stage.Window$TKBoundsConfigurator.apply(Window.java:1274)
at javafx.stage.Window$TKBoundsConfigurator.pulse(Window.java:1290)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:378)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.PaintCollector.liveRepaintRenderJob(PaintCollector.java:320)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$ViewEventNotification.run(GlassViewEventHandler.java:831)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$ViewEventNotification.run(GlassViewEventHandler.java:792)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleViewEvent$368(GlassViewEventHandler.java:871)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleViewEvent(GlassViewEventHandler.java:870)
at com.sun.glass.ui.View.handleViewEvent(View.java:539)
at com.sun.glass.ui.View.notifyResize(View.java:875)
at com.sun.glass.ui.win.WinWindow._setBounds(Native Method)
at com.sun.glass.ui.Window.setBounds(Window.java:572)
at com.sun.javafx.tk.quantum.WindowStage.setBounds(WindowStage.java:317)
at javafx.stage.Window$TKBoundsConfigurator.apply(Window.java:1274)
at javafx.stage.Window$TKBoundsConfigurator.pulse(Window.java:1290)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:378)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Unknown Source)
Edit 2: I have not found exactly what causes this lag, but I have found a workaround that lets me get rid of it. I figured out that the lag persists when a Pane of any kind is placed in the setRoot(...) call of the fourth line of my method. I used an AnchorPane there as a wrapper for my label since the label wouldn't display when passed in directly. When I removed the AnchorPane, the lag disappeared.
Anyways, after repeated tries, I got the text to display in the Popup without an AnchorPane wrapper. My method is now as follows:
/**
* Spawns a floating piece of text that flies upwards a little then
* disappears. The source point of the text is specified via the {#code x}
* and {#code y} parameters.
*
* #param text
* The text to render.
* #param color
* The color of the rendered text.
* #param x
* The starting x position of the text.
* #param y
* The starting y position of the text.
*/
public static void spawnLabel(final String text, final Color color, final double x, final double y) {
final Popup pc = new Popup();
final Label label = new Label(text);
final TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(2), label);
final FadeTransition opacityTransition = new FadeTransition(Duration.seconds(2), label);
pc.getScene().setRoot(label);
/* Style label */
label.setTextFill(color);
label.setBackground(null);
label.setStyle("-fx-font-weight: bold;");
/* Set Popup positions */
pc.setX(x);
pc.setWidth(label.getMaxWidth());
pc.setY(y - 50);
/* Build transitions */
translateTransition.setFromY(30);
translateTransition.setFromX(0);
translateTransition.setToX(0);
translateTransition.setToY(5);
translateTransition.setInterpolator(Interpolator.EASE_OUT);
opacityTransition.setFromValue(0.7);
opacityTransition.setToValue(0.0);
opacityTransition.setOnFinished(e -> pc.hide());
/* Show the Popup */
pc.show(stage);
pc.setHeight(50);
/* Play the transitions */
translateTransition.play();
opacityTransition.play();
}
Related
I am trying to cast an object contained in a node of a stackpane, into a circle object, so that I can get the radius of that object.
My application contains in the FXML document an initial track that contains a lane (basically a circle). I now want to add a new lane, for that I have a button that will get the radius of the last element contained in the stack pane that is on the FXML document, and increase the radius of the new circle object, creating a lane around the initial one.
public void addLaneOnClick(ActionEvent event) throws IOException
{
// This method will make the button add a track
// Data about coordinates to center objects according with the track //center
centerXCoordinate = trackCenter.getLayoutX();
centerYCoordinate = trackCenter.getLayoutY();
// Data from initial track
lastElementInTrack = (Circle) track.getChildren().get(track.getChildren().size() - 1);
lastElementRadius += lastElementInTrack.getRadius() + 22;
newRadius = lastElementRadius;
// The new lane
outerCircle = new Circle(centerXCoordinate, centerYCoordinate,newRadius, Color.TRANSPARENT);
outerCircle.setStroke(Color.rgb(211, 211, 211));
outerCircle.setStrokeWidth(25);
// Placing the circle in the stack
track.getChildren().add(outerCircle);
}
I was expecting that this would return me the lane. But it gives me a casting error.
java.lang.ClassCastException: javafx.scene.layout.StackPane cannot be cast to javafx.scene.shape.Circle
at application.Controller.addLaneOnClick(Controller.java:61)
I'm new to JavaFX and I have troubles with bindings.
I'm coding a game in which I draw lines between panes contains in the cells of a GridPane. I can draw lines with the code below, but I'd like to have the line coordinates to scale as the pane gets bigger when I resize the window.
The Panes and the GridPane already resize as wanted, and I've managed to bind the line's width to the size of the scene (cellSize is a binding on the scene width/height).
Pane lastPane = panes[lastC][lastR];
Pane curPane = panes[c][r];
Bounds boundsInSceneLast = lastPane.localToScene(lastPane.getBoundsInLocal());
Bounds boundsInSceneCur = curPane.localToScene(curPane.getBoundsInLocal());
double lastX = (boundsInSceneLast.getMinX() + boundsInSceneLast.getMaxX())/2;
double lastY = (boundsInSceneLast.getMinY() + boundsInSceneLast.getMaxY())/2;
double x = (boundsInSceneCur.getMinX() + boundsInSceneCur.getMaxX())/2;
double y= (boundsInSceneCur.getMinY() + boundsInSceneCur.getMaxY())/2;
Line line = new Line(lastX, lastY, x, y);
line.setStroke(Color.web(couleurs.get(symbole)));
line.strokeWidthProperty().bind(cellSize.divide(10));
line.setStrokeLineCap(StrokeLineCap.BUTT);
anchor.getChildren().add(line);
lines.get(symbole).add(line);
anchor is an AnchorPane which is the root of my Scene and lines is a HashMap which keeps track of the lines, but those shouldn't be relevant to my problem.
I'm sure it can be done pretty simply but I've searched the web pretty deeply and all I could understand and try wasn't getting the job done so I'm asking for your help.
Thanks in advance !! :)
It's problematic to listen to the bounds of a Node in the scene since there is no property for the scene bounds and every Parent on the path to the root node of the scene could apply a transform that require you to adjust the position of the line ends. This would require you to add a listener to descendant of the Panes.
It would be simpler to make the lines a part of the GridPane itself as unmanaged nodes. This allows you to work with the bounds of the nodes in the GridPane:
final Pane lastPane = panes[lastC][lastR];
final Pane curPane = panes[c][r];
final Line line = new Line();
line.setStroke(Color.web(couleurs.get(symbole)));
line.strokeWidthProperty().bind(cellSize.divide(10));
line.setStrokeLineCap(StrokeLineCap.BUTT);
InvalidationListener listener = o -> {
Bounds boundsInSceneLast = lastPane.getBoundsInParent();
Bounds boundsInSceneCur = curPane.getBoundsInParent();
double lastX = (boundsInSceneLast.getMinX() + boundsInSceneLast.getMaxX())/2;
double lastY = (boundsInSceneLast.getMinY() + boundsInSceneLast.getMaxY())/2;
double x = (boundsInSceneCur.getMinX() + boundsInSceneCur.getMaxX())/2;
double y = (boundsInSceneCur.getMinY() + boundsInSceneCur.getMaxY())/2;
line.setStartX(lastX);
line.setStartY(lastY);
line.setEndX(x);
line.setEndY(y);
};
// listen to location & size changes of panes in the GridPane
lastPane.boundsInParentProperty().addListener(listener);
curPane.boundsInParentProperty().addListener(listener);
// initial refresh
listener.invalidated(null);
// add line to GridPane without considering it for layout
line.setManaged(false);
gridPane.getChildren().add(line);
lines.get(symbole).add(line);
I'm playing around with code like this:
<s:Button id="test" label="test" transformX="{Math.floor(test.width/2)}" rotationY="20" x="20" y="20" />
The button is rotated on the Y axis and the rotate pivot is in the middle of the button.
This will create a button that looks something like this:
(source: jeffryhouser.com)
The rotated button is, visually, filling a different space than the x, y, height, and width values would you have believe.
The "A" value in my image is the height of the button. But, what I want to use for calculation and placement purposes is the B value.
Additionally, I'd like to perform similar calculations with the width; getting the width from the top right corner to the bottom left corner.
How do I do this?
I put together a sample to show off the various approaches for calculating this that people are suggesting. The source code is also available. Nothing is quite working like I'd expect. For example, turn the rotationSlider to 85. The button is effectively invisible, yet all approaches are still giving it height and width.
My math may be a bit rusty, but this is how I would find the answer :
You would extend a right-triangle from the right edge of the button to the bottom-most point of the diagram you have (A-B). You can then use the Law of Sines to get three angles : 90', 20' and 70' (90 will always be there, and then your variable - 180 for the third angle).
You can then use the following formula to find your answer :
B = ((button.width * sin(button.rotationY)) / (sin(90 -button.rotationY)) + (button.height)
getBounds(..) and getRect(..) are supposed to be the methods for getting the width and height of transformed objects.
Not tried them in Flex 4 yet, but they always worked for me in Flex 3.
The answer was in one of the comments from James Ward on this question and is located at this blog post.
The one thing the blog post doesn't say is that in many cases, the perspectiveProjection property of the transform property on the class in question will be null. The linked to example took care of this by setting the maintainProjectionCenter property to true. But, you could also create a new perspectiveProjection object like this:
object.transform.perspectiveProjection = new PerspectiveProjection();
I wrapped up the function from evtimmy into a class:
/**
* DotComIt/Flextras
* Utils3D.as
* Utils3D
* jhouser
* Aug 5, 2010
*/
package com.flextras.coverflow
{
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Rectangle;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
public class TransformUtilities
{
public function TransformUtilities()
{
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
//----------------------------------
// projectBounds
//----------------------------------
// info from
// http://evtimmy.com/2009/12/calculating-the-projected-bounds-using-utils3dprojectvector/
/**
* Method retrieved from
* http://evtimmy.com/2009/12/calculating-the-projected-bounds-using-utils3dprojectvector/
*
* #param bounds: The rectangle that makes up the object
* #param matrix The 3D Matrix of the item
* #param the projection of the item's parent.
*/
public static function projectBounds(bounds:Rectangle,
matrix:Matrix3D,
projection:PerspectiveProjection):Rectangle
{
// Setup the matrix
var centerX:Number = projection.projectionCenter.x;
var centerY:Number = projection.projectionCenter.y;
matrix.appendTranslation(-centerX, -centerY, projection.focalLength);
matrix.append(projection.toMatrix3D());
// Project the corner points
var pt1:Vector3D = new Vector3D(bounds.left, bounds.top, 0);
var pt2:Vector3D = new Vector3D(bounds.right, bounds.top, 0)
var pt3:Vector3D = new Vector3D(bounds.left, bounds.bottom, 0);
var pt4:Vector3D = new Vector3D(bounds.right, bounds.bottom, 0);
pt1 = Utils3D.projectVector(matrix, pt1);
pt2 = Utils3D.projectVector(matrix, pt2);
pt3 = Utils3D.projectVector(matrix, pt3);
pt4 = Utils3D.projectVector(matrix, pt4);
// Find the bounding box in 2D
var maxX:Number = Math.max(Math.max(pt1.x, pt2.x), Math.max(pt3.x, pt4.x));
var minX:Number = Math.min(Math.min(pt1.x, pt2.x), Math.min(pt3.x, pt4.x));
var maxY:Number = Math.max(Math.max(pt1.y, pt2.y), Math.max(pt3.y, pt4.y));
var minY:Number = Math.min(Math.min(pt1.y, pt2.y), Math.min(pt3.y, pt4.y));
// Add back the projection center
bounds.x = minX + centerX;
bounds.y = minY + centerY;
bounds.width = maxX - minX;
bounds.height = maxY - minY;
return bounds;
}
}
}
Although that is the answer to my question, I'm not sure if it was the solution to my problem. Thanks everyone!
How can I get the actual position of a node in the scene. The absolute position, regardless of any containers/transforms.
For example, I want to translate a certain node a so that it would temporarily overlap another node b. So I wish to set his translateX property to b.globalX-a.globalX.
The documentation says:
Defines the X coordinate of the
translation that is added to the
transformed coordinates of this Node
for the purpose of layout. Containers
or Groups performing layout will set
this variable relative to
layoutBounds.minX in order to position
the node at the desired layout
location.
For example, if child should have a
final location of finalX:
child.layoutX = finalX - child.layoutBounds.minX;
That is, the final coordinates of any node should be
finalX = node.layoutX + node.layoutBounds.minX
However running the following code:
var rect;
Stage {
title: "Application title"
width: 250
height:250
scene: Scene {
content: [
Stack{content:[rect = Rectangle { width:10 height:10}] layoutX:10}
]
}
}
println("finalX = {rect.layoutX+rect.layoutBounds.minX}");
gives me finalX = 0.0 instead of finalX = 10.0 as the docs seemingly state.
Is there a clear method to get the absolutely final positioning coordinates in JavaFX?
For bounds:
bounds = rect.localToScene(rect.getBoundsInLocal());
Work for JavaFx 1 and 2.
The only solution I found so far is
rect.localToScene(rect.layoutBounds.minX, rect.layoutBounds.minY) // a Point2D{x:Float y:Float} object
Which doesn't seem to me as the "best" way to do that (note that this function is not bound). Still it works for JavaFX 1.2.
Since JavaFX 8, there are additional methods converting local coordinates to screen coordinates. Their names start with "localToScreen"
You can check following link
http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#localToScreen-double-double-
I've ran into a weird problem with getCharBoundaries, I could not figure out what coordinate space the coordinates returned from the function was in. What ever I tried I could not get it to match up with what I expected.
So I made a new project and and added simple code to highlight the last charater in a textfield, and all of a sudden it worked fine. I then tried to copy over the TextField that had been causing me problems, into the new project. And now the same weird offset appeared 50px on the x axis. Everything else was spot on.
So after some headscracthing comparing the two TextFields, I simply can not see a difference in their properties or transformation.
So I was hoping that someone might now what property might affect the coordinates returned by getCharBoundaries.
I am using Flash CS4.
I've just had exactly the same problem and thought I'd help out by offering what my findings are. With a help from this thread, I tried to find everything that wasn't 'default' about the textfield I was using. I found that when I had switched my TextFormatAlign (or 'align' in the IDE) and TextFieldAutoSize properties to 'LEFT' as opposed to 'CENTER', it solved the problem.
A little late in the game perhaps, but worth knowing for anyone running into the same problem. This was the only thread I could find that raised the right flag...
Well the getCharBoundaries returns the boundaries in the textfield coordinate system. Where the origin is topleft corner of the textfield.
getCharBoundaries does not take into consideration the scrolling. you need to check if there are scrollbars on its parent (textarea) and if so relocate. One quick way of doing it is using localtoglobal and globaltolocal. Use the first to translate from the textfield coordinate system to the application coordinate system and then use the second to translate from the app coordinate system to the coordinate system of the parent of the textfield which is the textarea. I'm fine tuning a my method to get char boundaries i will publish it today on my blog
http://flexbuzz.blogspot.com/
Works For Me(tm) (Flex Builder AS3 project):
[Embed(systemFont="Segoe UI", fontWeight="bold", fontName="emb",
mimeType="application/x-font")]
private var EmbeddedFont:Class;
public function ScratchAs3()
{
stage.scaleMode = 'noScale';
stage.align = 'tl';
var m:Matrix = new Matrix(.8, .1, -.1, 1.1, 26, 78);
var t:TextField = new TextField();
t.autoSize = 'left';
t.wordWrap = false;
t.embedFonts = true;
t.defaultTextFormat = new TextFormat("emb", 100, 0, true);
t.transform.matrix = m;
t.text = "TEST STRING.";
addChild(t);
var r:Rectangle = t.getCharBoundaries(8);
var tl:Point = m.transformPoint(r.topLeft);
var tr:Point = m.transformPoint(new Point(r.right, r.top));
var bl:Point = m.transformPoint(new Point(r.left, r.bottom));
var br:Point = m.transformPoint(r.bottomRight);
graphics.beginFill(0xFF, .6);
graphics.moveTo(tl.x, tl.y);
graphics.lineTo(tr.x, tr.y);
graphics.lineTo(br.x, br.y);
graphics.lineTo(bl.x, bl.y);
graphics.lineTo(tl.x, tl.y);
}
To literally answer your question, it returns the coordinates in the TextField's coordinate system, not it's parent, and it is affected by DisplayObject.transform.matrix, which is the backing for the .x, .y, .scaleX, .scaleY, .width, .height, and .rotation properties.
What ever it was the solution was simple to add a new TextField, never found out what property screwed everything up.
The first answer is correct in most cases. However if your field is parented to another movie clip it may still return the wrong y coordinate. try this code:
//if this doesn't work:
myTextFormat = new TextFormat();
myTextFormat.align = TextFormatAlign.LEFT;
myFieldsParent.myField.autoSize = TextFieldAutoSize.LEFT;
myFieldsParent.myField.setTextFormat( myTextFormat);
//try this:
var x = myFieldsParent.myField.getCharBoundaries(o).x;
var y = myFieldsParent.myField.getCharBoundaries(o).y;
var myPoint:Point = new Point(myField.getCharBoundaries(o).x,myField.getCharBoundaries(o).y);
var pt:Point = new Point(myFieldsParent.myField.getCharBoundaries(o).x, myFieldsParent.myField.getCharBoundaries(o).y);
pt = myFieldsParent.myField.localToGlobal(pt);
//pt is the variable containing the coordinates of the char in the stage's coordinate space. You may still need to offset it with a fixed value but it should be constant.
I didn't test this code as I have adapted this example from code that is embedded into my project so I apologize if I'm missing something...