diff --git a/sampler/pom.xml b/sampler/pom.xml
index 41a3d9f..3d4659a 100755
--- a/sampler/pom.xml
+++ b/sampler/pom.xml
@@ -109,12 +109,6 @@
src/main/java/atlantafx/sampler/page
atlantafx/sampler/page
false
-
- **/AbstractPage.java
- **/CodeViewer.java
- **/Page.java
- **/SampleBlock.java
-
diff --git a/sampler/src/main/java/atlantafx/sampler/layout/ApplicationWindow.java b/sampler/src/main/java/atlantafx/sampler/layout/ApplicationWindow.java
index 9bfe80c..458988d 100755
--- a/sampler/src/main/java/atlantafx/sampler/layout/ApplicationWindow.java
+++ b/sampler/src/main/java/atlantafx/sampler/layout/ApplicationWindow.java
@@ -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()
+ );
}
}
diff --git a/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java b/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java
new file mode 100644
index 0000000..fd5f31c
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/layout/MainLayer.java
@@ -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);
+ }
+}
diff --git a/sampler/src/main/java/atlantafx/sampler/layout/Overlay.java b/sampler/src/main/java/atlantafx/sampler/layout/Overlay.java
new file mode 100644
index 0000000..d1f2aeb
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/layout/Overlay.java
@@ -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 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();
+ }
+}
diff --git a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java
index beb5c51..b325539 100644
--- a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java
+++ b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java
@@ -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");
diff --git a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java
index 84242f0..af13bbd 100755
--- a/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/AbstractPage.java
@@ -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());
diff --git a/sampler/src/main/java/atlantafx/sampler/page/OverlayDialog.java b/sampler/src/main/java/atlantafx/sampler/page/OverlayDialog.java
new file mode 100644
index 0000000..64f2d05
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/page/OverlayDialog.java
@@ -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 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;
+ }
+}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java b/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java
index 0195b5e..0f83cc2 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java
@@ -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));
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java b/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java
index c6d50a1..349c470 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java
@@ -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 blocks = new ArrayList<>();
- private final Consumer 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 bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
+ private final Consumer colorBlockActionHandler;
- public ReadOnlyBooleanProperty contrastCheckerActiveProperty() {
- return contrastCheckerActive.getReadOnlyProperty();
- }
-
- public ColorPalette() {
+ public ColorPalette(Consumer 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 bgBaseColorProperty() {
+ return bgBaseColor.getReadOnlyProperty();
+ }
}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorContrastChecker.java b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java
similarity index 84%
rename from sampler/src/main/java/atlantafx/sampler/page/general/ColorContrastChecker.java
rename to sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java
index 9c7cb25..7f4e4b3 100644
--- a/sampler/src/main/java/atlantafx/sampler/page/general/ColorContrastChecker.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java
@@ -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 bgBaseColor) {
+ public ContrastChecker(ReadOnlyObjectProperty bgBaseColor) {
super();
this.bgBaseColor = bgBaseColor;
@@ -98,6 +98,19 @@ public class ColorContrastChecker extends GridPane {
public Color getFgColor() { return fgColor.colorProperty().get(); }
+ public ReadOnlyObjectProperty bgColorProperty() { return bgColor.colorProperty(); }
+
+ public ReadOnlyObjectProperty 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();
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ContrastCheckerDialog.java b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastCheckerDialog.java
new file mode 100644
index 0000000..c75a59c
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastCheckerDialog.java
@@ -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 {
+
+ private final ContrastChecker contrastChecker;
+
+ public ContrastCheckerDialog(ReadOnlyObjectProperty 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;
+ }
+}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java
index b05e0c4..4dc1729 100755
--- a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java
@@ -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 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 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;
+ }
}
diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java b/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java
index 6576175..8c3584e 100755
--- a/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java
+++ b/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java
@@ -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 fontFamilyChooser() {
diff --git a/sampler/src/main/java/atlantafx/sampler/util/Animations.java b/sampler/src/main/java/atlantafx/sampler/util/Animations.java
new file mode 100644
index 0000000..76d048e
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/util/Animations.java
@@ -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))
+ );
+ }
+}
diff --git a/sampler/src/main/java/atlantafx/sampler/util/Containers.java b/sampler/src/main/java/atlantafx/sampler/util/Containers.java
index e418899..73ce1ac 100755
--- a/sampler/src/main/java/atlantafx/sampler/util/Containers.java
+++ b/sampler/src/main/java/atlantafx/sampler/util/Containers.java
@@ -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);
+ }
}
diff --git a/sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java b/sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java
index 88aa470..be2607e 100644
--- a/sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java
+++ b/sampler/src/main/java/atlantafx/sampler/util/JColorUtils.java
@@ -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();
+ }
}
diff --git a/sampler/src/main/java/atlantafx/sampler/util/NodeUtils.java b/sampler/src/main/java/atlantafx/sampler/util/NodeUtils.java
new file mode 100644
index 0000000..96258a4
--- /dev/null
+++ b/sampler/src/main/java/atlantafx/sampler/util/NodeUtils.java
@@ -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 getChildByIndex(Parent parent, int index, Class contentType) {
+ List 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;
+ }
+}
diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_index.scss b/sampler/src/main/resources/assets/styles/scss/layout/_index.scss
index cc6dc7b..93186fc 100644
--- a/sampler/src/main/resources/assets/styles/scss/layout/_index.scss
+++ b/sampler/src/main/resources/assets/styles/scss/layout/_index.scss
@@ -2,4 +2,5 @@
@use "sidebar";
@use "page";
-@use "components";
\ No newline at end of file
+@use "components";
+@use "overlay";
\ No newline at end of file
diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss b/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss
new file mode 100644
index 0000000..f3ab191
--- /dev/null
+++ b/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/sampler/src/main/resources/assets/styles/scss/widgets/_color-palette.scss b/sampler/src/main/resources/assets/styles/scss/widgets/_color-palette.scss
index d5fdc0d..60b93ea 100644
--- a/sampler/src/main/resources/assets/styles/scss/widgets/_color-palette.scss
+++ b/sampler/src/main/resources/assets/styles/scss/widgets/_color-palette.scss
@@ -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;
- }
- }
- }
- }
}
\ No newline at end of file
diff --git a/sampler/src/main/resources/assets/styles/scss/widgets/_contrast-checker.scss b/sampler/src/main/resources/assets/styles/scss/widgets/_contrast-checker.scss
new file mode 100644
index 0000000..a0d798c
--- /dev/null
+++ b/sampler/src/main/resources/assets/styles/scss/widgets/_contrast-checker.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss b/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss
index 04b1f3d..a94898f 100644
--- a/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss
+++ b/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: MIT
@use "color-palette";
+@use "contrast-checker";
@use "quick-config-menu";
\ No newline at end of file