From fc19ca5a369f4b2da478a77558ca2fa717bb21cc Mon Sep 17 00:00:00 2001 From: mkpaz Date: Thu, 1 Sep 2022 16:06:09 +0400 Subject: [PATCH] Add color scale preview --- .../sampler/page/QuickConfigMenu.java | 5 +- .../sampler/page/general/ColorPalette.java | 31 +++---- ...ColorBlock.java => ColorPaletteBlock.java} | 14 +-- .../sampler/page/general/ColorScale.java | 68 ++++++++++++++ .../sampler/page/general/ColorScaleBlock.java | 93 +++++++++++++++++++ .../sampler/page/general/ContrastChecker.java | 2 +- .../sampler/page/general/ThemePage.java | 12 ++- .../sampler/page/general/TypographyPage.java | 37 ++++---- .../assets/styles/scss/layout/_overlay.scss | 2 + .../styles/scss/widgets/_color-palette.scss | 4 - .../styles/scss/widgets/_color-scale.scss | 17 ++++ .../scss/widgets/_contrast-checker.scss | 1 + .../assets/styles/scss/widgets/_index.scss | 1 + 13 files changed, 233 insertions(+), 54 deletions(-) rename sampler/src/main/java/atlantafx/sampler/page/general/{ColorBlock.java => ColorPaletteBlock.java} (92%) create mode 100644 sampler/src/main/java/atlantafx/sampler/page/general/ColorScale.java create mode 100644 sampler/src/main/java/atlantafx/sampler/page/general/ColorScaleBlock.java create mode 100644 sampler/src/main/resources/assets/styles/scss/widgets/_color-scale.scss diff --git a/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java b/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java index 53cffcd..8d3084e 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java +++ b/sampler/src/main/java/atlantafx/sampler/page/QuickConfigMenu.java @@ -139,6 +139,7 @@ public class QuickConfigMenu extends StackPane { () -> FONT_SCALE.indexOf(fontScale.get()) <= 0, fontScale) ); + // FIXME: Default zoom value is always 100% which isn't correct because it may have been changed earlier var zoomLabel = new Label(); zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> fontScale.get() + "%", fontScale)); @@ -149,7 +150,9 @@ public class QuickConfigMenu extends StackPane { final var tm = ThemeManager.getInstance(); fontScale.addListener((obs, old, val) -> { if (val != null) { - double fontSize = ThemeManager.DEFAULT_FONT_SIZE / 100.0 * val.intValue(); + double fontSize = val.intValue() != 100 ? + ThemeManager.DEFAULT_FONT_SIZE / 100.0 * val.intValue() : + ThemeManager.DEFAULT_FONT_SIZE; tm.setFontSize((int) Math.ceil(fontSize)); tm.reloadCustomCSS(); } 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 349c470..7579c9b 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorPalette.java @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: MIT */ package atlantafx.sampler.page.general; -import atlantafx.base.controls.Spacer; import atlantafx.base.theme.Styles; -import javafx.application.Platform; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.geometry.Pos; @@ -12,18 +12,20 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import javafx.util.Duration; -import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.function.Consumer; class ColorPalette extends VBox { - private final List blocks = new ArrayList<>(); + private final List blocks = new ArrayList<>(); private final ReadOnlyObjectWrapper bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE); - private final Consumer colorBlockActionHandler; + private final Consumer colorBlockActionHandler; - public ColorPalette(Consumer blockClickedHandler) { + public ColorPalette(Consumer blockClickedHandler) { super(); this.colorBlockActionHandler = Objects.requireNonNull(blockClickedHandler); @@ -35,7 +37,7 @@ class ColorPalette extends VBox { headerLabel.getStyleClass().add(Styles.TITLE_4); var headerBox = new HBox(); - headerBox.getChildren().setAll(headerLabel, new Spacer()); + headerBox.getChildren().setAll(headerLabel); headerBox.setAlignment(Pos.CENTER_LEFT); headerBox.getStyleClass().add("header"); @@ -85,8 +87,8 @@ class ColorPalette extends VBox { return grid; } - private ColorBlock colorBlock(String fgColor, String bgColor, String borderColor) { - var block = new ColorBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty()); + private ColorPaletteBlock colorBlock(String fgColor, String bgColor, String borderColor) { + var block = new ColorPaletteBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty()); block.setOnAction(colorBlockActionHandler); blocks.add(block); return block; @@ -96,12 +98,9 @@ class ColorPalette extends VBox { // Unfortunately, JavaFX doesn't provide an API to observe when stylesheet changes has been applied. // The timer is introduced to defer widget update to a time when scene changes supposedly will be finished. public void updateColorInfo(Duration delay) { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> blocks.forEach(ColorBlock::update)); - } - }, delay.toMillis()); + var t = new Timeline(new KeyFrame(delay)); + t.setOnFinished(e -> blocks.forEach(ColorPaletteBlock::update)); + t.play(); } public ReadOnlyObjectProperty bgBaseColorProperty() { diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java b/sampler/src/main/java/atlantafx/sampler/page/general/ColorPaletteBlock.java similarity index 92% rename from sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java rename to sampler/src/main/java/atlantafx/sampler/page/general/ColorPaletteBlock.java index 0f83cc2..028d73b 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ColorBlock.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorPaletteBlock.java @@ -20,7 +20,7 @@ import static atlantafx.sampler.page.general.ContrastChecker.*; import static atlantafx.sampler.util.JColorUtils.flattenColor; import static atlantafx.sampler.util.JColorUtils.getColorLuminance; -class ColorBlock extends VBox { +class ColorPaletteBlock extends VBox { private final String fgColorName; private final String bgColorName; @@ -33,12 +33,12 @@ class ColorBlock extends VBox { private final Label wsagLabel = new Label(); private final FontIcon expandIcon = new FontIcon(Feather.MAXIMIZE_2); - private Consumer actionHandler; + private Consumer actionHandler; - public ColorBlock(String fgColorName, - String bgColorName, - String borderColorName, - ReadOnlyObjectProperty bgBaseColor) { + public ColorPaletteBlock(String fgColorName, + String bgColorName, + String borderColorName, + ReadOnlyObjectProperty bgBaseColor) { this.fgColorName = validateColorName(fgColorName); this.bgColorName = validateColorName(bgColorName); this.borderColorName = validateColorName(borderColorName); @@ -150,7 +150,7 @@ class ColorBlock extends VBox { return borderColorName; } - public void setOnAction(Consumer actionHandler) { + public void setOnAction(Consumer actionHandler) { this.actionHandler = actionHandler; } } diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorScale.java b/sampler/src/main/java/atlantafx/sampler/page/general/ColorScale.java new file mode 100644 index 0000000..71be1c4 --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorScale.java @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT */ +package atlantafx.sampler.page.general; + +import atlantafx.base.theme.Styles; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.util.Duration; + +import java.util.Arrays; +import java.util.List; + +public class ColorScale extends VBox { + + private final ReadOnlyObjectWrapper bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE); + private final List blocks = Arrays.asList( + ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-gray-", 10), + ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-blue-", 10), + ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-green-", 10), + ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-yellow-", 10), + ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-red-", 10), + ColorScaleBlock.forColorName(bgBaseColor, "-color-black", "-color-white") + ); + + public ColorScale() { + super(); + createView(); + } + + private void createView() { + var headerLabel = new Label("Color Scale"); + headerLabel.getStyleClass().add(Styles.TITLE_4); + + var headerBox = new HBox(); + headerBox.getChildren().setAll(headerLabel); + headerBox.setAlignment(Pos.CENTER_LEFT); + headerBox.getStyleClass().add("header"); + + backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set( + val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE + )); + + setId("color-scale"); + getChildren().setAll( + headerBox, + colorTable() + ); + } + + public void updateColorInfo(Duration delay) { + var t = new Timeline(new KeyFrame(delay)); + t.setOnFinished(e -> blocks.forEach(ColorScaleBlock::update)); + t.play(); + } + + private FlowPane colorTable() { + var root = new FlowPane(20, 20); + root.getStyleClass().add("table"); + root.getChildren().setAll(blocks); + return root; + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ColorScaleBlock.java b/sampler/src/main/java/atlantafx/sampler/page/general/ColorScaleBlock.java new file mode 100644 index 0000000..2e82921 --- /dev/null +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ColorScaleBlock.java @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: MIT */ +package atlantafx.sampler.page.general; + +import atlantafx.sampler.util.JColorUtils; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; + +import static atlantafx.sampler.util.JColorUtils.flattenColor; +import static atlantafx.sampler.util.JColorUtils.getColorLuminance; + +public class ColorScaleBlock extends VBox { + + private static final double BLOCK_WIDTH = 250; + private static final double BLOCK_HEIGHT = 50; + + private final ReadOnlyObjectProperty bgBaseColor; + + private ColorScaleBlock(ReadOnlyObjectProperty bgBaseColor) { + super(); + + this.bgBaseColor = bgBaseColor; + createView(); + } + + private void addCell(String colorName) { + getChildren().add(label(colorName)); + } + + public void update() { + getChildren().forEach(c -> { + if (c instanceof Label label) { + String colorName = (String) label.getUserData(); + label.setStyle(String.format("-fx-background-color:%s;-fx-text-fill:%s;", + colorName, + JColorUtils.toHexWithAlpha(getSafeFgColor(label)) + )); + } + }); + } + + private void createView() { + getStyleClass().add("column"); + } + + private static Label label(String colorName) { + var label = new Label(colorName); + label.setMinHeight(BLOCK_HEIGHT); + label.setMinWidth(BLOCK_WIDTH); + label.setPrefWidth(BLOCK_WIDTH); + label.setMaxWidth(BLOCK_WIDTH); + label.setAlignment(Pos.CENTER_LEFT); + label.getStyleClass().add("cell"); + label.setUserData(colorName); + label.setStyle(String.format("-fx-background-color:%s;", colorName)); + return label; + } + + private Color getSafeFgColor(Label label) { + var bg = getBgColor(label); + // deliberately reduce luminance threshold from 0.55 to 0.4 + // to improve readability which is an experimental value anyway + return getColorLuminance(flattenColor(bgBaseColor.get(), bg)) < 0.4 ? Color.WHITE : Color.BLACK; + } + + private Color getBgColor(Label label) { + return label.getBackground() != null && !label.getBackground().isEmpty() ? + (Color) label.getBackground().getFills().get(0).getFill() : Color.WHITE; + } + + /////////////////////////////////////////////////////////////////////////// + + public static ColorScaleBlock forColorPrefix(ReadOnlyObjectProperty bgBaseColor, + String colorPrefix, + int count) { + var block = new ColorScaleBlock(bgBaseColor); + for (int idx = 0; idx < count; idx++) { + block.addCell(colorPrefix + idx); + } + return block; + } + + public static ColorScaleBlock forColorName(ReadOnlyObjectProperty bgBaseColor, + String... colors) { + var block = new ColorScaleBlock(bgBaseColor); + for (String colorName : colors) { + block.addCell(colorName); + } + return block; + } +} diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java index 7f4e4b3..a96e18a 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ContrastChecker.java @@ -33,7 +33,7 @@ import org.kordamp.ikonli.material2.Material2AL; import java.util.Objects; -import static atlantafx.sampler.page.general.ColorBlock.validateColorName; +import static atlantafx.sampler.page.general.ColorPaletteBlock.validateColorName; import static atlantafx.sampler.util.JColorUtils.flattenColor; import static atlantafx.sampler.util.JColorUtils.getColorLuminance; 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 4dc1729..0ed0688 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java @@ -10,9 +10,9 @@ import javafx.geometry.HPos; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; +import javafx.util.Duration; import javafx.util.StringConverter; -import java.time.Duration; import java.util.Objects; import java.util.function.Consumer; @@ -20,7 +20,7 @@ public class ThemePage extends AbstractPage { public static final String NAME = "Theme"; - private final Consumer colorBlockActionHandler = colorBlock -> { + private final Consumer colorBlockActionHandler = colorBlock -> { ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog(); dialog.getContent().setValues(colorBlock.getFgColorName(), colorBlock.getFgColor(), @@ -32,6 +32,7 @@ public class ThemePage extends AbstractPage { }; private final ColorPalette colorPalette = new ColorPalette(colorBlockActionHandler); + private final ColorScale colorScale = new ColorScale(); private ContrastCheckerDialog contrastCheckerDialog; @@ -44,7 +45,8 @@ public class ThemePage extends AbstractPage { ThemeManager.getInstance().addEventListener(e -> { if (e.eventType() == EventType.THEME_CHANGE || e.eventType() == EventType.CUSTOM_CSS_CHANGE) { // only works for managed nodes - colorPalette.updateColorInfo(Duration.ofSeconds(1)); + colorPalette.updateColorInfo(Duration.seconds(1)); + colorScale.updateColorInfo(Duration.seconds(1)); } }); } @@ -53,12 +55,14 @@ public class ThemePage extends AbstractPage { protected void onRendered() { super.onRendered(); colorPalette.updateColorInfo(Duration.ZERO); + colorScale.updateColorInfo(Duration.ZERO); } private void createView() { userContent.getChildren().addAll( optionsGrid(), - colorPalette + colorPalette, + colorScale ); // if you want to enable quick menu don't forget that // theme selection choice box have to be updated accordingly 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 8c3584e..b2a6bb7 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/TypographyPage.java @@ -6,7 +6,8 @@ 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.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.collections.FXCollections; import javafx.geometry.Pos; import javafx.scene.Node; @@ -21,10 +22,7 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; - -import java.time.Duration; -import java.util.Timer; -import java.util.TimerTask; +import javafx.util.Duration; import static atlantafx.base.theme.Styles.*; @@ -46,7 +44,7 @@ public class TypographyPage extends AbstractPage { ThemeManager.getInstance().addEventListener(e -> { if (e.eventType() == EventType.FONT_FAMILY_CHANGE || e.eventType() == EventType.FONT_SIZE_CHANGE) { // only works for managed nodes - updateFontInfo(Duration.ofMillis(1000)); + updateFontInfo(Duration.seconds(1)); } }); } @@ -116,7 +114,7 @@ public class TypographyPage extends AbstractPage { if (val != null) { tm.setFontSize(val); tm.reloadCustomCSS(); - updateFontInfo(Duration.ofMillis(1000)); + updateFontInfo(Duration.seconds(1)); } }); @@ -130,21 +128,18 @@ public class TypographyPage extends AbstractPage { } private void updateFontInfo(Duration delay) { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - Platform.runLater(() -> { - for (Node node : fontSizeSampleContent.getChildren()) { - if (node instanceof Text textNode) { - var font = textNode.getFont(); - textNode.setText( - String.format("%s = %.1fpx", textNode.getUserData(), Math.ceil(font.getSize())) - ); - } - } - }); + var t = new Timeline(new KeyFrame(delay)); + t.setOnFinished(e -> { + for (Node node : fontSizeSampleContent.getChildren()) { + if (node instanceof Text textNode) { + var font = textNode.getFont(); + textNode.setText( + String.format("%s = %.1fpx", textNode.getUserData(), Math.ceil(font.getSize())) + ); + } } - }, delay.toMillis()); + }); + t.play(); } private SampleBlock fontSizeSample() { diff --git a/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss b/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss index f3ab191..3c2a667 100644 --- a/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss +++ b/sampler/src/main/resources/assets/styles/scss/layout/_overlay.scss @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT + .overlay { -fx-background-color: transparent; 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 60b93ea..11ba95c 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 @@ -9,10 +9,6 @@ $color-wsag-fg: white; -fx-spacing: 20px; -fx-background-color: -color-bg-default; // mandatory base bg for flatten color calc - >.header { - -fx-pref-height: 40px; - } - >.grid { -fx-vgap: 20px; -fx-hgap: 40px; diff --git a/sampler/src/main/resources/assets/styles/scss/widgets/_color-scale.scss b/sampler/src/main/resources/assets/styles/scss/widgets/_color-scale.scss new file mode 100644 index 0000000..3e60797 --- /dev/null +++ b/sampler/src/main/resources/assets/styles/scss/widgets/_color-scale.scss @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +#color-scale { + -fx-spacing: 20px; + -fx-background-color: -color-bg-default; // mandatory base bg for flatten color calc + + + >.table { + >.column { + >.cell { + -fx-text-fill: -color-fg-default; + -fx-font-size: 1.1em; + -fx-padding: 0 0 0 10px; + } + } + } +} \ 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 index a0d798c..bb3ebc8 100644 --- a/sampler/src/main/resources/assets/styles/scss/widgets/_contrast-checker.scss +++ b/sampler/src/main/resources/assets/styles/scss/widgets/_contrast-checker.scss @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT @use "color-palette" as palette; .contrast-checker { 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 a94898f..69d84fa 100644 --- a/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss +++ b/sampler/src/main/resources/assets/styles/scss/widgets/_index.scss @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT @use "color-palette"; +@use "color-scale"; @use "contrast-checker"; @use "quick-config-menu"; \ No newline at end of file