JavaFX 8, how to get center location of Scrollpane's Viewport - javafx-8

The Problem
I'm trying to figure out a way to get at which point in the content node the scroll pane's viewport is centered on.
To elaborate on the picture above, the big rectangle is the content (let's say a large image), and the small rectangle is the portion that is shown by the scroll pane. I'm trying to find x and y which would be coordinates from the top left of the content.
What I've Tried
My first thought was to use the getViewportBounds() method of the scroll pane and use its minX and maxX properties to determine the center x point:
Bounds b = scrollPane.getViewportBounds();
double centerX = (b.getMinX() + b.getMaxX()) / 2;
double centerY = (b.getMinY() + b.getMaxY()) / 2;
However, this doesn't work because these numbers are negative and don't seem to accurately describe the x and y I'm looking for anyways.
My next thought was to use the scroll pane's hValue and vValue to get the top left corner of the viewport relative to the content:
Bounds b = scrollPane.getViewportBounds();
double centerX = scrollPane.getHvalue() + b.getWidth() / 2;
double centerY = scrollPane.getVvalue() + b.getHeight() / 2;
This didn't work either though as the hValue and vValue seem to be way too large (when scrolled in only a few pixels, I'm getting numbers like 1600).
My Questions
I seem to have a fundamental misunderstanding of how the viewport works with a scroll pane.
What am I doing wrong here? Can someone explain where these numbers come from? How do I find x and y like in the picture above?

