Javafx linear-gradient repeat behaviour - javafx

Javafx linear-gradient repeat seems to reflect the colours rather than repeat.
I wrote a simple application to show what I see when using linear-gradient with repeat to create a striped pattern in my application on a custom Node (a StackPane). In my application this are added as overlays to a XYChart and their height varies. Using a Rectangle wasn't working well which is why I use a Stackpane and set a style on it rather than creating the LinearGradient programmatically.
The colour list is dynamic and varies in size in the application.
The issue is the way linear-gradient flips the list and reflects the colours on each repeat rather than just repeat.
This link describes a similar issue but just adding in endless stops seemless like a messy solution for my issue, it would be much better to add the colours once and repeat.
linear gradient repeat on css for javafx
java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
List<Color> colors = Arrays.asList( Color.RED,Color.BLUE,Color.YELLOW,Color.GREEN);
StackPane stackPane = new StackPane();
stackPane.setStyle(getLinearGradientStyle(colors));
root.setCenter(stackPane);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
catch (Exception e) {
e.printStackTrace();
}
}
private String getLinearGradientStyle(List<Color> colors) {
StringBuilder stringBuilder = new StringBuilder("-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat,");
for (int i = 0; i < colors.size(); i++) {
stringBuilder.append("rgb(")
.append((int) (colors.get(i).getRed() * 255)).append(",")
.append((int) (colors.get(i).getGreen() * 255)).append(",")
.append((int) (colors.get(i).getBlue() * 255))
.append(")")
.append(" ").append(getPercentage(i+1, colors.size()+1) );
if (i < colors.size() - 1) {
stringBuilder.append(",");
}
}
stringBuilder.append(");");
System.out.println("Main.getLinearGradientStyle():"+stringBuilder);
return stringBuilder.toString();
}
private String getPercentage(float i, int size) {
return (((1.0f / size) * 100 )*i)+ "%";
}
public static void main(String[] args) {
launch(args);
}
Here's a CSS3 example using repeating-linear-gradient:
https://tympanus.net/codrops/css_reference/repeating-linear-gradient/
scroll down to the following text: will create a striped background, where each linear gradient is a three-stripe gradient, repeated infinitely (this is the example)
My example uses a diagonal pattern which is what I need but the above example shows what I'd like to see in terms of solid repeating colours with out reflection in normal css.
Thanks for any help

