Cannot successfully add item to beginning of CategoryAxis series for a JavaFX BarChart - mouseevent

I've attempted to implement drag-left and drag-right functionality for a JavaFX BarChart. The drag-left functionality works correct where I add an item at the end of the x-axis data series and remove an item at the beginning.
However, the drag-right functionality does not work as expected. It's supposed to add an item at the beginning of the x-axis series and remove an item at the end. The removal works correctly, but the add operation results in the item being added to the end of the BarChart instead of the beginning.
I suspect it has to do with the use of a CategoryAxis that does not recognize the need to update the BarChart from the ObservableList in a sorted manner (even though I can see that the item gets added to the beginning of the underlying ObservableList).
Also, please let me know if there is a better way of implementing the drag operation.
Below is a full application to reproduce the problem.
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BarchartProblem extends Application {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private double dragStartX = 0;
private long minTime;
private long maxTime;
#FXML
private BarChart<String,Number> histogramBarChart;
#Override
public void start(Stage primaryStage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Time");
yAxis.setLabel("Entries");
histogramBarChart = new BarChart<String, Number>(xAxis, yAxis);
final XYChart.Series series1 = new XYChart.Series();
histogramBarChart.setLegendVisible(false);
histogramBarChart.getData().addAll(series1);
histogramBarChart.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println(mouseEvent);
dragStartX = mouseEvent.getX();
}
});
histogramBarChart.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (dragStartX > mouseEvent.getX()) {
XYChart.Series series = histogramBarChart.getData().get(0);
minTime += 60000;
maxTime += 60000;
series.getData().add(new XYChart.Data(dateFormat.format(new Date(maxTime)), 0));
series.getData().remove(0);
System.out.println("Drag Left");
} else if (dragStartX < mouseEvent.getX()) {
XYChart.Series series = histogramBarChart.getData().get(0);
minTime -= 60000;
maxTime -= 60000;
series.getData().add(0, new XYChart.Data(dateFormat.format(new Date(minTime)), 0));
series.getData().remove(series.getData().size()-1);
System.out.println("Drag Right");
}
}
});
final long currentTime = System.currentTimeMillis();
minTime = currentTime-60000*30;
maxTime = currentTime;
for (long i = minTime; i <= maxTime; i += 60000) {
series1.getData().add(new XYChart.Data(dateFormat.format(new Date(i)), Math.random()*500));
}
StackPane root = new StackPane();
root.getChildren().add(histogramBarChart);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("BarChart Problem");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

TornadoFX TableView. Scrolling the large amount of rows leads to UI freezing

