Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display custom color dialog directly- JavaFX ColorPicker


I need to show a "continuous" color palette for color selection inside a ContextMenu. Similar to CustomColorDialog that pops up on ColorPicker.
Is there a different class for this purpose or is it possible to work around by extending ColorPicker and showing directly CustomColorDialog instead of first showing ColorPicker.

ColorPicker JavaFX 8

TIA

like image 729
Ruturaj Patil Avatar asked Nov 27 '14 13:11

Ruturaj Patil


People also ask

How to get color from ColorPicker JavaFX?

ColorPicker is a part of JavaFX. ColorPicker allows the user to choose a color from given set of colors or make their own custom color. An initial Color can be set using the setValue() function or defining it in a constructor and the color selected by the user can be found using the getValue() function.

How to use colors for application in JavaFX?

You can apply colors to nodes in JavaFX using the setFill() and setStroke() methods. The setFill() method adds color to the surface area of the node whereas the setStroke() method applies color to the boundary of the node. Both methods accept an object of the javafx. scene.

How to add a color picker in JavaFX?

You can create a color picker by instantiating the javafx. scene. control. ColorPicker class.


1 Answers

For starters, com.sun.javafx.scene.control.skin.CustomColorDialog is private API, and it's not advisable to use it, as it may change in the future without notice.

Besides, it is a Dialog, what means you can't embed it into a ContextMenu, it has its own window and it's modal.

This is a short example of using this (very big, not customizable) dialog in your application, without using a ColorPicker.

@Override
public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Open Custom Color Dialog");
    btn.setOnAction(e -> {
        CustomColorDialog dialog = new CustomColorDialog(primaryStage.getOwner());
        dialog.show();
    });

    Scene scene = new Scene(new StackPane(btn), 300, 250);

    primaryStage.setTitle("CustomColorDialog");
    primaryStage.setScene(scene);
    primaryStage.show();
}

You'll get the dialog, but you won't get any possibility to send a custom color or retrieve the selected color, since properties like customColorProperty() are only accesible within the com.sun.javafx.scene.control.skin package.

So we need another way to implement our custom color selector. If you have a look at the source code of CustomColorDialog you'll see that it's relatively a simple control, and most important, almost based on public API: panes, regions and color.

Trying to put all in a ContextMenu could be overkilling, so I've come up with this basic example, where I'll just use the left part of the dialog, displaying the central bar on top. Most of the code is from the class. The CSS styling was also taken from modena.css (under custom-color-dialog CSS selector), but was customized as some of the nodes were rotated 90º.

This is a short version of CustomColorDialog class:

public class MyCustomColorPicker extends VBox {

    private final ObjectProperty<Color> currentColorProperty = 
        new SimpleObjectProperty<>(Color.WHITE);
    private final ObjectProperty<Color> customColorProperty = 
        new SimpleObjectProperty<>(Color.TRANSPARENT);

    private Pane colorRect;
    private final Pane colorBar;
    private final Pane colorRectOverlayOne;
    private final Pane colorRectOverlayTwo;
    private Region colorRectIndicator;
    private final Region colorBarIndicator;
    private Pane newColorRect;

    private DoubleProperty hue = new SimpleDoubleProperty(-1);
    private DoubleProperty sat = new SimpleDoubleProperty(-1);
    private DoubleProperty bright = new SimpleDoubleProperty(-1);

    private DoubleProperty alpha = new SimpleDoubleProperty(100) {
        @Override protected void invalidated() {
            setCustomColor(new Color(getCustomColor().getRed(), getCustomColor().getGreen(), 
                    getCustomColor().getBlue(), clamp(alpha.get() / 100)));
        }
    };

    public MyCustomColorPicker() {

        getStyleClass().add("my-custom-color");

        VBox box = new VBox();

        box.getStyleClass().add("color-rect-pane");
        customColorProperty().addListener((ov, t, t1) -> colorChanged());

        colorRectIndicator = new Region();
        colorRectIndicator.setId("color-rect-indicator");
        colorRectIndicator.setManaged(false);
        colorRectIndicator.setMouseTransparent(true);
        colorRectIndicator.setCache(true);

        final Pane colorRectOpacityContainer = new StackPane();

        colorRect = new StackPane();
        colorRect.getStyleClass().addAll("color-rect", "transparent-pattern");

        Pane colorRectHue = new Pane();
        colorRectHue.backgroundProperty().bind(new ObjectBinding<Background>() {

            {
                bind(hue);
            }

            @Override protected Background computeValue() {
                return new Background(new BackgroundFill(
                        Color.hsb(hue.getValue(), 1.0, 1.0), 
                        CornerRadii.EMPTY, Insets.EMPTY));

            }
        });            

        colorRectOverlayOne = new Pane();
        colorRectOverlayOne.getStyleClass().add("color-rect");
        colorRectOverlayOne.setBackground(new Background(new BackgroundFill(
                new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, 
                new Stop(0, Color.rgb(255, 255, 255, 1)), 
                new Stop(1, Color.rgb(255, 255, 255, 0))), 
                CornerRadii.EMPTY, Insets.EMPTY)));