Let (x, y) be the be coordinates of the top, left point shown in the viewport. You can write this as
((contentWidth - viewportWidth) * hValueRel, (contentHeight - viewportHeight) * vValueRel)
vValueRel = vValue / vMax
hValueRel = hValue / hMax
This means assuming hmin and vmin remain 0 you can keep a circle in the center of like this:
// update circle position to be centered in the viewport
private void update() {
Bounds viewportBounds = scrollPane.getViewportBounds();
Bounds contentBounds = content.getBoundsInLocal();
double hRel = scrollPane.getHvalue() / scrollPane.getHmax();
double vRel = scrollPane.getVvalue() / scrollPane.getVmax();
double x = Math.max(0, (contentBounds.getWidth() - viewportBounds.getWidth()) * hRel) + viewportBounds.getWidth() / 2;
double y = Math.max(0, (contentBounds.getHeight() - viewportBounds.getHeight()) * vRel) + viewportBounds.getHeight() / 2;
Point2D localCoordinates = content.parentToLocal(x, y);
circle.setCenterX(localCoordinates.getX());
circle.setCenterY(localCoordinates.getY());
}
private Circle circle;
private Pane content;
private ScrollPane scrollPane;
#Override
public void start(Stage primaryStage) {
// create ui
circle = new Circle(10);
content = new Pane(circle);
content.setPrefSize(4000, 4000);
scrollPane = new ScrollPane(content);
Scene scene = new Scene(scrollPane, 400, 400);
// add listener to properties that may change
InvalidationListener l = o -> update();
content.layoutBoundsProperty().addListener(l);
scrollPane.viewportBoundsProperty().addListener(l);
scrollPane.hvalueProperty().addListener(l);
scrollPane.vvalueProperty().addListener(l);
scrollPane.hmaxProperty().addListener(l);
scrollPane.vmaxProperty().addListener(l);
scrollPane.hminProperty().addListener(l);
scrollPane.vminProperty().addListener(l);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Unity: Dynamically build UI elements on Canvas: ScreenSpace-Camera, previously ScreenSpace-Overlay

My application works fine, 100% expected results with Canvas set on ScreenSpace-Overlay. I have written a function that takes an array of integers and based on its values it dynamically builds vertical bars inside a panel. I have an UI-Image as a prefab and that I instantiated multiple time, set its anchor to bottom left position and increase the x-offset from that point in increments of its width+padding. Everything works fine, check the screenshots.
I have now a fancy idea of animating the vertical bars... to have Unity Particle Effects on the top of each bar and to play it. From what I searched... you cannot get Particle Effects working on Canvas-UI unless the Canvas is set as ScreenSpace-Camera. I managed to obtain success with Particle Effects and ScreenSpace-Camera canvas... everything works... but now the same code that dynamically builds the UI doesnt work anymore... the vertical bars are set on a total new position outside the UI.
My Canvas
Working algorithm
My code:
private void ArrangeImagesOnCanvas(List<int> varArray)
{
Canvas canvas = FindObjectOfType<Canvas>();
//float h = canvas.GetComponent<RectTransform>().rect.height * canvas.GetComponent<RectTransform>().localScale.y;
//float w = canvas.GetComponent<RectTransform>().rect.width * canvas.GetComponent<RectTransform>().localScale.x;
float h = _panelImageForSortingAlgorithm.GetComponent<RectTransform>().rect.height * canvas.GetComponent<RectTransform>().localScale.y;
float w = _panelImageForSortingAlgorithm.GetComponent<RectTransform>().rect.width * canvas.GetComponent<RectTransform>().localScale.x;
float minH = 20.0f;
float maxH = h - 20;
int minList = varArray.Min();
int maxList = varArray.Max();
_imageWidth = (w - (varArray.Count * _PADDING)) / varArray.Count;
List<float> heightsList = new List<float>();
float _tempFloat = 0;
for (int i = 0; i < varArray.Count; i++)
{
_tempFloat = ScaleIntervals(varArray[i], new Vector2(minList, maxList), new Vector2(minH, maxH));
heightsList.Add(_tempFloat);
}
for(int i = 0; i<_instancesOfImages.Count;i++)
{
Destroy(_instancesOfImages[i]);
}
_instancesOfImages.Clear();
for (int i = 0; i < varArray.Count; i++)
{
GameObject instantiatedImage = Instantiate(_imageToInstance, _imageToInstance.transform.position, Quaternion.identity);
instantiatedImage.transform.SetParent(_panelImageForSortingAlgorithm.transform);
Image img = instantiatedImage.GetComponent<Image>();
img.rectTransform.anchorMin = new Vector2(0, 0);
img.rectTransform.anchorMax = new Vector2(0, 0);
img.rectTransform.pivot = new Vector2(0, 0);
img.rectTransform.position = new Vector3(i * (_imageWidth + _PADDING), 0, 0);
img.rectTransform.sizeDelta = new Vector2(_imageWidth, heightsList[i]);
_instancesOfImages.Add(instantiatedImage);
}
}
For the same code the vertical bars can be seen in this image...
Broken bars 1
if I put
float h = _panelImageForSortingAlgorithm.GetComponent<RectTransform>().rect.height;
float w = _panelImageForSortingAlgorithm.GetComponent<RectTransform>().rect.width;
then the vertical bars are like in this image.
Broken bars 2
How can I position correctly the vertical bars as in ScreenSpace-Overlay but this time to be on ScreenSpace-Camera ? Please help or give me some advices or tips...

Math: How to spin a wheel by drag&drop (Canvas, 2D)?

I'm struggling with probably simple math to spin/rotate a wheel using drag&drop.
There is a Radial Layout in a Canvas (Unity UI) and it can already be rotated by setting a property called StartAngle that is in a range from 0-360. In this Radial there are items, so the StartAngle is for the first item and places all the child elements around the layout radius.
I want to implement drag & drop for the items so that you can drag a child around and the Radial will spin accordingly (infinitely).
Right now, I have this as a starting point:
public void OnDrag(PointerEventData eventData)
{
var delta = eventData.delta.x * Time.deltaTime;
var newAngle = radialLayout.StartAngle + delta;
if (newAngle >= 360)
newAngle = newAngle - 360;
else if (newAngle < 0)
newAngle = Mathf.Abs(360 - newAngle);
radialLayout.StartAngle = newAngle;
}
It kind of works but doesn't feel very smooth. This is for mobile/touch, so I want both the X and Y delta of the drag operation to be taken into account. Apparently, the y delta is not considered in my example and I have no idea how to incorporate this correctly. The user might do a linear drag & drop on either axis or he/she might also do like a circular drag movement.
So how can I map mouse movement to a rotation angle from 0-360 so that it feels good?
Edit: Thanks for the help, I did it like this now:
public void OnDrag(PointerEventData eventData)
{
// Note the "Head-Minus-Tale rule for Vector subtraction, see http://www.maths.usyd.edu.au/u/MOW/vectors/vectors-3/v-3-7.html
// vSourceToDestination = vDestination - vSource;
// First, we draw a vector from the center point of the radial to the point where we started dragging
var from = dragStartPoint - (Vector2)radialLayout.transform.position;
// Next, we draw a vector from the center point of the radial to the point we are currently dragging on
var to = eventData.position - (Vector2)radialLayout.transform.position;
// Now, we calculate the angle between these two:
var dragAngle = Vector2.SignedAngle(from, to);
// Lerping makes movement fast at the beginning slow at the end
var lerpedAngle = Mathf.Round(Mathf.LerpAngle(radialLayout.StartAngle, dragAngle, 0.5f));
radialLayout.StartAngle = lerpedAngle;
}
I don't know all of your code and types but I would have an idea. I can't test this right now and can not garant that it even works like this but I hope the idea gets clear.
I would probably rather use something like
// This is the vector from the center of the object to the mouse/touch position
// (in screen pixel space)
var touchDirection = eventData.position - Camera.main.WorldToScreenPoint(transform.position);
// angle between the Up (Y) axis and this touchDirection
// for the angle the length of the up vector doesn't matter so there is
// no need to convert it to pixel space
var targetAngle = Vector2.SignedAngle(Vector2.up, touchDirection);
// since the returned angle might be negative wrap it to get values 0-360
if(targetAngle < 0) targetAngle += 360;
// Now either simply use Lerp
// this would simply interpolate each frame to the middle of both values
// the result is a fast movement at the beginning and a very slow at the end
radialLayout.StartAngle = Mathf.Lerp(radialLayout.StartAngle, targetAngle, 0.5f);
// or maybe use a fixed speed like 30°/second
var difference = targetAngle - radialLayout.StartAngle;
radialLayout.StartAngle += Mathf.Sign(difference) * Mathf.Min(30f * Time.deltaTime, Mathf.Abs(difference));
Typed on smartphone but I hope the idea gets clear

ITextSharp - Draw Rectangle top left corner

I'm trying to draw a rectangle to the very top left of a page using ITextSharp (5.5.13). I want to draw in the page margins. However, the rectangle is around 25 pixels too low. How can I draw the rectangle in the top left corner?
Below is how I'm adding the rectangle to the page:
using (PdfReader reader = new PdfReader(inputPdf.FullName))
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(outputPdf.FullName, FileMode.Create)))
{
PdfContentByte contentByte = stamper.GetOverContent(1);
PdfDocument doc = contentByte.PdfDocument;
float X = 0.0f;
float Y = 0.0f;
float Height = Utilities.InchesToPoints(0.50f);
float Width = Utilities.InchesToPoints(0.50f);
float llx = (doc.Left - doc.LeftMargin) + X;
float lly = (doc.Top - doc.TopMargin) - (Height + Y);
float urx = (doc.Left - doc.LeftMargin) + Width + X;
float ury = (doc.Top - doc.TopMargin) - Y;
Rectangle rectangle = new Rectangle(llx, lly, urx, ury)
{
BackgroundColor = BaseColor.BLACK
};
contentByte.Rectangle(rectangle);
}
Below are the debug values for each aforementioned variable:
Whenever you use a PdfStamper, the PdfDocument you can retrieve from its parts does not contain sensible information, it merely is a dummy object.
Thus, don't try to determine the page size from that PdfDocument doc, instead use the appropriate methods or properties of your PdfReader reader, e.g.
/** Gets the crop box without taking rotation into account. This
* is the value of the /CropBox key. The crop box is the part
* of the document to be displayed or printed. It usually is the same
* as the media box but may be smaller. If the page doesn't have a crop
* box the page size will be returned.
* #param index the page number. The first page is 1
* #return the crop box
*/
virtual public Rectangle GetCropBox(int index)