I have a windows desktop application on Kotlin and I'm using JDK Zulu11 with JavaFX and TornadoFX 2.0.0.
I faced the problem with scrolling of large amount of rows (~4mln) in the TableView.
I have something like a player and when it starts I just need to do autoscroll to the row corresponding to the player current position. So to make playing smooth I do it by calling scrollTo method every 50 milliseconds, 20 times per second.
I observed that approximately at 300000 UI starts freezing and at 500000 it is almost dead.
When I increase the delay from 50ms to 200ms or 500ms the situation is the same, UI gets freeze.
When I used JDK Zulu1.8 with JavaFX and TornadoFX 1.7.2 just for check all was perfect, all is playing very smooth and fast enough. With Oracle JDK 1.8 all is ok also.
But I need to migrate to JDK 11 because I have some important dependencies.
So the question is what is wrong with JDK 11(JavaFX) and TornadoFX 2.0.0 and how it can be fixed?
Thanks a lot for any help.
PS: Here is the minimal reproducible example, I just found some TableView example on javacodegeeks and modified it, so please chek with JDK1.8 and with OpenJDK11, I used Azul Zulu 11.
Also here is the video with demonstration.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.paint.Color;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.TableColumn;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.List;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class FxTableViewExample1 extends Application {
public static class Book {
private SimpleIntegerProperty index;
private SimpleStringProperty title;
private SimpleStringProperty author;
public Book () {
}
public Book (Integer i, String s1, String s2) {
index = new SimpleIntegerProperty(i);
title = new SimpleStringProperty(s1);
author = new SimpleStringProperty(s2);
}
public int getIndex() {
return index.get();
}
public void setIndex(int index) {
this.index.set(index);
}
public String getTitle() {
return title.get();
}
public void setTitle(String s) {
title.set(s);
}
public String getAuthor() {
return author.get();
}
public void setAuthor(String s) {
author.set(s);
}
#Override
public String toString() {
return (index.get() + ": " + title.get() + ", by " + author.get());
}
}
private static final Integer COUNT = 10000000;
private static final Integer DELTA = 5000;
private static final Integer PERIOD = 50;
public static final EventType<Event> ScrollEventType = new EventType<>("ScrollEvent");
public static final EventType<Event> StopEventType = new EventType<>("StopEvent");
public static class ScrollEvent extends Event {
public Integer position = 0;
public ScrollEvent(Integer position) {
super(ScrollEventType);
this.position = position;
}
}
public static class StopEvent extends Event {
public StopEvent() {
super(StopEventType);
}
}
private TableView<Book> table;
private ObservableList<Book> data;
private Text actionStatus;
private Button startButton;
private Button stopButton;
private Integer count = 0;
private SimpleIntegerProperty currentPositionProperty = new SimpleIntegerProperty(0);
private Timer timer = null;
public static void main(String [] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Table View Example 1");
// Books label
Label label = new Label("Books");
label.setTextFill(Color.DARKBLUE);
label.setFont(Font.font("Calibri", FontWeight.BOLD, 36));
HBox labelHb = new HBox();
labelHb.setAlignment(Pos.CENTER);
labelHb.getChildren().add(label);
// Table view, data, columns and properties
table = new TableView<>();
data = getInitialTableData();
table.setItems(data);
TableColumn<Book, Integer> indexCol = new TableColumn<>("Index");
indexCol.setCellValueFactory(new PropertyValueFactory<Book, Integer>("index"));
TableColumn<Book, String> titleCol = new TableColumn<Book, String>("Title");
titleCol.setCellValueFactory(new PropertyValueFactory<Book, String>("title"));
TableColumn<Book, String> authorCol = new TableColumn<Book, String>("Author");
authorCol.setCellValueFactory(new PropertyValueFactory<Book, String>("author"));
table.getColumns().setAll(indexCol, titleCol, authorCol);
table.setPrefWidth(450);
table.setPrefHeight(300);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.getSelectionModel().selectedIndexProperty().addListener(
new RowSelectChangeListener());
// Status message text
actionStatus = new Text();
actionStatus.setFill(Color.FIREBRICK);
startButton = new Button("Play");
stopButton = new Button("Stop");
stopButton.setDisable(true);
currentPositionProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
Platform.runLater(new Runnable() {
#Override
public void run() {
table.scrollTo(newValue.intValue());
table.getSelectionModel().select(newValue.intValue());
}
});
}
});
primaryStage.addEventHandler(ScrollEventType, new EventHandler<Event>() {
#Override
public void handle(Event event) {
if (event.getEventType() == ScrollEventType) {
currentPositionProperty.set(((ScrollEvent)event).position);
}
}
});
primaryStage.addEventHandler(StopEventType, new EventHandler<Event>() {
#Override
public void handle(Event event) {
if (timer != null) {
timer.cancel();
timer = null;
}
}
});
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
count = 0;
startButton.setDisable(true);
stopButton.setDisable(false);
if (timer == null) {
timer = new Timer(true);
timer.schedule(new TimerTask() {
#Override
public void run() {
count++;
int position = count * DELTA;
if (position >= COUNT) {
Event.fireEvent(primaryStage, new ScrollEvent(COUNT));
Event.fireEvent(primaryStage, new StopEvent());
} else {
Event.fireEvent(primaryStage, new ScrollEvent(position));
}
}
}, 0, PERIOD);
}
}
});
stopButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
startButton.setDisable(false);
stopButton.setDisable(true);
if (timer != null) {
timer.cancel();
timer = null;
}
}
});
HBox hbox = new HBox(20);
hbox.setPadding(new Insets(25, 25, 25, 25));
hbox.getChildren().addAll(startButton, stopButton);
// Vbox
VBox vbox = new VBox(20);
vbox.setPadding(new Insets(25, 25, 25, 25));
vbox.getChildren().addAll(labelHb, table, actionStatus, hbox);
// Scene
Scene scene = new Scene(vbox, 500, 475); // w x h
primaryStage.setScene(scene);
primaryStage.show();
// Select the first row
table.getSelectionModel().select(0);
Book book = table.getSelectionModel().getSelectedItem();
actionStatus.setText(book.toString());
} // start()
private class RowSelectChangeListener implements ChangeListener<Number> {
#Override
public void changed(ObservableValue<? extends Number> ov,
Number oldVal, Number newVal) {
int ix = newVal.intValue();
if ((ix < 0) || (ix >= data.size())) {
return; // invalid data
}
Book book = data.get(ix);
actionStatus.setText(book.toString());
}
}
private ObservableList<Book> getInitialTableData() {
List<Book> list = new ArrayList<>();
int i = 0;
while (i < COUNT) {
list.add(new Book(i++, "The Thief", "Fuminori Nakamura"));
list.add(new Book(i++, "Of Human Bondage", "Somerset Maugham"));
list.add(new Book(i++, "The Bluest Eye", "Toni Morrison"));
list.add(new Book(i++, "I Am Ok You Are Ok", "Thomas Harris"));
list.add(new Book(i++, "Magnificent Obsession", "Lloyd C Douglas"));
list.add(new Book(i++, "100 Years of Solitude", "Gabriel Garcia Marquez"));
list.add(new Book(i++, "What the Dog Saw", "Malcolm Gladwell"));
list.add(new Book(i++, "The Fakir", "Ruzbeh Bharucha"));
list.add(new Book(i++, "The Hobbit", "J.R.R. Tolkien"));
list.add(new Book(i++, "Strange Life of Ivan Osokin", "P.D. Ouspensky"));
list.add(new Book(i++, "The Hunt for Red October", "Tom Clancy"));
list.add(new Book(i++, "Coma", "Robin Cook"));
}
return FXCollections.observableList(list);
}
}
The problem was resolved by using OpenJDK11 and separate OpenJFX15 instead of Zulu JDK11+JFX.