This looks like a bug. If you run the following example (moved the CSS into a file):
Main.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Region region = new Region();
region.backgroundProperty().addListener((obs, ov, nv) ->
System.out.println(nv.getFills().get(0).getFill()));
Scene scene = new Scene(region, 500, 300);
scene.getStylesheets().add("Main.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Main.css
.root {
-fx-background-color: linear-gradient(from 0px 0px to 10px 10px, repeat, red 20%, blue 40%, yellow 60%, green 80%);
}
You'll see the following printed out:
linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, 0xff0000ff 0.0%, 0xff0000ff 20.0%, 0x0000ffff 40.0%, 0xffff00ff 60.0%, 0x008000ff 80.0%, 0x008000ff 100.0%)
As you can see, despite using "repeat" in the CSS the LinearGradient that is created uses "reflect".
There is likely nothing you can do about this bug yourself, but if you don't mind setting the background in code (or probably even FXML) then the following should do what you want:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
LinearGradient gradient = new LinearGradient(0, 0, 10, 10, false, CycleMethod.REPEAT,
new Stop(0.2, Color.RED),
new Stop(0.4, Color.BLUE),
new Stop(0.6, Color.YELLOW),
new Stop(0.8, Color.GREEN)
);
Region region = new Region();
region.setBackground(new Background(new BackgroundFill(gradient, null, null)));
Scene scene = new Scene(region, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
You can move the creation of the LinearGradient into a method that takes an arbitrary number of Colors, just like you're currently doing.
If you're interested, I believe the bug is located in javafx.css.CssParser around line 1872 (in JavaFX 12):
CycleMethod cycleMethod = CycleMethod.NO_CYCLE;
if ("reflect".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
} else if ("repeat".equalsIgnoreCase(arg.token.getText())) {
cycleMethod = CycleMethod.REFLECT;
prev = arg;
arg = arg.nextArg;
}
As you can see, it erroneously sets the CycleMethod to REFLECT when the text is equal to "repeat".
A bug report has been filed: JDK-8222222 (GitHub #437). Fix version: openjfx13.

Related

JavaFX Alert with multiple colors

I have a program that at some point (may) displays two warnings - one about errors - those are in red, and one about warnings - those are in orange.
I wonder however if there is a way - using css - to have just one warning with some text red and some text orange.
Here is an example of what I want to achieve (the two can be separated into "sections"):
RED ERROR1
RED ERROR2
RED ERROR3
ORANGE WARNING1
ORANGE WARNING2
I've seen some answers pointing to RichTextFX like this one, however I don't see (or don't know) how that could apply to generic Alerts. Is that even possible, without writing some custom ExpandedAlert class?
The Alert class inherits from Dialog, which provides a pretty rich API and allows arbitrarily complex scene graphs to be set via the content property.
If you just want static text with different colors, the simplest approach is probably to add labels to a VBox; though you could also use more complex structures such as TextFlow or the third-party RichTextFX mentioned in the question if you need.
A simple example is:
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
private final Random rng = new Random();
private void showErrorAlert(Stage stage) {
Alert alert = new Alert(Alert.AlertType.ERROR);
int numErrors = 2 + rng.nextInt(3);
int numWarnings = 2 + rng.nextInt(3);
VBox errorList = new VBox();
for (int i = 1 ; i <= numErrors ; i++) {
Label label = new Label("Error "+i);
label.setStyle("-fx-text-fill: red; ");
errorList.getChildren().add(label);
}
for (int i = 1 ; i <= numWarnings ; i++) {
Label label = new Label("Warning "+i);
label.setStyle("-fx-text-fill: orange; ");
errorList.getChildren().add(label);
}
alert.getDialogPane().setContent(errorList);
alert.initOwner(stage);
alert.show();
}
#Override
public void start(Stage stage) {
Button showErrors = new Button("Show Errors");
showErrors.setOnAction(e -> showErrorAlert(stage));
BorderPane root = new BorderPane(showErrors);
Scene scene = new Scene(root, 400, 400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
which gives this result:

How to get rid of extra-space (padding) between piechart and its border in JavaFX / TornadoFX?

I have a simple piechart without name, labels, legend. I need only a circle itself. But I can't get rid of that padding between borders and content. I've tried these all and received no result (TornadoFX CSS):
diagram {
padding = box(0.px)
labelPadding = box(0.px)
borderImageInsets += box(0.px)
borderInsets += box(0.px)
backgroundInsets += box(0.px)
maxWidth = 25.px
maxHeight = 25.px
labelLineLength = 0.px
borderColor += box(Color.GREEN)
}
I want to get rid of this extra-space between a circle and green borders. Does anybody know any Java / CSS / TornadoFX solutions/options here ?
You can use a negative value for e. g. padding. Please have a look at this small example (JavaFX):
package sample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
VBox vBox = new VBox();
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("", 75),
new PieChart.Data("", 25));
final PieChart chart = new PieChart(pieChartData);
chart.setLegendVisible(false);
// Negative value for padding:
chart.setStyle("-fx-padding: -35; -fx-border-color: green; -fx-border-width: 3;");
vBox.getChildren().addAll(chart);
stage.setScene(new Scene(vBox));
stage.show();
//chart.setMaxWidth(400d); // careful
}
public static void main(String[] args) {
launch(args);
}
}
You can also quickly set a maximum width for the chart to make the green box a square but be careful please, it could mess with the overall layout:

Can I only target bottom border at JavaFX Styling?

can I somehow only style the bottom border of an textfield?
I already tried
textfield.setStyle("-fx-border-bottom-color: #FF0000");
but it hasn't worked.
Is there an possibility to color the bottom border??
Greetings
MatsG23
Here is a quick and dirty example of how that can be done.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldStyleTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
root.setCenter(vBox);
HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(hBox);
TextField textField = new TextField("Hello World");
textField.setAlignment(Pos.BASELINE_CENTER);
hBox.getChildren().add(textField);
textField.setStyle("-fx-border-color: red; -fx-border-width: 0 0 10 0;");
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class TextFieldStyleTestLauncher {public static void main(String[] args) {TextFieldStyleTest.main(args);}}
Yes, it is possible to give each side a different color. From the JavaFX CSS Reference Guide, for Region:
CSS Property: -fx-border-color
Values: <paint> | <paint> <paint> <paint> <paint> [ , [<paint> | <paint> <paint> <paint> <paint>] ]*
Default: null
Comments: A series of paint values or sets of four paint values, separated by commas. For each item in the series, if a single paint value is specified, then that paint is used as the border for all sides of the region; and if a set of four paints is specified, they are used for the top, right, bottom, and left borders of the region, in that order. If the border is not rectangular, only the first paint value in the set is used.
Note: The above is actually from one row of a table, but Stack Overflow doesn't give a way of formatting things in a table.
Meaning you can target the bottom border only by using:
.text-field {
-fx-border-color: transparent transparent red transparent;
}
The -fx-border-width CSS property (and really all the CSS properties dealing with the Region#background and Region#border properties) behaves the same way. This means you can accomplish the same thing by setting the width of every side but the bottom to zero, just like in mipa's answer.
Here's an exaple using inline CSS (i.e. setStyle):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
TextField field = new TextField("Hello, World!");
field.setStyle("-fx-border-color: transparent transparent red transparent;");
field.setMaxWidth(Region.USE_PREF_SIZE);
primaryStage.setScene(new Scene(new StackPane(field), 300, 150));
primaryStage.show();
// Remove blue outline from when TextField is focused. This
// makes it easier to see the red border.
primaryStage.getScene().getRoot().requestFocus();
}
}
Which gives the following output:
Note that most of the "borders" added by modena.css (the default user-agent style sheet in JavaFX 8+) are not actually borders. Instead, they're multiple backgrounds with different insets.

JavaFX css border-radius issue

I am trying to simulate the effect one would get from this css example:
border-radius: 50%;
From searching the API and reading posts on forums including this one, I found that I should be using -fx-background-radius. This however is not giving me the wanted effect.
I setup a picture as the background using -fx-background-image:url(...) and then I want to make it into a circle.
How can I achieve this?
Update
So I see that I was not being too specific so let me try to elaborate:
I created a Pane object, that does extend the Region class from JavaFX.
main.fxml:
...
<Pane styleClass="wrapper">
<Pane layoutX="34.0" layoutY="28.0" styleClass="image" />
</Pane>
For this pane I created the styleclass image as seen above.
main.css:
.list-contact .image {
-fx-alignment:left;
-fx-pref-height:40px;
-fx-pref-width:40px;
-fx-background-radius:50%;
-fx-background-image:url(...);
-fx-background-repeat:no-repeat;
}
The effect I get:
The effect I want:
I hope this explains it better.
This is not possible from CSS alone, since ImageView does not support any of Region's CSS properties.
However you can use a Ellipse as clip for the ImageView:
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Space_Needle_2011-07-04.jpg/304px-Space_Needle_2011-07-04.jpg");
ImageView iv = new ImageView(img);
Ellipse ellipse = new Ellipse(img.getWidth() / 2, img.getHeight() / 2, img.getWidth() / 2, img.getHeight() / 2);
iv.setClip(ellipse);
Text text = new Text("Space Needle, Seattle, Washington, USA");
StackPane.setAlignment(text, Pos.TOP_CENTER);
StackPane root = new StackPane(text, iv);
Scene scene = new Scene(root, img.getWidth(), img.getHeight());
scene.setFill(Color.AQUAMARINE);
primaryStage.setScene(scene);
primaryStage.show();
}
I know it doesn't look good to let the image cover the text. This is only done for the purpose of demonstration.
It looks like a CSS border-radius: 50% should create an elliptical border, and JavaFX CSS does support the % shorthand for either -fx-border-radius or -fx-background-radius. To get the desired effect, however, use Path.subtract() to create an elliptical matte for the image, as shown below.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/38008678/230513
*/
public class Test extends Application {
private final Image IMAGE = new Image("http://i.imgur.com/kxXhIH1.jpg");
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
int w = (int) (IMAGE.getWidth());
int h = (int) (IMAGE.getHeight());
ImageView view = new ImageView(IMAGE);
Rectangle r = new Rectangle(w, h);
Ellipse e = new Ellipse(w / 2, h / 2, w / 2, h / 2);
Shape matte = Path.subtract(r, e);
matte.setFill(Color.SIENNA);
StackPane root = new StackPane();
root.getChildren().addAll(view, matte);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In one Line with Circle as Clip.You can use setClip(any shape).:
imageView.setClip(new Circle(width,height,radius);
The width,height,radius have to be slighty smaller that ImageView size to work.
Inspired by GuiGarage web site.

How to create custom border style in JavaFX CSS?

I would like to create border style similar to predifened "dashed" style
(-fx-border-style: dashed).
How to create dashed border in CSS with custom lengths of dash segments, line cap and line join?
See the JavaFX CSS reference for Region, in particular the possible values for -fx-border-style. You can use segments(...) to define arbitrary line segment lengths: there are also settings for line-cap (square, butt, or round) and line-join (miter, bevel, or round).
Quick example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CustomBorderExample extends Application {
#Override
public void start(Stage primaryStage) {
Region region = new Region();
region.getStyleClass().add("custom-dashed-border");
region.setMinSize(400, 400);
StackPane root = new StackPane(region);
root.setPadding(new Insets(16));
Scene scene = new Scene(root, 480, 480);
scene.getStylesheets().add("custom-dashed-border.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
with
custom-dashed-border.css:
.custom-dashed-border {
-fx-border-color: blue ;
-fx-border-width: 5 ;
-fx-border-style: segments(10, 15, 15, 15) line-cap round ;
}
which gives

Resources