Add color palette preview and color contrast checker
This commit is contained in:
parent
a7255949ce
commit
05e278e56b
@ -135,7 +135,6 @@ public abstract class AbstractPage extends BorderPane implements Page {
|
||||
|
||||
// set syntax highlight theme according to JavaFX theme
|
||||
ThemeManager tm = ThemeManager.getInstance();
|
||||
System.out.println(tm.getMatchingHighlightJSTheme(tm.getTheme()).getBackground());
|
||||
codeViewer.setContent(stream, tm.getMatchingHighlightJSTheme(tm.getTheme()));
|
||||
|
||||
graphic.setIconCode(ICON_SAMPLE);
|
||||
|
@ -0,0 +1,148 @@
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.util.Containers;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.sampler.page.general.ColorContrastChecker.*;
|
||||
import static atlantafx.sampler.util.JColorUtils.flattenColor;
|
||||
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
|
||||
|
||||
class ColorBlock extends VBox {
|
||||
|
||||
private final String fgColorName;
|
||||
private final String bgColorName;
|
||||
private final String borderColorName;
|
||||
|
||||
private final AnchorPane colorBox;
|
||||
private final Text fgText;
|
||||
private final FontIcon wsagIcon = new FontIcon();
|
||||
private final Label wsagLabel = new Label();
|
||||
private final FontIcon expandIcon = new FontIcon(Feather.MAXIMIZE_2);
|
||||
|
||||
private Consumer<ColorBlock> actionHandler;
|
||||
|
||||
public ColorBlock(String fgColorName, String bgColorName, String borderColorName) {
|
||||
this.fgColorName = validateColorName(fgColorName);
|
||||
this.bgColorName = validateColorName(bgColorName);
|
||||
this.borderColorName = validateColorName(borderColorName);
|
||||
|
||||
fgText = new Text();
|
||||
fgText.setStyle("-fx-fill:" + fgColorName + ";");
|
||||
fgText.getStyleClass().addAll("text", Styles.TITLE_3);
|
||||
Containers.setAnchors(fgText, new Insets(5, -1, -1, 5));
|
||||
|
||||
wsagLabel.setGraphic(wsagIcon);
|
||||
wsagLabel.getStyleClass().add("wsag-label");
|
||||
wsagLabel.setVisible(false);
|
||||
Containers.setAnchors(wsagLabel, new Insets(-1, 3, 3, -1));
|
||||
|
||||
expandIcon.setIconSize(24);
|
||||
expandIcon.getStyleClass().add("expand-icon");
|
||||
expandIcon.setVisible(false);
|
||||
expandIcon.setManaged(false);
|
||||
Containers.setAnchors(expandIcon, new Insets(3, 3, -1, -1));
|
||||
|
||||
colorBox = new AnchorPane();
|
||||
colorBox.setStyle("-fx-background-color:" + bgColorName + ";" + "-fx-border-color:" + borderColorName + ";");
|
||||
colorBox.getStyleClass().add("box");
|
||||
colorBox.getChildren().setAll(fgText, wsagLabel, expandIcon);
|
||||
colorBox.setOnMouseEntered(e -> {
|
||||
toggleHover(true);
|
||||
var bgFill = getBgColor();
|
||||
// doesn't play quite well with transparency, because we not calc
|
||||
// actual underlying background color to flatten bgFill
|
||||
expandIcon.setFill(getColorLuminance(flattenColor(Color.WHITE, bgFill)) < LUMINANCE_THRESHOLD ?
|
||||
Color.WHITE : Color.BLACK
|
||||
);
|
||||
});
|
||||
colorBox.setOnMouseExited(e -> toggleHover(false));
|
||||
colorBox.setOnMouseClicked(e -> {
|
||||
if (actionHandler != null) { actionHandler.accept(this); }
|
||||
});
|
||||
|
||||
getChildren().addAll(
|
||||
colorBox,
|
||||
description(fgColorName),
|
||||
description(bgColorName),
|
||||
description(borderColorName)
|
||||
);
|
||||
getStyleClass().add("color-block");
|
||||
}
|
||||
|
||||
static String validateColorName(String colorName) {
|
||||
if (colorName == null || !colorName.startsWith("-color")) {
|
||||
throw new IllegalArgumentException("Invalid color name: '" + colorName + "'.");
|
||||
}
|
||||
return colorName;
|
||||
}
|
||||
|
||||
private void toggleHover(boolean state) {
|
||||
expandIcon.setVisible(state);
|
||||
expandIcon.setManaged(state);
|
||||
fgText.setOpacity(state ? 0.5 : 1);
|
||||
wsagLabel.setOpacity(state ? 0.5 : 1);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
var fgFill = getFgColor();
|
||||
var bgFill = getBgColor();
|
||||
|
||||
if (fgFill == null || bgFill == null) {
|
||||
fgText.setText("");
|
||||
wsagLabel.setText("");
|
||||
wsagLabel.setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
double contrastRatio = 1 / getContrastRatioOpacityAware(bgFill, fgFill);
|
||||
colorBox.pseudoClassStateChanged(PASSED, contrastRatio >= 4.5);
|
||||
|
||||
wsagIcon.setIconCode(contrastRatio >= 4.5 ? Material2AL.CHECK : Material2AL.CLOSE);
|
||||
wsagLabel.setVisible(true);
|
||||
wsagLabel.setText(contrastRatio >= 7 ? "AAA" : "AA");
|
||||
fgText.setText(String.format("%.2f", contrastRatio));
|
||||
}
|
||||
|
||||
public Color getFgColor() {
|
||||
return (Color) fgText.getFill();
|
||||
}
|
||||
|
||||
public Color getBgColor() {
|
||||
return colorBox.getBackground() != null & !colorBox.getBackground().isEmpty() ?
|
||||
(Color) colorBox.getBackground().getFills().get(0).getFill() :
|
||||
null;
|
||||
}
|
||||
|
||||
public String getFgColorName() {
|
||||
return fgColorName;
|
||||
}
|
||||
|
||||
public String getBgColorName() {
|
||||
return bgColorName;
|
||||
}
|
||||
|
||||
public String getBorderColorName() {
|
||||
return borderColorName;
|
||||
}
|
||||
|
||||
public void setOnAction(Consumer<ColorBlock> actionHandler) {
|
||||
this.actionHandler = actionHandler;
|
||||
}
|
||||
|
||||
private Text description(String text) {
|
||||
var t = new Text(text);
|
||||
t.getStyleClass().addAll("description", Styles.TEXT_SMALL);
|
||||
return t;
|
||||
}
|
||||
}
|
@ -0,0 +1,457 @@
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.util.JColor;
|
||||
import atlantafx.sampler.util.JColorUtils;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.sampler.page.general.ColorBlock.validateColorName;
|
||||
import static atlantafx.sampler.util.JColorUtils.*;
|
||||
|
||||
// Inspired by the https://colourcontrast.cc/
|
||||
public class ColorContrastChecker extends GridPane {
|
||||
|
||||
static final PseudoClass PASSED = PseudoClass.getPseudoClass("passed");
|
||||
static final float[] COLOR_WHITE = new float[] { 255f, 255f, 255f, 1f };
|
||||
static final float[] COLOR_BLACK = new float[] { 0f, 0f, 0f, 1f };
|
||||
static final double LUMINANCE_THRESHOLD = 0.55;
|
||||
|
||||
private static final int SLIDER_WIDTH = 300;
|
||||
|
||||
private String bgColorName;
|
||||
private String fgColorName;
|
||||
|
||||
private final ObservableHSLAColor bgColor = new ObservableHSLAColor(Color.WHITE);
|
||||
private final ObservableHSLAColor fgColor = new ObservableHSLAColor(Color.BLACK);
|
||||
private final DoubleBinding contrastRatio = Bindings.createDoubleBinding(
|
||||
() -> 1 / getContrastRatioOpacityAware(bgColor.getColor(), fgColor.getColor()),
|
||||
bgColor.colorProperty(),
|
||||
fgColor.colorProperty()
|
||||
);
|
||||
|
||||
private Label bgColorNameLabel;
|
||||
private Label fgColorNameLabel;
|
||||
private Slider bgHueSlider;
|
||||
private Slider bgSaturationSlider;
|
||||
private Slider bgLightnessSlider;
|
||||
private Slider bgAlphaSlider;
|
||||
private Slider fgHueSlider;
|
||||
private Slider fgSaturationSlider;
|
||||
private Slider fgLightnessSlider;
|
||||
private Slider fgAlphaSlider;
|
||||
|
||||
public ColorContrastChecker() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
public void setValues(String fgColorName, Color fgColor,
|
||||
String bgColorName, Color bgColor) {
|
||||
this.bgColorName = validateColorName(bgColorName);
|
||||
bgColorNameLabel.setText(bgColorName);
|
||||
setBackground(bgColor);
|
||||
|
||||
this.fgColorName = validateColorName(fgColorName);
|
||||
fgColorNameLabel.setText(fgColorName);
|
||||
setForeground(fgColor);
|
||||
}
|
||||
|
||||
public String getBgColorName() { return bgColorName; }
|
||||
|
||||
public String getFgColorName() { return fgColorName; }
|
||||
|
||||
public Color getBgColor() { return bgColor.colorProperty().get(); }
|
||||
|
||||
public Color getFgColor() { return fgColor.colorProperty().get(); }
|
||||
|
||||
private void createView() {
|
||||
var textLabel = new Label("Aa");
|
||||
textLabel.getStyleClass().add("text");
|
||||
|
||||
var ratioLabel = new Label("0.0");
|
||||
ratioLabel.getStyleClass().add("ratio");
|
||||
ratioLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("%.2f", contrastRatio.get()), contrastRatio
|
||||
));
|
||||
|
||||
var fontBox = new HBox(20, textLabel, ratioLabel);
|
||||
fontBox.getStyleClass().add("font-box");
|
||||
fontBox.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
// !
|
||||
|
||||
var aaNormalLabel = wsagLabel();
|
||||
var aaNormalBox = wsagBox(aaNormalLabel, "AA Normal");
|
||||
|
||||
var aaLargeLabel = wsagLabel();
|
||||
var aaLargeBox = wsagBox(aaLargeLabel, "AA Large");
|
||||
|
||||
var aaaNormalLabel = wsagLabel();
|
||||
var aaaNormalBox = wsagBox(aaaNormalLabel, "AAA Normal");
|
||||
|
||||
var aaaLargeLabel = wsagLabel();
|
||||
var aaaLargeBox = wsagBox(aaaLargeLabel, "AAA Large");
|
||||
|
||||
var wsagBox = new HBox(20, aaNormalBox, aaLargeBox, aaaNormalBox, aaaLargeBox);
|
||||
wsagBox.getStyleClass().add("wsag-box");
|
||||
|
||||
contrastRatio.addListener((obs, old, val) -> {
|
||||
if (val == null) { return; }
|
||||
float ratio = val.floatValue();
|
||||
if (ratio >= 7) {
|
||||
updateWsagLabel(aaNormalLabel, true);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, true);
|
||||
updateWsagLabel(aaaLargeLabel, true);
|
||||
} else if (ratio < 7 && ratio >= 4.5) {
|
||||
updateWsagLabel(aaNormalLabel, true);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, true);
|
||||
} else if (ratio < 4.5 && ratio >= 3) {
|
||||
updateWsagLabel(aaNormalLabel, false);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, false);
|
||||
} else {
|
||||
updateWsagLabel(aaNormalLabel, false);
|
||||
updateWsagLabel(aaLargeLabel, false);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, false);
|
||||
}
|
||||
});
|
||||
|
||||
// ~
|
||||
|
||||
bgColorNameLabel = new Label("Background Color");
|
||||
bgColorNameLabel.setPadding(new Insets(-15, 0, 0, 0));
|
||||
bgColorNameLabel.getStyleClass().add(Styles.TEXT_SMALL);
|
||||
|
||||
var bgTextField = new CustomTextField();
|
||||
bgTextField.setEditable(false);
|
||||
bgTextField.setLeft(new FontIcon(Feather.HASH));
|
||||
bgTextField.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> bgColor.getColorHexWithAlpha().substring(1), bgColor.colorProperty()
|
||||
));
|
||||
|
||||
fgColorNameLabel = new Label("Foreground Color");
|
||||
fgColorNameLabel.setPadding(new Insets(-15, 0, 0, 0));
|
||||
fgColorNameLabel.getStyleClass().add(Styles.TEXT_SMALL);
|
||||
|
||||
var fgTextField = new CustomTextField();
|
||||
fgTextField.setEditable(false);
|
||||
fgTextField.setLeft(new FontIcon(Feather.HASH));
|
||||
fgTextField.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> fgColor.getColorHexWithAlpha().substring(1), fgColor.colorProperty()
|
||||
));
|
||||
|
||||
bgHueSlider = slider(1, 360, 1, 1);
|
||||
bgHueSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { bgColor.setHue(val.floatValue()); }
|
||||
});
|
||||
var bgHueLabel = new Label("Hue °");
|
||||
bgHueLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Hue %.0f °", bgHueSlider.getValue()), bgHueSlider.valueProperty())
|
||||
);
|
||||
|
||||
bgSaturationSlider = slider(0, 1, 0, 0.01);
|
||||
bgSaturationSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { bgColor.setSaturation(val.floatValue()); }
|
||||
});
|
||||
var bgSaturationLabel = new Label("Saturation");
|
||||
bgSaturationLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Saturation %.2f", bgSaturationSlider.getValue()), bgSaturationSlider.valueProperty())
|
||||
);
|
||||
|
||||
bgLightnessSlider = slider(0, 1, 0, 0.01);
|
||||
bgLightnessSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { bgColor.setLightness(val.floatValue()); }
|
||||
});
|
||||
var bgLightnessLabel = new Label("Lightness");
|
||||
bgLightnessLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Lightness %.2f", bgLightnessSlider.getValue()), bgLightnessSlider.valueProperty())
|
||||
);
|
||||
|
||||
bgAlphaSlider = slider(0, 1, 0, 0.01);
|
||||
bgAlphaSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { bgColor.setAlpha(val.floatValue()); }
|
||||
});
|
||||
var bgAlphaLabel = new Label("Alpha");
|
||||
bgAlphaLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Alpha %.2f", bgAlphaSlider.getValue()), bgAlphaSlider.valueProperty())
|
||||
);
|
||||
|
||||
// ~
|
||||
|
||||
fgHueSlider = slider(1, 360, 1, 1);
|
||||
fgHueSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { fgColor.setHue(val.floatValue()); }
|
||||
});
|
||||
var fgHueLabel = new Label("Hue °");
|
||||
fgHueLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Hue %.0f °", fgHueSlider.getValue()), fgHueSlider.valueProperty())
|
||||
);
|
||||
|
||||
fgSaturationSlider = slider(0, 1, 0, 0.01);
|
||||
fgSaturationSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { fgColor.setSaturation(val.floatValue()); }
|
||||
});
|
||||
var fgSaturationLabel = new Label("Saturation");
|
||||
fgSaturationLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Saturation %.2f", fgSaturationSlider.getValue()), fgSaturationSlider.valueProperty())
|
||||
);
|
||||
|
||||
fgLightnessSlider = slider(0, 1, 0, 0.01);
|
||||
fgLightnessSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { fgColor.setLightness(val.floatValue()); }
|
||||
});
|
||||
var fgLightnessLabel = new Label("Lightness");
|
||||
fgLightnessLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Lightness %.2f", fgLightnessSlider.getValue()), fgLightnessSlider.valueProperty())
|
||||
);
|
||||
|
||||
fgAlphaSlider = slider(0, 1, 0, 0.01);
|
||||
fgAlphaSlider.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { fgColor.setAlpha(val.floatValue()); }
|
||||
});
|
||||
var fgAlphaLabel = new Label("Alpha");
|
||||
fgAlphaLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Alpha %.2f", fgAlphaSlider.getValue()), fgAlphaSlider.valueProperty())
|
||||
);
|
||||
|
||||
// ~
|
||||
|
||||
getStyleClass().add("contrast-checker");
|
||||
|
||||
// column 0
|
||||
add(fontBox, 0, 0);
|
||||
add(new Label("Background Color"), 0, 1);
|
||||
add(bgColorNameLabel, 0, 2);
|
||||
add(bgTextField, 0, 3);
|
||||
add(bgHueLabel, 0, 4);
|
||||
add(bgHueSlider, 0, 5);
|
||||
add(bgSaturationLabel, 0, 6);
|
||||
add(bgSaturationSlider, 0, 7);
|
||||
add(bgLightnessLabel, 0, 8);
|
||||
add(bgLightnessSlider, 0, 9);
|
||||
add(bgAlphaLabel, 0, 10);
|
||||
add(bgAlphaSlider, 0, 11);
|
||||
|
||||
// column 1
|
||||
add(wsagBox, 1, 0);
|
||||
add(new Label("Foreground Color"), 1, 1);
|
||||
add(fgColorNameLabel, 1, 2);
|
||||
add(fgTextField, 1, 3);
|
||||
add(fgHueLabel, 1, 4);
|
||||
add(fgHueSlider, 1, 5);
|
||||
add(fgSaturationLabel, 1, 6);
|
||||
add(fgSaturationSlider, 1, 7);
|
||||
add(fgLightnessLabel, 1, 8);
|
||||
add(fgLightnessSlider, 1, 9);
|
||||
add(fgAlphaLabel, 1, 10);
|
||||
add(fgAlphaSlider, 1, 11);
|
||||
|
||||
bgColor.colorProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { updateStyle(); }
|
||||
});
|
||||
|
||||
fgColor.colorProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { updateStyle(); }
|
||||
});
|
||||
}
|
||||
|
||||
private void updateStyle() {
|
||||
float[] bg = bgColor.getRGBAColor();
|
||||
float[] fg = fgColor.getRGBAColor();
|
||||
|
||||
// use fallback color if contrast ratio is too low
|
||||
if (contrastRatio.get() <= LUMINANCE_THRESHOLD) {
|
||||
fg = getColorLuminance(flattenColor(Color.WHITE, bgColor.getColor())) < 0.55 ? COLOR_WHITE : COLOR_BLACK;
|
||||
}
|
||||
|
||||
setStyle(String.format("-color-contrast-checker-bg:rgba(%.0f,%.0f,%.0f,%.2f);-color-contrast-checker-fg:rgba(%.0f,%.0f,%.0f,%.2f);",
|
||||
bg[0], bg[1], bg[2], bg[3],
|
||||
fg[0], fg[1], fg[2], fg[3]
|
||||
));
|
||||
}
|
||||
|
||||
private void setBackground(Color color) {
|
||||
float[] hsl = JColorUtils.toHSL(
|
||||
(float) color.getRed(),
|
||||
(float) color.getGreen(),
|
||||
(float) color.getBlue()
|
||||
);
|
||||
bgHueSlider.setValue(hsl[0]);
|
||||
bgSaturationSlider.setValue(hsl[1]);
|
||||
bgLightnessSlider.setValue(hsl[2]);
|
||||
bgAlphaSlider.setValue(color.getOpacity());
|
||||
}
|
||||
|
||||
private void setForeground(Color color) {
|
||||
float[] hsl = JColorUtils.toHSL(
|
||||
(float) color.getRed(),
|
||||
(float) color.getGreen(),
|
||||
(float) color.getBlue()
|
||||
);
|
||||
fgHueSlider.setValue(hsl[0]);
|
||||
fgSaturationSlider.setValue(hsl[1]);
|
||||
fgLightnessSlider.setValue(hsl[2]);
|
||||
fgAlphaSlider.setValue(color.getOpacity());
|
||||
}
|
||||
|
||||
private void updateWsagLabel(Label label, boolean success) {
|
||||
FontIcon icon = Objects.requireNonNull((FontIcon) label.getGraphic());
|
||||
if (success) {
|
||||
label.setText("PASS");
|
||||
icon.setIconCode(Material2AL.CHECK);
|
||||
} else {
|
||||
label.setText("FAIL");
|
||||
icon.setIconCode(Material2AL.CLOSE);
|
||||
}
|
||||
label.pseudoClassStateChanged(PASSED, success);
|
||||
}
|
||||
|
||||
private Label wsagLabel() {
|
||||
var label = new Label("FAIL");
|
||||
label.getStyleClass().add("wsag-label");
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
label.setGraphic(new FontIcon(Material2AL.CLOSE));
|
||||
return label;
|
||||
}
|
||||
|
||||
private VBox wsagBox(Label label, String description) {
|
||||
var box = new VBox(10, label, new Label(description));
|
||||
box.setAlignment(Pos.CENTER);
|
||||
return box;
|
||||
}
|
||||
|
||||
private Slider slider(double min, double max, double value, double increment) {
|
||||
var slider = new Slider(min, max, value);
|
||||
slider.setMinWidth(SLIDER_WIDTH);
|
||||
slider.setMajorTickUnit(increment);
|
||||
slider.setBlockIncrement(increment);
|
||||
slider.setMinorTickCount(0);
|
||||
slider.setSnapToTicks(true);
|
||||
return slider;
|
||||
}
|
||||
|
||||
static double getContrastRatioOpacityAware(Color bgColor, Color fgColor) {
|
||||
double luminance1 = getColorLuminance(flattenColor(Color.WHITE, bgColor));
|
||||
double luminance2 = getColorLuminance(flattenColor(Color.WHITE, fgColor));
|
||||
return getContrastRatio(luminance1, luminance2);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class ObservableHSLAColor {
|
||||
|
||||
private final ObservableList<Float> values = FXCollections.observableArrayList(0f, 0f, 0f, 0f);
|
||||
private final ReadOnlyObjectWrapper<Color> color = new ReadOnlyObjectWrapper<>() { };
|
||||
|
||||
public ObservableHSLAColor(Color initialColor) {
|
||||
color.set(initialColor);
|
||||
values.addListener((ListChangeListener<Float>) c -> {
|
||||
float[] rgb = getRGBAArithmeticColor();
|
||||
color.set(Color.color(rgb[0], rgb[1], rgb[2], getAlpha()));
|
||||
});
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color.get();
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
float[] hsl = JColorUtils.toHSL(
|
||||
(float) color.getRed(),
|
||||
(float) color.getGreen(),
|
||||
(float) color.getBlue()
|
||||
);
|
||||
values.setAll(hsl[0], hsl[1], hsl[2], (float) color.getOpacity());
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<Color> colorProperty() {
|
||||
return color.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public float getHue() {
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
public void setHue(float value) {
|
||||
values.set(0, value);
|
||||
}
|
||||
|
||||
public float getSaturation() {
|
||||
return values.get(1);
|
||||
}
|
||||
|
||||
public void setSaturation(float value) {
|
||||
values.set(1, value);
|
||||
}
|
||||
|
||||
public float getLightness() {
|
||||
return values.get(2);
|
||||
}
|
||||
|
||||
public void setLightness(float value) {
|
||||
values.set(2, value);
|
||||
}
|
||||
|
||||
public float getAlpha() {
|
||||
return values.get(3);
|
||||
}
|
||||
|
||||
public void setAlpha(float value) {
|
||||
values.set(3, value);
|
||||
}
|
||||
|
||||
public float[] getRGBAArithmeticColor() {
|
||||
float[] hsl = new float[] { getHue(), getSaturation(), getLightness() };
|
||||
var color = JColor.color(hsl, getAlpha());
|
||||
return new float[] {
|
||||
color.getRedArithmetic(),
|
||||
color.getGreenArithmetic(),
|
||||
color.getBlueArithmetic(),
|
||||
getAlpha()
|
||||
};
|
||||
}
|
||||
|
||||
public float[] getRGBAColor() {
|
||||
float[] hsl = new float[] { getHue(), getSaturation(), getLightness() };
|
||||
var color = JColor.color(hsl, getAlpha());
|
||||
return new float[] {
|
||||
color.getRed(),
|
||||
color.getGreen(),
|
||||
color.getBlue(),
|
||||
getAlpha()
|
||||
};
|
||||
}
|
||||
|
||||
public String getColorHexWithAlpha() {
|
||||
float[] hsl = new float[] { getHue(), getSaturation(), getLightness() };
|
||||
return JColor.color(hsl, getAlpha()).getColorHexWithAlpha();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class ColorPalette extends VBox {
|
||||
|
||||
private final List<ColorBlock> blocks = new ArrayList<>();
|
||||
|
||||
private Label headerLabel;
|
||||
private Button backBtn;
|
||||
private GridPane colorGrid;
|
||||
private ColorContrastChecker contrastChecker;
|
||||
private VBox contrastCheckerArea;
|
||||
|
||||
public ColorPalette() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
headerLabel = new Label("Color Palette");
|
||||
headerLabel.getStyleClass().add(Styles.TITLE_4);
|
||||
|
||||
backBtn = new Button("Back", new FontIcon(Feather.CHEVRONS_LEFT));
|
||||
backBtn.getStyleClass().add(Styles.FLAT);
|
||||
backBtn.setVisible(false);
|
||||
backBtn.setManaged(false);
|
||||
backBtn.setOnAction(e -> {
|
||||
headerLabel.setText("Color Palette");
|
||||
backBtn.setVisible(false);
|
||||
backBtn.setManaged(false);
|
||||
getChildren().set(1, colorGrid);
|
||||
});
|
||||
|
||||
var headerBox = new HBox();
|
||||
headerBox.getChildren().setAll(headerLabel, new Spacer(), backBtn);
|
||||
headerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
contrastCheckerArea = new VBox();
|
||||
contrastCheckerArea.getStyleClass().add("contrast-checker-area");
|
||||
|
||||
colorGrid = colorGrid();
|
||||
|
||||
getChildren().setAll(headerBox, colorGrid);
|
||||
setId("color-palette");
|
||||
}
|
||||
|
||||
private GridPane colorGrid() {
|
||||
var grid = new GridPane();
|
||||
grid.getStyleClass().add("grid");
|
||||
|
||||
grid.add(colorBlock("-color-fg-default", "-color-bg-default", "-color-border-default"), 0, 0);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-bg-default", "-color-border-muted"), 1, 0);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-bg-default", "-color-border-subtle"), 2, 0);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-accent-emphasis", "-color-accent-emphasis"), 0, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-bg-default", "-color-accent-emphasis"), 1, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-muted", "-color-accent-muted"), 2, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-subtle", "-color-accent-subtle"), 3, 1);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis-plus", "-color-neutral-emphasis-plus"), 0, 2);
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis", "-color-neutral-emphasis"), 1, 2);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-neutral-muted", "-color-neutral-muted"), 2, 2);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-neutral-subtle", "-color-neutral-subtle"), 3, 2);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-success-emphasis", "-color-success-emphasis"), 0, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-bg-default", "-color-success-emphasis"), 1, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-muted", "-color-success-muted"), 2, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-subtle", "-color-success-subtle"), 3, 3);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-warning-emphasis", "-color-warning-emphasis"), 0, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-bg-default", "-color-warning-emphasis"), 1, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-muted", "-color-warning-muted"), 2, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-subtle", "-color-warning-subtle"), 3, 4);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-danger-emphasis", "-color-danger-emphasis"), 0, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-bg-default", "-color-danger-emphasis"), 1, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-muted", "-color-danger-muted"), 2, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-subtle", "-color-danger-subtle"), 3, 5);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private ColorBlock colorBlock(String fgColor, String bgColor, String borderColor) {
|
||||
var actionHandler = new Consumer<ColorBlock>() {
|
||||
@Override
|
||||
public void accept(ColorBlock colorBlock) {
|
||||
ColorContrastChecker c = getOrCreateContrastChecker();
|
||||
c.setValues(colorBlock.getFgColorName(),
|
||||
colorBlock.getFgColor(),
|
||||
colorBlock.getBgColorName(),
|
||||
colorBlock.getBgColor()
|
||||
);
|
||||
|
||||
if (contrastCheckerArea.getChildren().isEmpty()) {
|
||||
contrastCheckerArea.getChildren().setAll(c);
|
||||
}
|
||||
|
||||
headerLabel.setText("Contrast Checker");
|
||||
backBtn.setVisible(true);
|
||||
backBtn.setManaged(true);
|
||||
getChildren().set(1, contrastCheckerArea);
|
||||
}
|
||||
};
|
||||
|
||||
var block = new ColorBlock(fgColor, bgColor, borderColor);
|
||||
block.setOnAction(actionHandler);
|
||||
blocks.add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
private ColorContrastChecker getOrCreateContrastChecker() {
|
||||
if (contrastChecker == null) { contrastChecker = new ColorContrastChecker(); }
|
||||
VBox.setVgrow(contrastChecker, Priority.ALWAYS);
|
||||
return contrastChecker;
|
||||
}
|
||||
|
||||
// To calculate contrast ratio, we have to obtain all components colors first.
|
||||
// Unfortunately, JavaFX doesn't provide an API to observe when stylesheet changes has been applied.
|
||||
// The timer is introduced to defer widget update to a time when scene changes supposedly will be finished.
|
||||
public void updateColorInfo(Duration delay) {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Platform.runLater(() -> blocks.forEach(ColorBlock::update));
|
||||
}
|
||||
}, delay.toMillis());
|
||||
}
|
||||
}
|
@ -6,16 +6,18 @@ import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ThemePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Theme";
|
||||
|
||||
private final ColorPalette colorPalette = new ColorPalette();
|
||||
|
||||
@Override
|
||||
public String getName() { return NAME; }
|
||||
|
||||
@ -24,8 +26,17 @@ public class ThemePage extends AbstractPage {
|
||||
createView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
colorPalette.updateColorInfo(Duration.ZERO);
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
userContent.getChildren().add(optionsGrid());
|
||||
userContent.getChildren().addAll(
|
||||
optionsGrid(),
|
||||
colorPalette
|
||||
);
|
||||
sourceCodeToggleBtn.setVisible(false);
|
||||
}
|
||||
|
||||
@ -33,10 +44,6 @@ public class ThemePage extends AbstractPage {
|
||||
ChoiceBox<Theme> themeSelector = themeSelector();
|
||||
themeSelector.setPrefWidth(200);
|
||||
|
||||
Spinner<Integer> fontSizeSpinner = fontSizeSpinner();
|
||||
fontSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
fontSizeSpinner.setPrefWidth(200);
|
||||
|
||||
// ~
|
||||
|
||||
var grid = new GridPane();
|
||||
@ -46,9 +53,6 @@ public class ThemePage extends AbstractPage {
|
||||
grid.add(new Label("Color theme"), 0, 0);
|
||||
grid.add(themeSelector, 1, 0);
|
||||
|
||||
grid.add(new Label("Font size"), 0, 1);
|
||||
grid.add(fontSizeSpinner, 1, 1);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
@ -59,6 +63,7 @@ public class ThemePage extends AbstractPage {
|
||||
selector.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && getScene() != null) {
|
||||
ThemeManager.getInstance().setTheme(getScene(), val);
|
||||
colorPalette.updateColorInfo(Duration.ofSeconds(1));
|
||||
}
|
||||
});
|
||||
|
||||
@ -70,7 +75,8 @@ public class ThemePage extends AbstractPage {
|
||||
|
||||
@Override
|
||||
public Theme fromString(String themeName) {
|
||||
return manager.getAvailableThemes().stream().filter(t -> Objects.equals(themeName, t.getName()))
|
||||
return manager.getAvailableThemes().stream()
|
||||
.filter(t -> Objects.equals(themeName, t.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
@ -86,23 +92,4 @@ public class ThemePage extends AbstractPage {
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
private Spinner<Integer> fontSizeSpinner() {
|
||||
var spinner = new Spinner<Integer>(10, 24, 14);
|
||||
|
||||
// Instead of this we should obtain font size from a rendered node.
|
||||
// But since it's not trivial (thanks to JavaFX doesn't expose relevant API)
|
||||
// we just keep current font size inside ThemeManager singleton.
|
||||
// It works fine if ThemeManager default font size value matches
|
||||
// default theme font size value.
|
||||
spinner.getValueFactory().setValue(ThemeManager.getInstance().getFontSize());
|
||||
|
||||
spinner.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && getScene() != null) {
|
||||
ThemeManager.getInstance().setFontSize(getScene(), val);
|
||||
}
|
||||
});
|
||||
|
||||
return spinner;
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,22 @@ package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static atlantafx.base.theme.Styles.*;
|
||||
|
||||
public class TypographyPage extends AbstractPage {
|
||||
@ -20,7 +28,7 @@ public class TypographyPage extends AbstractPage {
|
||||
@Override
|
||||
public String getName() { return NAME; }
|
||||
|
||||
private GridPane fontSizeBox;
|
||||
private GridPane fontSizeSampleContent;
|
||||
|
||||
public TypographyPage() {
|
||||
super();
|
||||
@ -28,10 +36,18 @@ public class TypographyPage extends AbstractPage {
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
Spinner<Integer> fontSizeSpinner = fontSizeSpinner();
|
||||
fontSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
fontSizeSpinner.setPrefWidth(200);
|
||||
|
||||
var fontSizeBox = new HBox(20, new Label("Font size"), fontSizeSpinner);
|
||||
fontSizeBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var fontSizeSample = fontSizeSample();
|
||||
fontSizeBox = (GridPane) fontSizeSample.getContent();
|
||||
fontSizeSampleContent = (GridPane) fontSizeSample.getContent();
|
||||
|
||||
userContent.getChildren().setAll(
|
||||
fontSizeBox,
|
||||
fontSizeSample.getRoot(),
|
||||
fontWeightSample().getRoot(),
|
||||
fontStyleSample().getRoot(),
|
||||
@ -41,6 +57,50 @@ public class TypographyPage extends AbstractPage {
|
||||
);
|
||||
}
|
||||
|
||||
private Spinner<Integer> fontSizeSpinner() {
|
||||
var spinner = new Spinner<Integer>(10, 24, 14);
|
||||
|
||||
// Instead of this we should obtain font size from a rendered node.
|
||||
// But since it's not trivial (thanks to JavaFX doesn't expose relevant API)
|
||||
// we just keep current font size inside ThemeManager singleton.
|
||||
// It works fine if ThemeManager default font size value matches
|
||||
// default theme font size value.
|
||||
spinner.getValueFactory().setValue(ThemeManager.getInstance().getFontSize());
|
||||
|
||||
spinner.valueProperty().addListener((obs, old, val) -> {
|
||||
if (val != null && getScene() != null) {
|
||||
ThemeManager.getInstance().setFontSize(getScene(), val);
|
||||
updateFontInfo(Duration.ofMillis(1000));
|
||||
}
|
||||
});
|
||||
|
||||
return spinner;
|
||||
}
|
||||
|
||||
// font metrics can only be obtained by requesting from a rendered node
|
||||
protected void onRendered() {
|
||||
super.onRendered();
|
||||
updateFontInfo(Duration.ZERO);
|
||||
}
|
||||
|
||||
private void updateFontInfo(Duration delay) {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Platform.runLater(() -> {
|
||||
for (Node node : fontSizeSampleContent.getChildren()) {
|
||||
if (node instanceof Text textNode) {
|
||||
var font = textNode.getFont();
|
||||
textNode.setText(
|
||||
String.format("%s = %.1fpx", textNode.getUserData(), Math.ceil(font.getSize()))
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, delay.toMillis());
|
||||
}
|
||||
|
||||
private SampleBlock fontSizeSample() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(40);
|
||||
@ -116,6 +176,7 @@ public class TypographyPage extends AbstractPage {
|
||||
private Text text(String text, String... styleClasses) {
|
||||
var t = new Text(text);
|
||||
t.getStyleClass().addAll(styleClasses);
|
||||
t.setUserData(text);
|
||||
return t;
|
||||
}
|
||||
|
||||
@ -144,17 +205,4 @@ public class TypographyPage extends AbstractPage {
|
||||
|
||||
return new SampleBlock("Text flow", textFlow);
|
||||
}
|
||||
|
||||
// font metrics can only be obtained by requesting from a rendered node
|
||||
protected void onRendered() {
|
||||
for (Node node : fontSizeBox.getChildren()) {
|
||||
if (node instanceof Text textNode) {
|
||||
var font = textNode.getFont();
|
||||
textNode.setText(String.format("%s = %.1fpx",
|
||||
textNode.getText(),
|
||||
Math.ceil(font.getSize())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
@ -15,11 +15,11 @@ public final class Containers {
|
||||
|
||||
public static final ColumnConstraints H_GROW_NEVER = columnConstraints(Priority.NEVER);
|
||||
|
||||
public static void setAnchors(Parent parent, Insets insets) {
|
||||
if (insets.getTop() >= 0) { AnchorPane.setTopAnchor(parent, insets.getTop()); }
|
||||
if (insets.getRight() >= 0) { AnchorPane.setRightAnchor(parent, insets.getRight()); }
|
||||
if (insets.getBottom() >= 0) { AnchorPane.setBottomAnchor(parent, insets.getBottom()); }
|
||||
if (insets.getLeft() >= 0) { AnchorPane.setLeftAnchor(parent, insets.getLeft()); }
|
||||
public static void setAnchors(Node node, Insets insets) {
|
||||
if (insets.getTop() >= 0) { AnchorPane.setTopAnchor(node, insets.getTop()); }
|
||||
if (insets.getRight() >= 0) { AnchorPane.setRightAnchor(node, insets.getRight()); }
|
||||
if (insets.getBottom() >= 0) { AnchorPane.setBottomAnchor(node, insets.getBottom()); }
|
||||
if (insets.getLeft() >= 0) { AnchorPane.setLeftAnchor(node, insets.getLeft()); }
|
||||
}
|
||||
|
||||
public static void setScrollConstraints(ScrollPane scrollPane,
|
||||
|
992
sampler/src/main/java/atlantafx/sampler/util/JColor.java
Normal file
992
sampler/src/main/java/atlantafx/sampler/util/JColor.java
Normal file
@ -0,0 +1,992 @@
|
||||
/**
|
||||
* MIT License
|
||||
* <p>
|
||||
* Copyright (c) 2022 National Geospatial-Intelligence Agency
|
||||
* Source: https://github.com/ngageoint/color-java
|
||||
* <p>
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* <p>
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* <p>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
/**
|
||||
* Color representation with support for hex, RGB, arithmetic RGB, HSL, and
|
||||
* integer colors.
|
||||
*
|
||||
* @author osbornb
|
||||
*/
|
||||
public class JColor {
|
||||
|
||||
/**
|
||||
* Red arithmetic color value
|
||||
*/
|
||||
private float red = 0.0f;
|
||||
|
||||
/**
|
||||
* Green arithmetic color value
|
||||
*/
|
||||
private float green = 0.0f;
|
||||
|
||||
/**
|
||||
* Blue arithmetic color value
|
||||
*/
|
||||
private float blue = 0.0f;
|
||||
|
||||
/**
|
||||
* Opacity arithmetic value
|
||||
*/
|
||||
private float opacity = 1.0f;
|
||||
|
||||
/**
|
||||
* Create the color in hex
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String color) {
|
||||
return new JColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color in hex with an opacity
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String color, float opacity) {
|
||||
return new JColor(color, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color in hex with an alpha
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String color, int alpha) {
|
||||
return new JColor(color, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String red, String green, String blue) {
|
||||
return new JColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors and alpha
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param alpha alpha hex color in format AA
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String red, String green, String blue,
|
||||
String alpha) {
|
||||
return new JColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors and opacity
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(String red, String green, String blue,
|
||||
float opacity) {
|
||||
return new JColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGB values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(int red, int green, int blue) {
|
||||
return new JColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(int red, int green, int blue, int alpha) {
|
||||
return new JColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(int red, int green, int blue, float opacity) {
|
||||
return new JColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(float red, float green, float blue) {
|
||||
return new JColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(float red, float green, float blue,
|
||||
float opacity) {
|
||||
return new JColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with HSL (hue, saturation, lightness) or HSL (alpha)
|
||||
* values
|
||||
*
|
||||
* @param hsl HSL array where: 0 = hue, 1 = saturation, 2 = lightness,
|
||||
* optional 3 = alpha
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(float[] hsl) {
|
||||
return new JColor(hsl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with HSLA (hue, saturation, lightness, alpha) values
|
||||
*
|
||||
* @param hsl HSL array where: 0 = hue, 1 = saturation, 2 = lightness
|
||||
* @param alpha alpha inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(float[] hsl, float alpha) {
|
||||
return new JColor(hsl, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color as a single integer
|
||||
*
|
||||
* @param color color integer
|
||||
*
|
||||
* @return color
|
||||
*/
|
||||
public static JColor color(int color) {
|
||||
return new JColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default color constructor, opaque black
|
||||
*/
|
||||
public JColor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color in hex
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
*/
|
||||
public JColor(String color) {
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color in hex with an opacity
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(String color, float opacity) {
|
||||
setColor(color, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color in hex with an alpha
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public JColor(String color, int alpha) {
|
||||
setColor(color, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
*/
|
||||
public JColor(String red, String green, String blue) {
|
||||
setColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors and alpha
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param alpha alpha hex color in format AA
|
||||
*/
|
||||
public JColor(String red, String green, String blue, String alpha) {
|
||||
setColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with individual hex colors and opacity
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(String red, String green, String blue, float opacity) {
|
||||
setColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGB values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*/
|
||||
public JColor(int red, int green, int blue) {
|
||||
setColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public JColor(int red, int green, int blue, int alpha) {
|
||||
setColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(int red, int green, int blue, float opacity) {
|
||||
setColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(float red, float green, float blue) {
|
||||
setColor(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(float red, float green, float blue, float opacity) {
|
||||
setColor(red, green, blue, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with HSL (hue, saturation, lightness) or HSL (alpha)
|
||||
* values
|
||||
*
|
||||
* @param hsl HSL array where: 0 = hue, 1 = saturation, 2 = lightness,
|
||||
* optional 3 = alpha
|
||||
*/
|
||||
public JColor(float[] hsl) {
|
||||
if (hsl.length > 3) {
|
||||
setColorByHSL(hsl[0], hsl[1], hsl[2], hsl[3]);
|
||||
} else {
|
||||
setColorByHSL(hsl[0], hsl[1], hsl[2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color with HSLA (hue, saturation, lightness, alpha) values
|
||||
*
|
||||
* @param hsl HSL array where: 0 = hue, 1 = saturation, 2 = lightness
|
||||
* @param alpha alpha inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public JColor(float[] hsl, float alpha) {
|
||||
setColorByHSL(hsl[0], hsl[1], hsl[2], alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the color as a single integer
|
||||
*
|
||||
* @param color color integer
|
||||
*/
|
||||
public JColor(int color) {
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
*
|
||||
* @param color color to copy
|
||||
*/
|
||||
public JColor(JColor color) {
|
||||
this.red = color.red;
|
||||
this.green = color.green;
|
||||
this.blue = color.blue;
|
||||
this.opacity = color.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color in hex
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
*/
|
||||
public void setColor(String color) {
|
||||
setRed(JColorUtils.getRed(color));
|
||||
setGreen(JColorUtils.getGreen(color));
|
||||
setBlue(JColorUtils.getBlue(color));
|
||||
String alpha = JColorUtils.getAlpha(color);
|
||||
if (alpha != null) {
|
||||
setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color in hex with an opacity
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColor(String color, float opacity) {
|
||||
setColor(color);
|
||||
setOpacity(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color in hex with an alpha
|
||||
*
|
||||
* @param color hex color in format #RRGGBB, RRGGBB, #RGB, RGB, #AARRGGBB,
|
||||
* AARRGGBB, #ARGB, or ARGB
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setColor(String color, int alpha) {
|
||||
setColor(color);
|
||||
setAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with individual hex colors
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
*/
|
||||
public void setColor(String red, String green, String blue) {
|
||||
setRed(red);
|
||||
setGreen(green);
|
||||
setBlue(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with individual hex colors and alpha
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param alpha alpha hex color in format AA
|
||||
*/
|
||||
public void setColor(String red, String green, String blue, String alpha) {
|
||||
setColor(red, green, blue);
|
||||
setAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with individual hex colors and opacity
|
||||
*
|
||||
* @param red red hex color in format RR
|
||||
* @param green green hex color in format GG
|
||||
* @param blue blue hex color in format BB
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColor(String red, String green, String blue, float opacity) {
|
||||
setColor(red, green, blue);
|
||||
setOpacity(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with RGB values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setColor(int red, int green, int blue) {
|
||||
setRed(red);
|
||||
setGreen(green);
|
||||
setBlue(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setColor(int red, int green, int blue, int alpha) {
|
||||
setColor(red, green, blue);
|
||||
setAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with RGBA values
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColor(int red, int green, int blue, float opacity) {
|
||||
setColor(red, green, blue);
|
||||
setOpacity(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColor(float red, float green, float blue) {
|
||||
setRed(red);
|
||||
setGreen(green);
|
||||
setBlue(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with arithmetic RGB values
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
* @param opacity opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColor(float red, float green, float blue, float opacity) {
|
||||
setColor(red, green, blue);
|
||||
setOpacity(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with HSL (hue, saturation, lightness) values
|
||||
*
|
||||
* @param hue hue value inclusively between 0.0 and 360.0
|
||||
* @param saturation saturation inclusively between 0.0 and 1.0
|
||||
* @param lightness lightness inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColorByHSL(float hue, float saturation, float lightness) {
|
||||
float[] arithmeticRGB = JColorUtils.toArithmeticRGB(hue, saturation,
|
||||
lightness);
|
||||
setRed(arithmeticRGB[0]);
|
||||
setGreen(arithmeticRGB[1]);
|
||||
setBlue(arithmeticRGB[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color with HSLA (hue, saturation, lightness, alpha) values
|
||||
*
|
||||
* @param hue hue value inclusively between 0.0 and 360.0
|
||||
* @param saturation saturation inclusively between 0.0 and 1.0
|
||||
* @param lightness lightness inclusively between 0.0 and 1.0
|
||||
* @param alpha alpha inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setColorByHSL(float hue, float saturation, float lightness,
|
||||
float alpha) {
|
||||
setColorByHSL(hue, saturation, lightness);
|
||||
setAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color as a single integer
|
||||
*
|
||||
* @param color color integer
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
setRed(JColorUtils.getRed(color));
|
||||
setGreen(JColorUtils.getGreen(color));
|
||||
setBlue(JColorUtils.getBlue(color));
|
||||
if (color > 16777215 || color < 0) {
|
||||
setAlpha(JColorUtils.getAlpha(color));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the red color in hex
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
*/
|
||||
public void setRed(String red) {
|
||||
setRed(JColorUtils.toArithmeticRGB(red));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the green color in hex
|
||||
*
|
||||
* @param green green hex color in format GG or G
|
||||
*/
|
||||
public void setGreen(String green) {
|
||||
setGreen(JColorUtils.toArithmeticRGB(green));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blue color in hex
|
||||
*
|
||||
* @param blue blue hex color in format BB or B
|
||||
*/
|
||||
public void setBlue(String blue) {
|
||||
setBlue(JColorUtils.toArithmeticRGB(blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha color in hex
|
||||
*
|
||||
* @param alpha alpha hex color in format AA or A
|
||||
*/
|
||||
public void setAlpha(String alpha) {
|
||||
setOpacity(JColorUtils.toArithmeticRGB(alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the red color as an integer
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setRed(int red) {
|
||||
setRed(JColorUtils.toHex(red));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the green color as an integer
|
||||
*
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setGreen(int green) {
|
||||
setGreen(JColorUtils.toHex(green));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blue color as an integer
|
||||
*
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setBlue(int blue) {
|
||||
setBlue(JColorUtils.toHex(blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha color as an integer
|
||||
*
|
||||
* @param alpha alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public void setAlpha(int alpha) {
|
||||
setOpacity(JColorUtils.toArithmeticRGB(alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the red color as an arithmetic float
|
||||
*
|
||||
* @param red red float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setRed(float red) {
|
||||
JColorUtils.validateArithmeticRGB(red);
|
||||
this.red = red;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the green color as an arithmetic float
|
||||
*
|
||||
* @param green green float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setGreen(float green) {
|
||||
JColorUtils.validateArithmeticRGB(green);
|
||||
this.green = green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blue color as an arithmetic float
|
||||
*
|
||||
* @param blue blue float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setBlue(float blue) {
|
||||
JColorUtils.validateArithmeticRGB(blue);
|
||||
this.blue = blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the opacity as an arithmetic float
|
||||
*
|
||||
* @param opacity opacity float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setOpacity(float opacity) {
|
||||
JColorUtils.validateArithmeticRGB(opacity);
|
||||
this.opacity = opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha color as an arithmetic float
|
||||
*
|
||||
* @param alpha alpha float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public void setAlpha(float alpha) {
|
||||
setOpacity(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the color is opaque (opacity or alpha of 1.0, 255, or x00)
|
||||
*
|
||||
* @return true if opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
return opacity == 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as a hex string
|
||||
*
|
||||
* @return hex color in the format #RRGGBB
|
||||
*/
|
||||
public String getColorHex() {
|
||||
return JColorUtils.toColor(getRedHex(), getGreenHex(), getBlueHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as a hex string with alpha
|
||||
*
|
||||
* @return hex color in the format #AARRGGBB
|
||||
*/
|
||||
public String getColorHexWithAlpha() {
|
||||
return JColorUtils.toColorWithAlpha(getRedHex(), getGreenHex(),
|
||||
getBlueHex(), getAlphaHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as a hex string, shorthanded when possible
|
||||
*
|
||||
* @return hex color in the format #RGB or #RRGGBB
|
||||
*/
|
||||
public String getColorHexShorthand() {
|
||||
return JColorUtils.toColorShorthand(getRedHex(), getGreenHex(),
|
||||
getBlueHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as a hex string with alpha, shorthanded when possible
|
||||
*
|
||||
* @return hex color in the format #ARGB or #AARRGGBB
|
||||
*/
|
||||
public String getColorHexShorthandWithAlpha() {
|
||||
return JColorUtils.toColorShorthandWithAlpha(getRedHex(), getGreenHex(),
|
||||
getBlueHex(), getAlphaHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as an integer
|
||||
*
|
||||
* @return integer color
|
||||
*/
|
||||
public int getColor() {
|
||||
return JColorUtils.toColor(getRed(), getGreen(), getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color as an integer including the alpha
|
||||
*
|
||||
* @return integer color
|
||||
*/
|
||||
public int getColorWithAlpha() {
|
||||
return JColorUtils.toColorWithAlpha(getRed(), getGreen(), getBlue(),
|
||||
getAlpha());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red color in hex
|
||||
*
|
||||
* @return red hex color in format RR
|
||||
*/
|
||||
public String getRedHex() {
|
||||
return JColorUtils.toHex(red);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green color in hex
|
||||
*
|
||||
* @return green hex color in format GG
|
||||
*/
|
||||
public String getGreenHex() {
|
||||
return JColorUtils.toHex(green);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue color in hex
|
||||
*
|
||||
* @return blue hex color in format BB
|
||||
*/
|
||||
public String getBlueHex() {
|
||||
return JColorUtils.toHex(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha color in hex
|
||||
*
|
||||
* @return alpha hex color in format AA
|
||||
*/
|
||||
public String getAlphaHex() {
|
||||
return JColorUtils.toHex(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red color in hex, shorthand when possible
|
||||
*
|
||||
* @return red hex color in format R or RR
|
||||
*/
|
||||
public String getRedHexShorthand() {
|
||||
return JColorUtils.shorthandHexSingle(getRedHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green color in hex, shorthand when possible
|
||||
*
|
||||
* @return green hex color in format G or GG
|
||||
*/
|
||||
public String getGreenHexShorthand() {
|
||||
return JColorUtils.shorthandHexSingle(getGreenHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue color in hex, shorthand when possible
|
||||
*
|
||||
* @return blue hex color in format B or BB
|
||||
*/
|
||||
public String getBlueHexShorthand() {
|
||||
return JColorUtils.shorthandHexSingle(getBlueHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha color in hex, shorthand when possible
|
||||
*
|
||||
* @return alpha hex color in format A or AA
|
||||
*/
|
||||
public String getAlphaHexShorthand() {
|
||||
return JColorUtils.shorthandHexSingle(getAlphaHex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red color as an integer
|
||||
*
|
||||
* @return red integer color inclusively between 0 and 255
|
||||
*/
|
||||
public int getRed() {
|
||||
return JColorUtils.toRGB(red);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green color as an integer
|
||||
*
|
||||
* @return green integer color inclusively between 0 and 255
|
||||
*/
|
||||
public int getGreen() {
|
||||
return JColorUtils.toRGB(green);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue color as an integer
|
||||
*
|
||||
* @return blue integer color inclusively between 0 and 255
|
||||
*/
|
||||
public int getBlue() {
|
||||
return JColorUtils.toRGB(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha color as an integer
|
||||
*
|
||||
* @return alpha integer color inclusively between 0 and 255
|
||||
*/
|
||||
public int getAlpha() {
|
||||
return JColorUtils.toRGB(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red color as an arithmetic float
|
||||
*
|
||||
* @return red float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public float getRedArithmetic() {
|
||||
return red;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green color as an arithmetic float
|
||||
*
|
||||
* @return green float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public float getGreenArithmetic() {
|
||||
return green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue color as an arithmetic float
|
||||
*
|
||||
* @return blue float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public float getBlueArithmetic() {
|
||||
return blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opacity as an arithmetic float
|
||||
*
|
||||
* @return opacity float inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public float getOpacity() {
|
||||
return opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha color as an arithmetic float
|
||||
*
|
||||
* @return alpha float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public float getAlphaArithmetic() {
|
||||
return getOpacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HSL (hue, saturation, lightness) values
|
||||
*
|
||||
* @return HSL array where: 0 = hue, 1 = saturation, 2 = lightness
|
||||
*/
|
||||
public float[] getHSL() {
|
||||
return JColorUtils.toHSL(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HSL hue value
|
||||
*
|
||||
* @return hue value
|
||||
*/
|
||||
public float getHue() {
|
||||
return getHSL()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HSL saturation value
|
||||
*
|
||||
* @return saturation value
|
||||
*/
|
||||
public float getSaturation() {
|
||||
return getHSL()[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HSL lightness value
|
||||
*
|
||||
* @return lightness value
|
||||
*/
|
||||
public float getLightness() {
|
||||
return getHSL()[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the color
|
||||
*
|
||||
* @return color copy
|
||||
*/
|
||||
public JColor copy() {
|
||||
return new JColor(this);
|
||||
}
|
||||
|
||||
}
|
900
sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java
Normal file
900
sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java
Normal file
@ -0,0 +1,900 @@
|
||||
/**
|
||||
* MIT License
|
||||
* <p>
|
||||
* Copyright (c) 2022 National Geospatial-Intelligence Agency
|
||||
* Source: https://github.com/ngageoint/color-java
|
||||
* <p>
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* <p>
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* <p>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Color utilities with support for hex, RGB, arithmetic RGB, HSL, and integer colors.
|
||||
*
|
||||
* @author osbornb
|
||||
*/
|
||||
public class JColorUtils {
|
||||
|
||||
/**
|
||||
* Hex color pattern
|
||||
*/
|
||||
private static final Pattern hexColorPattern = Pattern
|
||||
.compile("^#?((\\p{XDigit}{3}){1,2}|(\\p{XDigit}{4}){1,2})$");
|
||||
|
||||
/**
|
||||
* Hex single color pattern
|
||||
*/
|
||||
private static final Pattern hexSingleColorPattern = Pattern
|
||||
.compile("^\\p{XDigit}{1,2}$");
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
*
|
||||
* @return hex color in format #RRGGBB
|
||||
*/
|
||||
public static String toColor(String red, String green, String blue) {
|
||||
return toColorWithAlpha(red, green, blue, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color, shorthanded when possible
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
*
|
||||
* @return hex color in format #RGB or #RRGGBB
|
||||
*/
|
||||
public static String toColorShorthand(String red, String green,
|
||||
String blue) {
|
||||
return shorthandHex(toColor(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color including an opaque alpha
|
||||
* value of FF
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
*
|
||||
* @return hex color in format #AARRGGBB
|
||||
*/
|
||||
public static String toColorWithAlpha(String red, String green,
|
||||
String blue) {
|
||||
String defaultAlpha = "FF";
|
||||
if (red != null && !red.isEmpty()
|
||||
&& Character.isLowerCase(red.charAt(0))) {
|
||||
defaultAlpha = defaultAlpha.toLowerCase();
|
||||
}
|
||||
return toColorWithAlpha(red, green, blue, defaultAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color including an opaque alpha
|
||||
* value of FF or F, shorthanded when possible
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
*
|
||||
* @return hex color in format #ARGB or #AARRGGBB
|
||||
*/
|
||||
public static String toColorShorthandWithAlpha(String red, String green,
|
||||
String blue) {
|
||||
return shorthandHex(toColorWithAlpha(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
* @param alpha alpha hex color in format AA or A, null to not include alpha
|
||||
*
|
||||
* @return hex color in format #AARRGGBB or #RRGGBB
|
||||
*/
|
||||
public static String toColorWithAlpha(String red, String green, String blue,
|
||||
String alpha) {
|
||||
validateHexSingle(red);
|
||||
validateHexSingle(green);
|
||||
validateHexSingle(blue);
|
||||
StringBuilder color = new StringBuilder("#");
|
||||
if (alpha != null) {
|
||||
color.append(expandShorthandHexSingle(alpha));
|
||||
}
|
||||
color.append(expandShorthandHexSingle(red));
|
||||
color.append(expandShorthandHexSingle(green));
|
||||
color.append(expandShorthandHexSingle(blue));
|
||||
return color.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex color values to a hex color, shorthanded when possible
|
||||
*
|
||||
* @param red red hex color in format RR or R
|
||||
* @param green green hex color in format GG or G
|
||||
* @param blue blue hex color in format BB or B
|
||||
* @param alpha alpha hex color in format AA or A, null to not include alpha
|
||||
*
|
||||
* @return hex color in format #ARGB, #RGB, #AARRGGBB, or #RRGGBB
|
||||
*/
|
||||
public static String toColorShorthandWithAlpha(String red, String green,
|
||||
String blue, String alpha) {
|
||||
return shorthandHex(toColorWithAlpha(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the RGB values to a color integer
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return integer color
|
||||
*/
|
||||
public static int toColor(int red, int green, int blue) {
|
||||
return toColorWithAlpha(red, green, blue, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the RGB values to a color integer including an opaque alpha value
|
||||
* of 255
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return integer color
|
||||
*/
|
||||
public static int toColorWithAlpha(int red, int green, int blue) {
|
||||
return toColorWithAlpha(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the RGBA values to a color integer
|
||||
*
|
||||
* @param red red integer color inclusively between 0 and 255
|
||||
* @param green green integer color inclusively between 0 and 255
|
||||
* @param blue blue integer color inclusively between 0 and 255
|
||||
* @param alpha alpha integer color inclusively between 0 and 255, -1 to not
|
||||
* include alpha
|
||||
*
|
||||
* @return integer color
|
||||
*/
|
||||
public static int toColorWithAlpha(int red, int green, int blue,
|
||||
int alpha) {
|
||||
validateRGB(red);
|
||||
validateRGB(green);
|
||||
validateRGB(blue);
|
||||
int color = (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
|
||||
if (alpha != -1) {
|
||||
validateRGB(alpha);
|
||||
color = (alpha & 0xff) << 24 | color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the RGB integer to a hex single color
|
||||
*
|
||||
* @param color integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return hex single color in format FF
|
||||
*/
|
||||
public static String toHex(int color) {
|
||||
validateRGB(color);
|
||||
String hex = Integer.toHexString(color).toUpperCase();
|
||||
if (hex.length() == 1) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the arithmetic RGB float to a hex single color
|
||||
*
|
||||
* @param color float color inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return hex single color in format FF
|
||||
*/
|
||||
public static String toHex(float color) {
|
||||
return toHex(toRGB(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex single color to a RGB integer
|
||||
*
|
||||
* @param color hex single color in format FF or F
|
||||
*
|
||||
* @return integer color inclusively between 0 and 255
|
||||
*/
|
||||
public static int toRGB(String color) {
|
||||
validateHexSingle(color);
|
||||
if (color.length() == 1) {
|
||||
color += color;
|
||||
}
|
||||
return Integer.parseInt(color, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the arithmetic RGB float to a RGB integer
|
||||
*
|
||||
* @param color float color inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return integer color inclusively between 0 and 255
|
||||
*/
|
||||
public static int toRGB(float color) {
|
||||
validateArithmeticRGB(color);
|
||||
return Math.round(255 * color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hex single color to an arithmetic RGB float
|
||||
*
|
||||
* @param color hex single color in format FF or F
|
||||
*
|
||||
* @return float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public static float toArithmeticRGB(String color) {
|
||||
return toArithmeticRGB(toRGB(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the RGB integer to an arithmetic RGB float
|
||||
*
|
||||
* @param color integer color inclusively between 0 and 255
|
||||
*
|
||||
* @return float color inclusively between 0.0 and 1.0
|
||||
*/
|
||||
public static float toArithmeticRGB(int color) {
|
||||
validateRGB(color);
|
||||
return color / 255.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert red, green, and blue arithmetic values to HSL (hue, saturation,
|
||||
* lightness) values
|
||||
*
|
||||
* @param red red color inclusively between 0.0 and 1.0
|
||||
* @param green green color inclusively between 0.0 and 1.0
|
||||
* @param blue blue color inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return HSL array where: 0 = hue, 1 = saturation, 2 = lightness
|
||||
*/
|
||||
public static float[] toHSL(float red, float green, float blue) {
|
||||
|
||||
validateArithmeticRGB(red);
|
||||
validateArithmeticRGB(green);
|
||||
validateArithmeticRGB(blue);
|
||||
|
||||
float min = Math.min(Math.min(red, green), blue);
|
||||
float max = Math.max(Math.max(red, green), blue);
|
||||
|
||||
float range = max - min;
|
||||
|
||||
float hue = 0.0f;
|
||||
if (range > 0.0f) {
|
||||
if (red >= green && red >= blue) {
|
||||
hue = (green - blue) / range;
|
||||
} else if (green >= blue) {
|
||||
hue = 2 + (blue - red) / range;
|
||||
} else {
|
||||
hue = 4 + (red - green) / range;
|
||||
}
|
||||
}
|
||||
|
||||
hue *= 60.0f;
|
||||
if (hue < 0.0f) {
|
||||
hue += 360.0f;
|
||||
}
|
||||
|
||||
float sum = min + max;
|
||||
|
||||
float lightness = sum / 2.0f;
|
||||
|
||||
float saturation;
|
||||
if (min == max) {
|
||||
saturation = 0.0f;
|
||||
} else {
|
||||
if (lightness < 0.5f) {
|
||||
saturation = range / sum;
|
||||
} else {
|
||||
saturation = range / (2.0f - max - min);
|
||||
}
|
||||
}
|
||||
|
||||
return new float[] { hue, saturation, lightness };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert red, green, and blue integer values to HSL (hue, saturation,
|
||||
* lightness) values
|
||||
*
|
||||
* @param red red color inclusively between 0 and 255
|
||||
* @param green green color inclusively between 0 and 255
|
||||
* @param blue blue color inclusively between 0 and 255
|
||||
*
|
||||
* @return HSL array where: 0 = hue, 1 = saturation, 2 = lightness
|
||||
*/
|
||||
public static float[] toHSL(int red, int green, int blue) {
|
||||
return toHSL(toArithmeticRGB(red), toArithmeticRGB(green),
|
||||
toArithmeticRGB(blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL (hue, saturation, and lightness) values to RGB arithmetic
|
||||
* values
|
||||
*
|
||||
* @param hue hue value inclusively between 0.0 and 360.0
|
||||
* @param saturation saturation inclusively between 0.0 and 1.0
|
||||
* @param lightness lightness inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return arithmetic RGB array where: 0 = red, 1 = green, 2 = blue
|
||||
*/
|
||||
public static float[] toArithmeticRGB(float hue, float saturation,
|
||||
float lightness) {
|
||||
|
||||
validateHue(hue);
|
||||
validateSaturation(saturation);
|
||||
validateLightness(lightness);
|
||||
|
||||
hue /= 60.0f;
|
||||
float t2;
|
||||
if (lightness <= 0.5f) {
|
||||
t2 = lightness * (saturation + 1);
|
||||
} else {
|
||||
t2 = lightness + saturation - (lightness * saturation);
|
||||
}
|
||||
float t1 = lightness * 2.0f - t2;
|
||||
|
||||
float red = hslConvert(t1, t2, hue + 2);
|
||||
float green = hslConvert(t1, t2, hue);
|
||||
float blue = hslConvert(t1, t2, hue - 2);
|
||||
|
||||
return new float[] { red, green, blue };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL (hue, saturation, and lightness) values to RGB integer values
|
||||
*
|
||||
* @param hue hue value inclusively between 0.0 and 360.0
|
||||
* @param saturation saturation inclusively between 0.0 and 1.0
|
||||
* @param lightness lightness inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @return RGB integer array where: 0 = red, 1 = green, 2 = blue
|
||||
*/
|
||||
public static int[] toRGB(float hue, float saturation, float lightness) {
|
||||
float[] arithmeticRGB = toArithmeticRGB(hue, saturation, lightness);
|
||||
return new int[] { toRGB(arithmeticRGB[0]), toRGB(arithmeticRGB[1]), toRGB(arithmeticRGB[2]) };
|
||||
}
|
||||
|
||||
/**
|
||||
* HSL convert helper method
|
||||
*
|
||||
* @param t1 t1
|
||||
* @param t2 t2
|
||||
* @param hue hue
|
||||
*
|
||||
* @return arithmetic RGB value
|
||||
*/
|
||||
private static float hslConvert(float t1, float t2, float hue) {
|
||||
float value;
|
||||
if (hue < 0) {
|
||||
hue += 6;
|
||||
}
|
||||
if (hue >= 6) {
|
||||
hue -= 6;
|
||||
}
|
||||
if (hue < 1) {
|
||||
value = (t2 - t1) * hue + t1;
|
||||
} else if (hue < 3) {
|
||||
value = t2;
|
||||
} else if (hue < 4) {
|
||||
value = (t2 - t1) * (4 - hue) + t1;
|
||||
} else {
|
||||
value = t1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex red color from the hex string
|
||||
*
|
||||
* @param hex hex color
|
||||
*
|
||||
* @return hex red color in format RR
|
||||
*/
|
||||
public static String getRed(String hex) {
|
||||
return getHexSingle(hex, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex green color from the hex string
|
||||
*
|
||||
* @param hex hex color
|
||||
*
|
||||
* @return hex green color in format GG
|
||||
*/
|
||||
public static String getGreen(String hex) {
|
||||
return getHexSingle(hex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex blue color from the hex string
|
||||
*
|
||||
* @param hex hex color
|
||||
*
|
||||
* @return hex blue color in format BB
|
||||
*/
|
||||
public static String getBlue(String hex) {
|
||||
return getHexSingle(hex, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex alpha color from the hex string if it exists
|
||||
*
|
||||
* @param hex hex color
|
||||
*
|
||||
* @return hex alpha color in format AA or null
|
||||
*/
|
||||
public static String getAlpha(String hex) {
|
||||
return getHexSingle(hex, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex single color
|
||||
*
|
||||
* @param hex hex color
|
||||
* @param colorIndex red=0, green=1, blue=2, alpha=-1
|
||||
*
|
||||
* @return hex single color in format FF or null
|
||||
*/
|
||||
private static String getHexSingle(String hex, int colorIndex) {
|
||||
validateHex(hex);
|
||||
|
||||
if (hex.startsWith("#")) {
|
||||
hex = hex.substring(1);
|
||||
}
|
||||
|
||||
int colorCharacters = 1;
|
||||
int numColors = hex.length();
|
||||
if (numColors > 4) {
|
||||
colorCharacters++;
|
||||
numColors /= 2;
|
||||
}
|
||||
|
||||
String color = null;
|
||||
if (colorIndex >= 0 || numColors > 3) {
|
||||
if (numColors > 3) {
|
||||
colorIndex++;
|
||||
}
|
||||
int startIndex = colorIndex;
|
||||
if (colorCharacters > 1) {
|
||||
startIndex *= 2;
|
||||
}
|
||||
color = hex.substring(startIndex, startIndex + colorCharacters);
|
||||
color = expandShorthandHexSingle(color);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red color from color integer
|
||||
*
|
||||
* @param color color integer
|
||||
*
|
||||
* @return red color
|
||||
*/
|
||||
public static int getRed(int color) {
|
||||
return (color >> 16) & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green color from color integer
|
||||
*
|
||||
* @param color color integer
|
||||
*
|
||||
* @return green color
|
||||
*/
|
||||
public static int getGreen(int color) {
|
||||
return (color >> 8) & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue color from color integer
|
||||
*
|
||||
* @param color color integer
|
||||
*
|
||||
* @return blue color
|
||||
*/
|
||||
public static int getBlue(int color) {
|
||||
return color & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha color from color integer
|
||||
*
|
||||
* @param color color integer
|
||||
*
|
||||
* @return alpha color
|
||||
*/
|
||||
public static int getAlpha(int color) {
|
||||
return (color >> 24) & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand the hex color if possible
|
||||
*
|
||||
* @param color hex color
|
||||
*
|
||||
* @return shorthand hex color or original value
|
||||
*/
|
||||
public static String shorthandHex(String color) {
|
||||
validateHex(color);
|
||||
if (color.length() > 5) {
|
||||
StringBuilder shorthandColor = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
if (color.startsWith("#")) {
|
||||
shorthandColor.append("#");
|
||||
startIndex++;
|
||||
}
|
||||
for (; startIndex < color.length(); startIndex += 2) {
|
||||
String shorthand = shorthandHexSingle(
|
||||
color.substring(startIndex, startIndex + 2));
|
||||
if (shorthand.length() > 1) {
|
||||
shorthandColor = null;
|
||||
break;
|
||||
}
|
||||
shorthandColor.append(shorthand);
|
||||
}
|
||||
if (shorthandColor != null) {
|
||||
color = shorthandColor.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the hex if it is in shorthand
|
||||
*
|
||||
* @param color hex color
|
||||
*
|
||||
* @return expanded hex color or original value
|
||||
*/
|
||||
public static String expandShorthandHex(String color) {
|
||||
validateHex(color);
|
||||
if (color.length() < 6) {
|
||||
StringBuilder expandColor = new StringBuilder();
|
||||
int startIndex = 0;
|
||||
if (color.startsWith("#")) {
|
||||
expandColor.append("#");
|
||||
startIndex++;
|
||||
}
|
||||
for (; startIndex < color.length(); startIndex++) {
|
||||
String expand = expandShorthandHexSingle(
|
||||
color.substring(startIndex, startIndex + 1));
|
||||
expandColor.append(expand);
|
||||
}
|
||||
color = expandColor.toString();
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand the hex single color if possible
|
||||
*
|
||||
* @param color hex single color
|
||||
*
|
||||
* @return shorthand hex color or original value
|
||||
*/
|
||||
public static String shorthandHexSingle(String color) {
|
||||
validateHexSingle(color);
|
||||
if (color.length() > 1
|
||||
&& Character.toUpperCase(color.charAt(0)) == Character
|
||||
.toUpperCase(color.charAt(1))) {
|
||||
color = color.substring(0, 1);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the hex single if it is in shorthand
|
||||
*
|
||||
* @param color hex single color
|
||||
*
|
||||
* @return expanded hex color or original value
|
||||
*/
|
||||
public static String expandShorthandHexSingle(String color) {
|
||||
validateHexSingle(color);
|
||||
if (color.length() == 1) {
|
||||
color += color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the hex color value is valid
|
||||
*
|
||||
* @param color hex color
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidHex(String color) {
|
||||
return color != null && hexColorPattern.matcher(color).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the hex color value
|
||||
*
|
||||
* @param color hex color
|
||||
*/
|
||||
public static void validateHex(String color) {
|
||||
if (!isValidHex(color)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Hex color must be in format #RRGGBB, #RGB, #AARRGGBB, #ARGB, RRGGBB, RGB, AARRGGBB, or ARGB, invalid value: "
|
||||
+ color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the hex single color value is valid
|
||||
*
|
||||
* @param color hex single color
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidHexSingle(String color) {
|
||||
return color != null && hexSingleColorPattern.matcher(color).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the hex single color value
|
||||
*
|
||||
* @param color hex single color
|
||||
*/
|
||||
public static void validateHexSingle(String color) {
|
||||
if (!isValidHexSingle(color)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be in format FF or F, invalid value: " + color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the RGB integer color is valid, inclusively between 0 and 255
|
||||
*
|
||||
* @param color decimal color
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidRGB(int color) {
|
||||
return color >= 0 && color <= 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the RGB integer color is inclusively between 0 and 255
|
||||
*
|
||||
* @param color decimal color
|
||||
*/
|
||||
public static void validateRGB(int color) {
|
||||
if (!isValidRGB(color)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be inclusively between 0 and 255, invalid value: "
|
||||
+ color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the arithmetic RGB float color is valid, inclusively between 0.0
|
||||
* and 1.0
|
||||
*
|
||||
* @param color decimal color
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidArithmeticRGB(float color) {
|
||||
return color >= 0.0 && color <= 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the arithmetic RGB float color is inclusively between 0.0 and
|
||||
* 1.0
|
||||
*
|
||||
* @param color decimal color
|
||||
*/
|
||||
public static void validateArithmeticRGB(float color) {
|
||||
if (!isValidArithmeticRGB(color)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be inclusively between 0.0 and 1.0, invalid value: "
|
||||
+ color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the HSL hue float value is valid, inclusively between 0.0 and
|
||||
* 360.0
|
||||
*
|
||||
* @param hue hue value
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidHue(float hue) {
|
||||
return hue >= 0.0 && hue <= 360.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the HSL hue float value is inclusively between 0.0 and 360.0
|
||||
*
|
||||
* @param hue hue value
|
||||
*/
|
||||
public static void validateHue(float hue) {
|
||||
if (!isValidHue(hue)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be inclusively between 0.0 and 360.0, invalid value: "
|
||||
+ hue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the HSL saturation float value is valid, inclusively between 0.0
|
||||
* and 1.0
|
||||
*
|
||||
* @param saturation saturation value
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidSaturation(float saturation) {
|
||||
return saturation >= 0.0 && saturation <= 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the HSL saturation float value is inclusively between 0.0 and
|
||||
* 1.0
|
||||
*
|
||||
* @param saturation saturation value
|
||||
*/
|
||||
public static void validateSaturation(float saturation) {
|
||||
if (!isValidSaturation(saturation)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be inclusively between 0.0 and 1.0, invalid value: "
|
||||
+ saturation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the HSL lightness float value is valid, inclusively between 0.0
|
||||
* and 1.0
|
||||
*
|
||||
* @param lightness lightness value
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean isValidLightness(float lightness) {
|
||||
return lightness >= 0.0 && lightness <= 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the HSL lightness float value is inclusively between 0.0 and 1.0
|
||||
*
|
||||
* @param lightness lightness value
|
||||
*/
|
||||
public static void validateLightness(float lightness) {
|
||||
if (!isValidLightness(lightness)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must be inclusively between 0.0 and 1.0, invalid value: "
|
||||
+ lightness);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Additional Utils, mkpaz (c) //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The WCAG contrast measures the difference in brightness (luminance) between two colours.
|
||||
* It ranges from 1:1 (white on white) to 21:1 (black on white). WCAG requirements are:
|
||||
* <ul>
|
||||
* <li>0.14285 (7.0:1) for small text in AAA-level</li>
|
||||
* <li>0.22222 (4.5:1) for small text in AA-level, or large text in AAA-level</li>
|
||||
* <li>0.33333 (3.0:1) for large text in AA-level</li>
|
||||
* </ul>
|
||||
* WCAG defines large text as text that is 18pt and larger, or 14pt and larger if it is bold.
|
||||
* <br/>
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">More info</a>.
|
||||
*/
|
||||
public static double getContrastRatio(Color color1, Color color2) {
|
||||
return getContrastRatio(getColorLuminance(color1), getColorLuminance(color2));
|
||||
}
|
||||
|
||||
/** @see JColorUtils#getContrastRatio(Color, Color) */
|
||||
public static double getContrastRatio(double luminance1, double luminance2) {
|
||||
return luminance1 > luminance2 ?
|
||||
(luminance2 + 0.05) / (luminance1 + 0.05) :
|
||||
(luminance1 + 0.05) / (luminance2 + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures relative color luminance according to the
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">W3C</a>.
|
||||
* <br/>
|
||||
* Note that JavaFX provides {@link Color#getBrightness()} which
|
||||
* IS NOT the same thing as luminance.
|
||||
*/
|
||||
public static double getColorLuminance(double[] rgb) {
|
||||
double[] tmp = Arrays.stream(rgb)
|
||||
.map(v -> v <= 0.03928 ? (v / 12.92) : Math.pow((v + 0.055) / 1.055, 2.4))
|
||||
.toArray();
|
||||
return (tmp[0] * 0.2126) + (tmp[1] * 0.7152) + (tmp[2] * 0.0722);
|
||||
}
|
||||
|
||||
/** @see JColorUtils#getColorLuminance(double[]) */
|
||||
public static double getColorLuminance(Color color) {
|
||||
return getColorLuminance(new double[] { color.getRed(), color.getGreen(), color.getBlue() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given color opacity, if present.
|
||||
* <br/><br/>
|
||||
* When implementing designs, you'll sometimes want to use a lighter shade
|
||||
* of a color for a background. A simple way to achieve lightness is by
|
||||
* increasing the transparency or reducing the opacity of the color
|
||||
* (changing what is known as the alpha channel). Against a white background,
|
||||
* the color will look lighter.
|
||||
* <br/><br/>
|
||||
* There are however several issues. Adding an alpha channel means that the
|
||||
* rendered color depends on what color lies underneath. Your elements may
|
||||
* look fine when drawn over a default white background, but if they end up
|
||||
* over another color, the foreground will be affected. Even if a white
|
||||
* background is enforced, if your elements ever overlap, you'll also run
|
||||
* into a problem when using transparency: the overlapping regions will get
|
||||
* darker than the individual elements.
|
||||
* <br/><br/>
|
||||
* To remove the transparency we need to blend the foreground color with the
|
||||
* background color, using the transparency value to determine how much to
|
||||
* weight the foreground layer.
|
||||
* <br/>
|
||||
* <a href="https://filosophy.org/code/online-tool-to-lighten-color-without-alpha-channel/">Source</a>.
|
||||
*/
|
||||
public static double[] flattenColor(Color bgColor, Color fgColor) {
|
||||
var opacity = fgColor.getOpacity();
|
||||
return opacity < 1 ?
|
||||
new double[] {
|
||||
opacity * fgColor.getRed() + (1 - opacity) * bgColor.getRed(),
|
||||
opacity * fgColor.getGreen() + (1 - opacity) * bgColor.getGreen(),
|
||||
opacity * fgColor.getBlue() + (1 - opacity) * bgColor.getBlue(),
|
||||
} :
|
||||
new double[] {
|
||||
fgColor.getRed(),
|
||||
fgColor.getGreen(),
|
||||
fgColor.getBlue(),
|
||||
};
|
||||
}
|
||||
}
|
@ -93,6 +93,88 @@
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
#color-palette {
|
||||
-fx-spacing: 20px;
|
||||
}
|
||||
#color-palette > .grid {
|
||||
-fx-vgap: 20px;
|
||||
-fx-hgap: 40px;
|
||||
}
|
||||
#color-palette > .grid > .color-block {
|
||||
-fx-spacing: 5px;
|
||||
}
|
||||
#color-palette > .grid > .color-block > .box {
|
||||
-fx-min-width: 12em;
|
||||
-fx-min-height: 5em;
|
||||
-fx-max-width: 12em;
|
||||
-fx-max-height: 5em;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
#color-palette > .grid > .color-block > .box > .wsag-label {
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: #ef5350;
|
||||
-fx-background-radius: 6px;
|
||||
-fx-padding: 3px;
|
||||
}
|
||||
#color-palette > .grid > .color-block > .box:passed > .wsag-label {
|
||||
-fx-background-color: #388e3c;
|
||||
}
|
||||
#color-palette > .grid > .color-block > .box > .wsag-label > .ikonli-font-icon {
|
||||
-fx-fill: white;
|
||||
-fx-icon-color: white;
|
||||
}
|
||||
|
||||
#color-palette > .contrast-checker-area {
|
||||
-fx-padding: 0 0 0 -20px;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker {
|
||||
-fx-background-color: -color-contrast-checker-bg;
|
||||
-fx-hgap: 40px;
|
||||
-fx-vgap: 20px;
|
||||
-fx-padding: 20px 20px 40px 20px;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker .label {
|
||||
-fx-text-fill: -color-contrast-checker-fg;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker .text-field {
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0;
|
||||
-fx-text-fill: -color-contrast-checker-fg;
|
||||
-fx-border-color: -color-contrast-checker-fg;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker .ikonli-font-icon {
|
||||
-fx-icon-color: -color-contrast-checker-fg;
|
||||
-fx-fill: -color-contrast-checker-fg;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker .slider > .thumb {
|
||||
-fx-background-color: -color-contrast-checker-fg;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker .slider > .track {
|
||||
-fx-background-color: transparent, -color-contrast-checker-fg;
|
||||
-fx-opacity: 0.5;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker > .font-box > .text {
|
||||
-fx-font-size: 4em;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker > .font-box > .ratio {
|
||||
-fx-font-size: 2em;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker > .wsag-box > * > .wsag-label {
|
||||
-fx-padding: 0.5em 1em 0.5em 1em;
|
||||
-fx-background-color: #ef5350;
|
||||
-fx-background-radius: 6px;
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker > .wsag-box > * > .wsag-label > .ikonli-font-icon {
|
||||
-fx-fill: white;
|
||||
-fx-icon-color: white;
|
||||
}
|
||||
#color-palette > .contrast-checker-area > .contrast-checker > .wsag-box > * > .wsag-label:passed {
|
||||
-fx-background-color: #388e3c;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: -color-border-muted;
|
||||
|
Loading…
Reference in New Issue
Block a user