How to make JavaFX ColorPicker disabled without dim appearence? - javafx-2

I've been trying to disable a javafx.scene.control.ColorPicker without dimming it in my UI. Meaning that I want it to look as enabled (enabled appearence), but just do not respond to any command, also not showing the table of colors on mouse click (actually disabled). This behaviour will be interchangeable with normal behaviour, according to the system state (i.e., sometimes color picker will behave as normal).
I've tried some options, but none seemed to work, as follows:
1. Use setEditable(boolean):
myColorPicker.setEditable(false);
This just doesn't work, the color picker remains editable.
2. Use setDisable(boolean) and setOpacity(double) together:
myColorPicker.setDisable(true);
myColorPicker.setOpacity(1.0f);
This makes the color picker actually not editable, and the resulting color picker appear a little less dimmed than just using setDisable(true), but still not the appearence of an enabled color picker.
3. Overriding onMouseClick(), onMousePressed() and onMouseReleased() with empty implementations:
myColorPicker.setOnMouseClicked(new EventHandler <MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("Mouse clicked.");
}
});
myColorPicker.setOnMousePressed(new EventHandler <MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("Mouse pressed.");
}
});
myColorPicker.setOnMouseReleased(new EventHandler <MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("Mouse released.");
}
});
The above approach printed on my console the corresponding messages, but color picker still responded to mouse click (showing the color table and allowing to pick a new color). Also tried to override setOnAction(EventHandler<ActionEvent>), but with same effect (except that no console printing when I clicked the color picker).
Here it is the excerpt of my FXML:
<VBox fx:controller="mypackage.ElementConfigWidget"
xmlns:fx="http://javafx.com/fxml" fx:id="root" styleClass="elementConfigPane">
<HBox id="elementInfo">
(...)
<ColorPicker fx:id="elementColor" styleClass="elementColor" />
</HBox>
(...)
</VBox>
Here it is my CSS exerpt:
.elementColor {
-fx-cursor: hand;
-fx-background-color: #fff;
-fx-focus-color: transparent;
-fx-faint-focus-color: transparent;
-fx-pref-width: 50.0;
}
I actually expected setEditable(boolean) to solve my problem, by keeping the element appearence and ignoring input actions. What am I missing?
Thanks a lot!

I manage to achieve it with some CSS
.Also you need to setDisable(true) and opacity to 1.0
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
ColorPicker myColorPicker = new ColorPicker();
myColorPicker.setDisable(true);
myColorPicker.setOpacity(1.0);
Scene s = new Scene(myColorPicker);
s.getStylesheets().add(this.getClass().getResource("test.css").toExternalForm());
primaryStage.setScene(s);
primaryStage.show();
}
}
And the test.css
.color-picker > .label:disabled {
-fx-opacity : 1.0;
}

Related

JDialog doesn't size correctly with wrapped JTextArea

While making a program, I noticed a bug with the JOptionPane.showMessageDialog() call. I use a button to create a JTextArea that wraps and then display a dialog containing this text area.
If the text area is too large, however, the dialog does not size itself correctly to the height of the JTextArea. The Dialog cuts off the OK button in this example.
I replicated the bug in the following code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DialogBug {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final String text = "looooooooooooooooooooooong text looooooooooooooooooooooooooooooooooooooong text";
JButton button = new JButton();
button.setPreferredSize(new Dimension(30, 30));
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JTextArea area = new JTextArea(text, 0, 50);
area.setEditable(false);
area.setLineWrap(true);
area.setWrapStyleWord(true);
area.append(text);
area.append(text);
area.append(text);
JOptionPane.showMessageDialog(frame, area, "why does it do this", JOptionPane.WARNING_MESSAGE);
}
});
frame.add(button);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
I would post a picture, but I don't have enough reputation...
Is there a way to fix this without having to use a JScrollPane?
Here's a screenshot:
If you run the pack command on the dialog (a function in the Window class) it will resize based on subcomponents. For your case you will have to rewrite without using the showMessageDialog() to get the resize to work (so make the dialog first, add the text, pack, then show it)
Dialog b = new Dialog();
// add stuff
b.pack();
For my test code it worked perfectly to get the dialogs to be the right sizes
Without pack()
With pack()

How to redirect MouseEvent.MOUSE_PRESSED to a different Node?

