MartinJoo
MartinJoo

Reputation: 2872

JavaFX ColorPicker show uncolor option

I want to show un-color option in my ColorPicker. How i can show it?

Thanks. enter image description here

Upvotes: 1

Views: 834

Answers (2)

JOpolka
JOpolka

Reputation: 121

The approach posted above didn't work for me anymore so I came up with a slightly different solution, though the idea is the same. It also avoids using deprecated functions.

I subclassed the ColorPicker class to build my own CustomColorPicker which can be used instead.

Here is my code:

@SuppressWarnings("restriction")
public class CustomColorPicker extends ColorPicker {

    final static LinearGradient RED_LINE = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, 
                                                        new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE), 
                                                        new Stop(0.46, Color.RED), new Stop(0.54, Color.RED), 
                                                        new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));

    @Override 
    protected Skin<?> createDefaultSkin() {

        final CustomColorPickerSkin skin = new CustomColorPickerSkin(this);
        final Label lbl = (Label)skin.getDisplayNode();
        final StackPane pane = (StackPane)lbl.getGraphic();
        final Rectangle rect = (Rectangle)pane.getChildren().get(0);

        // set initial color to red line if transparent is shown
        if (getValue().equals(Color.TRANSPARENT))
            rect.setFill(RED_LINE);

        // set color to red line when transparent is selected
        rect.fillProperty().addListener((o, oldVal, newVal) -> {
            if (newVal != null && newVal.equals(Color.TRANSPARENT))
                rect.setFill(RED_LINE);     
        });

        return skin;
     }

    private class CustomColorPickerSkin extends ColorPickerSkin {

        private boolean initialized = false;

        public CustomColorPickerSkin(ColorPicker colorPicker) {
            super(colorPicker);
        }

        @Override 
        protected Node getPopupContent() {
            final ColorPalette popupContent = (ColorPalette)super.getPopupContent();

            // make sure listeners and geometry are only created once
            if (!initialized) {
                final VBox paletteBox = (VBox)popupContent.getChildrenUnmodifiable().get(0);
                final StackPane hoverSquare = (StackPane)popupContent.getChildrenUnmodifiable().get(1); // ColorSquare
                final Rectangle hoverRect = (Rectangle)hoverSquare.getChildren().get(0); // ColorSquare
                final GridPane grid = (GridPane)paletteBox.getChildren().get(0); // ColorPalette
                final StackPane colorSquare = (StackPane)grid.getChildren().get(grid.getChildren().size()-1); // ColorSquare
                final Rectangle colorRect = (Rectangle)colorSquare.getChildren().get(0);

                // set fill color of original color rectangle to transparent
                // (can't be set to red line gradient because ComboBoxBase<Color> tries to cast it to Color)
                colorRect.setFill(Color.TRANSPARENT);
                // put another rectangle with red line on top of it
                colorSquare.getChildren().add(new Rectangle(colorRect.getWidth(), colorRect.getHeight(), RED_LINE));
                // show red line gradient also in hover rectangle when the transparent color is selected
                hoverRect.fillProperty().addListener((o, oldVal, newVal) -> {
                    if (newVal.equals(Color.TRANSPARENT))
                        hoverRect.setFill(RED_LINE);
                });

                initialized = true;
            }

            return popupContent;
        }
    }
}

Upvotes: 1

Jos&#233; Pereda
Jos&#233; Pereda

Reputation: 45486

The solution is a bit of a hack, but it avoids using private API.

These are the required steps:

  • Get the Popup control that shows up when you click on the ColorPicker.

You can find it here or here.

  • Get the square colors on that popup, so we can change one of them. I'll use the last one.

Once we have the popup, we'll get the set of square colors by using lookups: Set<Node> squares = popup.lookupAll(".color-rect");

Let's use the last color to add our customized 'un-color'.

  • Find out how to draw that red diagonal line.

I've come up with a LinearGradient:

final LinearGradient redLine = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, 
    new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE), 
    new Stop(0.46, Color.RED), new Stop(0.54, Color.RED), 
    new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));

That works fine, but sadly the gradient breaks the ColorPicker control, that is an extension of ComboBoxBase<Color>, and all the fills used for the rectangles will be casted to Color instead of Paint. That means we'll have to use a color (for instance Color.TRANSPARENT) during the transitions.

  • Solve other issues like the square color that will be seen when the popup closes, or the square color that shows up on hovering.

For this, we need to lookup for both the square color in the color picker and hovered square, and when those match our transparent one, replace the color with the gradient.

This is the code:

public class UnColorPicker extends Application {

    private final LinearGradient redLine = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, 
                            new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE), new Stop(0.46, Color.RED),
                            new Stop(0.54, Color.RED), new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));

    @Override
    public void start(Stage primaryStage) {
        ColorPicker picker = new ColorPicker();
        StackPane root = new StackPane(picker);
        Scene scene = new Scene(root, 500, 400);

        primaryStage.setScene(scene);
        primaryStage.show();

        Rectangle rect = (Rectangle) root.lookup(".picker-color-rect");
        Label label = (Label) root.lookup(".color-picker-label");
        picker.showingProperty().addListener((obs, b, b1) -> {
            if (b1) {
                PopupWindow popupWindow = getPopupWindow();
                Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
                StackPane hover = (StackPane) popup.lookup(".hover-square");
                Rectangle rectH = (Rectangle) hover.getChildren().get(0);
                Set<Node> squares = popup.lookupAll(".color-rect");
                squares.stream()
                        .skip(squares.size()-2)
                        .map(Rectangle.class::cast)
                        .findFirst()
                        .ifPresent(r -> {
                            r.getParent().setOnMousePressed(e -> {
                                // avoid CastException
                                r.setFill(Color.TRANSPARENT);
                                e.consume();
                            });
                            r.getParent().setOnMouseReleased(e -> {
                                Platform.runLater(() -> {
                                    rect.setFill(redLine);
                                    label.setText("Un-color");
                                });
                            });
                            r.setFill(redLine);
                            Tooltip.install(r.getParent(), new Tooltip("Un-color"));
                        });
                hover.visibleProperty().addListener((obs2, ov, nv) -> {
                    if (nv && rectH.getFill().equals(Color.TRANSPARENT)) {
                        Platform.runLater(() -> rectH.setFill(redLine));
                    }
                });
            }
        });
    }

    private PopupWindow getPopupWindow() {
        @SuppressWarnings("deprecation") 
        final Iterator<Window> windows = Window.impl_getWindows();
        while (windows.hasNext()) {
            final Window window = windows.next();
            if (window instanceof PopupWindow) {
                return (PopupWindow)window;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        launch(args);
    }

}

Uncolor picker

Upvotes: 0

Related Questions