Java OOP using Translate Transition and JavaFX

The Translate Transition does not output. It uses a method in the main class. I believe it is not working because it is used as an object. There has to be a different code to implement. It uses a method in a main and then puts it into a tester. However, I do not know how to use it because it uses a constructor/object as well. Then, the object turns and changes into a node which I changed it. I do not know how the Translate Transition method is attached to the object and displays it into the javafx console. Please help solve the problem for positive feedback as it shows.
import javafx.animation.TranslateTransition;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class AnxiousShapes {
private Shape shape;
private double delay ;
private int howFarToMoveX;
private int howFarToMoveY;
public AnxiousShapes(int type, int x, int y, int size, double delay, Color color, int hftmX, int hftmY) {
if (type == 0) {shape = new Circle(x, y, size, color);}
//these are the only lines you can change
//in this main class
//else if (type == 1){shape = new Rectangle(x,y,color);}
//else if (type == 2){shape = new Polygon();}
//else if (type == 3) {shape = new Circle(x, y, size, color);}
//else { System.out.println("Error in type");shape = new
//Circle(???????);}
this.delay = delay;
this.howFarToMoveX = hftmX;
this.howFarToMoveY = hftmY;
}
// getter and setters
public TranslateTransition calculateTt() {
TranslateTransition tt = new TranslateTransition(Duration.seconds(this.delay), this.shape);
tt.setToX(this.shape.getLayoutX() + howFarToMoveX);
tt.setToY(shape.getLayoutY() + howFarToMoveY);
// Let the animation run forever -- if the shape
// tries to move "off-screen" it will return to the beginning
tt.setCycleCount(TranslateTransition.INDEFINITE);
return tt;
}
#Override
public String toString() {
return "AnxiousShape [shape=" + shape + ", delay=" + delay + ", howFarToMoveX=" + howFarToMoveX
+ ", howFarToMoveY=" + howFarToMoveY + "]";
}
}
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.scene.Group;
import javafx.scene.Node;
import java.util.Random;
public class AnxiousShapesTester extends Application {
#Override
public void start(Stage stage) {
// adding the new things
Group root = new Group();
stage.setTitle("Welcome to JavaFX!");
// create the shape circle
AnxiousShapes circle1 = new AnxiousShapes(0, 200, 200, 50, 15,
Color.GREEN, 10 ,35);
root.getChildren().add(circle1.getShape());
// this does not work
// TranslateTransition trans = circle1.calculateTt();
// trans.setNode(root);
// trans.play();
// and I tried this and I already have the movement in constructor for
// delay and x and y but TranslateTransition
// asks for duration.millis(500)
TranslateTransition tt = new
TranslateTransition(Duration.millis(500), root);
tt.play();
Scene scene = new Scene(root, 600, 600, Color.WHITE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
In a comment, you say, "this does not work." The problem is trans.setNode(root), which attempts to make root "The target node of this TranslateTransition." Your implementation of calculateTt() already specifies this.shape as the target node. Instead, add a suitable accessor to AnxiousShapes and use the transition as constructed; the following changes are illustrated below:
public Shape getShape() { return this.shape; }
…
AnxiousShapes circle1 = new AnxiousShapes(0, 100, 100, 100, 3, Color.GREEN, 400, 400);
root.getChildren().add(circle1.getShape());
TranslateTransition trans = circle1.calculateTt();
trans.play();

How to draw several lines slowly in constant velocity on canvas by Android?

I need capture the mark to draw a figure on canvas in Android, and the effect just like the follow gif:
Well, as far, I can draw a side with constant velocity by ValueAnimator. However, I just only can draw one side at one time, because I can't save the last side when drawing the next side. So, is there a good way to solve the problem?
Code for draw a line slowly by ValueAnimator:
GraphicsView.java
public class GraphicsView extends View {
private int stepX, stepY = 0;
private int startX, startY, stopX, stopY = 0;
private Paint paint = null;
public GraphicsView(Context context) {
super(context);
// Paint
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
startX = 40;
startY = 397;
stopX = 1040;
stopY = 397;
Init();
}
public void Init(){
ValueAnimator animatorX = ValueAnimator.ofFloat(startX, stopX);
ValueAnimator animatorY = ValueAnimator.ofFloat(startY, stopY);
animatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
stepX = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();
}
});
animatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
stepY = Math.round((Float)valueAnimator.getAnimatedValue()); invalidate();
}
});
AnimatorSet set = new AnimatorSet();
LinearInterpolator l = new LinearInterpolator();
set.setInterpolator(l);
set.setDuration(3000);
set.playTogether(animatorX, animatorY);
set.start();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(startX, startY, stepX, stepY, paint);
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Display display = null;
private GraphicsView view = null;
private ConstraintLayout layout = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
display = getWindowManager().getDefaultDisplay();
layout = (ConstraintLayout)findViewById(R.id.main_layout);
view = new GraphicsView(this);
view.setMinimumWidth(display.getWidth());
view.setMinimumHeight(display.getHeight());
layout.addView(view);
}
}
you can use the ObjectAnimator class to callback
to one of your class methods every time you'd like to draw a bit more of the path.
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import android.util.Log;
public class PathView extends View
{
Path path;
Paint paint;
float length;
public PathView(Context context)
{
super(context);
}
public PathView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public PathView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public void init()
{
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
path = new Path();
path.moveTo(50, 50);
path.lineTo(50, 500);
path.lineTo(200, 500);
path.lineTo(200, 300);
path.lineTo(350, 300);
// Measure the path
PathMeasure measure = new PathMeasure(path, false);
length = measure.getLength();
float[] intervals = new float[]{length, length};
ObjectAnimator animator = ObjectAnimator.ofFloat(PathView.this, "phase", 1.0f, 0.0f);
animator.setDuration(3000);
animator.start();
}
//is called by animtor object
public void setPhase(float phase)
{
Log.d("pathview","setPhase called with:" + String.valueOf(phase));
paint.setPathEffect(createPathEffect(length, phase, 0.0f));
invalidate();//will calll onDraw
}
private static PathEffect createPathEffect(float pathLength, float phase, float offset)
{
return new DashPathEffect(new float[] { pathLength, pathLength },
Math.max(phase * pathLength, offset));
}
#Override
public void onDraw(Canvas c)
{
super.onDraw(c);
c.drawPath(path, paint);
}
}
Then, just call init() to begin the animation, like this (or if you'd like it to start as soon as the view is inflated, put the init() call inside the constructors):
PathView path_view = (PathView) root_view.findViewById(R.id.path);
path_view.init();
Also see this question here, and
Using Value Animator Example
Reference 1
Reference 2
Reference 3

Make Icon Notification javafx

I would like to ask how is it possible to make a notification icon over an existing Node !
Here is a link I'm using as inspiration !
http://www.red-team-design.com/notification-bubble-css3-keyframe-animation
It would really save me if anyone could give me a help ^^
You can find an example here:
https://www.billmann.de/post/2012/08/05/javafx-miniiconanimationbutton/
I just solved The problem.
I've used the animation created by billman but not extending the StackPane this time, but by extending the Button Node, and I've set the Notification icon to be extending from the StackPane and it worked out perfectly !!
Here the way to do so:
AnimatedButton.java
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.scene.Node;
import javafx.scene.control.MenuItem;
import javafx.util.Duration;
public class Icons extends MenuItem {
private final Node icon;
private AnimationType type;
public Icons(String text, Node icon, AnimationType type) {
setText(text);
setGraphic(icon);
this.icon = icon;
this.type = type;
addAnimation();
}
private void addBlinkAnimation() {
final Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
final KeyValue kv = new KeyValue(icon.opacityProperty(), 0.0);
final KeyFrame kf = new KeyFrame(Duration.millis(700), kv);
timeline.getKeyFrames().add(kf);
timeline.play();
}
private void addJumpAnimation() {
final TranslateTransition translateTransition = new TranslateTransition(Duration.millis(200), icon);
final double start = 0.0;
final double end = start - 4.0;
translateTransition.setFromY(start);
translateTransition.setToY(end);
translateTransition.setCycleCount(-1);
translateTransition.setAutoReverse(true);
translateTransition.setInterpolator(Interpolator.EASE_BOTH);
translateTransition.play();
}
public enum AnimationType {
BLINK, JUMP, NONE;
};
private void addAnimation() {
switch (type) {
case BLINK:
addBlinkAnimation();
break;
case JUMP:
addJumpAnimation();
break;
case NONE:
break;
default:
break;
}
}
}
here's the main.java
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
MenuButton menu = new MenuButton("Bienvenue, Yassine", new ImageView(new Image("http://cdn1.iconfinder.com/data/icons/fs-icons-ubuntu-by-franksouza-/32/google-chrome.png", 20, 20, true, true)));
menu.setCenterShape(true);
Icons btn = new Icons("Notification", createNotification("5"), Icons.AnimationType.JUMP);
Icons btn2 = new Icons("Mails", createNotification("4"), Icons.AnimationType.JUMP);
Icons btn3 = new Icons("Private Messages", createNotification("10"), Icons.AnimationType.JUMP);
menu.getItems().addAll(btn, btn2, btn3);
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("notification_icon.css").toExternalForm());
root.getChildren().add(menu);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private Node createNotification(String number) {
StackPane p = new StackPane();
Label lab = new Label(number);
lab.setStyle("-fx-text-fill:white");
Circle cercle = new Circle(10, Color.rgb(200, 0, 0, .9));
cercle.setStrokeWidth(2.0);
cercle.setStyle("-fx-background-insets: 0 0 -1 0, 0, 1, 2;");
cercle.setSmooth(true);
p.getChildren().addAll(cercle, lab);
return p;
}
}
and the css
.label{
-fx-font-size: 12;
-fx-font-weight: bold;
-fx-text-fill: black;
-fx-padding:5;
}

