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)
);
// 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();
}

@ -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<ColorBlock> blocks = new ArrayList<>();
private final List<ColorPaletteBlock> blocks = new ArrayList<>();
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();
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<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.getColorLuminance;
class ColorBlock extends VBox {
class ColorPaletteBlock extends VBox {
private final String fgColorName;
private final String bgColorName;
@ -33,9 +33,9 @@ class ColorBlock extends VBox {
private final Label wsagLabel = new Label();
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 borderColorName,
ReadOnlyObjectProperty<Color> bgBaseColor) {
@ -150,7 +150,7 @@ class ColorBlock extends VBox {
return borderColorName;
}
public void setOnAction(Consumer<ColorBlock> actionHandler) {
public void setOnAction(Consumer<ColorPaletteBlock> 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 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;

@ -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<ColorBlock> colorBlockActionHandler = colorBlock -> {
private final Consumer<ColorPaletteBlock> 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

@ -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,10 +128,8 @@ public class TypographyPage extends AbstractPage {
}
private void updateFontInfo(Duration delay) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
var t = new Timeline(new KeyFrame(delay));
t.setOnFinished(e -> {
for (Node node : fontSizeSampleContent.getChildren()) {
if (node instanceof Text textNode) {
var font = textNode.getFont();
@ -143,8 +139,7 @@ public class TypographyPage extends AbstractPage {
}
}
});
}
}, delay.toMillis());
t.play();
}
private SampleBlock fontSizeSample() {

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

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

@ -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;
.contrast-checker {

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