        EventHandler<MouseEvent> rectMouseHandler = event -> {
            final double x = event.getX();
            final double y = event.getY();
            sat.set(clamp(x / colorRect.getWidth()) * 100);
            bright.set(100 - (clamp(y / colorRect.getHeight()) * 100));
            updateHSBColor();
        };

        colorRectOverlayTwo = new Pane();
        colorRectOverlayTwo.getStyleClass().addAll("color-rect");
        colorRectOverlayTwo.setBackground(new Background(new BackgroundFill(
                new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, 
                new Stop(0, Color.rgb(0, 0, 0, 0)), new Stop(1, Color.rgb(0, 0, 0, 1))), 
                CornerRadii.EMPTY, Insets.EMPTY)));
        colorRectOverlayTwo.setOnMouseDragged(rectMouseHandler);
        colorRectOverlayTwo.setOnMousePressed(rectMouseHandler);

        Pane colorRectBlackBorder = new Pane();
        colorRectBlackBorder.setMouseTransparent(true);
        colorRectBlackBorder.getStyleClass().addAll("color-rect", "color-rect-border");

        colorBar = new Pane();
        colorBar.getStyleClass().add("color-bar");
        colorBar.setBackground(new Background(new BackgroundFill(createHueGradient(), 
                CornerRadii.EMPTY, Insets.EMPTY)));

        colorBarIndicator = new Region();
        colorBarIndicator.setId("color-bar-indicator");
        colorBarIndicator.setMouseTransparent(true);
        colorBarIndicator.setCache(true);

        colorRectIndicator.layoutXProperty().bind(
            sat.divide(100).multiply(colorRect.widthProperty()));
        colorRectIndicator.layoutYProperty().bind(
            Bindings.subtract(1, bright.divide(100)).multiply(colorRect.heightProperty()));
        colorBarIndicator.layoutXProperty().bind(
            hue.divide(360).multiply(colorBar.widthProperty()));
        colorRectOpacityContainer.opacityProperty().bind(alpha.divide(100));

        EventHandler<MouseEvent> barMouseHandler = event -> {
            final double x = event.getX();
            hue.set(clamp(x / colorRect.getWidth()) * 360);
            updateHSBColor();
        };

        colorBar.setOnMouseDragged(barMouseHandler);
        colorBar.setOnMousePressed(barMouseHandler);

        newColorRect = new Pane();
        newColorRect.getStyleClass().add("color-new-rect");
        newColorRect.setId("new-color");
        newColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
            {
                bind(customColorProperty);
            }
            @Override protected Background computeValue() {
                return new Background(new BackgroundFill(customColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
            }
        });

        colorBar.getChildren().setAll(colorBarIndicator);
        colorRectOpacityContainer.getChildren().setAll(colorRectHue, colorRectOverlayOne, colorRectOverlayTwo);
        colorRect.getChildren().setAll(colorRectOpacityContainer, colorRectBlackBorder, colorRectIndicator);
        VBox.setVgrow(colorRect, Priority.SOMETIMES);
        box.getChildren().addAll(colorBar, colorRect, newColorRect);

        getChildren().add(box);

        if (currentColorProperty.get() == null) {
            currentColorProperty.set(Color.TRANSPARENT);
        }
        updateValues();

    }

    private void updateValues() {
        hue.set(getCurrentColor().getHue());
        sat.set(getCurrentColor().getSaturation()*100);
        bright.set(getCurrentColor().getBrightness()*100);
        alpha.set(getCurrentColor().getOpacity()*100);
        setCustomColor(Color.hsb(hue.get(), clamp(sat.get() / 100), 
                clamp(bright.get() / 100), clamp(alpha.get()/100)));
    }

    private void colorChanged() {
        hue.set(getCustomColor().getHue());
        sat.set(getCustomColor().getSaturation() * 100);
        bright.set(getCustomColor().getBrightness() * 100);
    }

    private void updateHSBColor() {
        Color newColor = Color.hsb(hue.get(), clamp(sat.get() / 100), 
                        clamp(bright.get() / 100), clamp(alpha.get() / 100));
        setCustomColor(newColor);
    }

    @Override 
    protected void layoutChildren() {
        super.layoutChildren();            
        colorRectIndicator.autosize();
    }

    static double clamp(double value) {
        return value < 0 ? 0 : value > 1 ? 1 : value;
    }

    private static LinearGradient createHueGradient() {
        double offset;
        Stop[] stops = new Stop[255];
        for (int x = 0; x < 255; x++) {
            offset = (double)((1.0 / 255) * x);
            int h = (int)((x / 255.0) * 360);
            stops[x] = new Stop(offset, Color.hsb(h, 1.0, 1.0));
        }
        return new LinearGradient(0f, 0f, 1f, 0f, true, CycleMethod.NO_CYCLE, stops);
    }

    public void setCurrentColor(Color currentColor) {
        this.currentColorProperty.set(currentColor);
        updateValues();
    }

    Color getCurrentColor() {
        return currentColorProperty.get();
    }

    final ObjectProperty<Color> customColorProperty() {
        return customColorProperty;
    }

    void setCustomColor(Color color) {
        customColorProperty.set(color);
    }

    Color getCustomColor() {
        return customColorProperty.get();
    }
}

