/*******************************************************************************
* Copyright (c) 2014, 2015 itemis AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.fx.swt.controls;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.embed.swt.FXCanvas;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
/**
* An SWT control that can be used to select a JavaFX color (and indicates the
* selected color via an image).
*
* @author anyssen
*
*/
public class FXColorPicker extends Composite {
/**
* Property name used in change events related to {@link #colorProperty()}.
*/
public static final String COLOR_PROPERTY = "color";
/**
* Opens a {@link ColorDialog} to let the user pick a {@link Color}. Returns
* the picked {@link Color}, or <code>null</code> if no color was picked.
*
* @param shell
* The {@link Shell} which serves as the parent for the
* {@link ColorDialog}.
* @param initial
* The initial {@link Color} to display in the
* {@link ColorDialog}.
* @return The picked {@link Color}, or <code>null</code>.
*/
protected static Color pickColor(Shell shell, Color initial) {
ColorDialog cd = new ColorDialog(shell);
RGB rgb = new RGB((int) (255 * initial.getRed()),
(int) (255 * initial.getGreen()),
(int) (255 * initial.getBlue()));
cd.setRGB(rgb);
RGB newRgb = cd.open();
if (newRgb != null) {
return Color.rgb(newRgb.red, newRgb.green, newRgb.blue);
}
return null;
}
private ObjectProperty<Color> color = new SimpleObjectProperty<>(this,
COLOR_PROPERTY);
private Rectangle colorRectangle;
/**
* Constructs a new {@link FXColorPicker}.
*
* @param parent
* The parent {@link Composite}.
* @param color
* The initial {@link Color} to set.
*/
public FXColorPicker(final Composite parent, Color color) {
super(parent, SWT.NONE);
setLayout(new FillLayout());
FXCanvas canvas = new FXCanvas(this, SWT.NONE);
// container
Group colorPickerGroup = new Group();
HBox hbox = new HBox();
colorPickerGroup.getChildren().add(hbox);
// color wheel
WritableImage colorWheelImage = new WritableImage(64, 64);
renderColorWheel(colorWheelImage, 0, 0, 64);
ImageView colorWheel = new ImageView(colorWheelImage);
colorWheel.setFitWidth(16);
colorWheel.setFitHeight(16);
BorderPane colorWheelPane = new BorderPane();
Insets insets = new Insets(2.0);
colorWheelPane.setPadding(insets);
colorWheelPane.setCenter(colorWheel);
// use background color of parent composite (the wheel image is
// transparent outside the wheel, so otherwise the hbox color would look
// through)
colorWheelPane.setStyle("-fx-background-color: "
+ computeRgbString(Color.rgb(parent.getBackground().getRed(),
parent.getBackground().getGreen(),
parent.getBackground().getBlue())));
colorRectangle = new Rectangle(50, 20);
// bind to ColorWheel instead of buttonPane to prevent layout
// problems.
colorRectangle.widthProperty().bind(colorWheel.fitWidthProperty()
.add(insets.getLeft()).add(insets.getRight()).multiply(2.5));
colorRectangle.heightProperty().bind(colorWheelPane.heightProperty());
// draw 'border' around hbox (and fill background, which covers the
// space beween color rect and wheel
hbox.setStyle("-fx-border-color: " + computeRgbString(Color.DARKGREY)
+ "; -fx-background-color: "
+ computeRgbString(Color.DARKGREY));
hbox.getChildren().addAll(colorRectangle, colorWheelPane);
hbox.setSpacing(0.5);
// interaction
colorWheelPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Color colorOrNull = FXColorPicker.pickColor(getShell(),
getColor());
if (colorOrNull != null) {
setColor(colorOrNull);
}
}
});
Scene scene = new Scene(colorPickerGroup);
// copy background color from parent composite
org.eclipse.swt.graphics.Color backgroundColor = parent.getBackground();
scene.setFill(Color.rgb(backgroundColor.getRed(),
backgroundColor.getGreen(), backgroundColor.getBlue()));
canvas.setScene(scene);
// initialize some color
setColor(color);
colorRectangle.fillProperty().bind(this.color);
}
/**
* A writable property for the color controlled by this
* {@link FXColorPicker}.
*
* @return A writable {@link Property}.
*/
public Property<Color> colorProperty() {
return color;
}
private String computeRgbString(Color color) {
return "rgb(" + (int) (255 * color.getRed()) + ","
+ (int) (255 * color.getGreen()) + ","
+ (int) (255 * color.getBlue()) + ")";
}
/**
* Returns the currently selected {@link Color}.
*
* @return The currently selected {@link Color}.
*/
public Color getColor() {
return color.get();
}
/**
* Draws a color wheel into the given {@link WritableImage}, starting at the
* given offsets, in the given size (in pixel).
*
* @param image
* The {@link WritableImage} in which the color wheel is drawn.
* @param offsetX
* The horizontal offset (in pixel).
* @param offsetY
* The vertical offset (in pixel).
* @param size
* The size (in pixel).
*/
private void renderColorWheel(WritableImage image, int offsetX, int offsetY,
int size) {
PixelWriter px = image.getPixelWriter();
double radius = size / 2;
Point2D mid = new Point2D(radius, radius);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
double d = mid.distance(x, y);
if (d <= radius) {
// compute hue angle
double angleRad = d == 0 ? 0
: Math.atan2(y - mid.getY(), x - mid.getX());
// compute saturation depending on distance to
// middle
// ([0;1])
double sat = d / radius;
Color color = Color.hsb(angleRad * 180 / Math.PI, sat, 1);
px.setColor(offsetX + x, offsetY + y, color);
} else {
px.setColor(offsetX + x, offsetY + y, Color.TRANSPARENT);
}
}
}
}
/**
* Changes the currently selected {@link Color} to the given value.
*
* @param color
* The newly selected {@link Color}.
*/
public void setColor(Color color) {
this.color.set(color);
}
}