How to build a custom draw2d layoutmanager?

I would like to have a layout manager that can arrange two elements as follows:
one main element ABCDEF centered
one "postscript" element XYZ, positioned on the top right corner of the encapsulating figure
For example:
***********XYZ*
* ABCDEF *
***************
Can I use an existing layoutmanager for this? How do I build a custom layout manager that supports it?
Many thanks for your advice.
You can do that by using XYLayout.
There is a sample you can build on:
import org.eclipse.draw2d.AbstractLayout;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class HHH {
public static void main(String[] args) {
Display d = new Display();
Shell s = new Shell();
s.setLayout(new FillLayout());
FigureCanvas canvas = new FigureCanvas(s);
Figure content = new Figure();
content.setLayoutManager(new XYLayout());
PostScriptedFigure figure = new PostScriptedFigure();
content.add(figure, new Rectangle(100, 100, -1, -1));
figure.setMainFigure(new Label("The Main figure"));
figure.setPostScriptFigure(new Label("ps"));
figure.setBorder(new LineBorder());
canvas.setContents(content);
s.setSize(600, 500);
s.open();
while (!s.isDisposed()) {
if (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
class PostScriptedFigure extends Panel {
private IFigure mainFigure, postScriptFigure;
private class PostScriptedLayoutManager extends AbstractLayout {
#Override
public void layout(IFigure container) {
final Rectangle clientArea = container.getClientArea();
final IFigure mainFigure = getMainFigure();
final IFigure postScriptFigure = getPostScriptFigure();
if (mainFigure != null) {
final Rectangle bounds = new PrecisionRectangle();
bounds.setSize(mainFigure.getPreferredSize(SWT.DEFAULT, SWT.DEFAULT));
final Rectangle mainClientArea = clientArea.getCopy();
if (postScriptFigure != null) {
mainClientArea.shrink(new Insets(postScriptFigure.getPreferredSize().height(), 0, 0, 0));
}
bounds.translate(mainClientArea.getCenter().getTranslated(bounds.getSize().getScaled(0.5f).getNegated()));
mainFigure.setBounds(bounds);
}
if (postScriptFigure != null) {
final Rectangle bounds = new PrecisionRectangle();
bounds.setSize(postScriptFigure.getPreferredSize(SWT.DEFAULT, SWT.DEFAULT));
bounds.translate(clientArea.getTopRight().getTranslated(bounds.getSize().getNegated().width(), 0));
postScriptFigure.setBounds(bounds);
}
// note that other potentionally added figures are ignored
}
#Override
protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) {
final Rectangle rect = new PrecisionRectangle();
final IFigure mainFigure = getMainFigure();
if (mainFigure != null) {
rect.setSize(mainFigure.getPreferredSize());
}
final IFigure postScriptFigure = getPostScriptFigure();
if (postScriptFigure != null) {
rect.resize(mainFigure != null ? 0 : postScriptFigure.getPreferredSize().width() , postScriptFigure.getPreferredSize().height());
}
// note that other potentionally added figures are ignored
final Dimension d = rect.getSize();
final Insets insets = container.getInsets();
return new Dimension(d.width + insets.getWidth(), d.height + insets.getHeight()).union(getBorderPreferredSize(container));
}
}
public PostScriptedFigure() {
super.setLayoutManager(new PostScriptedLayoutManager());
}
#Override
public void setLayoutManager(LayoutManager manager) {
// prevent from setting wrong layout manager
}
public IFigure getMainFigure() {
return mainFigure;
}
public void setMainFigure(IFigure mainFigure) {
if (getMainFigure() != null) {
remove(getMainFigure());
}
this.mainFigure = mainFigure;
add(mainFigure);
}
public IFigure getPostScriptFigure() {
return postScriptFigure;
}
public void setPostScriptFigure(IFigure postScriptFigure) {
if (getPostScriptFigure() != null) {
remove(getPostScriptFigure());
}
this.postScriptFigure = postScriptFigure;
add(postScriptFigure);
}
}