Display contrast checker in overlay dialog

This commit is contained in:
mkpaz 2022-09-01 14:11:02 +04:00
parent 3ab95551f3
commit 9b0b0bf44c
22 changed files with 780 additions and 368 deletions

@ -109,12 +109,6 @@
<directory>src/main/java/atlantafx/sampler/page</directory>
<targetPath>atlantafx/sampler/page</targetPath>
<filtering>false</filtering>
<excludes>
<exclude>**/AbstractPage.java</exclude>
<exclude>**/CodeViewer.java</exclude>
<exclude>**/Page.java</exclude>
<exclude>**/SampleBlock.java</exclude>
</excludes>
</resource>
<!-- copy icons -->
<resource>

@ -1,61 +1,14 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.components.OverviewPage;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import java.util.Objects;
import static javafx.scene.layout.Priority.ALWAYS;
public class ApplicationWindow extends BorderPane {
public class ApplicationWindow extends StackPane {
public ApplicationWindow() {
var sidebar = new Sidebar();
sidebar.setMinWidth(200);
final var pageContainer = new StackPane();
HBox.setHgrow(pageContainer, ALWAYS);
sidebar.setOnSelect(pageClass -> {
try {
final Page prevPage = (!pageContainer.getChildren().isEmpty() && pageContainer.getChildren().get(0) instanceof Page page) ? page : null;
final Page nextPage = pageClass.getDeclaredConstructor().newInstance();
// startup, no animation
if (getScene() == null) {
pageContainer.getChildren().add(nextPage.getView());
return;
}
Objects.requireNonNull(prevPage);
// reset previous page, e.g. to free resources
prevPage.reset();
// animate switching between pages
pageContainer.getChildren().add(nextPage.getView());
FadeTransition transition = new FadeTransition(Duration.millis(300), nextPage.getView());
transition.setFromValue(0.0);
transition.setToValue(1.0);
transition.setOnFinished(t -> pageContainer.getChildren().remove(prevPage.getView()));
transition.play();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// ~
setLeft(sidebar);
setCenter(pageContainer);
sidebar.select(OverviewPage.class);
Platform.runLater(sidebar::requestFocus);
getChildren().setAll(
new Overlay(),
new MainLayer()
);
}
}

@ -0,0 +1,66 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.components.OverviewPage;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import java.util.Objects;
import static javafx.scene.layout.Priority.ALWAYS;
class MainLayer extends BorderPane {
public MainLayer() {
super();
createView();
}
private void createView() {
var sidebar = new Sidebar();
sidebar.setMinWidth(200);
final var pageContainer = new StackPane();
HBox.setHgrow(pageContainer, ALWAYS);
sidebar.setOnSelect(pageClass -> {
try {
final Page prevPage = (!pageContainer.getChildren().isEmpty() && pageContainer.getChildren().get(0) instanceof Page page) ? page : null;
final Page nextPage = pageClass.getDeclaredConstructor().newInstance();
// startup, no animation
if (getScene() == null) {
pageContainer.getChildren().add(nextPage.getView());
return;
}
Objects.requireNonNull(prevPage);
// reset previous page, e.g. to free resources
prevPage.reset();
// animate switching between pages
pageContainer.getChildren().add(nextPage.getView());
FadeTransition transition = new FadeTransition(Duration.millis(300), nextPage.getView());
transition.setFromValue(0.0);
transition.setToValue(1.0);
transition.setOnFinished(t -> pageContainer.getChildren().remove(prevPage.getView()));
transition.play();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// ~
setLeft(sidebar);
setCenter(pageContainer);
sidebar.select(OverviewPage.class);
Platform.runLater(sidebar::requestFocus);
}
}

@ -0,0 +1,156 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import atlantafx.sampler.util.Animations;
import atlantafx.sampler.util.Containers;
import atlantafx.sampler.util.NodeUtils;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.event.Event;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import java.util.Objects;
import java.util.function.Consumer;
public class Overlay extends StackPane {
public static final String STYLE_CLASS = "overlay";
private ScrollPane scrollPane;
private AnchorPane edgeContentWrapper;
private StackPane centerContentWrapper;
private final ReadOnlyBooleanWrapper onFrontProperty = new ReadOnlyBooleanWrapper(false);
private final Timeline fadeInTransition = Animations.fadeIn(this, Duration.millis(100));
private final Timeline fadeOutTransition = Animations.fadeOut(this, Duration.millis(200));
private HPos currentContentPos;
public Overlay() {
createView();
}
private void createView() {
edgeContentWrapper = new AnchorPane();
edgeContentWrapper.getStyleClass().add("scrollable-content");
centerContentWrapper = new StackPane();
centerContentWrapper.getStyleClass().add("scrollable-content");
centerContentWrapper.setAlignment(Pos.CENTER);
scrollPane = new ScrollPane();
Containers.setScrollConstraints(scrollPane,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true,
ScrollPane.ScrollBarPolicy.NEVER, true
);
scrollPane.setMaxHeight(10_000); // scroll pane won't work without height specified
// ~
Consumer<Event> hideAndConsume = e -> {
removeContent();
toBack();
e.consume();
};
// hide overlay by pressing ESC (only works when overlay or one of its children has focus,
// that's why we requesting it in the toFront())
addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) { hideAndConsume.accept(e); }
});
// hide overlay by clicking outside content area
setOnMouseClicked(e -> {
Pane content = getContent();
Node eventSource = e.getPickResult().getIntersectedNode();
if (e.getButton() == MouseButton.PRIMARY && content != null && !NodeUtils.isDescendant(content, eventSource)) {
hideAndConsume.accept(e);
}
});
getChildren().add(scrollPane);
getStyleClass().add(STYLE_CLASS);
}
public Pane getContent() {
return NodeUtils.getChildByIndex(getContentWrapper(), 0, Pane.class);
}
private Pane getContentWrapper() {
return currentContentPos == HPos.CENTER ? centerContentWrapper : edgeContentWrapper;
}
public void setContent(Pane content, HPos pos) {
Objects.requireNonNull(content);
Objects.requireNonNull(pos);
// clear old content
if (pos != currentContentPos) {
removeContent();
currentContentPos = pos;
}
switch (pos) {
case LEFT -> {
edgeContentWrapper.getChildren().setAll(content);
Containers.setAnchors(content, new Insets(0, -1, 0, 0));
}
case RIGHT -> {
edgeContentWrapper.getChildren().setAll(content);
Containers.setAnchors(content, new Insets(0, 0, 0, -1));
}
case CENTER -> {
centerContentWrapper.getChildren().setAll(content);
StackPane.setAlignment(content, Pos.CENTER);
}
}
scrollPane.setContent(getContentWrapper());
}
public void removeContent() {
getContentWrapper().getChildren().clear();
}
public boolean contains(Pane content) {
return content != null &&
getContentWrapper().getChildren().size() > 0 &&
getContentWrapper().getChildren().get(0).equals(content);
}
@Override
public void toFront() {
if (onFrontProperty.get()) { return; }
super.toFront();
fadeInTransition.playFromStart();
onFrontProperty.set(true);
}
@Override
public void toBack() {
if (!onFrontProperty.get()) { return; }
super.toBack();
fadeOutTransition.playFromStart();
onFrontProperty.set(false);
}
public ReadOnlyBooleanProperty onFrontProperty() {
return onFrontProperty.getReadOnlyProperty();
}
public boolean isOnFront() {
return onFrontProperty().get();
}
}

@ -30,7 +30,7 @@ import java.util.function.Consumer;
import static javafx.scene.layout.Priority.ALWAYS;
public class Sidebar extends VBox {
class Sidebar extends VBox {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");

@ -4,6 +4,7 @@ package atlantafx.sampler.page;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.layout.Overlay;
import atlantafx.sampler.theme.ThemeManager;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@ -48,6 +49,7 @@ public abstract class AbstractPage extends BorderPane implements Page {
protected StackPane codeViewerWrapper;
protected CodeViewer codeViewer;
protected VBox userContent;
protected Overlay overlay;
protected boolean isRendered = false;
protected AbstractPage() {
@ -96,8 +98,8 @@ public abstract class AbstractPage extends BorderPane implements Page {
var scrollPane = new ScrollPane(userContentWrapper);
setScrollConstraints(scrollPane,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true
ScrollPane.ScrollBarPolicy.AS_NEEDED, true,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true
);
scrollPane.setMaxHeight(10_000);
@ -129,9 +131,16 @@ public abstract class AbstractPage extends BorderPane implements Page {
// Some properties can only be obtained after node placed
// to the scene graph and here is the place do this.
protected void onRendered() { }
protected void onRendered() {
this.overlay = lookupOverlay();
}
private void showThemeConfigPopover() {
protected Overlay lookupOverlay() {
return getScene() != null && getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay overlay ?
overlay : null;
}
protected void showThemeConfigPopover() {
if (quickConfigPopover == null) {
var content = new QuickConfigMenu();
content.setExitHandler(() -> quickConfigPopover.hide());

@ -0,0 +1,113 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page;
import atlantafx.base.controls.Spacer;
import atlantafx.sampler.util.Containers;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import java.util.Objects;
import static atlantafx.base.theme.Styles.*;
public abstract class OverlayDialog<T extends Region> extends VBox {
protected static final int CONTENT_CHILD_INDEX = 1;
protected Label titleLabel;
protected Button topCloseBtn;
protected HBox headerBox;
protected Button bottomCloseBtn;
protected HBox footerBox;
protected Runnable onCloseCallback;
public OverlayDialog() {
createView();
}
protected void createView() {
titleLabel = new Label();
titleLabel.getStyleClass().addAll(TITLE_4, "title");
topCloseBtn = new Button("", new FontIcon(Material2AL.CLOSE));
topCloseBtn.getStyleClass().addAll(BUTTON_ICON, BUTTON_CIRCLE, FLAT, "close-button");
topCloseBtn.setOnAction(e -> close());
headerBox = new HBox(10);
headerBox.getStyleClass().add("header");
headerBox.setAlignment(Pos.CENTER_LEFT);
headerBox.getChildren().setAll(
titleLabel,
new Spacer(),
topCloseBtn
);
VBox.setVgrow(headerBox, Priority.NEVER);
bottomCloseBtn = new Button("Close");
bottomCloseBtn.getStyleClass().add("form-action");
bottomCloseBtn.setOnAction(e -> close());
bottomCloseBtn.setCancelButton(true);
footerBox = new HBox(10);
footerBox.getStyleClass().add("footer");
footerBox.setAlignment(Pos.CENTER_RIGHT);
footerBox.getChildren().setAll(
new Spacer(),
bottomCloseBtn
);
VBox.setVgrow(footerBox, Priority.NEVER);
// IMPORTANT: this guarantees client will use correct width and height
Containers.usePrefWidth(this);
Containers.usePrefHeight(this);
// if you're updating this line, update setContent() method as well
getChildren().setAll(headerBox, footerBox);
getStyleClass().add("overlay-dialog");
}
protected void setContent(T content) {
Objects.requireNonNull(content);
VBox.setVgrow(content, Priority.ALWAYS);
// content have to be placed exactly between header and footer
if (getChildren().size() == 2) {
// add new content
getChildren().add(CONTENT_CHILD_INDEX, content);
} else if (getChildren().size() == 3) {
// overwrite existing content
getChildren().set(CONTENT_CHILD_INDEX, content);
} else {
throw new UnsupportedOperationException("Content cannot be placed because of unexpected children size. " +
"You should override 'OverlayDialog#setContent()' and place it manually.");
}
}
protected void setTitle(String title) {
titleLabel.setText(title);
}
public void close() {
if (onCloseCallback != null) {
onCloseCallback.run();
}
}
public Runnable getOnCloseRequest() {
return onCloseCallback;
}
public void setOnCloseRequest(Runnable handler) {
this.onCloseCallback = handler;
}
}

@ -16,7 +16,7 @@ import org.kordamp.ikonli.material2.Material2AL;
import java.util.function.Consumer;
import static atlantafx.sampler.page.general.ColorContrastChecker.*;
import static atlantafx.sampler.page.general.ContrastChecker.*;
import static atlantafx.sampler.util.JColorUtils.flattenColor;
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
@ -71,8 +71,8 @@ class ColorBlock extends VBox {
if (bgFill == null) { return; }
toggleHover(true);
expandIcon.setFill(
getColorLuminance(flattenColor(bgBaseColor.get(), bgFill)) < LUMINANCE_THRESHOLD ? Color.WHITE : Color.BLACK
expandIcon.setFill(getColorLuminance(flattenColor(bgBaseColor.get(), bgFill)) < LUMINANCE_THRESHOLD ?
Color.WHITE : Color.BLACK
);
});
colorBox.setOnMouseExited(e -> toggleHover(false));

@ -4,82 +4,42 @@ package atlantafx.sampler.page.general;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
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 javafx.scene.paint.Color;
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.*;
import java.util.function.Consumer;
class ColorPalette extends VBox {
private Label headerLabel;
private Button backBtn;
private GridPane colorGrid;
private ColorContrastChecker contrastChecker;
private VBox contrastCheckerArea;
private final List<ColorBlock> blocks = new ArrayList<>();
private final Consumer<ColorBlock> colorBlockActionHandler = colorBlock -> {
ColorContrastChecker c = getOrCreateContrastChecker();
c.setValues(colorBlock.getFgColorName(),
colorBlock.getFgColor(),
colorBlock.getBgColorName(),
colorBlock.getBgColor()
);
if (contrastCheckerArea.getChildren().isEmpty()) {
contrastCheckerArea.getChildren().setAll(c);
}
showContrastChecker();
};
private final ReadOnlyBooleanWrapper contrastCheckerActive = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final Consumer<ColorBlock> colorBlockActionHandler;
public ReadOnlyBooleanProperty contrastCheckerActiveProperty() {
return contrastCheckerActive.getReadOnlyProperty();
}
public ColorPalette() {
public ColorPalette(Consumer<ColorBlock> blockClickedHandler) {
super();
this.colorBlockActionHandler = Objects.requireNonNull(blockClickedHandler);
createView();
}
private void createView() {
headerLabel = new Label("Color Palette");
var 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 -> showColorPalette());
var headerBox = new HBox();
headerBox.getChildren().setAll(headerLabel, new Spacer(), backBtn);
headerBox.getChildren().setAll(headerLabel, new Spacer());
headerBox.setAlignment(Pos.CENTER_LEFT);
headerBox.getStyleClass().add("header");
contrastCheckerArea = new VBox();
contrastCheckerArea.getStyleClass().add("contrast-checker-area");
colorGrid = colorGrid();
var colorGrid = colorGrid();
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE
@ -132,28 +92,6 @@ class ColorPalette extends VBox {
return block;
}
private ColorContrastChecker getOrCreateContrastChecker() {
if (contrastChecker == null) { contrastChecker = new ColorContrastChecker(bgBaseColor.getReadOnlyProperty()); }
VBox.setVgrow(contrastChecker, Priority.ALWAYS);
return contrastChecker;
}
private void showColorPalette() {
headerLabel.setText("Color Palette");
backBtn.setVisible(false);
backBtn.setManaged(false);
getChildren().set(1, colorGrid);
contrastCheckerActive.set(false);
}
private void showContrastChecker() {
headerLabel.setText("Contrast Checker");
backBtn.setVisible(true);
backBtn.setManaged(true);
getChildren().set(1, contrastCheckerArea);
contrastCheckerActive.set(true);
}
// 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.
@ -165,4 +103,8 @@ class ColorPalette extends VBox {
}
}, delay.toMillis());
}
public ReadOnlyObjectProperty<Color> bgBaseColorProperty() {
return bgBaseColor.getReadOnlyProperty();
}
}

@ -2,6 +2,7 @@
package atlantafx.sampler.page.general;
import atlantafx.base.controls.CustomTextField;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.JColor;
@ -33,16 +34,15 @@ import org.kordamp.ikonli.material2.Material2AL;
import java.util.Objects;
import static atlantafx.sampler.page.general.ColorBlock.validateColorName;
import static atlantafx.sampler.util.JColorUtils.*;
import static atlantafx.sampler.util.JColorUtils.flattenColor;
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
// Inspired by the https://colourcontrast.cc/
public class ColorContrastChecker extends GridPane {
public class ContrastChecker 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 CONTRAST_RATIO_THRESHOLD = 1.5;
static final double LUMINANCE_THRESHOLD = 0.55;
public static final double CONTRAST_RATIO_THRESHOLD = 1.5;
public static final double LUMINANCE_THRESHOLD = 0.55;
public static final PseudoClass PASSED = PseudoClass.getPseudoClass("passed");
private static final int SLIDER_WIDTH = 300;
@ -65,7 +65,7 @@ public class ColorContrastChecker extends GridPane {
private Slider fgLightnessSlider;
private Slider fgAlphaSlider;
public ColorContrastChecker(ReadOnlyObjectProperty<Color> bgBaseColor) {
public ContrastChecker(ReadOnlyObjectProperty<Color> bgBaseColor) {
super();
this.bgBaseColor = bgBaseColor;
@ -98,6 +98,19 @@ public class ColorContrastChecker extends GridPane {
public Color getFgColor() { return fgColor.colorProperty().get(); }
public ReadOnlyObjectProperty<Color> bgColorProperty() { return bgColor.colorProperty(); }
public ReadOnlyObjectProperty<Color> fgColorProperty() { return fgColor.colorProperty(); }
// Returns fg color that guaranteed to be visible on the current bg.
public Color getSafeFgColor() {
if (contrastRatio.get() <= CONTRAST_RATIO_THRESHOLD) {
return getColorLuminance(flattenColor(bgBaseColor.get(), bgColor.getColor())) < LUMINANCE_THRESHOLD ? Color.WHITE : Color.BLACK;
} else {
return fgColor.getColor();
}
}
private void createView() {
var textLabel = new Label("Aa");
textLabel.getStyleClass().add("text");
@ -268,21 +281,22 @@ public class ColorContrastChecker extends GridPane {
var applyBtn = new Button("Apply");
applyBtn.setOnAction(e -> {
var tm = ThemeManager.getInstance();
var tm = ThemeManager.getInstance();
tm.setColor(getBgColorName(), bgColor.getColor());
tm.setColor(getFgColorName(), fgColor.getColor());
tm.reloadCustomCSS();
});
var controlsBox = new HBox(20, flattenBtn, applyBtn);
var controlsBox = new HBox(20, new Spacer(), flattenBtn, applyBtn);
controlsBox.setAlignment(Pos.CENTER_LEFT);
controlsBox.setPadding(new Insets(10, 0, 0, 0));
// ~
getStyleClass().add("contrast-checker");
// column 0
add(fontBox, 0, 0);
add(new HBox(fontBox, new Spacer(), wsagBox), 0, 0, REMAINING, 1);
add(new Label("Background Color"), 0, 1);
add(bgColorNameLabel, 0, 2);
add(bgTextField, 0, 3);
@ -298,7 +312,6 @@ public class ColorContrastChecker extends GridPane {
add(controlsBox, 0, 12, REMAINING, 1);
// column 1
add(wsagBox, 1, 0);
add(new Label("Foreground Color"), 1, 1);
add(fgColorNameLabel, 1, 2);
add(fgTextField, 1, 3);
@ -321,40 +334,14 @@ public class ColorContrastChecker extends GridPane {
}
private void updateStyle() {
float[] bg = bgColor.getRGBAColor();
float[] fg = fgColor.getRGBAColor();
// use fallback color if contrast ratio is too low
if (contrastRatio.get() <= CONTRAST_RATIO_THRESHOLD) {
fg = getColorLuminance(flattenColor(bgBaseColor.get(), bgColor.getColor())) < LUMINANCE_THRESHOLD ?
COLOR_WHITE :
COLOR_BLACK;
}
// flat colors are necessary for controls that use reverse styling (bg color over fg color),
// it won't be readable if we not remove transparency first
double[] bgFlat = flattenColor(bgBaseColor.get(), bgColor.getColor());
double[] fgFlat = flattenColor(bgBaseColor.get(), fgColor.getColor());
var style = String.format("-color-contrast-checker-bg:rgba(%.0f,%.0f,%.0f,%.2f);" +
"-color-contrast-checker-fg:rgba(%.0f,%.0f,%.0f,%.2f);" +
"-color-contrast-checker-bg-flat:%s;" +
"-color-contrast-checker-fg-flat:%s;",
bg[0], bg[1], bg[2], bg[3],
fg[0], fg[1], fg[2], fg[3],
JColor.color((float) bgFlat[0], (float) bgFlat[1], (float) bgFlat[2]).getColorHex(),
JColor.color((float) fgFlat[0], (float) fgFlat[1], (float) fgFlat[2]).getColorHex()
);
setStyle(style);
setStyle(String.format("-color-contrast-checker-bg:%s;-color-contrast-checker-fg:%s;",
JColorUtils.toHexWithAlpha(bgColor.getColor()),
JColorUtils.toHexWithAlpha(getSafeFgColor())
));
}
private void setBackground(Color color) {
float[] hsl = JColorUtils.toHSL(
(float) color.getRed(),
(float) color.getGreen(),
(float) color.getBlue()
);
float[] hsl = JColorUtils.toHSL(color);
bgHueSlider.setValue(hsl[0]);
bgSaturationSlider.setValue(hsl[1]);
bgLightnessSlider.setValue(hsl[2]);
@ -362,11 +349,7 @@ public class ColorContrastChecker extends GridPane {
}
private void setForeground(Color color) {
float[] hsl = JColorUtils.toHSL(
(float) color.getRed(),
(float) color.getGreen(),
(float) color.getBlue()
);
float[] hsl = JColorUtils.toHSL(color);
fgHueSlider.setValue(hsl[0]);
fgSaturationSlider.setValue(hsl[1]);
fgLightnessSlider.setValue(hsl[2]);
@ -412,7 +395,7 @@ public class ColorContrastChecker extends GridPane {
static double getContrastRatioOpacityAware(Color bgColor, Color fgColor, Color bgBaseColor) {
double luminance1 = getColorLuminance(flattenColor(bgBaseColor, bgColor));
double luminance2 = getColorLuminance(flattenColor(bgBaseColor, fgColor));
return getContrastRatio(luminance1, luminance2);
return JColorUtils.getContrastRatio(luminance1, luminance2);
}
///////////////////////////////////////////////////////////////////////////
@ -435,11 +418,7 @@ public class ColorContrastChecker extends GridPane {
}
public void setColor(Color color) {
float[] hsl = JColorUtils.toHSL(
(float) color.getRed(),
(float) color.getGreen(),
(float) color.getBlue()
);
float[] hsl = JColorUtils.toHSL(color);
values.setAll(hsl[0], hsl[1], hsl[2], (float) color.getOpacity());
}
@ -490,17 +469,6 @@ public class ColorContrastChecker extends GridPane {
};
}
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,36 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.general;
import atlantafx.sampler.page.OverlayDialog;
import atlantafx.sampler.util.JColorUtils;
import atlantafx.sampler.util.NodeUtils;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.paint.Color;
class ContrastCheckerDialog extends OverlayDialog<ContrastChecker> {
private final ContrastChecker contrastChecker;
public ContrastCheckerDialog(ReadOnlyObjectProperty<Color> bgBaseColor) {
this.contrastChecker = new ContrastChecker(bgBaseColor);
contrastChecker.bgColorProperty().addListener((obs, old, val) -> updateStyle());
contrastChecker.fgColorProperty().addListener((obs, old, val) -> updateStyle());
getStyleClass().add("contrast-checker-dialog");
setTitle("Contrast Checker");
setContent(contrastChecker);
NodeUtils.toggleVisibility(footerBox, false);
}
private void updateStyle() {
setStyle(String.format("-color-dialog-bg:%s;-color-dialog-fg:%s;",
JColorUtils.toHexWithAlpha(contrastChecker.getBgColor()),
JColorUtils.toHexWithAlpha(contrastChecker.getSafeFgColor())
));
}
public ContrastChecker getContent() {
return contrastChecker;
}
}

@ -5,6 +5,8 @@ import atlantafx.base.theme.Theme;
import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.theme.ThemeEvent.EventType;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.NodeUtils;
import javafx.geometry.HPos;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
@ -12,12 +14,26 @@ import javafx.util.StringConverter;
import java.time.Duration;
import java.util.Objects;
import java.util.function.Consumer;
public class ThemePage extends AbstractPage {
public static final String NAME = "Theme";
private final ColorPalette colorPalette = new ColorPalette();
private final Consumer<ColorBlock> colorBlockActionHandler = colorBlock -> {
ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog();
dialog.getContent().setValues(colorBlock.getFgColorName(),
colorBlock.getFgColor(),
colorBlock.getBgColorName(),
colorBlock.getBgColor()
);
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
};
private final ColorPalette colorPalette = new ColorPalette(colorBlockActionHandler);
private ContrastCheckerDialog contrastCheckerDialog;
@Override
public String getName() { return NAME; }
@ -46,16 +62,13 @@ public class ThemePage extends AbstractPage {
);
// if you want to enable quick menu don't forget that
// theme selection choice box have to be updated accordingly
quickConfigBtn.setVisible(false);
quickConfigBtn.setManaged(false);
sourceCodeToggleBtn.setVisible(false);
sourceCodeToggleBtn.setManaged(false);
NodeUtils.toggleVisibility(quickConfigBtn, false);
NodeUtils.toggleVisibility(sourceCodeToggleBtn, false);
}
private GridPane optionsGrid() {
ChoiceBox<Theme> themeSelector = themeSelector();
themeSelector.setPrefWidth(200);
themeSelector.disableProperty().bind(colorPalette.contrastCheckerActiveProperty());
// ~
@ -106,4 +119,17 @@ public class ThemePage extends AbstractPage {
return selector;
}
private ContrastCheckerDialog getOrCreateContrastCheckerDialog() {
if (contrastCheckerDialog == null) {
contrastCheckerDialog = new ContrastCheckerDialog(colorPalette.bgBaseColorProperty());
}
contrastCheckerDialog.setOnCloseRequest(() -> {
overlay.removeContent();
overlay.toBack();
});
return contrastCheckerDialog;
}
}

@ -5,6 +5,7 @@ import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.ThemeEvent.EventType;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.NodeUtils;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
@ -74,10 +75,8 @@ public class TypographyPage extends AbstractPage {
);
// if you want to enable quick menu don't forget that
// font size spinner value have to be updated accordingly
quickConfigBtn.setVisible(false);
quickConfigBtn.setManaged(false);
sourceCodeToggleBtn.setVisible(false);
sourceCodeToggleBtn.setManaged(false);
NodeUtils.toggleVisibility(quickConfigBtn, false);
NodeUtils.toggleVisibility(sourceCodeToggleBtn, false);
}
private ComboBox<String> fontFamilyChooser() {

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.util;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.util.Duration;
public final class Animations {
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
public static Timeline fadeIn(Node node, Duration duration) {
return new Timeline(
new KeyFrame(Duration.millis(0), new KeyValue(node.opacityProperty(), 0, EASE)),
new KeyFrame(duration, new KeyValue(node.opacityProperty(), 1, EASE))
);
}
public static Timeline fadeOut(Node node, Duration duration) {
return new Timeline(
new KeyFrame(Duration.millis(0), new KeyValue(node.opacityProperty(), 1, EASE)),
new KeyFrame(duration, new KeyValue(node.opacityProperty(), 0, EASE))
);
}
public static Timeline zoomIn(Node node, Duration duration) {
return new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(node.opacityProperty(), 0, EASE),
new KeyValue(node.scaleXProperty(), 0.3, EASE),
new KeyValue(node.scaleYProperty(), 0.3, EASE),
new KeyValue(node.scaleZProperty(), 0.3, EASE)
),
new KeyFrame(duration,
new KeyValue(node.opacityProperty(), 1, EASE),
new KeyValue(node.scaleXProperty(), 1, EASE),
new KeyValue(node.scaleYProperty(), 1, EASE),
new KeyValue(node.scaleZProperty(), 1, EASE)
)
);
}
public static Timeline zoomOut(Node node, Duration duration) {
return new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(node.opacityProperty(), 1, EASE),
new KeyValue(node.scaleXProperty(), 1, EASE),
new KeyValue(node.scaleYProperty(), 1, EASE),
new KeyValue(node.scaleZProperty(), 0.3, EASE)
),
new KeyFrame(duration.divide(2),
new KeyValue(node.opacityProperty(), 0, EASE),
new KeyValue(node.scaleXProperty(), 0.3, EASE),
new KeyValue(node.scaleYProperty(), 0.3, EASE),
new KeyValue(node.scaleZProperty(), 0.3, EASE)
),
new KeyFrame(duration, new KeyValue(node.opacityProperty(), 0, EASE))
);
}
}

@ -7,6 +7,7 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
@ -41,4 +42,14 @@ public final class Containers {
constraints.setHgrow(hgrow);
return constraints;
}
public static void usePrefWidth(Region region) {
region.setMinWidth(USE_PREF_SIZE);
region.setMaxWidth(USE_PREF_SIZE);
}
public static void usePrefHeight(Region region) {
region.setMinHeight(USE_PREF_SIZE);
region.setMaxHeight(USE_PREF_SIZE);
}
}

@ -51,10 +51,9 @@ public class JColorUtils {
/**
* Convert the hex color values to a hex color
*
* @param red red hex color in format RR or R
* @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 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) {
@ -64,10 +63,9 @@ public class JColorUtils {
/**
* Convert the hex color values to a hex color, shorthanded when possible
*
* @param red red hex color in format RR or R
* @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 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,
@ -79,10 +77,9 @@ public class JColorUtils {
* 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 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 blue blue hex color in format BB or B
* @return hex color in format #AARRGGBB
*/
public static String toColorWithAlpha(String red, String green,
@ -99,10 +96,9 @@ public class JColorUtils {
* 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 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 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,
@ -113,11 +109,10 @@ public class JColorUtils {
/**
* Convert the hex color values to a hex color
*
* @param red red hex color in format RR or R
* @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 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,
@ -140,11 +135,10 @@ public class JColorUtils {
/**
* Convert the hex color values to a hex color, shorthanded when possible
*
* @param red red hex color in format RR or R
* @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 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,
@ -155,10 +149,9 @@ public class JColorUtils {
/**
* Convert the RGB values to a color integer
*
* @param red red integer color inclusively between 0 and 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
*
* @param blue blue integer color inclusively between 0 and 255
* @return integer color
*/
public static int toColor(int red, int green, int blue) {
@ -169,10 +162,9 @@ public class JColorUtils {
* 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 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 blue blue integer color inclusively between 0 and 255
* @return integer color
*/
public static int toColorWithAlpha(int red, int green, int blue) {
@ -182,12 +174,11 @@ public class JColorUtils {
/**
* Convert the RGBA values to a color integer
*
* @param red red integer color inclusively between 0 and 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
* @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
*
* include alpha
* @return integer color
*/
public static int toColorWithAlpha(int red, int green, int blue,
@ -207,7 +198,6 @@ public class JColorUtils {
* 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) {
@ -223,7 +213,6 @@ public class JColorUtils {
* 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) {
@ -234,7 +223,6 @@ public class JColorUtils {
* 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) {
@ -249,7 +237,6 @@ public class JColorUtils {
* 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) {
@ -261,7 +248,6 @@ public class JColorUtils {
* 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) {
@ -272,7 +258,6 @@ public class JColorUtils {
* 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) {
@ -284,10 +269,9 @@ public class JColorUtils {
* 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 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
*
* @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) {
@ -339,25 +323,23 @@ public class JColorUtils {
* Convert red, green, and blue integer values to HSL (hue, saturation,
* lightness) values
*
* @param red red color inclusively between 0 and 255
* @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
*
* @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));
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 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 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,
@ -386,10 +368,9 @@ public class JColorUtils {
/**
* Convert HSL (hue, saturation, and lightness) values to RGB integer values
*
* @param hue hue value inclusively between 0.0 and 360.0
* @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 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) {
@ -400,10 +381,9 @@ public class JColorUtils {
/**
* HSL convert helper method
*
* @param t1 t1
* @param t2 t2
* @param t1 t1
* @param t2 t2
* @param hue hue
*
* @return arithmetic RGB value
*/
private static float hslConvert(float t1, float t2, float hue) {
@ -430,7 +410,6 @@ public class JColorUtils {
* 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) {
@ -441,7 +420,6 @@ public class JColorUtils {
* 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) {
@ -452,7 +430,6 @@ public class JColorUtils {
* 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) {
@ -463,7 +440,6 @@ public class JColorUtils {
* 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) {
@ -473,9 +449,8 @@ public class JColorUtils {
/**
* Get the hex single color
*
* @param hex hex 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) {
@ -512,7 +487,6 @@ public class JColorUtils {
* Get the red color from color integer
*
* @param color color integer
*
* @return red color
*/
public static int getRed(int color) {
@ -523,7 +497,6 @@ public class JColorUtils {
* Get the green color from color integer
*
* @param color color integer
*
* @return green color
*/
public static int getGreen(int color) {
@ -534,7 +507,6 @@ public class JColorUtils {
* Get the blue color from color integer
*
* @param color color integer
*
* @return blue color
*/
public static int getBlue(int color) {
@ -545,7 +517,6 @@ public class JColorUtils {
* Get the alpha color from color integer
*
* @param color color integer
*
* @return alpha color
*/
public static int getAlpha(int color) {
@ -556,7 +527,6 @@ public class JColorUtils {
* Shorthand the hex color if possible
*
* @param color hex color
*
* @return shorthand hex color or original value
*/
public static String shorthandHex(String color) {
@ -589,7 +559,6 @@ public class JColorUtils {
* 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) {
@ -615,7 +584,6 @@ public class JColorUtils {
* 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) {
@ -632,7 +600,6 @@ public class JColorUtils {
* 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) {
@ -647,7 +614,6 @@ public class JColorUtils {
* Check if the hex color value is valid
*
* @param color hex color
*
* @return true if valid
*/
public static boolean isValidHex(String color) {
@ -671,7 +637,6 @@ public class JColorUtils {
* Check if the hex single color value is valid
*
* @param color hex single color
*
* @return true if valid
*/
public static boolean isValidHexSingle(String color) {
@ -694,7 +659,6 @@ public class JColorUtils {
* 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) {
@ -719,7 +683,6 @@ public class JColorUtils {
* and 1.0
*
* @param color decimal color
*
* @return true if valid
*/
public static boolean isValidArithmeticRGB(float color) {
@ -745,7 +708,6 @@ public class JColorUtils {
* 360.0
*
* @param hue hue value
*
* @return true if valid
*/
public static boolean isValidHue(float hue) {
@ -770,7 +732,6 @@ public class JColorUtils {
* and 1.0
*
* @param saturation saturation value
*
* @return true if valid
*/
public static boolean isValidSaturation(float saturation) {
@ -796,7 +757,6 @@ public class JColorUtils {
* and 1.0
*
* @param lightness lightness value
*
* @return true if valid
*/
public static boolean isValidLightness(float lightness) {
@ -899,4 +859,21 @@ public class JColorUtils {
fgColor.getBlue(),
};
}
public static float[] toHSL(Color color) {
return JColorUtils.toHSL(
(float) color.getRed(),
(float) color.getGreen(),
(float) color.getBlue()
);
}
public static String toHexWithAlpha(Color color) {
return JColor.color(
(float) color.getRed(),
(float) color.getGreen(),
(float) color.getBlue(),
(float) color.getOpacity()
).getColorHexShorthandWithAlpha();
}
}

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.util;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import java.util.List;
public final class NodeUtils {
public static void toggleVisibility(Node node, boolean on) {
node.setVisible(on);
node.setManaged(on);
}
public static boolean isDoubleClick(MouseEvent e) {
return e.getButton().equals(MouseButton.PRIMARY) && e.getClickCount() == 2;
}
public static <T> T getChildByIndex(Parent parent, int index, Class<T> contentType) {
List<Node> children = parent.getChildrenUnmodifiable();
if (index < 0 || index >= children.size()) { return null; }
Node node = children.get(index);
return contentType.isInstance(node) ? contentType.cast(node) : null;
}
public static boolean isDescendant(Node ancestor, Node descendant) {
if (ancestor == null) { return true; }
while (descendant != null) {
if (descendant == ancestor) { return true; }
descendant = descendant.getParent();
}
return false;
}
}

@ -2,4 +2,5 @@
@use "sidebar";
@use "page";
@use "components";
@use "components";
@use "overlay";

@ -0,0 +1,31 @@
.overlay {
-fx-background-color: transparent;
>.scroll-pane {
>.viewport {
>* {
>.scrollable-content {
-fx-background-color: rgba(0, 0, 0, 0.5);
}
}
}
}
}
.overlay-dialog {
-fx-background-color: -color-bg-default;
-fx-background-radius: 5px;
-fx-border-radius: 5px;
-fx-border-width: 1px;
-fx-border-color: -color-border-default;
>.header {
-fx-padding: 10px 5px 5px 20px;
}
>.footer {
-fx-border-width: 1 0 0 0;
-fx-border-color: -color-border-default;
-fx-padding: 10;
}
}

@ -45,88 +45,4 @@ $color-wsag-fg: white;
}
}
}
>.contrast-checker-area {
-fx-padding: 0 0 0 -20px;
>.contrast-checker {
-fx-background-color: -color-contrast-checker-bg;
-fx-hgap: 40px;
-fx-vgap: 20px;
-fx-padding: 20px 20px 40px 20px;
.label {
-fx-text-fill: -color-contrast-checker-fg;
}
.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;
}
.button {
-color-button-bg: -color-contrast-checker-fg-flat;
-color-button-fg: -color-contrast-checker-bg-flat;
-color-button-border: -color-contrast-checker-bg-flat;
-color-button-bg-hover: -color-contrast-checker-fg-flat;
-color-button-fg-hover: -color-contrast-checker-bg-flat;
-color-button-border-hover: -color-contrast-checker-bg-flat;
-color-button-bg-focused: -color-contrast-checker-fg-flat;
-color-button-fg-focused: -color-contrast-checker-bg-flat;
-color-button-border-focused: -color-contrast-checker-bg-flat;
-color-button-bg-pressed: -color-contrast-checker-bg-flat;
-color-button-fg-pressed: -color-contrast-checker-fg-flat;
-color-button-border-pressed: -color-contrast-checker-fg-flat;
}
.ikonli-font-icon {
-fx-icon-color: -color-contrast-checker-fg;
-fx-fill: -color-contrast-checker-fg;
}
.slider {
>.thumb {
-fx-background-color: -color-contrast-checker-fg;
}
>.track {
-fx-background-color: transparent, -color-contrast-checker-fg;
-fx-opacity: 0.5;
}
}
.font-box {
>.text {
-fx-font-size: 4em;
}
>.ratio {
-fx-font-size: 2em;
}
}
>.wsag-box>*>.wsag-label {
-fx-padding: 0.5em 1em 0.5em 1em;
-fx-background-color: $color-wsag-bg-failed;
-fx-background-radius: 6px;
-fx-text-fill: $color-wsag-fg;
&:passed {
-fx-background-color: $color-wsag-bg-passed;
}
>.ikonli-font-icon {
-fx-fill: $color-wsag-fg;
-fx-icon-color: $color-wsag-fg;
}
}
}
}
}

@ -0,0 +1,112 @@
@use "color-palette" as palette;
.contrast-checker {
-fx-background-color: -color-contrast-checker-bg;
-fx-hgap: 40px;
-fx-vgap: 20px;
-fx-padding: 20px;
.label {
-fx-text-fill: -color-contrast-checker-fg;
}
.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;
}
.button {
-color-button-bg: transparent;
-color-button-fg: -color-contrast-checker-fg;
-color-button-border: transparent;
-color-button-bg-hover: transparent;
-color-button-fg-hover: -color-contrast-checker-fg;
-color-button-border-hover: transparent;
-color-button-bg-focused: transparent;
-color-button-fg-focused: -color-contrast-checker-fg;
-color-button-border-focused: transparent;
-color-button-bg-pressed: transparent;
-color-button-fg-pressed: -color-contrast-checker-fg;
-color-button-border-pressed: transparent;
-fx-border-width: 1px;
-fx-border-color: -color-contrast-checker-fg;
-fx-border-radius: 4px;
&:armed,
&:focused:armed {
-fx-border-color: transparent;
}
}
.ikonli-font-icon {
-fx-icon-color: -color-contrast-checker-fg;
-fx-fill: -color-contrast-checker-fg;
}
.slider {
>.thumb {
-fx-background-color: -color-contrast-checker-fg;
}
>.track {
-fx-background-color: transparent, -color-contrast-checker-fg;
-fx-opacity: 0.5;
}
}
.font-box {
-fx-padding: 0 40px 0 0;
>.text {
-fx-font-size: 4em;
}
>.ratio {
-fx-font-size: 2em;
}
}
.wsag-box>*>.wsag-label {
-fx-padding: 0.5em 1em 0.5em 1em;
-fx-background-color: palette.$color-wsag-bg-failed;
-fx-background-radius: 4px;
-fx-text-fill: palette.$color-wsag-fg;
&:passed {
-fx-background-color: palette.$color-wsag-bg-passed;
}
>.ikonli-font-icon {
-fx-fill: palette.$color-wsag-fg;
-fx-icon-color: palette.$color-wsag-fg;
}
}
}
.contrast-checker-dialog {
-color-dialog-bg: white;
-color-dialog-fg: black;
>.header {
-fx-background-color: -color-dialog-bg;
>.title {
-fx-text-fill: -color-dialog-fg;
}
>.close-button {
-color-button-fg: -color-dialog-fg;
-color-button-bg-hover: derive(-color-dialog-bg, 10%);
}
}
-fx-border-color: -color-dialog-bg;
}

@ -1,4 +1,5 @@
// SPDX-License-Identifier: MIT
@use "color-palette";
@use "contrast-checker";
@use "quick-config-menu";