I am new to JavaFx and i am making a simple drawing program where i draw shapes. The problem i am having now is that i dont know how to make the circle appear on the screen when i click on the screen. So first I want to press a button that says "Circle" and then when i click on the canvas i want it to spawn. (I am switching between scenebuilder and intellij btw).
This is some of my program:
Classes:
public abstract class Shape {
double x;
double y;
double width;
double height;
public Color color = Color.WHITE;
public void creatingShapes(double x, double y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void setColor(Color color) {
this.color = color;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Color getColor() {
return color;
}
public abstract void draw(GraphicsContext g);
}
public class Circle extends Shape {
#Override
public void draw(GraphicsContext g) {
g.setFill(color);
g.fillOval(200,200,200,200);
g.fillRect(getX(),getY(),getWidth(),getHeight());
g.setStroke(Color.BLACK);
g.strokeOval(getX(),getY(),getWidth(),getHeight());
====================================================
Controller class:
public class HelloController {
#FXML
private Button logoutButton;
#FXML
private BorderPane scenePane;
Stage stage;
#FXML
private ColorPicker myColorPicker;
#FXML
private ChoiceBox<String> myChoiceBox;
#FXML
private Button circle;
#FXML
private GraphicsContext g;
private final Shape[] shapes = new Shape[500];
Canvas canvas = new Canvas(310,333);
boolean drawShape = true;
int count = 0;
private final Color currentColor = Color.BLUE;
private final String[] menuAlternatives = {"Sparning", "Testing?", "Exit?"};
public void onCircleClicked() {
circle.setOnAction((event) -> addShape(new Circle()));
}
//skapa shapes
public void addShape(Shape shape) {
shape.setColor(currentColor);
shape.creatingShapes(10,10,150,100);
shapes[count] = shape;
count++;
paintCanvas();
}
public void changeColor(ActionEvent event) {
Color myColor = myColorPicker.getValue();
scenePane.setBackground(new Background(new BackgroundFill(myColor, null, null)));
}
public void initialize() {
g = canvas.getGraphicsContext2D();
}
public void paintCanvas() {
g.setFill(Color.WHITE);
g.fillRect(0,0,400,400);
Arrays.stream(shapes, 0, count).forEach(s -> s.draw(g));
}
Placing a circle on canvas with mouse event
From Doc
public void fillOval(double x,
double y,
double w,
double h)
Fills an oval using the current fill paint.
This method will be affected by any of the global common or fill attributes
as specified in the Rendering Attributes Table.
Parameters:
x - the X coordinate of the upper left bound of the oval.
y - the Y coordinate of the upper left bound of the oval.
w - the width at the center of the oval.
h - the height at the center of the oval.
As we can see x and y params from fillOval() will put the circle at the left upper corner of its bound . to spawn at its very center , we need to subtract half widht and half height to x and y coordinates given by mouseclick event.
this is a single class javafx app you can try
App.java
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
Canvas canvas = new Canvas(640, 480);
GraphicsContext context = canvas.getGraphicsContext2D();
canvas.setOnMouseClicked(e -> {
double x = e.getX();
double y = e.getY();
context.setFill(Color.AQUAMARINE);
context.setStroke(Color.BLACK);
context.fillOval(x - 20, y - 20, 40, 40);
context.strokeOval(x - 20, y - 20, 40, 40);
});
var scene = new Scene(new Group(canvas), 640, 480);
stage.setScene(scene);
stage.setTitle("spawn with mouse click");
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Related
I have a Container (1) that I want to grow with its content.
Inside this Container (1), I have a Scrollrect (2) that I also want to grow with its content but with max height.
The goal is that if there is only one line of text in (2), (1) will be much smaller. When a lot of text is added, (2) will grow until its max height is reached and the scrollbar will take over.
I have been at it for hours and I cannot seem to find a way to make it work.
You can do this via Text.preferredHeight e.g. like this component on your scroll content
public class ContentScaler : MonoBehaviour
{
[SerializeField] private RectTransform _scrollRectTransform;
[SerializeField] private RectTransform _contentRectTransform;
[SerializeField] private Text _text;
[SerializeField] private float minScrollHeight;
[SerializeField] private float maxScrollHeight;
private void Awake()
{
if (!_contentRectTransform) _contentRectTransform = GetComponent<RectTransform>();
}
private void Update()
{
// Get the preferred Height the text component would require in order to
// display all available text
var desiredHeight = _text.preferredHeight;
// actually make your text have that height
var textSizeDelta = _text.rectTransform.sizeDelta;
textSizeDelta.y = desiredHeight;
_text.rectTransform.sizeDelta = textSizeDelta;
// Then make the content have the same height
var contentSizeDelta = _contentRectTransform.sizeDelta;
contentSizeDelta.y = desiredHeight;
_contentRectTransform.sizeDelta = contentSizeDelta;
var scrollSizeDelta = _scrollRectTransform.sizeDelta;
scrollSizeDelta.y = Mathf.Clamp(desiredHeight, minScrollHeight, maxScrollHeight);
_scrollRectTransform.sizeDelta = scrollSizeDelta;
}
}
Building upon Ryan Pergent's answer, I made the code do what he wanted (Mostly) and added the ability to set the preferred height and width
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class AdaptablePreferred : LayoutElement, ILayoutElement {
#if UNITY_EDITOR
[CustomEditor(typeof(AdaptablePreferred))]
public class Drawer : Editor {
public override void OnInspectorGUI() {
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
EditorGUI.EndDisabledGroup();
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Height");
var prop = serializedObject.FindProperty("_usePreferredHeight");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredHeightMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Width");
prop = serializedObject.FindProperty("_usePreferredWidth");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight + 2));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredWidthMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(serializedObject.FindProperty("_contentToGrowWith"));
if(EditorGUI.EndChangeCheck()) {
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
[SerializeField] private RectTransform _contentToGrowWith;
[SerializeField] private bool _usePreferredHeight;
[SerializeField] private float _preferredHeightMax;
[SerializeField] private bool _usePreferredWidth;
[SerializeField] private float _preferredWidthMax;
private float _preferredHeight;
private float _preferredWidth;
public override float preferredHeight => _preferredHeight;
public override float preferredWidth => _preferredWidth;
public override void CalculateLayoutInputVertical() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredHeight) {
var height = LayoutUtility.GetPreferredHeight(_contentToGrowWith);
_preferredHeight = _preferredHeightMax > height ? height : _preferredHeightMax;
} else {
_preferredHeight = -1;
}
}
public override void CalculateLayoutInputHorizontal() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredWidth) {
var width = LayoutUtility.GetPreferredWidth(_contentToGrowWith);
_preferredWidth = _preferredWidthMax > width ? width : _preferredWidthMax;
} else {
_preferredWidth = -1;
}
}
}
I achieved it by adding the following script to the Viewport:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(LayoutElement))]
public class AdaptablePreferredHeight : UIBehaviour, ILayoutElement
{
public float MaxHeight = 100;
public RectTransform ContentToGrowWith;
public int LayoutPriority = 10;
LayoutElement m_LayoutElement;
float m_Preferredheight;
public float minWidth => m_LayoutElement.minWidth;
public float preferredWidth => m_LayoutElement.preferredWidth;
public float flexibleWidth => m_LayoutElement.flexibleWidth;
public float minHeight => m_LayoutElement.minHeight;
public float preferredHeight => m_Preferredheight;
public float flexibleHeight => m_LayoutElement.flexibleHeight;
public int layoutPriority => LayoutPriority;
public void CalculateLayoutInputHorizontal()
{
if (m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
}
public void CalculateLayoutInputVertical()
{
if(m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
float contentHeight = ContentToGrowWith.sizeDelta.y;
if (contentHeight < MaxHeight)
{
m_Preferredheight = contentHeight;
}
else
{
m_Preferredheight = MaxHeight;
}
}
}
I would have just inherited LayoutElement instead of making it a RequireComponent, but then the inspector won't show my new MaxHeight and ContentToGrowWith variables.
The ContentToGrowWith variable points to the content object inside the ViewPort.
The final setup is:
Scroll View with an horizontal layout group Control Child Size: "Height". (no child force expand, so it can shrink with its children)
Viewport with LayoutElement and my script AdaptablePreferredHeight.
Content with ContentSizeFitterset on Vertical Fit: "Preferred Size"
I want to make animation moving a button from nX, nY to n1X, n1Y during 1000 ms.
My class:
public class Start extends Animation implements EntryPoint {
AbsolutePanel panel = new AbsolutePanel();
Label label;
Button b;
int a;
#Override
protected void onUpdate(double progress) {
panel.setWidgetPosition(b, 2*2, 2*2);
a++;
}
public void onModuleLoad() {
panel = new AbsolutePanel();
b = new Button("Click!");
label.setText("111");
RootPanel.get().add(label);
RootPanel.get().add(panel);
}
}
Making animation in GWT is quite easy:
Timer r1 = new Timer() {
int num = 100;
#Override
public void run() {
widget.setStyleName("after");
}
};
Timer r = new Timer() {
int num = 100;
#Override
public void run() {
widget.setStyleName("before");
num += 10;
}
};
r.scheduleRepeating(1000);
r1.scheduleRepeating(2000);`
I am using an animated sprite and want to perform some action on its long click events, but it is not working. Please help guys...
AnimatedSprite playBtn = new AnimatedSprite(MainActivity.CAMERA_WIDTH/2, MainActivity.CAMERA_HEIGHT - 100, activity.tTextureRegion3, activity.getVertexBufferObjectManager())
{
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float X, float Y)
{
if (pSceneTouchEvent.isActionDown())
{
scoreInc = scoreInc + 10;
score.setText("Score: " + scoreInc);
width = width + 5;
progressRectangle();
return true;
}
return false;
};
};
this.registerTouchArea(playBtn);
this.setTouchAreaBindingOnActionDownEnabled(true);
I have used this
#Override
public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float X, float Y)
{
switch(pSceneTouchEvent.getAction()) {
case TouchEvent.ACTION_MOVE:{ //Done my Work}
and it worked
I'm trying to implement a scene with a ScrollPane in which the user can drag a node around and scale it dynamically. I have the dragging and scaling with the mouse wheel working as well as a reset zoom, but I'm having trouble with the calculations to fit the node to the width of the parent.
Here is my code as an sscce.
(works) Mouse wheel will zoom in and out around the mouse pointer
(works) Left or right mouse press to drag the rectangle around
(works) Left double-click to reset the zoom
(doesn't work) Right double-click to fit the width
If I zoom in or out or change the window size, the fit to width does not work.
If anyone can help me with the calculations to fit the node to the width of the parent, I would very much appreciate it.
EDITED:
I marked the method that is not working correctly. It is fitWidth(), which is invoked by right mouse button double-clicking.
I edited the text of the question for clarity and focus
Hopefully this is more clear now.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ZoomAndPanExample extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
AnchorPane.setTopAnchor(scrollPane, 10.0d);
AnchorPane.setRightAnchor(scrollPane, 10.0d);
AnchorPane.setBottomAnchor(scrollPane, 10.0d);
AnchorPane.setLeftAnchor(scrollPane, 10.0d);
AnchorPane root = new AnchorPane();
Rectangle rect = new Rectangle(80, 60);
rect.setStroke(Color.NAVY);
rect.setFill(Color.NAVY);
rect.setStrokeType(StrokeType.INSIDE);
group.getChildren().add(rect);
// create canvas
PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.3d;
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(60);
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
);
timeline.play();
}
/**
* !!!! The problem is in this method !!!!
*
* The calculations are incorrect, and result in unpredictable behavior
*
*/
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = (scale / oldScale)-1;
double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
double newX = f*dx + getBoundsInParent().getMinX();
double newY = f*dy + getBoundsInParent().getMinY();
setPivot(newX, newY, scale);
}
public void resetZoom () {
double scale = 1.0d;
double x = getTranslateX();
double y = getTranslateY();
setPivot(x, y, scale);
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.resetZoom();
}
}
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.fitWidth();
}
}
}
};
}
}
I found the answer. I was looking at the wrong calculations, assuming it to be related to the translations. The real culprit was the calculation for the difference in scale. I simply changed this:
double f = (scale / oldScale)-1;
to this:
double f = scale - oldScale;
I want to drag and drop nodes between flowpanes. I implemented the drag and drop in this way:
public class TouchTask extends BorderPane{
setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Dragboard dragboard = startDragAndDrop(TransferMode.ANY);
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(TASK_DRAG_KEY);
dragboard.setContent(clipboardContent);
event.consume();
}
});
}
The drag and drop works fine, but the problem is, that the node is not moved during the drag and drop gesture. I wanted to implement this drag and drop so that the node has the same position as the mouse during the gesture.
I tried to implement this in the following way:
onMousePressedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// record the current mouse X and Y position on Node
mousex = event.getSceneX();
mousey = event.getSceneY();
x = getLayoutX();
y = getLayoutY();
if (isMoveToFront()) {
toFront();
}
}
});
onMouseDraggedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// Get the exact moved X and Y
double offsetX = event.getSceneX() - mousex;
double offsetY = event.getSceneY() - mousey;
x += offsetX;
y += offsetY;
double scaledX = x;
double scaledY = y;
setLayoutX(scaledX);
setLayoutY(scaledY);
// again set current Mouse x AND y position
mousex = event.getSceneX();
mousey = event.getSceneY();
event.consume();
}
});
But with this solution, the node is only moved for like 3 pixel and then it stops.