Add color scale preview

This commit is contained in:
mkpaz 2022-09-01 16:06:09 +04:00
parent 9b0b0bf44c
commit fc19ca5a36
13 changed files with 233 additions and 54 deletions

@ -139,6 +139,7 @@ public class QuickConfigMenu extends StackPane {
() -> FONT_SCALE.indexOf(fontScale.get()) <= 0, fontScale) () -> 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(); var zoomLabel = new Label();
zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> fontScale.get() + "%", fontScale)); zoomLabel.textProperty().bind(Bindings.createStringBinding(() -> fontScale.get() + "%", fontScale));
@ -149,7 +150,9 @@ public class QuickConfigMenu extends StackPane {
final var tm = ThemeManager.getInstance(); final var tm = ThemeManager.getInstance();
fontScale.addListener((obs, old, val) -> { fontScale.addListener((obs, old, val) -> {
if (val != null) { 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.setFontSize((int) Math.ceil(fontSize));
tm.reloadCustomCSS(); tm.reloadCustomCSS();
} }

@ -1,9 +1,9 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.general; package atlantafx.sampler.page.general;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles; 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.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -12,18 +12,20 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.util.Duration;
import java.time.Duration; import java.util.ArrayList;
import java.util.*; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
class ColorPalette extends VBox { class ColorPalette extends VBox {
private final List<ColorBlock> blocks = new ArrayList<>(); private final List<ColorPaletteBlock> blocks = new ArrayList<>();
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE); private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final Consumer<ColorBlock> colorBlockActionHandler; private final Consumer<ColorPaletteBlock> colorBlockActionHandler;
public ColorPalette(Consumer<ColorBlock> blockClickedHandler) { public ColorPalette(Consumer<ColorPaletteBlock> blockClickedHandler) {
super(); super();
this.colorBlockActionHandler = Objects.requireNonNull(blockClickedHandler); this.colorBlockActionHandler = Objects.requireNonNull(blockClickedHandler);
@ -35,7 +37,7 @@ class ColorPalette extends VBox {
headerLabel.getStyleClass().add(Styles.TITLE_4); headerLabel.getStyleClass().add(Styles.TITLE_4);
var headerBox = new HBox(); var headerBox = new HBox();
headerBox.getChildren().setAll(headerLabel, new Spacer()); headerBox.getChildren().setAll(headerLabel);
headerBox.setAlignment(Pos.CENTER_LEFT); headerBox.setAlignment(Pos.CENTER_LEFT);
headerBox.getStyleClass().add("header"); headerBox.getStyleClass().add("header");
@ -85,8 +87,8 @@ class ColorPalette extends VBox {
return grid; return grid;
} }
private ColorBlock colorBlock(String fgColor, String bgColor, String borderColor) { private ColorPaletteBlock colorBlock(String fgColor, String bgColor, String borderColor) {
var block = new ColorBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty()); var block = new ColorPaletteBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty());
block.setOnAction(colorBlockActionHandler); block.setOnAction(colorBlockActionHandler);
blocks.add(block); blocks.add(block);
return 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. // 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. // The timer is introduced to defer widget update to a time when scene changes supposedly will be finished.
public void updateColorInfo(Duration delay) { public void updateColorInfo(Duration delay) {
new Timer().schedule(new TimerTask() { var t = new Timeline(new KeyFrame(delay));
@Override t.setOnFinished(e -> blocks.forEach(ColorPaletteBlock::update));
public void run() { t.play();
Platform.runLater(() -> blocks.forEach(ColorBlock::update));
}
}, delay.toMillis());
} }
public ReadOnlyObjectProperty<Color> bgBaseColorProperty() { public ReadOnlyObjectProperty<Color> bgBaseColorProperty() {

@ -20,7 +20,7 @@ import static atlantafx.sampler.page.general.ContrastChecker.*;
import static atlantafx.sampler.util.JColorUtils.flattenColor; import static atlantafx.sampler.util.JColorUtils.flattenColor;
import static atlantafx.sampler.util.JColorUtils.getColorLuminance; import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
class ColorBlock extends VBox { class ColorPaletteBlock extends VBox {
private final String fgColorName; private final String fgColorName;
private final String bgColorName; private final String bgColorName;
@ -33,12 +33,12 @@ class ColorBlock extends VBox {
private final Label wsagLabel = new Label(); private final Label wsagLabel = new Label();
private final FontIcon expandIcon = new FontIcon(Feather.MAXIMIZE_2); private final FontIcon expandIcon = new FontIcon(Feather.MAXIMIZE_2);
private Consumer<ColorBlock> actionHandler; private Consumer<ColorPaletteBlock> actionHandler;
public ColorBlock(String fgColorName, public ColorPaletteBlock(String fgColorName,
String bgColorName, String bgColorName,
String borderColorName, String borderColorName,
ReadOnlyObjectProperty<Color> bgBaseColor) { ReadOnlyObjectProperty<Color> bgBaseColor) {
this.fgColorName = validateColorName(fgColorName); this.fgColorName = validateColorName(fgColorName);
this.bgColorName = validateColorName(bgColorName); this.bgColorName = validateColorName(bgColorName);
this.borderColorName = validateColorName(borderColorName); this.borderColorName = validateColorName(borderColorName);
@ -150,7 +150,7 @@ class ColorBlock extends VBox {
return borderColorName; return borderColorName;
} }
public void setOnAction(Consumer<ColorBlock> actionHandler) { public void setOnAction(Consumer<ColorPaletteBlock> actionHandler) {
this.actionHandler = actionHandler; this.actionHandler = actionHandler;
} }
} }

@ -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<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final List<ColorScaleBlock> 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;
}
}

@ -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<Color> bgBaseColor;
private ColorScaleBlock(ReadOnlyObjectProperty<Color> 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<Color> 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<Color> bgBaseColor,
String... colors) {
var block = new ColorScaleBlock(bgBaseColor);
for (String colorName : colors) {
block.addCell(colorName);
}
return block;
}
}

@ -33,7 +33,7 @@ import org.kordamp.ikonli.material2.Material2AL;
import java.util.Objects; 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.flattenColor;
import static atlantafx.sampler.util.JColorUtils.getColorLuminance; import static atlantafx.sampler.util.JColorUtils.getColorLuminance;

@ -10,9 +10,9 @@ import javafx.geometry.HPos;
import javafx.scene.control.ChoiceBox; import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.util.Duration;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import java.time.Duration;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -20,7 +20,7 @@ public class ThemePage extends AbstractPage {
public static final String NAME = "Theme"; public static final String NAME = "Theme";
private final Consumer<ColorBlock> colorBlockActionHandler = colorBlock -> { private final Consumer<ColorPaletteBlock> colorBlockActionHandler = colorBlock -> {
ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog(); ContrastCheckerDialog dialog = getOrCreateContrastCheckerDialog();
dialog.getContent().setValues(colorBlock.getFgColorName(), dialog.getContent().setValues(colorBlock.getFgColorName(),
colorBlock.getFgColor(), colorBlock.getFgColor(),
@ -32,6 +32,7 @@ public class ThemePage extends AbstractPage {
}; };
private final ColorPalette colorPalette = new ColorPalette(colorBlockActionHandler); private final ColorPalette colorPalette = new ColorPalette(colorBlockActionHandler);
private final ColorScale colorScale = new ColorScale();
private ContrastCheckerDialog contrastCheckerDialog; private ContrastCheckerDialog contrastCheckerDialog;
@ -44,7 +45,8 @@ public class ThemePage extends AbstractPage {
ThemeManager.getInstance().addEventListener(e -> { ThemeManager.getInstance().addEventListener(e -> {
if (e.eventType() == EventType.THEME_CHANGE || e.eventType() == EventType.CUSTOM_CSS_CHANGE) { if (e.eventType() == EventType.THEME_CHANGE || e.eventType() == EventType.CUSTOM_CSS_CHANGE) {
// only works for managed nodes // 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() { protected void onRendered() {
super.onRendered(); super.onRendered();
colorPalette.updateColorInfo(Duration.ZERO); colorPalette.updateColorInfo(Duration.ZERO);
colorScale.updateColorInfo(Duration.ZERO);
} }
private void createView() { private void createView() {
userContent.getChildren().addAll( userContent.getChildren().addAll(
optionsGrid(), optionsGrid(),
colorPalette colorPalette,
colorScale
); );
// if you want to enable quick menu don't forget that // if you want to enable quick menu don't forget that
// theme selection choice box have to be updated accordingly // theme selection choice box have to be updated accordingly

@ -6,7 +6,8 @@ import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.ThemeEvent.EventType; import atlantafx.sampler.theme.ThemeEvent.EventType;
import atlantafx.sampler.theme.ThemeManager; import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.NodeUtils; import atlantafx.sampler.util.NodeUtils;
import javafx.application.Platform; import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
@ -21,10 +22,7 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.scene.text.TextFlow; import javafx.scene.text.TextFlow;
import javafx.util.Duration;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
import static atlantafx.base.theme.Styles.*; import static atlantafx.base.theme.Styles.*;
@ -46,7 +44,7 @@ public class TypographyPage extends AbstractPage {
ThemeManager.getInstance().addEventListener(e -> { ThemeManager.getInstance().addEventListener(e -> {
if (e.eventType() == EventType.FONT_FAMILY_CHANGE || e.eventType() == EventType.FONT_SIZE_CHANGE) { if (e.eventType() == EventType.FONT_FAMILY_CHANGE || e.eventType() == EventType.FONT_SIZE_CHANGE) {
// only works for managed nodes // 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) { if (val != null) {
tm.setFontSize(val); tm.setFontSize(val);
tm.reloadCustomCSS(); tm.reloadCustomCSS();
updateFontInfo(Duration.ofMillis(1000)); updateFontInfo(Duration.seconds(1));
} }
}); });
@ -130,21 +128,18 @@ public class TypographyPage extends AbstractPage {
} }
private void updateFontInfo(Duration delay) { private void updateFontInfo(Duration delay) {
new Timer().schedule(new TimerTask() { var t = new Timeline(new KeyFrame(delay));
@Override t.setOnFinished(e -> {
public void run() { for (Node node : fontSizeSampleContent.getChildren()) {
Platform.runLater(() -> { if (node instanceof Text textNode) {
for (Node node : fontSizeSampleContent.getChildren()) { var font = textNode.getFont();
if (node instanceof Text textNode) { textNode.setText(
var font = textNode.getFont(); String.format("%s = %.1fpx", textNode.getUserData(), Math.ceil(font.getSize()))
textNode.setText( );
String.format("%s = %.1fpx", textNode.getUserData(), Math.ceil(font.getSize())) }
);
}
}
});
} }
}, delay.toMillis()); });
t.play();
} }
private SampleBlock fontSizeSample() { private SampleBlock fontSizeSample() {

@ -1,3 +1,5 @@
// SPDX-License-Identifier: MIT
.overlay { .overlay {
-fx-background-color: transparent; -fx-background-color: transparent;

@ -9,10 +9,6 @@ $color-wsag-fg: white;
-fx-spacing: 20px; -fx-spacing: 20px;
-fx-background-color: -color-bg-default; // mandatory base bg for flatten color calc -fx-background-color: -color-bg-default; // mandatory base bg for flatten color calc
>.header {
-fx-pref-height: 40px;
}
>.grid { >.grid {
-fx-vgap: 20px; -fx-vgap: 20px;
-fx-hgap: 40px; -fx-hgap: 40px;

@ -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;
}
}
}
}

@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
@use "color-palette" as palette; @use "color-palette" as palette;
.contrast-checker { .contrast-checker {

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