I have a Region called 'R', and a Node called 'N'. N is the only child on R. Consider this behaviour:
the user presses the left-mouse button on some part of R
N (which is somewhere else on R) moves so that it is centered on the spot where the user pressed
the user releases the left-mouse button, then presses it again without moving the mouse
with the left-mouse button still pressed, the user drags the mouse, and N now follows the mouse cursor around as it is dragged.
I have no problems implementing the behaviour I've just described. I put a MOUSE_PRESSED handler on R that implements step 2. And I put a MOUSE_DRAGGED handler on N that implements step 4. JavaFX automatically directs the MouseEvents to these handlers on R and N for the presses in step 1 and 3 respectively.
The Problem:
I need to do this WITHOUT step 3. That is, the user should not have to press-release-press-drag, but rather should simply press-drag, and N should "jump" to the mouse location on the "press", and then start receiving MOUSE_DRAGGED events immediately.
Unfortunately, this doesn't happen. The release-click that I'm trying to omit seems to be necessary, otherwise the drag events all happen on R instead of N.
I'm thinking the solution will involve redispatching the initial MOUSE_PRESSED, or something along those lines. Does anyone know a way to do this (or a better way to solve my problem?)
Node has api to mark it as the target of drag gestures:
public void startFullDrag()
Starts a full press-drag-release gesture with this node as gesture
source. This method can be called only from a DRAG_DETECTED mouse
event handler. More detail about dragging gestures can be found in the
overview of MouseEvent and MouseDragEvent.
Assuming circle being your currently active node in a pane (borrowing code/names from James's answer), the collaborators are handlers on
mousePressed on pane that snaps the position of circle to the the current location
dragDetected on pane that calls startsFullDrag on circle
dragAny on circle that does the actual moving
In code:
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
pane.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.startFullDrag();
}
});
circle.addEventHandler(MouseDragEvent.ANY, new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
I chose the simplest node to work with for this, but I think this would work in general:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ClickAndDragTest extends Application {
#Override
public void start(Stage primaryStage) {
final Pane pane = new Pane();
final Circle circle = new Circle(100, 100, 50, Color.CORNFLOWERBLUE);
pane.getChildren().add(circle);
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
final Scene scene = new Scene(pane, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Hover effect over icon

I would like to create buttons like these for settings panel navigation:
Can you tell me how I can create this hover effect over the icons? The most difficult part for me is to create CSS code which looks like the the picture.
Although the above answer works. You should really do this completely in CSS using pseudo-selectors:
java:
btnsa.getStyleClass().add("myButton");
css:
.myButton {
-fx-background-color:transparent;
}
.myButton:hover {
-fx-background-color:#dae7f3;
}
You have to use MouseEntered and MouseExited events for getting hover effects over the icons.
try this its working.........
btnsa.setStyle("-fx-background-color:transparent;");
btnsa.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("JavafxSm.gif"))));
btnsa.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
btnsa.setStyle("-fx-background-color:#dae7f3;");
}
});
btnsa.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
btnsa.setStyle("-fx-background-color:transparent;");
}
});
some snap shots of above code......
Instead, you can do just 1 line code in CSS, if your FXML file connected with CSS
yourButtonId:hover{-fx-background-color: #6695e2}

Less CSS trickery for hover and multiple fonts on button with round edges

Goal: Create a round button that has multiple text fonts.
Example: See RoundButtonWithMultipleFonts.java and RoundButtonWithMultipleFonts.css
RoundButtonWithMultipleFonts.java:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class RoundButtonWithMultipleFonts extends Application {
public static void main(String... args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Button with multiple fonts?");
stage.setScene(new Scene(getRoot(), 400, 400));
stage.getScene().getStylesheets().addAll(getClass().getResource("RoundButtonWithMultipleFonts.css").toExternalForm());
stage.sizeToScene();
stage.show();
}
private Parent getRoot() {
Button button = new Button(""); // The labels should be the buttons text
button.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Button clicked");
}
});
Label header = new Label("A Prideful Header"); // Label for big font on button
header.getStyleClass().addAll("header");
Label footer = new Label("a humble footer"); // Label for small font on button
footer.getStyleClass().addAll("footer");
// Since the labels are on top of the button, pass any events they capture to the button
configurePassThroughEvents(button, header, footer);
StackPane stack = new StackPane();
stack.getChildren().addAll(button, header, footer);
return stack;
}
private void configurePassThroughEvents(Control targetControl, Control... sourceControls) {
MouseEventPassThrough passThroughEvent = new MouseEventPassThrough(targetControl);
for(Control sourceControl : sourceControls) {
sourceControl.setOnMouseClicked(passThroughEvent);
sourceControl.setOnMouseDragged(passThroughEvent);
sourceControl.setOnMouseDragEntered(passThroughEvent);
sourceControl.setOnMouseDragExited(passThroughEvent);
sourceControl.setOnMouseDragOver(passThroughEvent);
sourceControl.setOnMouseDragReleased(passThroughEvent);
sourceControl.setOnMouseEntered(passThroughEvent);
sourceControl.setOnMouseExited(passThroughEvent);
sourceControl.setOnMouseMoved(passThroughEvent);
sourceControl.setOnMousePressed(passThroughEvent);
sourceControl.setOnMouseReleased(passThroughEvent);
}
}
private static class MouseEventPassThrough implements EventHandler<MouseEvent> {
private final Control targetControl;
private MouseEventPassThrough(Control targetControl) { this.targetControl = targetControl; }
#Override public void handle(MouseEvent mouseEvent) { targetControl.fireEvent(mouseEvent); }
}
}
RoundButtonWithMultipleFonts.css:
.button {
-fx-border-width: 1px;
-fx-border-color: #000000;
-fx-border-radius: 45;
-fx-background-color: linear-gradient(#ffffff 0%, #cccccc 100%);
-fx-background-radius: 45;
-fx-padding: 50 100;
}
.button:hover {
-fx-background-color: linear-gradient(#ffffff 0%, coral 100%);
}
.label {
-fx-padding: 10;
-fx-background-color: cornflowerblue;
}
.header {
-fx-font-size: 110%;
-fx-font-weight: bold;
-fx-translate-y: -20;
}
.footer {
-fx-font-size: 80%;
-fx-translate-y: 20;
}
Runtime Results:
Problem:
When the mouse scrolls over one of the button's corners, the button enters the hovered state, but the mouse is still outside the visual bounds of the button indicated by the button's border and background. (See image.)
This example uses a stack pane, multiple labels, an event pass through mechanism, and css trickery to give the appearance of a button containing text with multiple fonts.
Questions:
How can I specify that the button should enter the hovered state only if the mouse collides with the buttons visual boundary as specified in the css with the border or background properties?
Is there a simpler way to specify multiple fonts (with general text layout) for a button than what is done in this example? Ideally I would want to just use a Button with a nested Node as the text. That would allow me to put anything I wanted inside the buttons textual area without needing the event pass through mechanism, the StackPane, and the css trickery.
You can use setGraphic(Node node); method of Button class to set your custom labels on button. Here is an example,
Label header = new Label("A Prideful Header");
header.getStyleClass().addAll("header");
Label footer = new Label("a humble footer");
footer.getStyleClass().addAll("footer");
VBox box = new VBox();
box.getChildren().addAll(header,footer);
Button button = new Button();
button.setGraphic(box);

Checking Collision of Shapes with JavaFX

I am trying to do some collision detection. For this test I am using simple rectangular Shape, and checking their Bound, to figure if they are colliding. Although the detection does not work as expected. I have tried using different ways to move the object(relocate, setLayoutX,Y) and also different bound checks (boundsInLocal,boundsInParrent etc) but I still cannot get this to work. As you can see the detection works only for one object, even when you have three objects only one detects collision. This is some working code demonstrating the problem:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
public class CollisionTester extends Application {
private ArrayList<Rectangle> rectangleArrayList;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
primaryStage.setTitle("The test");
Group root = new Group();
Scene scene = new Scene(root, 400, 400);
rectangleArrayList = new ArrayList<Rectangle>();
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.GREEN));
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.RED));
rectangleArrayList.add(new Rectangle(30.0, 30.0, Color.CYAN));
for(Rectangle block : rectangleArrayList){
setDragListeners(block);
}
root.getChildren().addAll(rectangleArrayList);
primaryStage.setScene(scene);
primaryStage.show();
}
public void setDragListeners(final Rectangle block) {
final Delta dragDelta = new Delta();
block.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = block.getTranslateX() - mouseEvent.getSceneX();
dragDelta.y = block.getTranslateY() - mouseEvent.getSceneY();
block.setCursor(Cursor.NONE);
}
});
block.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
block.setCursor(Cursor.HAND);
}
});
block.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
block.setTranslateX(mouseEvent.getSceneX() + dragDelta.x);
block.setTranslateY(mouseEvent.getSceneY() + dragDelta.y);
checkBounds(block);
}
});
}
private void checkBounds(Rectangle block) {
for (Rectangle static_bloc : rectangleArrayList)
if (static_bloc != block) {
if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
block.setFill(Color.BLUE); //collision
} else {
block.setFill(Color.GREEN); //no collision
}
} else {
block.setFill(Color.GREEN); //no collision -same block
}
}
class Delta {
double x, y;
}
}
Looks like you have a slight logic error in your checkBounds routine - you are correctly detecting collisions (based on bounds) but are overwriting the fill of your block when you perform subsequent collision checks in the same routine.
Try something like this - it adds a flag so that the routine does not "forget" that a collision was detected:
private void checkBounds(Shape block) {
boolean collisionDetected = false;
for (Shape static_bloc : nodes) {
if (static_bloc != block) {
static_bloc.setFill(Color.GREEN);
if (block.getBoundsInParent().intersects(static_bloc.getBoundsInParent())) {
collisionDetected = true;
}
}
}
if (collisionDetected) {
block.setFill(Color.BLUE);
} else {
block.setFill(Color.GREEN);
}
}
Note that the check you are doing (based on bounds in parent) will report intersections of the rectangle enclosing the visible bounds of nodes within the same parent group.
Alternate Implementation
In case you need it, I updated your original sample so that it is able to check based on the visual shape of the Node rather than the bounding box of the visual shape. This lets you to accurately detect collisions for non-rectangular shapes such as Circles. The key for this is the Shape.intersects(shape1, shape2) method.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.ArrayList;
import javafx.scene.shape.*;
public class CircleCollisionTester extends Application {
private ArrayList<Shape> nodes;
public static void main(String[] args) { launch(args); }
#Override public void start(Stage primaryStage) {
primaryStage.setTitle("Drag circles around to see collisions");
Group root = new Group();
Scene scene = new Scene(root, 400, 400);
nodes = new ArrayList<>();
nodes.add(new Circle(15, 15, 30));
nodes.add(new Circle(90, 60, 30));
nodes.add(new Circle(40, 200, 30));
for (Shape block : nodes) {
setDragListeners(block);
}
root.getChildren().addAll(nodes);
checkShapeIntersection(nodes.get(nodes.size() - 1));
primaryStage.setScene(scene);
primaryStage.show();
}
public void setDragListeners(final Shape block) {
final Delta dragDelta = new Delta();
block.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = block.getLayoutX() - mouseEvent.getSceneX();
dragDelta.y = block.getLayoutY() - mouseEvent.getSceneY();
block.setCursor(Cursor.NONE);
}
});
block.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
block.setCursor(Cursor.HAND);
}
});
block.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
block.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
block.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
checkShapeIntersection(block);
}
});
}
private void checkShapeIntersection(Shape block) {
boolean collisionDetected = false;
for (Shape static_bloc : nodes) {
if (static_bloc != block) {
static_bloc.setFill(Color.GREEN);
Shape intersect = Shape.intersect(block, static_bloc);
if (intersect.getBoundsInLocal().getWidth() != -1) {
collisionDetected = true;
}
}
}
if (collisionDetected) {
block.setFill(Color.BLUE);
} else {
block.setFill(Color.GREEN);
}
}
class Delta { double x, y; }
}
Sample program output. In the sample the circles have been dragged around and the user is currently dragging a circle which has been marked as colliding with another circle (by painting it blue) - for demonstration purposes only the circle currently being dragged has it's collision color marked.
Comments based on additional questions
The link I posted to an intersection demo application in a prior comment was to illustrate the use of various bounds types rather than as a specific type of collision detection sample. For your use case, you don't need the additional complexity of the change listener and checking on various different kinds of bounds types - just settling on one type will be enough. Most collision detection is only going to be interested in intersection of visual bounds rather than other JavaFX bounds types such as the layout bounds or local bounds of a node. So you can either:
Check for intersection of getBoundsInParent (as you did in your original question) which works on the smallest rectangular box which will encompass the visual extremities of the node OR
Use the Shape.intersect(shape1, shape2) routine if you need to check based on the visual shape of the Node rather than the bounding box of the visual shape.
Should I be using setLayoutX or translateX for the rectangle
The layoutX and layoutY properties are intended for positioning or laying out nodes. The translateX and translateY properties are intended for temporary changes to the visual location of a node (for example when the node is undergoing an animation). For your example, though either property will work, it is perhaps better form to use the layout properties than the translate ones, that way if you did want to run something like a TranslateTransition on the nodes, it will be more obvious what the start and end translate values should be as those values will be relative to the current layout position of the node rather than the position in the parent group.
Another way you could use these layout and translate co-ordinates in tandem in your sample is if you had something like an ESC to cancel during the course of a drag operation. You could set layoutX,Y to the initial location of your node, start a drag operation which sets translateX,Y values and if the user presses ESC, set translateX,Y back to 0 to cancel the drag operation or if the user releases the mouse set layoutX,Y to layoutX,Y+translateX,Y and set translateX,Y back to 0. The idea is that the translation is values are used for a temporary modification of the visual co-ordinates of the node from it's original layout position.
will the intersect work even though the circles are animated? I mean without dragging the circle by mouse, what will happen if I made them to move around randomly. Will the colour change in this case also?
To do this, just change where the collision detection function is called and the collision handler invoked. Rather than checking for intersections based upon a mouse drag event (like the example above), instead check for collisions within a change listener on each node's boundsInParentProperty().
block.boundsInParentProperty().addListener((observable, oldValue, newValue) ->
checkShapeIntersection(block)
);
Note: if you have lots of shapes being animated, then checking for collisions once per frame within a game loop will be more efficient than running a collision check whenever any node moves (as is done in the boundsInParentProperty change listener above).
Additional info for handling input on non-rectangular shapes
For input detection not collision detection, so not directly related to your question, look at the node.pickOnBounds setting if you need mouse or touch interaction with a non-rectangular node.

Resources