This is the color.css file:

.context-menu{
    -fx-background-color: derive(#ececec,26.4%);
}
.menu-item:focused {
    -fx-background-color: transparent;
}

/* CUSTOM COLOR */

.my-custom-color {
    -fx-background-color: derive(#ececec,26.4%);
    -fx-padding: 1.25em;
    -fx-spacing: 1.25em;
    -fx-min-width: 20em;
    -fx-pref-width: 20em;
    -fx-max-width: 20em;
}

.my-custom-color:focused,
.my-custom-color:selected {
    -fx-background-color: transparent;
}

.my-custom-color > .color-rect-pane {
    -fx-spacing: 0.75em;
    -fx-pref-height: 16.666667em;
    -fx-alignment: top-left;
    -fx-fill-height: true;
}

.my-custom-color .color-rect-pane .color-rect {
    -fx-min-width: 16.666667em;
    -fx-min-height: 16.666667em;
}

.my-custom-color .color-rect-pane .color-rect-border {
    -fx-border-color: derive(#ececec, -20%);
}

.my-custom-color > .color-rect-pane #color-rect-indicator {
    -fx-background-color: null;
    -fx-border-color: white;
    -fx-border-radius: 0.4166667em;
    -fx-translate-x: -0.4166667em;
    -fx-translate-y: -0.4166667em;
    -fx-pref-width: 0.833333em;
    -fx-pref-height: 0.833333em;
    -fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
}

.my-custom-color > .color-rect-pane > .color-bar {
    -fx-min-height: 1.666667em;
    -fx-min-width: 16.666667em;
    -fx-max-height: 1.666667em;
    -fx-border-color: derive(#ececec, -20%);
}

.my-custom-color > .color-rect-pane > .color-bar > #color-bar-indicator {
    -fx-border-radius: 0.333333em;
    -fx-border-color: white;
    -fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
    -fx-pref-height: 2em;
    -fx-pref-width: 0.833333em;
    -fx-translate-y: -0.1666667em;
    -fx-translate-x: -0.4166667em;
}

.my-custom-color .transparent-pattern {
    -fx-background-image: url("pattern-transparent.png"); 
    -fx-background-repeat: repeat;
    -fx-background-size: auto;
}

.my-custom-color .color-new-rect {
    -fx-min-width: 10.666667em;
    -fx-min-height: 1.75em;
    -fx-pref-width: 10.666667em;
    -fx-pref-height: 1.75em;
    -fx-border-color: derive(#ececec, -20%);
}

The image can be found here.

And finally, our application class.

public class CustomColorContextMenu extends Application {

    private final ObjectProperty<Color> sceneColorProperty = 
        new SimpleObjectProperty<>(Color.WHITE);

    @Override
    public void start(Stage primaryStage) {

        Rectangle rect = new Rectangle(400,400);
        rect.fillProperty().bind(sceneColorProperty);

        Scene scene = new Scene(new StackPane(rect), 400, 400);
        scene.getStylesheets().add(getClass().getResource("color.css").toExternalForm());
        scene.setOnMouseClicked(e->{
            if(e.getButton().equals(MouseButton.SECONDARY)){
                MyCustomColorPicker myCustomColorPicker = new MyCustomColorPicker();
                myCustomColorPicker.setCurrentColor(sceneColorProperty.get());

                CustomMenuItem itemColor = new CustomMenuItem(myCustomColorPicker);
                itemColor.setHideOnClick(false);
                sceneColorProperty.bind(myCustomColorPicker.customColorProperty());
                ContextMenu contextMenu = new ContextMenu(itemColor);
                contextMenu.setOnHiding(t->sceneColorProperty.unbind());
                contextMenu.show(scene.getWindow(),e.getScreenX(),e.getScreenY());
            }
        });

        primaryStage.setTitle("Custom Color Selector");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

Note the use of CustomMenuItem to allow clicking on the color selectors without closing the context menu. To close it just click anywhere outside the popup window.

This is how it looks like:

Custom Color Selector

Based on this custom dialog, you can improve it and add the functionality you may need.

like image 182
José Pereda Avatar answered Oct 24 '22 05:10

José Pereda