I'm Developing a JavaFX Application :
In here, I want the Left Pane to have a Blur Background Effect, i.e, when ever the user scrolls the map, the Content Behind the Left Pane Changes, and i want to Use That Content(Blurred), as the Background of the left Pane. & i've almost done it.
Whenever I scroll the Map, it does work, and content Behind gets updated, but In the System Monitor i can see the CPU usage, Temperature and Overall Power Usage Drastically Rises.
To achieve the Frost Glass Effect, I've Added a Event Listener(for detecting mouse move) to the webEngine(Contains the Map) :
Document doc = webEngine.getDocument();
((EventTarget) doc).addEventListener("mousemove", listener, false);
Listener Executes a Method Which :
Retrieves the Actual Content Beneath the Left Pane(Map).
Blur's the Image.
Updates the Screen
To Update the Screen, the Method removes, the Left Pane(VBox) and the Previous Image(Which was The Background). & then again First Add's the Blurred Image Pane and Then the Left Pane to the Root Pane.
So, I think the reason I'm having Performance Issues with this is because, it has to very rapidly remove and Add Panes(Left Pane and Background Image), to the Root Pane, while the user is dragging the Map.
Problem : Very High CPU Usage
So, Is there any other Approach in JavaFX, Wherein it does not require, such high CPU Usage ?
Something Like, which doesn't require removing and Adding Panes all the time.
Create two panes in a HBox, render a view of the relevant section of the map into each pane. Set a blur effect on the left pane. No listeners, snapshots or dynamic adding or removing of panes is required.
Try a couple of different blur effects (there is BoxBlur and GuassianBlur) with different settings, if needed, to adjust performance characteristics.
setting the Blur effect directly on the left Pane, blur's everything (Button text), and as i've set Transparency effect, this setup only blur's the left Pane,
Use a stackpane for the left pane with the left map section at the bottom of stack (with the blur effect applied to it) and the transparent overlay at the top of the stack (with no effect applied to it).
Is there a way , i can blur a part of a pane, so the part lying under the left Pane could be selected & blurred?
Yes, you use a technique similar to:
How to implement a transparent Pane with non-transparent children?
Frosted Glass Effect in JavaFX?
Sample
Here is a quick sample, just as a proof of concept, obviously for your solution you will need something slightly different.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
/**
* Constructs a scene with a pannable Map background.
*/
public class FrostedPannableView extends Application {
private Image backgroundImage;
private static final double W = 800;
private static final double H = 600;
#Override
public void init() {
backgroundImage = new Image("http://www.narniaweb.com/wp-content/uploads/2009/08/NarniaMap.jpg");
}
#Override
public void start(Stage stage) {
stage.setTitle("Drag the mouse to pan the map");
stage.setResizable(false);
// make a transparent pale blue overlay with non transparent blue writing on it.
final Label label = new Label("Map\nof\nNarnia");
label.setTextAlignment(TextAlignment.CENTER);
label.setStyle("-fx-text-fill: midnightblue; -fx-font: bold italic 40 'serif'; -fx-padding: 0 0 20 0;");
StackPane glass = new StackPane();
StackPane.setAlignment(label, Pos.BOTTOM_CENTER);
glass.getChildren().addAll(label);
glass.setStyle("-fx-background-color: rgba(0, 100, 100, 0.5);");
glass.setMaxWidth(W * 1/4);
glass.setMaxHeight(H);
StackPane.setAlignment(glass, Pos.CENTER_LEFT);
// construct a partitioned node with left side blurred.
ImageView leftMap = new ImageView(backgroundImage);
ImageView rightMap = new ImageView(backgroundImage);
// wrap the partitioned node in a pannable scroll pane.
ScrollPane leftScroll = createScrollPane(leftMap);
Rectangle leftClip = new Rectangle(W * 1/4, H);
leftScroll.setClip(leftClip);
leftScroll.setEffect(new GaussianBlur());
ScrollPane rightScroll = createScrollPane(rightMap);
Rectangle rightClip = new Rectangle(W * 1/4, 0, W * 3/4, H);
rightScroll.setClip(rightClip);
StackPane composite = new StackPane();
composite.getChildren().setAll(
leftScroll,
rightScroll
);
StackPane layout = new StackPane(
composite,
glass
);
// show the scene.
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
// bind the scroll values together and center the scroll contents.
leftScroll.hvalueProperty().bind(rightScroll.hvalueProperty());
leftScroll.vvalueProperty().bind(rightScroll.vvalueProperty());
rightScroll.setHvalue(rightScroll.getHmin() + (rightScroll.getHmax() - rightScroll.getHmin()) / 2);
rightScroll.setVvalue(rightScroll.getVmin() + (rightScroll.getVmax() - rightScroll.getVmin()) / 2);
}
/**
* #return a ScrollPane which scrolls the node.
*/
private ScrollPane createScrollPane(Node node) {
ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setPannable(true);
scroll.setMinSize(ScrollPane.USE_PREF_SIZE, ScrollPane.USE_PREF_SIZE);
scroll.setPrefSize(W, H);
scroll.setMaxSize(ScrollPane.USE_PREF_SIZE, ScrollPane.USE_PREF_SIZE);
scroll.setContent(node);
return scroll;
}
public static void main(String[] args) {
launch(args);
}
}
Related
I'm working with javafx and I need to have one square divided by its diagonal where each half is a different button, I have found how to shape a button, but I have no idea on how to code this new component.
The idea is:
Each half has to be a different button as explained above, any help would be great. Thank you!
Here's an example of what you want to accomplish (explanations in code comments):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class Main extends Application {
/*
* You can manipulate the shape of any Region via the Region#shape property.
* The shape will, by default, be scaled to fit the size of the region (see
* Region#scaleShape property). This means you only need to set the proportions
* of the shape.
*
* You'll also want to set the Node#pickOnBounds property to false. This way
* the mouse only interacts with the shape of the Region instead of the whole
* bounds which will remain rectangular.
*/
#Override
public void start(Stage primaryStage) {
var btn1 = new Button();
// triangle with its 90° corner in the top-left
btn1.setShape(new Polygon(0, 0, 1, 0, 0, 1));
// only interact with the shape of the button (the bounds are still rectangular)
btn1.setPickOnBounds(false);
// allow the button to grow to fill available space
btn1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btn1.setOnAction(e -> System.out.println("button-1"));
btn1.setStyle("-fx-base: green;");
var btn2 = new Button();
// triangle with its 90° corner in the bottom-right
btn2.setShape(new Polygon(1, 1, 0, 1, 1, 0));
// only interact with the shape of the button (the bounds are still rectangular)
btn2.setPickOnBounds(false);
// allow the button to grow to fill available space
btn2.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btn2.setOnAction(e -> System.out.println("button-2"));
btn2.setStyle("-fx-base: purple;");
// a StackPane centers its children on top of each other, but since
// we have two triangles taking up half a square the buttons will
// appear to be positioned in the corners
var container = new StackPane(btn1, btn2);
// keep container square (so the triangles take up half the area)
container.setMaxSize(150, 150);
primaryStage.setScene(new Scene(new StackPane(container), 300, 300));
primaryStage.show();
}
}
And this is what it looks like:
Unfortunately, I don't believe you can add text or graphics to the buttons. The text/graphic will still be centered on the button as if it were a rectangle. And when you set the shape property to a non-null value any background images are ignored. If you need more control then consider creating your own "control" (e.g. out of shapes); the reason the above uses Button is so you get all the built-in behavior of buttons.
As the title basically. I have a node in JavaFX which I want to be displayed in front of all other nodes according to certain CSS rules. I do not want this to change the ordering of the nodes in my VBox which .toFront() appears to do. See this question.
Is this even possible?
EDIT: To clarify. The situation is the following. I have a VBox containing a bunch of tightly packed ImageViews. When I hover over one I want it to grow slightly to give it the feel that the image is being lifted off of the screen. But since the ImageViews are so tightly packed only the top edge grows (visibly). The bottom edge grows but is below the following image and cannot be seen.
EDIT 2: Upon request here is a screenshot of what I am doing.
The different colour gradients are ImageViews and as I hover over one it should grow as the top edge of the top gradient has in this image (look closely at the top right corner next to the X). However as is also visible in this image the bottom edge of this ImageView has become hidden by the next gradient in this VBox and the grow is not visible.
This sounds like the perfect situation for using the viewOrder property of Node added in Java 9. The viewOrder controls how Nodes are drawn in relation to other Nodes of the same Parent without changing the order of the Nodes in the child list. Here's the Javadoc:
Defines the rendering and picking order of this Node within its parent.
This property is used to alter the rendering and picking order of a
node within its parent without reordering the parent's children list.
For example, this can be used as a more efficient way to implement
transparency sorting. To do this, an application can assign the
viewOrder value of each node to the computed distance between that
node and the viewer.
The parent will traverse its children in decreasing viewOrder order.
This means that a child with a lower viewOrder will be in front of a
child with a higher viewOrder. If two children have the same
viewOrder, the parent will traverse them in the order they appear in
the parent's children list.
However, viewOrder does not alter the layout and focus traversal order
of this Node within its parent. A parent always traverses its children
list in order when doing layout or focus traversal.
Here's an example using this property:
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var box = new HBox(createRectangles(Color.DARKBLUE, Color.FIREBRICK, 25));
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(50, 20, 50, 20));
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
private Rectangle[] createRectangles(Color start, Color end, int count) {
var list = new ArrayList<Rectangle>(count);
for (double i = 0; i < count; i++) {
var rect = new Rectangle(30, 60, start.interpolate(end, i / count));
var scaleTrans = new ScaleTransition(Duration.millis(250), rect);
scaleTrans.setFromX(1.0);
scaleTrans.setFromY(1.0);
scaleTrans.setToX(1.2);
scaleTrans.setToY(1.2);
rect.setOnMouseEntered(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(1.0);
rect.setViewOrder(-1.0);
scaleTrans.play();
});
rect.setOnMouseExited(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(-1.0);
rect.setViewOrder(0.0);
scaleTrans.play();
});
// *the "stop()"'s don't seem to be necessary. When I commented
// them out the animation still worked. In fact, the animation
// actually seems smoother in the situation where you move the
// mouse over and then away quickly (before the zoom-in completes).
list.add(rect);
}
return list.toArray(new Rectangle[0]);
}
}
It uses Rectangles instead of ImageViews but the concept is the same. When the mouse hovers over a Rectangle it sets the view order to be lower than the others and then plays a ScaleTransition to make it bigger. When the mouse exits it resets the view order back to 0 and then reverses the ScaleTransition.
Note: I used the var keyword which was added in Java 10.
And here is a GIF of the example in action:
Edit: Since you brought up CSS I went and checked if the view order could be set from a stylesheet. And it appears it can. Looking at the CSS Reference Guide there is a CSS property defined for Node named -fx-view-order.
Here is one such solution, creating a new stage to show the zoomed in image.
I do not set the proper coordinates in this sample, but this works as a proof of concept.
In a nutshell: capture the onMouseEntered and onMouseExited events and hide or show the new stage.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 0; i < 25; i++) {
root.getChildren().add(getImagePane());
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private VBox getImagePane() {
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(50);
setHeight(50);
setStyle("-fx-border-color: black");
}};
ImageView img = new ImageView("sample/imageGallery/cerulean.png") {{
setFitWidth(50);
setFitHeight(50);
setPreserveRatio(true);
Stage stage = zoomedStage(pane, this);
setOnMouseEntered(mouseEvent -> stage.show());
setOnMouseExited(mouseEvent -> stage.hide());
}};
pane.getChildren().add(img);
return pane;
}
private Stage zoomedStage(VBox parent, ImageView img) {
Stage stage = new Stage();
stage.setWidth(110);
stage.setHeight(110);
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(110);
setHeight(110);
setStyle("-fx-border-color: black");
}};
pane.setPickOnBounds(false);
ImageView zoomedImage = new ImageView(img.getImage());
zoomedImage.setFitHeight(100);
zoomedImage.setFitWidth(100);
pane.getChildren().add(zoomedImage);
stage.setScene(new Scene(pane));
stage.initStyle(StageStyle.UNDECORATED);
return stage;
}
}
From here, it should just be a matter or fixing the stage's coordinates to be centered over the image and then remove the stage's decoration.
Known Issues:
You will also need to handle the issue with the mouse cursor being blocked by the new stage. This will lead to a loop where the mouse is constantly entering and exiting the thumbnail of the image, causing the zoomed in stage to flicker.
From my point of view, you have a VBox with spacing 0 so each ImageView is tightly packed to each other so the glow effect is not well visible in every image. In that case you could just add a margin each time you want to select an ImageView in order to 'help' the glowing effect to appear.
For Java 8 :
Sadly, this can't be happened from a CSS cause the ImageView does not provide any rule for setting margin or padding. So you are more or less (in my opinion) bound to write that behaviour through code.
:
private Node createImageView(String imageLink) {
// Setting the image view
ImageView imageView = new ImageView(imageLink);
// setting the fit width of the image view
imageView.setFitWidth(400);
// Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
// Instantiating the Glow class
Glow glow = new Glow();
imageView.setOnMouseEntered(e -> {
// setting level of the glow effect
glow.setLevel(0.9);
// Adding a margin on TOP and Bottom side just to make the
// glowing effect visible
VBox.setMargin(imageView, new Insets(2,0,2,0));
});
imageView.setOnMouseExited(e -> {
// remove the glow effect
glow.setLevel(0.0);
// remove the margins
VBox.setMargin(imageView, new Insets(0));
});
// Applying bloom effect to text
imageView.setEffect(glow);
return imageView;
}
After working with JavaFX applications on a daily basis for about one and a half year, I just recently noticed that the application window only has a inner border (the border separating the frame from the content inside the stage) on the top and left side of the window. And now when I've seen it, I can't unsee it.
Here's an MCVE displaying a empty application window:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class MCVE extends Application {
#Override
public void start(Stage stage) {
AnchorPane pane = new AnchorPane();
stage.setScene(new Scene(pane, 800, 800));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
And here's the result:
If you zoom in you can clearly see what I mean:
Why is it designed like that? To me, it looks really weird. I'd like the window to have an inner border on all four sides, but maybe I'm missing some design principle here or something.
And how would I change the style of the application window so it has all four borders if I wanted to? The closest I could get was to set primaryStage.initStyle(StageStyle.UNIFIED); and then style the border of the window content pane instead. But the problem with this approach is that the standard inner border replicates the look of the outer border of the window, and just setting a custom border on the content pane somehow made it hard to replicate the style of the outer border perfectly. Either it looked too sharp or too blurry, or the color was wrong.
In the code below, the green rectangle is not removed on mouse click. However, if you resize the stage after the mouse click, the scene is repainted and the green rect vanishes.
If you set the green rectangle's size to 150/150, then some of it is immediately on top of the pane, and it vanishes immediately on mouse click.
Is this a JavaFX bug, or am I overlooking something?
How can I make the rectangle disappear on mouse click?
My environment: Windows 7 / Java 1.8.0 64-Bit Server VM build 25.0-b70.
package xschach.client;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main3 extends Application {
public static void main(String[] args) {
Application.launch(Main3.class.getName());
}
public Main3() {}
#Override
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
Rectangle rect = new Rectangle(10, 10, 200, 200);
pane.getChildren().add(rect);
stage.setScene(new Scene(pane, 300, 300));
stage.show();
Rectangle rect2 = new Rectangle(100, 100, 50, 50);
rect2.setFill(Color.GREEN);
pane.getChildren().add(rect2);
pane.setOnMouseClicked(event -> pane.getChildren().remove(rect2));
}
}
It really seems to me a bug.
Doing some tests, what happens is the very last node (on top), no matter how many we have, that lays within the bounds of the first one, when it's removed, it is not visible to the scene graph, it's not marked as dirty, and no requestLayout() is called.
I've found also other workaround. Just allow some (minimal) transparency to the first child, and it will work...
rect.setFill(Color.web("000000FE"));
And you can always put this node behind the first one...
pane.setOnMouseClicked(event -> {
rect2.toBack();
pane.getChildren().remove(rect2);
});
Anyway, consider filing a bug to Jira.
I know it's a little bit late, but it might help someone.
If you use the method .clear() on the children list of the pane if forces the render of the view.
So this solution, quite extreme, I admit it, works :
pane.setOnMouseClicked(event -> {
pane.getChildren().clear();
pane.getChildren().add(rect);
});
I found another workaround. Move the node that is not erasing to the back with the toBack() method, and then move it back to its place with the toFront() method. This seems to activate the dirty mechanism and repaints the background node correctly.
Want to add drop shadow only to the border of Grid pane not to inner child elements
here is the image of screen showing the effect.
Use a StackPane and place your GridPane in it.
Style your StackPane with CSS to apply a background color, insets and a drop shadow.
See Drop Shadow in an undecorated Pane! JAVAFX for some sample CSS.
Here is a small standalone sample app (used Java 8b120 on OS X 10.9), to demonstrate the effect:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Shadowed extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
Label clear = new Label("Clear, with no shadow");
StackPane shadowedPane = new StackPane(clear);
shadowedPane.setStyle(
"-fx-background-color: palegreen; " +
"-fx-background-insets: 10; " +
"-fx-background-radius: 10; " +
"-fx-effect: dropshadow(three-pass-box, purple, 10, 0, 0, 0);"
);
shadowedPane.setPrefSize(200, 50);
stage.setScene(new Scene(shadowedPane));
stage.show();
}
}
Addressing additional questions
Is CSS the only option?
No, this could be done in code instead of CSS, by using the DropShadow effect.
shadowedPane.setEffect(new DropShadow(10, Color.PURPLE));
Why does it work?
Because setting a background provides a clear edge to the node to which the shadow effect is applied.
Why does DropShadow work differently depending on if it's applied on a root node or a nested container?
There is no real differing behavior in the drop shadow processing between a root node or nested container. The drop shadow effect depends on whether the item having the effect applied has a transparent (or null) background or not. Note though that a root node usually fills a stage. So, if the root node has a non-transparent background color and no insets are supplied, then the shadow on the root node will not be seen as it will fall outside the visible area of the stage.
I'll offer a guess at what is happening. I think what the drop shadow effect is doing is calculating the exterior shape of the node, then applying the shadow to it. When that node has a background color, then the you will see the shadow of the node background, which is what is seen in the image supplied. If the node has no background color, then the edges of the nodes are calculated from the child nodes, so all of the children get shadows.
Now what happened is that between JavaFX 2.2 when the question was asked and JavaFX 8, the default theme for JavaFX moved from Caspian to Modena. With Caspian panes did not have any background color by default. However for Modena, panes do have a slight off-white background color by default. This means that the original poster's blurry text inside the grid pane won't occur by default in Java 8, as the GridPane will have a background which is shadowed, instead of interior text being shadowed. This can be verified via the running the following program and varying the commented out line for setting the stylesheet to Caspian:
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Shadowed extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
// setUserAgentStylesheet(STYLESHEET_CASPIAN);
Label clear = new Label("Clear, with no shadow");
GridPane shadowedPane = new GridPane();
shadowedPane.add(clear, 0, 0);
GridPane.setHalignment(clear, HPos.CENTER);
GridPane.setValignment(clear, VPos.CENTER);
GridPane.setHgrow(clear, Priority.ALWAYS);
GridPane.setVgrow(clear, Priority.ALWAYS);
shadowedPane.setStyle(
// "-fx-background-color: transparent; " +
// "-fx-background-color: palegreen; " +
"-fx-background-insets: 10; " +
"-fx-background-radius: 10; "
);
shadowedPane.setPrefSize(200, 50);
shadowedPane.setEffect(new DropShadow(10, Color.PURPLE));
stage.setScene(new Scene(shadowedPane));
stage.show();
}
}
jewelsea's answer is correct, but please note that it does not work when
// explicitly transparent
-fx-background-color: transparent;
or
// effectively transparent
-fx-background-color: #FFFFFF00;
This can be really frustrating if you are trying to apply the drop shadow to the (Stack)Pane's entire bounding box area and not it's child contents! Setting a non-transparent background color works, but if you have many elements side by side or on top of each other (i.e. close together), you may see undesirable overlap effects with the drop shadows.