How to get screen/scene center x,y of Rectangle within a GridPane?

I'm working on a simple board game implementation in JavaFX8.
For the game board, my decision was to use a 10x10 GridPane and fill it's cells with Rectangles in form's initialize method.
private void drawBoard() {
gridpaneBoard.getChildren().clear();
for (int y = 0; y < gridpaneBoard.getRowConstraints().size(); y++)
for (int x = 0; x < gridpaneBoard.getColumnConstraints().size(); x++) {
Rectangle rect = new Rectangle(55,55);
rect.setStroke(Color.BLACK);
Tile tile = GameController.getInstance().getBoard().getTile(x, y);
if (tile.hasBranch())
rect.setFill(QuestionDifficulty.values()[tile.getBranch()
.getQuestion().getQuestion()
.getLevel()].getColor());
else
rect.setFill(Color.WHITE);
gridpaneBoard.add(rect, x, y);
gridpaneBoard.add(new Label(String.valueOf(tile.getNumber())), x, y);
}
}
In order to animate player token movement after dice rolls, I figured I need to know the center x & center y of each tile (to create a path transition from source tile to destination tile).
I've tried all sorts of answers given to other people's questions, but everything returned 0,0 for me.
This is the container hierarchy in this scene:
This is how the output looks at the moment:
If GridPane is fine for what I'm trying to achieve, how can I get a child's (in this case a rectangle's) screen / scene center x,y?
If GridPane is not fine, can you point me to alternatives and how I can achieve what I want then..
Thank you!
You can simply call getBoundsInParent to get the dimensions of the Node in it's parent.
The following example is a bit simplified, but it should demonstrate the approach nonetheless:
#Override
public void start(Stage primaryStage) {
GridPane gridpaneBoard = new GridPane();
for (int y = 0; y < 10; y++) {
for (int x = 0; x < 10; x++) {
Rectangle rect = new Rectangle(55, 55);
rect.setStroke(Color.BLACK);
rect.setFill((x + y) % 2 == 0 ? Color.WHITE : Color.DARKGRAY);
gridpaneBoard.add(rect, x, y);
}
}
gridpaneBoard.setOnMouseClicked(evt -> {
Node target = evt.getPickResult().getIntersectedNode();
if (target != gridpaneBoard) {
// in your case you'd need to make sure this is not the Label
Bounds bounds = target.getBoundsInParent();
System.out.println("bounds = " +bounds);
System.out.println("centerX = " +(bounds.getMinX() + bounds.getWidth()/2));
System.out.println("centerY = " +(bounds.getMinY() + bounds.getHeight()/2));
}
});
Scene scene = new Scene(gridpaneBoard);
primaryStage.setScene(scene);
primaryStage.show();
}
If coordinates different to the GridPane coordinates are required, you could use getBoundsInLocal in combination with localTo... instead:
Bounds bounds = target.localToScene(target.getBoundsInLocal());
for scene bounds or
Bounds bounds = target.localToScreen(target.getBoundsInLocal());
for screen bounds.
Note: This works independent from any properties modifying how GridPane layouts it's children.
You can use :
-GridPane.getColumnIndex(Node) to get the column index.
-GridPane.getRowIndex(Node) to get the row index.
-Since you know the Width and the Height (55,55) of your child (Rectangle) you can just calculate its centerX, centerY which is relative to its position in the container, but since you use a GridPane I don't think it is possible since this one has Constraints. You can fix it by changing the container of your object or completely redraw it inside another raw/column, here is an example Replace a node at (row,col).

How can i create random rectangles (automatically)?

I want to create many rectangles. This should be done automatically. How can I do this without typing thousands of values in my code? Is there an solution?
In my code I wrote every single coordinate point (4 points of each rectangle) manually in my vector "V".
Also how to connect them. "F"
And the value of each rectangle. "C"
My code is
clc
clear all
figure;
V = [0,0;1,0;1,1;0,1;5,5;10,5;10,10;5,10;2,2;4,2;4,4;2,4];
F = [1,2,3,4;5,6,7,8;9,10,11,12];%Dieser Vektor sagt mir in welcher Reihenfolge die Punkte
C = [50;24;99];
patch('Faces',F,'Vertices',V,'FaceVertexCData',C,'FaceColor','flat','EdgeColor','none') %Befehl fürs "zeichnen"
colormap(parula)
colorbar
You can use the following function to create a random rectangle, by randomly generating an (x, y) position for the bottom left corner, and randomly generating a width and height -
function rect = createRandomRectangle(maxX, maxY, minHeight, maxHeight, minWidth, maxWidth)
bottom = maxY * rand;
left = maxX * rand;
height = minHeight + rand * (maxHeight - minHeight);
width = minWidth + rand * (maxWidth - minWidth);
rect = [
left, bottom
left, bottom + height
left + width, bottom + height
left + width, bottom
];
end
Then you just need to take care of creating your V, F, C matrices (by calling createRandomRectangle in a loop) and plotting them.