Improve color palette widget
* add description * use better color combinations for color blocks * refactor and pay tech debts
This commit is contained in:
parent
fc19ca5a36
commit
8a7204d93c
@ -1,6 +1,9 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler;
|
||||
|
||||
import atlantafx.sampler.event.BrowseEvent;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import atlantafx.sampler.event.Listener;
|
||||
import atlantafx.sampler.layout.ApplicationWindow;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import fr.brouillard.oss.cssfx.CSSFX;
|
||||
@ -63,6 +66,9 @@ public class Launcher extends Application {
|
||||
stage.setResizable(true);
|
||||
stage.setOnCloseRequest(t -> Platform.exit());
|
||||
|
||||
// register event listeners
|
||||
DefaultEventBus.getInstance().subscribe(BrowseEvent.class, this::onBrowseEvent);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
@ -109,4 +115,9 @@ public class Launcher extends Application {
|
||||
);
|
||||
CSSFX.start(scene);
|
||||
}
|
||||
|
||||
@Listener
|
||||
private void onBrowseEvent(BrowseEvent event) {
|
||||
getHostServices().showDocument(event.getUri().toString());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public class BrowseEvent extends Event {
|
||||
|
||||
private final URI uri;
|
||||
|
||||
public BrowseEvent(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BrowseEvent{" +
|
||||
"uri=" + uri +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Simple event bus implementation.
|
||||
* <p>
|
||||
* Subscribe and publish events. Events are published in channels distinguished by event type.
|
||||
* Channels can be grouped using an event type hierarchy.
|
||||
* <p>
|
||||
* You can use the default event bus instance {@link #getInstance}, which is a singleton
|
||||
* or you can create one or multiple instances of {@link DefaultEventBus}.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public final class DefaultEventBus implements EventBus {
|
||||
|
||||
public DefaultEventBus() {}
|
||||
|
||||
private final Map<Class<?>, Set<Consumer>> subscribers = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <E extends Event> void subscribe(Class<? extends E> eventType, Consumer<E> subscriber) {
|
||||
Objects.requireNonNull(eventType);
|
||||
Objects.requireNonNull(subscriber);
|
||||
|
||||
Set<Consumer> eventSubscribers = getOrCreateSubscribers(eventType);
|
||||
eventSubscribers.add(subscriber);
|
||||
}
|
||||
|
||||
private <E> Set<Consumer> getOrCreateSubscribers(Class<E> eventType) {
|
||||
Set<Consumer> eventSubscribers = subscribers.get(eventType);
|
||||
if (eventSubscribers == null) {
|
||||
eventSubscribers = new CopyOnWriteArraySet<>();
|
||||
subscribers.put(eventType, eventSubscribers);
|
||||
}
|
||||
return eventSubscribers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Event> void unsubscribe(Consumer<E> subscriber) {
|
||||
Objects.requireNonNull(subscriber);
|
||||
|
||||
subscribers.values().forEach(eventSubscribers -> eventSubscribers.remove(subscriber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Event> void unsubscribe(Class<? extends E> eventType, Consumer<E> subscriber) {
|
||||
Objects.requireNonNull(eventType);
|
||||
Objects.requireNonNull(subscriber);
|
||||
|
||||
subscribers.keySet().stream()
|
||||
.filter(eventType::isAssignableFrom)
|
||||
.map(subscribers::get)
|
||||
.forEach(eventSubscribers -> eventSubscribers.remove(subscriber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Event> void publish(E event) {
|
||||
Objects.requireNonNull(event);
|
||||
|
||||
Class<?> eventType = event.getClass();
|
||||
subscribers.keySet().stream()
|
||||
.filter(type -> type.isAssignableFrom(eventType))
|
||||
.flatMap(type -> subscribers.get(type).stream())
|
||||
.forEach(subscriber -> publish(event, subscriber));
|
||||
}
|
||||
|
||||
private <E extends Event> void publish(E event, Consumer<E> subscriber) {
|
||||
try {
|
||||
subscriber.accept(event);
|
||||
} catch (Exception e) {
|
||||
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class InstanceHolder {
|
||||
|
||||
private static final DefaultEventBus INSTANCE = new DefaultEventBus();
|
||||
}
|
||||
|
||||
public static DefaultEventBus getInstance() {
|
||||
return InstanceHolder.INSTANCE;
|
||||
}
|
||||
}
|
35
sampler/src/main/java/atlantafx/sampler/event/Event.java
Normal file
35
sampler/src/main/java/atlantafx/sampler/event/Event.java
Normal file
@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class Event {
|
||||
|
||||
protected final UUID id = UUID.randomUUID();
|
||||
|
||||
protected Event() { }
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
Event event = (Event) o;
|
||||
return id.equals(event.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Event{" +
|
||||
"id=" + id +
|
||||
'}';
|
||||
}
|
||||
}
|
43
sampler/src/main/java/atlantafx/sampler/event/EventBus.java
Normal file
43
sampler/src/main/java/atlantafx/sampler/event/EventBus.java
Normal file
@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
* Subscribe to an event type
|
||||
*
|
||||
* @param eventType the event type, can be a super class of all events to subscribe.
|
||||
* @param subscriber the subscriber which will consume the events.
|
||||
* @param <T> the event type class.
|
||||
*/
|
||||
<T extends Event> void subscribe(Class<? extends T> eventType, Consumer<T> subscriber);
|
||||
|
||||
/**
|
||||
* Unsubscribe from all event types.
|
||||
*
|
||||
* @param subscriber the subscriber to unsubscribe.
|
||||
*/
|
||||
<T extends Event> void unsubscribe(Consumer<T> subscriber);
|
||||
|
||||
/**
|
||||
* Unsubscribe from an event type.
|
||||
*
|
||||
* @param eventType the event type, can be a super class of all events to unsubscribe.
|
||||
* @param subscriber the subscriber to unsubscribe.
|
||||
* @param <T> the event type class.
|
||||
*/
|
||||
<T extends Event> void unsubscribe(Class<? extends T> eventType, Consumer<T> subscriber);
|
||||
|
||||
/**
|
||||
* Publish an event to all subscribers.
|
||||
* <p>
|
||||
* The event type is the class of <code>event</code>. The event is published to all consumers which subscribed to
|
||||
* this event type or any super class.
|
||||
*
|
||||
* @param event the event.
|
||||
*/
|
||||
<T extends Event> void publish(T event);
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Listener {}
|
@ -12,13 +12,18 @@ import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
class ColorPalette extends VBox {
|
||||
|
||||
private final List<ColorPaletteBlock> blocks = new ArrayList<>();
|
||||
@ -41,13 +46,30 @@ class ColorPalette extends VBox {
|
||||
headerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
headerBox.getStyleClass().add("header");
|
||||
|
||||
var noteText = new VBox(6);
|
||||
noteText.getChildren().setAll(
|
||||
new TextFlow(
|
||||
new Text("Color contrast between text and its background must meet "),
|
||||
hyperlink("required WCAG standards",
|
||||
URI.create("https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html")
|
||||
),
|
||||
new Text(":")
|
||||
),
|
||||
new Text(" • 4.5:1 for normal text"),
|
||||
new Text(" • 3:1 for large text (>24px)"),
|
||||
new Text(" • 3:1 for UI elements and graphics"),
|
||||
new Text(" • no contrast requirement for decorative and disabled elements"),
|
||||
new Text(),
|
||||
new Text("Click on any color block to observe and modify color combination via built-in contrast checker.")
|
||||
);
|
||||
|
||||
var colorGrid = colorGrid();
|
||||
|
||||
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
|
||||
val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE
|
||||
));
|
||||
|
||||
getChildren().setAll(headerBox, colorGrid);
|
||||
getChildren().setAll(headerBox, noteText, colorGrid);
|
||||
setId("color-palette");
|
||||
}
|
||||
|
||||
@ -56,33 +78,34 @@ class ColorPalette extends VBox {
|
||||
grid.getStyleClass().add("grid");
|
||||
|
||||
grid.add(colorBlock("-color-fg-default", "-color-bg-default", "-color-border-default"), 0, 0);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-bg-default", "-color-border-muted"), 1, 0);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-bg-default", "-color-border-subtle"), 2, 0);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-bg-overlay", "-color-border-default"), 1, 0);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-bg-default", "-color-border-muted"), 2, 0);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-bg-default", "-color-border-subtle"), 3, 0);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-accent-emphasis", "-color-accent-emphasis"), 0, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-bg-default", "-color-accent-emphasis"), 1, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-muted", "-color-accent-muted"), 2, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-subtle", "-color-accent-subtle"), 3, 1);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-accent-muted", "-color-accent-emphasis"), 2, 1);
|
||||
grid.add(colorBlock("-color-accent-fg", "-color-accent-subtle", "-color-accent-emphasis"), 3, 1);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis-plus", "-color-neutral-emphasis-plus"), 0, 2);
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-neutral-emphasis", "-color-neutral-emphasis"), 1, 2);
|
||||
grid.add(colorBlock("-color-fg-muted", "-color-neutral-muted", "-color-neutral-muted"), 2, 2);
|
||||
grid.add(colorBlock("-color-fg-subtle", "-color-neutral-subtle", "-color-neutral-subtle"), 3, 2);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-neutral-muted", "-color-neutral-emphasis"), 2, 2);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-neutral-subtle", "-color-neutral-emphasis"), 3, 2);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-success-emphasis", "-color-success-emphasis"), 0, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-bg-default", "-color-success-emphasis"), 1, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-muted", "-color-success-muted"), 2, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-subtle", "-color-success-subtle"), 3, 3);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-success-muted", "-color-success-emphasis"), 2, 3);
|
||||
grid.add(colorBlock("-color-success-fg", "-color-success-subtle", "-color-success-emphasis"), 3, 3);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-warning-emphasis", "-color-warning-emphasis"), 0, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-bg-default", "-color-warning-emphasis"), 1, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-muted", "-color-warning-muted"), 2, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-subtle", "-color-warning-subtle"), 3, 4);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-warning-muted", "-color-warning-emphasis"), 2, 4);
|
||||
grid.add(colorBlock("-color-warning-fg", "-color-warning-subtle", "-color-warning-emphasis"), 3, 4);
|
||||
|
||||
grid.add(colorBlock("-color-fg-emphasis", "-color-danger-emphasis", "-color-danger-emphasis"), 0, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-bg-default", "-color-danger-emphasis"), 1, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-muted", "-color-danger-muted"), 2, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-subtle", "-color-danger-subtle"), 3, 5);
|
||||
grid.add(colorBlock("-color-fg-default", "-color-danger-muted", "-color-danger-emphasis"), 2, 5);
|
||||
grid.add(colorBlock("-color-danger-fg", "-color-danger-subtle", "-color-danger-emphasis"), 3, 5);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package atlantafx.sampler.page.general;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.util.Containers;
|
||||
import atlantafx.sampler.util.ContrastLevel;
|
||||
import atlantafx.sampler.util.NodeUtils;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@ -10,15 +12,17 @@ import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.sampler.page.general.ContrastChecker.*;
|
||||
import static atlantafx.base.theme.Styles.TITLE_3;
|
||||
import static atlantafx.sampler.page.general.ContrastChecker.LUMINANCE_THRESHOLD;
|
||||
import static atlantafx.sampler.page.general.ContrastChecker.PASSED;
|
||||
import static atlantafx.sampler.util.ContrastLevel.getColorLuminance;
|
||||
import static atlantafx.sampler.util.ContrastLevel.getContrastRatioOpacityAware;
|
||||
import static atlantafx.sampler.util.JColorUtils.flattenColor;
|
||||
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
|
||||
|
||||
class ColorPaletteBlock extends VBox {
|
||||
|
||||
@ -27,11 +31,11 @@ class ColorPaletteBlock extends VBox {
|
||||
private final String borderColorName;
|
||||
private final ReadOnlyObjectProperty<Color> bgBaseColor;
|
||||
|
||||
private final AnchorPane colorBox;
|
||||
private final Text fgText;
|
||||
private final FontIcon wsagIcon = new FontIcon();
|
||||
private final Label wsagLabel = new Label();
|
||||
private final FontIcon expandIcon = new FontIcon(Feather.MAXIMIZE_2);
|
||||
private final AnchorPane colorRectangle;
|
||||
private final Text contrastRatioText;
|
||||
private final FontIcon contrastLevelIcon = new FontIcon();
|
||||
private final Label contrastLevelLabel = new Label();
|
||||
private final FontIcon editIcon = new FontIcon(Material2AL.COLORIZE);
|
||||
|
||||
private Consumer<ColorPaletteBlock> actionHandler;
|
||||
|
||||
@ -44,69 +48,54 @@ class ColorPaletteBlock extends VBox {
|
||||
this.borderColorName = validateColorName(borderColorName);
|
||||
this.bgBaseColor = bgBaseColor;
|
||||
|
||||
fgText = new Text();
|
||||
fgText.setStyle("-fx-fill:" + fgColorName + ";");
|
||||
fgText.getStyleClass().addAll("text", Styles.TITLE_3);
|
||||
Containers.setAnchors(fgText, new Insets(5, -1, -1, 5));
|
||||
contrastRatioText = new Text();
|
||||
contrastRatioText.setStyle("-fx-fill:" + fgColorName + ";");
|
||||
contrastRatioText.getStyleClass().addAll("contrast-ratio-text", TITLE_3);
|
||||
Containers.setAnchors(contrastRatioText, new Insets(5, -1, -1, 5));
|
||||
|
||||
wsagLabel.setGraphic(wsagIcon);
|
||||
wsagLabel.getStyleClass().add("wsag-label");
|
||||
wsagLabel.setVisible(false);
|
||||
Containers.setAnchors(wsagLabel, new Insets(-1, 3, 3, -1));
|
||||
contrastLevelLabel.setGraphic(contrastLevelIcon);
|
||||
contrastLevelLabel.getStyleClass().add("contrast-level-label");
|
||||
contrastLevelLabel.setVisible(false);
|
||||
Containers.setAnchors(contrastLevelLabel, new Insets(-1, 3, 3, -1));
|
||||
|
||||
expandIcon.setIconSize(24);
|
||||
expandIcon.getStyleClass().add("expand-icon");
|
||||
expandIcon.setVisible(false);
|
||||
expandIcon.setManaged(false);
|
||||
Containers.setAnchors(expandIcon, new Insets(3, 3, -1, -1));
|
||||
editIcon.setIconSize(24);
|
||||
editIcon.getStyleClass().add("edit-icon");
|
||||
NodeUtils.toggleVisibility(editIcon, false);
|
||||
Containers.setAnchors(editIcon, new Insets(3, 3, -1, -1));
|
||||
|
||||
colorBox = new AnchorPane();
|
||||
colorBox.setStyle("-fx-background-color:" + bgColorName + ";" + "-fx-border-color:" + borderColorName + ";");
|
||||
colorBox.getStyleClass().add("box");
|
||||
colorBox.getChildren().setAll(fgText, wsagLabel, expandIcon);
|
||||
colorBox.setOnMouseEntered(e -> {
|
||||
colorRectangle = new AnchorPane();
|
||||
colorRectangle.setStyle(
|
||||
String.format("-fx-background-color:%s;-fx-border-color:%s;", bgColorName, borderColorName)
|
||||
);
|
||||
colorRectangle.getStyleClass().add("rectangle");
|
||||
colorRectangle.getChildren().setAll(contrastRatioText, contrastLevelLabel, editIcon);
|
||||
colorRectangle.setOnMouseEntered(e -> {
|
||||
var bgFill = getBgColor();
|
||||
|
||||
// this happens when css isn't updated yet
|
||||
if (bgFill == null) { return; }
|
||||
|
||||
toggleHover(true);
|
||||
expandIcon.setFill(getColorLuminance(flattenColor(bgBaseColor.get(), bgFill)) < LUMINANCE_THRESHOLD ?
|
||||
editIcon.setFill(getColorLuminance(flattenColor(bgBaseColor.get(), bgFill)) < LUMINANCE_THRESHOLD ?
|
||||
Color.WHITE : Color.BLACK
|
||||
);
|
||||
});
|
||||
colorBox.setOnMouseExited(e -> toggleHover(false));
|
||||
colorBox.setOnMouseClicked(e -> {
|
||||
colorRectangle.setOnMouseExited(e -> toggleHover(false));
|
||||
colorRectangle.setOnMouseClicked(e -> {
|
||||
if (actionHandler != null) { actionHandler.accept(this); }
|
||||
});
|
||||
|
||||
getChildren().addAll(
|
||||
colorBox,
|
||||
description(fgColorName),
|
||||
description(bgColorName),
|
||||
description(borderColorName)
|
||||
colorRectangle,
|
||||
colorNameText(fgColorName),
|
||||
colorNameText(bgColorName),
|
||||
colorNameText(borderColorName)
|
||||
);
|
||||
getStyleClass().add("color-block");
|
||||
getStyleClass().add("block");
|
||||
}
|
||||
|
||||
static String validateColorName(String colorName) {
|
||||
if (colorName == null || !colorName.startsWith("-color")) {
|
||||
throw new IllegalArgumentException("Invalid color name: '" + colorName + "'.");
|
||||
}
|
||||
return colorName;
|
||||
}
|
||||
|
||||
private void toggleHover(boolean state) {
|
||||
expandIcon.setVisible(state);
|
||||
expandIcon.setManaged(state);
|
||||
fgText.setOpacity(state ? 0.5 : 1);
|
||||
wsagLabel.setOpacity(state ? 0.5 : 1);
|
||||
}
|
||||
|
||||
private Text description(String text) {
|
||||
var t = new Text(text);
|
||||
t.getStyleClass().addAll("description", Styles.TEXT_SMALL);
|
||||
return t;
|
||||
public void setOnAction(Consumer<ColorPaletteBlock> actionHandler) {
|
||||
this.actionHandler = actionHandler;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
@ -114,28 +103,28 @@ class ColorPaletteBlock extends VBox {
|
||||
var bgFill = getBgColor();
|
||||
|
||||
if (fgFill == null || bgFill == null) {
|
||||
fgText.setText("");
|
||||
wsagLabel.setText("");
|
||||
wsagLabel.setVisible(false);
|
||||
contrastRatioText.setText("");
|
||||
contrastLevelLabel.setText("");
|
||||
contrastLevelLabel.setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
double contrastRatio = 1 / getContrastRatioOpacityAware(bgFill, fgFill, bgBaseColor.get());
|
||||
colorBox.pseudoClassStateChanged(PASSED, contrastRatio >= 4.5);
|
||||
double contrastRatio = getContrastRatioOpacityAware(bgFill, fgFill, bgBaseColor.get());
|
||||
colorRectangle.pseudoClassStateChanged(PASSED, ContrastLevel.AA_NORMAL.satisfies(contrastRatio));
|
||||
|
||||
wsagIcon.setIconCode(contrastRatio >= 4.5 ? Material2AL.CHECK : Material2AL.CLOSE);
|
||||
wsagLabel.setVisible(true);
|
||||
wsagLabel.setText(contrastRatio >= 7 ? "AAA" : "AA");
|
||||
fgText.setText(String.format("%.2f", contrastRatio));
|
||||
contrastRatioText.setText(String.format("%.2f", contrastRatio));
|
||||
contrastLevelIcon.setIconCode(ContrastLevel.AA_NORMAL.satisfies(contrastRatio) ? Material2AL.CHECK : Material2AL.CLOSE);
|
||||
contrastLevelLabel.setVisible(true);
|
||||
contrastLevelLabel.setText(ContrastLevel.AAA_NORMAL.satisfies(contrastRatio) ? "AAA" : "AA");
|
||||
}
|
||||
|
||||
public Color getFgColor() {
|
||||
return (Color) fgText.getFill();
|
||||
return (Color) contrastRatioText.getFill();
|
||||
}
|
||||
|
||||
public Color getBgColor() {
|
||||
return colorBox.getBackground() != null && !colorBox.getBackground().isEmpty() ?
|
||||
(Color) colorBox.getBackground().getFills().get(0).getFill() : null;
|
||||
return colorRectangle.getBackground() != null && !colorRectangle.getBackground().isEmpty() ?
|
||||
(Color) colorRectangle.getBackground().getFills().get(0).getFill() : null;
|
||||
}
|
||||
|
||||
public String getFgColorName() {
|
||||
@ -150,7 +139,22 @@ class ColorPaletteBlock extends VBox {
|
||||
return borderColorName;
|
||||
}
|
||||
|
||||
public void setOnAction(Consumer<ColorPaletteBlock> actionHandler) {
|
||||
this.actionHandler = actionHandler;
|
||||
private void toggleHover(boolean state) {
|
||||
NodeUtils.toggleVisibility(editIcon, state);
|
||||
contrastRatioText.setOpacity(state ? 0.5 : 1);
|
||||
contrastLevelLabel.setOpacity(state ? 0.5 : 1);
|
||||
}
|
||||
|
||||
private Text colorNameText(String text) {
|
||||
var t = new Text(text);
|
||||
t.getStyleClass().addAll("color-name", Styles.TEXT_SMALL);
|
||||
return t;
|
||||
}
|
||||
|
||||
static String validateColorName(String colorName) {
|
||||
if (colorName == null || !colorName.startsWith("-color")) {
|
||||
throw new IllegalArgumentException("Invalid color name: '" + colorName + "'.");
|
||||
}
|
||||
return colorName;
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ColorScale extends VBox {
|
||||
class ColorScale extends VBox {
|
||||
|
||||
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
|
||||
private final List<ColorScaleBlock> blocks = Arrays.asList(
|
||||
@ -42,6 +44,10 @@ public class ColorScale extends VBox {
|
||||
headerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
headerBox.getStyleClass().add("header");
|
||||
|
||||
var noteText = new TextFlow(
|
||||
new Text("Avoid referencing scale variables directly when building UI that needs to adapt to different color themes. Instead, use the functional variables listed above.")
|
||||
);
|
||||
|
||||
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
|
||||
val != null && !val.getFills().isEmpty() ? (Color) val.getFills().get(0).getFill() : Color.WHITE
|
||||
));
|
||||
@ -49,6 +55,7 @@ public class ColorScale extends VBox {
|
||||
setId("color-scale");
|
||||
getChildren().setAll(
|
||||
headerBox,
|
||||
noteText,
|
||||
colorTable()
|
||||
);
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import static atlantafx.sampler.util.ContrastLevel.getColorLuminance;
|
||||
import static atlantafx.sampler.util.JColorUtils.flattenColor;
|
||||
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
|
||||
|
||||
public class ColorScaleBlock extends VBox {
|
||||
class ColorScaleBlock extends VBox {
|
||||
|
||||
private static final double BLOCK_WIDTH = 250;
|
||||
private static final double BLOCK_HEIGHT = 50;
|
||||
|
@ -5,6 +5,7 @@ import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.theme.ThemeManager;
|
||||
import atlantafx.sampler.util.ContrastLevel;
|
||||
import atlantafx.sampler.util.JColor;
|
||||
import atlantafx.sampler.util.JColorUtils;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@ -34,16 +35,19 @@ import org.kordamp.ikonli.material2.Material2AL;
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.sampler.page.general.ColorPaletteBlock.validateColorName;
|
||||
import static atlantafx.sampler.util.ContrastLevel.getColorLuminance;
|
||||
import static atlantafx.sampler.util.ContrastLevel.getContrastRatioOpacityAware;
|
||||
import static atlantafx.sampler.util.JColorUtils.flattenColor;
|
||||
import static atlantafx.sampler.util.JColorUtils.getColorLuminance;
|
||||
|
||||
// Inspired by the https://colourcontrast.cc/
|
||||
public class ContrastChecker extends GridPane {
|
||||
class ContrastChecker extends GridPane {
|
||||
|
||||
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 String STATE_PASS = "PASS";
|
||||
private static final String STATE_FAIL = "FAIL";
|
||||
private static final int SLIDER_WIDTH = 300;
|
||||
|
||||
private String bgColorName;
|
||||
@ -70,7 +74,7 @@ public class ContrastChecker extends GridPane {
|
||||
|
||||
this.bgBaseColor = bgBaseColor;
|
||||
this.contrastRatio = Bindings.createDoubleBinding(
|
||||
() -> 1 / getContrastRatioOpacityAware(bgColor.getColor(), fgColor.getColor(), bgBaseColor.get()),
|
||||
() -> getContrastRatioOpacityAware(bgColor.getColor(), fgColor.getColor(), bgBaseColor.get()),
|
||||
bgColor.colorProperty(),
|
||||
fgColor.colorProperty(),
|
||||
bgBaseColor
|
||||
@ -112,60 +116,42 @@ public class ContrastChecker extends GridPane {
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var textLabel = new Label("Aa");
|
||||
textLabel.getStyleClass().add("text");
|
||||
var largeFontLabel = new Label("Aa");
|
||||
largeFontLabel.getStyleClass().add("large-font");
|
||||
|
||||
var ratioLabel = new Label("0.0");
|
||||
ratioLabel.getStyleClass().add("ratio");
|
||||
ratioLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
var contrastRatioLabel = new Label("0.0");
|
||||
contrastRatioLabel.getStyleClass().add("ratio");
|
||||
contrastRatioLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("%.2f", contrastRatio.get()), contrastRatio
|
||||
));
|
||||
|
||||
var fontBox = new HBox(20, textLabel, ratioLabel);
|
||||
fontBox.getStyleClass().add("font-box");
|
||||
fontBox.setAlignment(Pos.BASELINE_LEFT);
|
||||
var contrastRatioBox = new HBox(20, largeFontLabel, contrastRatioLabel);
|
||||
contrastRatioBox.getStyleClass().add("contrast-ratio");
|
||||
contrastRatioBox.setAlignment(Pos.BASELINE_LEFT);
|
||||
|
||||
// !
|
||||
|
||||
var aaNormalLabel = wsagLabel();
|
||||
var aaNormalBox = wsagBox(aaNormalLabel, "AA Normal");
|
||||
var aaNormalLabel = contrastLevelLabel();
|
||||
var aaNormalBox = contrastLevelBox(aaNormalLabel, "AA Normal");
|
||||
|
||||
var aaLargeLabel = wsagLabel();
|
||||
var aaLargeBox = wsagBox(aaLargeLabel, "AA Large");
|
||||
var aaLargeLabel = contrastLevelLabel();
|
||||
var aaLargeBox = contrastLevelBox(aaLargeLabel, "AA Large");
|
||||
|
||||
var aaaNormalLabel = wsagLabel();
|
||||
var aaaNormalBox = wsagBox(aaaNormalLabel, "AAA Normal");
|
||||
var aaaNormalLabel = contrastLevelLabel();
|
||||
var aaaNormalBox = contrastLevelBox(aaaNormalLabel, "AAA Normal");
|
||||
|
||||
var aaaLargeLabel = wsagLabel();
|
||||
var aaaLargeBox = wsagBox(aaaLargeLabel, "AAA Large");
|
||||
var aaaLargeLabel = contrastLevelLabel();
|
||||
var aaaLargeBox = contrastLevelBox(aaaLargeLabel, "AAA Large");
|
||||
|
||||
var wsagBox = new HBox(20, aaNormalBox, aaLargeBox, aaaNormalBox, aaaLargeBox);
|
||||
wsagBox.getStyleClass().add("wsag-box");
|
||||
var contrastLevels = new HBox(20, aaNormalBox, aaLargeBox, aaaNormalBox, aaaLargeBox);
|
||||
|
||||
contrastRatio.addListener((obs, old, val) -> {
|
||||
if (val == null) { return; }
|
||||
float ratio = val.floatValue();
|
||||
if (ratio >= 7) {
|
||||
updateWsagLabel(aaNormalLabel, true);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, true);
|
||||
updateWsagLabel(aaaLargeLabel, true);
|
||||
} else if (ratio < 7 && ratio >= 4.5) {
|
||||
updateWsagLabel(aaNormalLabel, true);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, true);
|
||||
} else if (ratio < 4.5 && ratio >= 3) {
|
||||
updateWsagLabel(aaNormalLabel, false);
|
||||
updateWsagLabel(aaLargeLabel, true);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, false);
|
||||
} else {
|
||||
updateWsagLabel(aaNormalLabel, false);
|
||||
updateWsagLabel(aaLargeLabel, false);
|
||||
updateWsagLabel(aaaNormalLabel, false);
|
||||
updateWsagLabel(aaaLargeLabel, false);
|
||||
}
|
||||
updateWsagLabel(aaNormalLabel, ContrastLevel.AA_NORMAL.satisfies(ratio));
|
||||
updateWsagLabel(aaLargeLabel, ContrastLevel.AA_LARGE.satisfies(ratio));
|
||||
updateWsagLabel(aaaNormalLabel, ContrastLevel.AAA_NORMAL.satisfies(ratio));
|
||||
updateWsagLabel(aaaLargeLabel, ContrastLevel.AAA_LARGE.satisfies(ratio));
|
||||
});
|
||||
|
||||
// ~
|
||||
@ -296,7 +282,7 @@ public class ContrastChecker extends GridPane {
|
||||
getStyleClass().add("contrast-checker");
|
||||
|
||||
// column 0
|
||||
add(new HBox(fontBox, new Spacer(), wsagBox), 0, 0, REMAINING, 1);
|
||||
add(new HBox(contrastRatioBox, new Spacer(), contrastLevels), 0, 0, REMAINING, 1);
|
||||
add(new Label("Background Color"), 0, 1);
|
||||
add(bgColorNameLabel, 0, 2);
|
||||
add(bgTextField, 0, 3);
|
||||
@ -359,25 +345,26 @@ public class ContrastChecker extends GridPane {
|
||||
private void updateWsagLabel(Label label, boolean success) {
|
||||
FontIcon icon = Objects.requireNonNull((FontIcon) label.getGraphic());
|
||||
if (success) {
|
||||
label.setText("PASS");
|
||||
label.setText(STATE_PASS);
|
||||
icon.setIconCode(Material2AL.CHECK);
|
||||
} else {
|
||||
label.setText("FAIL");
|
||||
label.setText(STATE_FAIL);
|
||||
icon.setIconCode(Material2AL.CLOSE);
|
||||
}
|
||||
label.pseudoClassStateChanged(PASSED, success);
|
||||
}
|
||||
|
||||
private Label wsagLabel() {
|
||||
var label = new Label("FAIL");
|
||||
label.getStyleClass().add("wsag-label");
|
||||
private Label contrastLevelLabel() {
|
||||
var label = new Label(STATE_FAIL);
|
||||
label.getStyleClass().add("state");
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
label.setGraphic(new FontIcon(Material2AL.CLOSE));
|
||||
return label;
|
||||
}
|
||||
|
||||
private VBox wsagBox(Label label, String description) {
|
||||
private VBox contrastLevelBox(Label label, String description) {
|
||||
var box = new VBox(10, label, new Label(description));
|
||||
box.getStyleClass().add("contrast-level");
|
||||
box.setAlignment(Pos.CENTER);
|
||||
return box;
|
||||
}
|
||||
@ -392,12 +379,6 @@ public class ContrastChecker extends GridPane {
|
||||
return slider;
|
||||
}
|
||||
|
||||
static double getContrastRatioOpacityAware(Color bgColor, Color fgColor, Color bgBaseColor) {
|
||||
double luminance1 = getColorLuminance(flattenColor(bgBaseColor, bgColor));
|
||||
double luminance2 = getColorLuminance(flattenColor(bgBaseColor, fgColor));
|
||||
return JColorUtils.getContrastRatio(luminance1, luminance2);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static class ObservableHSLAColor {
|
||||
|
@ -10,12 +10,17 @@ import javafx.geometry.HPos;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static atlantafx.sampler.util.Controls.hyperlink;
|
||||
|
||||
public class ThemePage extends AbstractPage {
|
||||
|
||||
public static final String NAME = "Theme";
|
||||
@ -59,11 +64,21 @@ public class ThemePage extends AbstractPage {
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var noteText = new TextFlow(
|
||||
new Text("AtlantaFX follows "),
|
||||
hyperlink("Github Primer interface guidelines",
|
||||
URI.create("https://primer.style/design/foundations/color")
|
||||
),
|
||||
new Text(" and color system.")
|
||||
);
|
||||
|
||||
userContent.getChildren().addAll(
|
||||
optionsGrid(),
|
||||
noteText,
|
||||
colorPalette,
|
||||
colorScale
|
||||
);
|
||||
|
||||
// if you want to enable quick menu don't forget that
|
||||
// theme selection choice box have to be updated accordingly
|
||||
NodeUtils.toggleVisibility(quickConfigBtn, false);
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static atlantafx.sampler.util.JColorUtils.flattenColor;
|
||||
|
||||
/**
|
||||
* The WCAG contrast measures the difference in brightness (luminance) between two colours.
|
||||
* It ranges from 1:1 (white on white) to 21:1 (black on white). WCAG requirements are:
|
||||
* <ul>
|
||||
* <li>0.14285 (7.0:1) for small text in AAA-level</li>
|
||||
* <li>0.22222 (4.5:1) for small text in AA-level, or large text in AAA-level</li>
|
||||
* <li>0.33333 (3.0:1) for large text in AA-level</li>
|
||||
* </ul>
|
||||
* WCAG defines large text as text that is 18pt and larger, or 14pt and larger if it is bold.
|
||||
* <br/>
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">More info</a>.
|
||||
*/
|
||||
public enum ContrastLevel {
|
||||
|
||||
AA_NORMAL,
|
||||
AA_LARGE,
|
||||
AAA_NORMAL,
|
||||
AAA_LARGE;
|
||||
|
||||
public boolean satisfies(double ratio) {
|
||||
switch (this) {
|
||||
case AA_NORMAL -> { return ratio >= 4.5; }
|
||||
case AA_LARGE -> { return ratio >= 3; }
|
||||
case AAA_NORMAL -> { return ratio >= 7; }
|
||||
case AAA_LARGE -> { return ratio >= 4.5; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static double getContrastRatio(Color color1, Color color2) {
|
||||
return getContrastRatio(getColorLuminance(color1), getColorLuminance(color2));
|
||||
}
|
||||
|
||||
public static double getContrastRatio(double luminance1, double luminance2) {
|
||||
return 1 / (luminance1 > luminance2 ?
|
||||
(luminance2 + 0.05) / (luminance1 + 0.05) :
|
||||
(luminance1 + 0.05) / (luminance2 + 0.05)
|
||||
);
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures relative color luminance according to the
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">W3C</a>.
|
||||
* <br/>
|
||||
* Note that JavaFX provides {@link Color#getBrightness()} which
|
||||
* IS NOT the same thing as luminance.
|
||||
*/
|
||||
public static double getColorLuminance(double[] rgb) {
|
||||
double[] tmp = Arrays.stream(rgb)
|
||||
.map(v -> v <= 0.03928 ? (v / 12.92) : Math.pow((v + 0.055) / 1.055, 2.4))
|
||||
.toArray();
|
||||
return (tmp[0] * 0.2126) + (tmp[1] * 0.7152) + (tmp[2] * 0.0722);
|
||||
}
|
||||
|
||||
/** @see ContrastLevel#getColorLuminance(double[]) */
|
||||
public static double getColorLuminance(Color color) {
|
||||
return getColorLuminance(new double[] { color.getRed(), color.getGreen(), color.getBlue() });
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.util;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import atlantafx.sampler.event.BrowseEvent;
|
||||
import atlantafx.sampler.event.DefaultEventBus;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
|
||||
public final class Controls {
|
||||
@ -52,4 +53,12 @@ public final class Controls {
|
||||
|
||||
return toggleButton;
|
||||
}
|
||||
|
||||
public static Hyperlink hyperlink(String text, URI uri) {
|
||||
var hyperlink = new Hyperlink(text);
|
||||
if (uri != null) {
|
||||
hyperlink.setOnAction(event -> DefaultEventBus.getInstance().publish(new BrowseEvent(uri)));
|
||||
}
|
||||
return hyperlink;
|
||||
}
|
||||
}
|
||||
|
@ -780,48 +780,6 @@ public class JColorUtils {
|
||||
// Additional Utils, mkpaz (c) //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The WCAG contrast measures the difference in brightness (luminance) between two colours.
|
||||
* It ranges from 1:1 (white on white) to 21:1 (black on white). WCAG requirements are:
|
||||
* <ul>
|
||||
* <li>0.14285 (7.0:1) for small text in AAA-level</li>
|
||||
* <li>0.22222 (4.5:1) for small text in AA-level, or large text in AAA-level</li>
|
||||
* <li>0.33333 (3.0:1) for large text in AA-level</li>
|
||||
* </ul>
|
||||
* WCAG defines large text as text that is 18pt and larger, or 14pt and larger if it is bold.
|
||||
* <br/>
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">More info</a>.
|
||||
*/
|
||||
public static double getContrastRatio(Color color1, Color color2) {
|
||||
return getContrastRatio(getColorLuminance(color1), getColorLuminance(color2));
|
||||
}
|
||||
|
||||
/** @see JColorUtils#getContrastRatio(Color, Color) */
|
||||
public static double getContrastRatio(double luminance1, double luminance2) {
|
||||
return luminance1 > luminance2 ?
|
||||
(luminance2 + 0.05) / (luminance1 + 0.05) :
|
||||
(luminance1 + 0.05) / (luminance2 + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures relative color luminance according to the
|
||||
* <a href="https://www.w3.org/TR/WCAG20-TECHS/G18.html">W3C</a>.
|
||||
* <br/>
|
||||
* Note that JavaFX provides {@link Color#getBrightness()} which
|
||||
* IS NOT the same thing as luminance.
|
||||
*/
|
||||
public static double getColorLuminance(double[] rgb) {
|
||||
double[] tmp = Arrays.stream(rgb)
|
||||
.map(v -> v <= 0.03928 ? (v / 12.92) : Math.pow((v + 0.055) / 1.055, 2.4))
|
||||
.toArray();
|
||||
return (tmp[0] * 0.2126) + (tmp[1] * 0.7152) + (tmp[2] * 0.0722);
|
||||
}
|
||||
|
||||
/** @see JColorUtils#getColorLuminance(double[]) */
|
||||
public static double getColorLuminance(Color color) {
|
||||
return getColorLuminance(new double[] { color.getRed(), color.getGreen(), color.getBlue() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given color opacity, if present.
|
||||
* <br/><br/>
|
||||
|
@ -20,6 +20,7 @@ module atlantafx.sampler {
|
||||
exports atlantafx.sampler;
|
||||
exports atlantafx.sampler.fake;
|
||||
exports atlantafx.sampler.fake.domain;
|
||||
exports atlantafx.sampler.event;
|
||||
exports atlantafx.sampler.layout;
|
||||
exports atlantafx.sampler.page;
|
||||
exports atlantafx.sampler.page.general;
|
||||
|
@ -13,21 +13,21 @@ $color-wsag-fg: white;
|
||||
-fx-vgap: 20px;
|
||||
-fx-hgap: 40px;
|
||||
|
||||
>.color-block {
|
||||
>.block {
|
||||
-fx-spacing: 5px;
|
||||
|
||||
>.box {
|
||||
>.rectangle {
|
||||
-fx-min-width: 12em;
|
||||
-fx-min-height: 5em;
|
||||
-fx-max-width: 12em;
|
||||
-fx-max-height: 5em;
|
||||
-fx-cursor: hand;
|
||||
|
||||
&:passed>.wsag-label {
|
||||
&:passed>.contrast-level-label {
|
||||
-fx-background-color: $color-wsag-bg-passed;
|
||||
}
|
||||
|
||||
>.wsag-label {
|
||||
>.contrast-level-label {
|
||||
-fx-text-fill: $color-wsag-fg;
|
||||
-fx-background-color: $color-wsag-bg-failed;
|
||||
-fx-background-radius: 6px;
|
||||
|
@ -2,6 +2,7 @@
|
||||
@use "color-palette" as palette;
|
||||
|
||||
.contrast-checker {
|
||||
|
||||
-fx-background-color: -color-contrast-checker-bg;
|
||||
-fx-hgap: 40px;
|
||||
-fx-vgap: 20px;
|
||||
@ -47,11 +48,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
@ -63,10 +59,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.font-box {
|
||||
.ikonli-font-icon {
|
||||
-fx-icon-color: -color-contrast-checker-fg;
|
||||
-fx-fill: -color-contrast-checker-fg;
|
||||
}
|
||||
|
||||
.contrast-ratio {
|
||||
-fx-padding: 0 40px 0 0;
|
||||
|
||||
>.text {
|
||||
>.large-font {
|
||||
-fx-font-size: 4em;
|
||||
}
|
||||
|
||||
@ -75,7 +76,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.wsag-box>*>.wsag-label {
|
||||
.contrast-level {
|
||||
>.state {
|
||||
-fx-padding: 0.5em 1em 0.5em 1em;
|
||||
-fx-background-color: palette.$color-wsag-bg-failed;
|
||||
-fx-background-radius: 4px;
|
||||
@ -90,6 +92,7 @@
|
||||
-fx-icon-color: palette.$color-wsag-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contrast-checker-dialog {
|
||||
|
Loading…
Reference in New Issue
Block a user