Use new ModalPane control for Sampler dialogs

This commit is contained in:
mkpaz 2023-05-26 12:46:51 +04:00
parent e86c95af29
commit 6ef6b5502c
24 changed files with 240 additions and 521 deletions

@ -9,9 +9,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import org.jetbrains.annotations.Nullable;
@ -95,10 +93,18 @@ public class DialogPane extends AnchorPane {
getChildren().add(getChildren().indexOf(closeButton), node);
}
/**
* Manually closes the DialogPane in case one needs to use their
* own close button.
*/
public void close() {
handleClose();
}
protected void createLayout() {
closeButton.getStyleClass().add("close-button");
closeButton.getChildren().setAll(closeButtonIcon);
closeButton.setOnMouseClicked(this::handleClose);
closeButton.setOnMouseClicked(e -> handleClose());
closeButtonIcon.getStyleClass().add("icon");
@ -112,7 +118,7 @@ public class DialogPane extends AnchorPane {
setRightAnchor(closeButton, 10d);
}
protected void handleClose(MouseEvent event) {
protected void handleClose() {
if (modalPane != null) {
modalPane.hide(clearOnClose.get());
} else if (selector != null && getScene() != null) {
@ -125,7 +131,7 @@ public class DialogPane extends AnchorPane {
// call user specified close handler
if (onClose.get() != null) {
onClose.get().handle(event);
onClose.get().run();
}
}
@ -139,18 +145,18 @@ public class DialogPane extends AnchorPane {
* handler will be executed after the default close handler. Therefore, you
* can use it to perform arbitrary actions on dialog close.
*/
protected final ObjectProperty<EventHandler<? super MouseEvent>> onClose =
protected final ObjectProperty<Runnable> onClose =
new SimpleObjectProperty<>(this, "onClose");
public EventHandler<? super MouseEvent> getOnClose() {
public Runnable getOnClose() {
return onClose.get();
}
public ObjectProperty<EventHandler<? super MouseEvent>> onCloseProperty() {
public ObjectProperty<Runnable> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super MouseEvent> onClose) {
public void setOnClose(Runnable onClose) {
this.onClose.set(onClose);
}

@ -2,6 +2,7 @@
package atlantafx.sampler.layout;
import atlantafx.base.controls.ModalPane;
import atlantafx.sampler.util.NodeUtils;
import javafx.geometry.Insets;
import javafx.scene.layout.AnchorPane;
@ -11,16 +12,19 @@ public final class ApplicationWindow extends AnchorPane {
public static final int MIN_WIDTH = 1200;
public static final int SIDEBAR_WIDTH = 250;
public static final String MAIN_MODAL_ID = "modal-pane";
public ApplicationWindow() {
// this is the place to apply user custom CSS,
// one level below the ':root'
var body = new StackPane();
body.getStyleClass().add("body");
body.getChildren().setAll(
new Overlay(),
new MainLayer()
);
var modalPane = new ModalPane();
modalPane.setId(MAIN_MODAL_ID);
body.getChildren().setAll(modalPane, new MainLayer());
NodeUtils.setAnchors(body, Insets.EMPTY);
getChildren().setAll(body);

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import atlantafx.base.controls.Card;
import atlantafx.base.controls.ModalPane;
import atlantafx.base.controls.Spacer;
import atlantafx.base.controls.Tile;
import atlantafx.base.layout.DialogPane;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public abstract class ModalDialog extends DialogPane {
protected Card content = new Card();
protected Tile header = new Tile();
public ModalDialog() {
super("#" + ApplicationWindow.MAIN_MODAL_ID);
createView();
}
public void show(Scene scene) {
var modalPane = (ModalPane) scene.lookup("#" + ApplicationWindow.MAIN_MODAL_ID);
modalPane.show(this);
}
protected void createView() {
content.setHeader(header);
// IMPORTANT: this guarantees client will use correct width and height
setMinWidth(USE_PREF_SIZE);
setMaxWidth(USE_PREF_SIZE);
setMinHeight(USE_PREF_SIZE);
setMaxHeight(USE_PREF_SIZE);
AnchorPane.setTopAnchor(content, 0d);
AnchorPane.setRightAnchor(content, 0d);
AnchorPane.setBottomAnchor(content, 0d);
AnchorPane.setLeftAnchor(content, 0d);
addContent(content);
getStyleClass().add("modal-dialog");
}
protected HBox createDefaultFooter() {
var closeBtn = new Button("Close");
closeBtn.getStyleClass().add("form-action");
closeBtn.setCancelButton(true);
closeBtn.setOnAction(e -> close());
var footer = new HBox(10, new Spacer(), closeBtn);
footer.getStyleClass().add("footer");
footer.setAlignment(Pos.CENTER_RIGHT);
VBox.setVgrow(footer, Priority.NEVER);
return footer;
}
}

@ -1,162 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import atlantafx.base.util.Animations;
import atlantafx.sampler.util.NodeUtils;
import java.util.Objects;
import java.util.function.Consumer;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.event.Event;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
public final class Overlay extends StackPane {
public static final String STYLE_CLASS = "overlay";
private ScrollPane scrollPane;
private AnchorPane edgeContentWrapper;
private StackPane centerContentWrapper;
private final ReadOnlyBooleanWrapper onFrontProperty = new ReadOnlyBooleanWrapper(false);
private final Timeline fadeInTransition = Animations.fadeIn(this, Duration.millis(100));
private final Timeline fadeOutTransition = Animations.fadeOut(this, Duration.millis(200));
private HPos currentContentPos;
public Overlay() {
createView();
}
private void createView() {
edgeContentWrapper = new AnchorPane();
edgeContentWrapper.getStyleClass().add("scrollable-content");
centerContentWrapper = new StackPane();
centerContentWrapper.getStyleClass().add("scrollable-content");
centerContentWrapper.setAlignment(Pos.CENTER);
scrollPane = new ScrollPane();
NodeUtils.setScrollConstraints(scrollPane,
ScrollPane.ScrollBarPolicy.AS_NEEDED, true,
ScrollPane.ScrollBarPolicy.NEVER, true
);
scrollPane.setMaxHeight(10_000); // scroll pane won't work without height specified
// ~
Consumer<Event> hideAndConsume = e -> {
removeContent();
toBack();
e.consume();
};
// hide overlay by pressing ESC (only works when overlay or one of its children has focus,
// that's why we requesting it in the toFront())
addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
hideAndConsume.accept(e);
}
});
// hide overlay by clicking outside content area
setOnMouseClicked(e -> {
Pane content = getContent();
Node eventSource = e.getPickResult().getIntersectedNode();
if (e.getButton() == MouseButton.PRIMARY && content != null
&& !NodeUtils.isDescendant(content, eventSource)) {
hideAndConsume.accept(e);
}
});
getChildren().add(scrollPane);
getStyleClass().add(STYLE_CLASS);
}
public Pane getContent() {
return NodeUtils.getChildByIndex(getContentWrapper(), 0, Pane.class);
}
private Pane getContentWrapper() {
return currentContentPos == HPos.CENTER ? centerContentWrapper : edgeContentWrapper;
}
public void setContent(Pane content, HPos pos) {
Objects.requireNonNull(content);
Objects.requireNonNull(pos);
// clear old content
if (pos != currentContentPos) {
removeContent();
currentContentPos = pos;
}
switch (pos) {
case LEFT -> {
edgeContentWrapper.getChildren().setAll(content);
NodeUtils.setAnchors(content, new Insets(0, -1, 0, 0));
}
case RIGHT -> {
edgeContentWrapper.getChildren().setAll(content);
NodeUtils.setAnchors(content, new Insets(0, 0, 0, -1));
}
case CENTER -> {
centerContentWrapper.getChildren().setAll(content);
StackPane.setAlignment(content, Pos.CENTER);
}
}
scrollPane.setContent(getContentWrapper());
}
public void removeContent() {
getContentWrapper().getChildren().clear();
}
public boolean contains(Pane content) {
return content != null
&& getContentWrapper().getChildren().size() > 0
&& getContentWrapper().getChildren().get(0).equals(content);
}
@Override
public void toFront() {
if (onFrontProperty.get()) {
return;
}
super.toFront();
fadeInTransition.playFromStart();
onFrontProperty.set(true);
}
@Override
public void toBack() {
if (!onFrontProperty.get()) {
return;
}
super.toBack();
fadeOutTransition.playFromStart();
onFrontProperty.set(false);
}
public ReadOnlyBooleanProperty onFrontProperty() {
return onFrontProperty.getReadOnlyProperty();
}
public boolean isOnFront() {
return onFrontProperty().get();
}
}

@ -1,117 +0,0 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.layout;
import static atlantafx.base.theme.Styles.BUTTON_CIRCLE;
import static atlantafx.base.theme.Styles.BUTTON_ICON;
import static atlantafx.base.theme.Styles.FLAT;
import static atlantafx.base.theme.Styles.TITLE_4;
import atlantafx.base.controls.Spacer;
import java.util.Objects;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
public abstract class OverlayDialog<T extends Region> extends VBox {
protected static final int CONTENT_CHILD_INDEX = 1;
protected Label titleLabel;
protected Button topCloseBtn;
protected HBox headerBox;
protected Button bottomCloseBtn;
protected HBox footerBox;
protected Runnable onCloseCallback;
public OverlayDialog() {
createView();
}
protected void createView() {
titleLabel = new Label();
titleLabel.getStyleClass().addAll(TITLE_4, "title");
topCloseBtn = new Button("", new FontIcon(Material2AL.CLOSE));
topCloseBtn.getStyleClass().addAll(BUTTON_ICON, BUTTON_CIRCLE, FLAT, "close-button");
topCloseBtn.setOnAction(e -> close());
headerBox = new HBox(10);
headerBox.getStyleClass().add("header");
headerBox.setAlignment(Pos.CENTER_LEFT);
headerBox.getChildren().setAll(
titleLabel,
new Spacer(),
topCloseBtn
);
VBox.setVgrow(headerBox, Priority.NEVER);
bottomCloseBtn = new Button("Close");
bottomCloseBtn.getStyleClass().add("form-action");
bottomCloseBtn.setOnAction(e -> close());
bottomCloseBtn.setCancelButton(true);
footerBox = new HBox(10);
footerBox.getStyleClass().add("footer");
footerBox.setAlignment(Pos.CENTER_RIGHT);
footerBox.getChildren().setAll(
new Spacer(),
bottomCloseBtn
);
VBox.setVgrow(footerBox, Priority.NEVER);
// IMPORTANT: this guarantees client will use correct width and height
setMinWidth(USE_PREF_SIZE);
setMaxWidth(USE_PREF_SIZE);
setMinHeight(USE_PREF_SIZE);
setMaxHeight(USE_PREF_SIZE);
// if you're updating this line, update setContent() method as well
getChildren().setAll(headerBox, footerBox);
getStyleClass().add("overlay-dialog");
}
protected void setContent(T content) {
Objects.requireNonNull(content);
VBox.setVgrow(content, Priority.ALWAYS);
// content have to be placed exactly between header and footer
if (getChildren().size() == 2) {
// add new content
getChildren().add(CONTENT_CHILD_INDEX, content);
} else if (getChildren().size() == 3) {
// overwrite existing content
getChildren().set(CONTENT_CHILD_INDEX, content);
} else {
throw new UnsupportedOperationException("Content cannot be placed because of unexpected children size. "
+ "You should override 'OverlayDialog#setContent()' and place it manually.");
}
}
protected void setTitle(String title) {
titleLabel.setText(title);
}
public void close() {
if (onCloseCallback != null) {
onCloseCallback.run();
}
}
public Runnable getOnCloseRequest() {
return onCloseCallback;
}
public void setOnCloseRequest(Runnable handler) {
this.onCloseCallback = handler;
}
}

@ -7,7 +7,6 @@ import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.base.theme.Tweaks;
import java.util.function.Consumer;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
@ -22,7 +21,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
final class SearchDialog extends OverlayDialog<VBox> {
final class SearchDialog extends ModalDialog {
private final MainModel model;
@ -35,8 +34,11 @@ final class SearchDialog extends OverlayDialog<VBox> {
this.model = model;
setId("search-dialog");
setTitle("Search");
setContent(createContent());
header.setTitle("Search");
content.setBody(createContent());
content.setFooter(createDefaultFooter());
content.setPrefSize(600, 440);
init();
}
@ -61,11 +63,7 @@ final class SearchDialog extends OverlayDialog<VBox> {
resultList.setCellFactory(c -> new ResultListCell(clickHandler));
VBox.setVgrow(resultList, Priority.ALWAYS);
var content = new VBox(10, searchField, resultList);
content.setPadding(new Insets(10, 20, 10, 20));
content.setPrefSize(600, 440);
return content;
return new VBox(10, searchField, resultList);
}
private void init() {

@ -19,7 +19,6 @@ import atlantafx.sampler.util.Lazy;
import java.net.URI;
import java.util.Objects;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
@ -43,7 +42,6 @@ import org.kordamp.ikonli.material2.Material2OutlinedAL;
final class Sidebar extends VBox {
private final NavTree navTree;
private Overlay overlay;
private final Lazy<SearchDialog> searchDialog;
private final Lazy<ThemeDialog> themeDialog;
@ -56,21 +54,13 @@ final class Sidebar extends VBox {
searchDialog = new Lazy<>(() -> {
var dialog = new SearchDialog(model);
dialog.setOnCloseRequest(() -> {
var overlay = lookupOverlay();
overlay.removeContent();
overlay.toBack();
});
dialog.setClearOnClose(true);
return dialog;
});
themeDialog = new Lazy<>(() -> {
var dialog = new ThemeDialog();
dialog.setOnCloseRequest(() -> {
var overlay = lookupOverlay();
overlay.removeContent();
overlay.toBack();
});
dialog.setClearOnClose(true);
return dialog;
});
@ -129,27 +119,16 @@ final class Sidebar extends VBox {
private void openSearchDialog() {
var dialog = searchDialog.get();
var overlay = lookupOverlay();
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
dialog.show(getScene());
Platform.runLater(dialog::begForFocus);
}
private void openThemeDialog() {
var dialog = themeDialog.get();
var overlay = lookupOverlay();
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
dialog.show(getScene());
Platform.runLater(dialog::requestFocus);
}
private Overlay lookupOverlay() {
return Objects.requireNonNullElse(overlay,
overlay = getScene() != null
&& getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay o ? o : null
);
}
///////////////////////////////////////////////////////////////////////////
private class Header extends VBox {

@ -2,7 +2,6 @@ package atlantafx.sampler.layout;
import atlantafx.sampler.theme.SamplerTheme;
import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.NodeUtils;
import java.util.Objects;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -10,7 +9,7 @@ import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
final class ThemeDialog extends OverlayDialog<VBox> {
final class ThemeDialog extends ModalDialog {
private final TilePane thumbnailsPane = new TilePane(20, 20);
private final ToggleGroup thumbnailsGroup = new ToggleGroup();
@ -19,17 +18,14 @@ final class ThemeDialog extends OverlayDialog<VBox> {
super();
setId("theme-dialog");
setTitle("Select a theme");
setContent(createContent());
NodeUtils.toggleVisibility(footerBox, false);
header.setTitle("Select a theme");
content.setBody(createContent());
content.setFooter(null);
updateThumbnails();
thumbnailsGroup.selectedToggleProperty().addListener((obs, old, val) -> {
System.out.println(0);
System.out.println(val.getUserData().getClass().getName());
if (val != null && val.getUserData() instanceof SamplerTheme theme) {
System.out.println(1);
ThemeManager.getInstance().setTheme(theme);
}
});

@ -6,7 +6,6 @@ import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.layout.Overlay;
import atlantafx.sampler.util.NodeUtils;
import java.net.URI;
import javafx.geometry.Pos;
@ -21,7 +20,6 @@ public abstract class AbstractPage extends StackPane implements Page {
protected final VBox userContent = new VBox();
protected final StackPane userContentArea = new StackPane(userContent);
protected Overlay overlay;
protected boolean isRendered = false;
protected AbstractPage() {
@ -88,7 +86,6 @@ public abstract class AbstractPage extends StackPane implements Page {
// Some properties can only be obtained after node placed
// to the scene graph and here is the place do this.
protected void onRendered() {
this.overlay = lookupOverlay();
}
protected void addPageHeader() {
@ -103,9 +100,4 @@ public abstract class AbstractPage extends StackPane implements Page {
protected void addFormattedText(String text) {
userContent.getChildren().add(BBCodeParser.createFormattedText(text));
}
protected Overlay lookupOverlay() {
return getScene() != null
&& getScene().lookup("." + Overlay.STYLE_CLASS) instanceof Overlay ov ? ov : null;
}
}

@ -6,7 +6,6 @@ import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED;
import static javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.layout.Overlay;
import atlantafx.sampler.util.NodeUtils;
import java.net.URI;
import java.util.LinkedHashSet;
@ -39,8 +38,6 @@ public abstract class OutlinePage extends StackPane implements Page {
protected final VBox userContent = new VBox();
protected final StackPane userContentArea = new StackPane(userContent);
protected final Outline outline = new Outline(createOutlineHandler());
protected Overlay overlay;
protected boolean isRendered = false;
protected OutlinePage() {
@ -200,12 +197,6 @@ public abstract class OutlinePage extends StackPane implements Page {
// Some properties can only be obtained after node placed
// to the scene graph and here is the place do this.
protected void onRendered() {
this.overlay = lookupOverlay();
}
protected Overlay lookupOverlay() {
var scene = getScene();
return scene != null && scene.lookup("." + Overlay.STYLE_CLASS) instanceof Overlay o ? o : null;
}
///////////////////////////////////////////////////////////////////////////

@ -9,7 +9,6 @@ import java.util.function.Consumer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
@ -17,13 +16,15 @@ import javafx.util.Duration;
final class ColorPalette extends GridPane {
private final List<ColorPaletteBlock> blocks = new ArrayList<>();
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final Consumer<ColorPaletteBlock> colorBlockActionHandler;
private final ReadOnlyObjectProperty<Color> bgBaseColor;
public ColorPalette(Consumer<ColorPaletteBlock> actionHandler) {
public ColorPalette(Consumer<ColorPaletteBlock> actionHandler,
ReadOnlyObjectProperty<Color> bgBaseColor) {
super();
this.colorBlockActionHandler = Objects.requireNonNull(actionHandler, "actionHandler");
this.bgBaseColor = bgBaseColor;
add(colorBlock("-color-fg-default", "-color-bg-default", "-color-border-default"), 0, 0);
add(colorBlock("-color-fg-default", "-color-bg-overlay", "-color-border-default"), 1, 0);
@ -59,7 +60,7 @@ final class ColorPalette extends GridPane {
}
private ColorPaletteBlock colorBlock(String fgColor, String bgColor, String borderColor) {
var block = new ColorPaletteBlock(fgColor, bgColor, borderColor, bgBaseColor.getReadOnlyProperty());
var block = new ColorPaletteBlock(fgColor, bgColor, borderColor, bgBaseColor);
block.setOnAction(colorBlockActionHandler);
blocks.add(block);
return block;
@ -73,8 +74,4 @@ final class ColorPalette extends GridPane {
t.setOnFinished(e -> blocks.forEach(ColorPaletteBlock::update));
t.play();
}
public ReadOnlyObjectProperty<Color> bgBaseColorProperty() {
return bgBaseColor.getReadOnlyProperty();
}
}

@ -6,31 +6,26 @@ import java.util.Arrays;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
final class ColorScale extends FlowPane {
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final List<ColorScaleBlock> blocks = Arrays.asList(
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-base-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-accent-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-success-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-warning-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-danger-", 10),
ColorScaleBlock.forColorName(bgBaseColor, "-color-dark", "-color-light")
);
private final List<ColorScaleBlock> blocks;
public ColorScale() {
public ColorScale(ReadOnlyObjectProperty<Color> bgBaseColor) {
super();
backgroundProperty().addListener((obs, old, val) -> bgBaseColor.set(
val != null && !val.getFills().isEmpty()
? (Color) val.getFills().get(0).getFill()
: Color.WHITE
));
blocks = Arrays.asList(
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-base-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-accent-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-success-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-warning-", 10),
ColorScaleBlock.forColorPrefix(bgBaseColor, "-color-danger-", 10),
ColorScaleBlock.forColorName(bgBaseColor, "-color-dark", "-color-light")
);
setId("color-scale");
getChildren().setAll(blocks);

@ -77,7 +77,11 @@ final class ContrastChecker extends GridPane {
this.bgBaseColor = bgBaseColor;
this.contrastRatio = Bindings.createDoubleBinding(
() -> getContrastRatioOpacityAware(bgColor.getColor(), fgColor.getColor(), bgBaseColor.get()),
() -> getContrastRatioOpacityAware(
bgColor.getColor(),
fgColor.getColor(),
bgBaseColor.get()
),
bgColor.colorProperty(),
fgColor.colorProperty(),
bgBaseColor
@ -97,6 +101,10 @@ final class ContrastChecker extends GridPane {
setForeground(fgColor);
}
public Color getBgBaseColor() {
return bgBaseColor.get();
}
public String getBgColorName() {
return bgColorName;
}
@ -109,8 +117,9 @@ final class ContrastChecker extends GridPane {
return bgColor.colorProperty().get();
}
public Color getFgColor() {
return fgColor.colorProperty().get();
public Color getFlatBgColor() {
double[] flatBg = JColorUtils.flattenColor(getBgBaseColor(), getBgColor());
return Color.color(flatBg[0], flatBg[1], flatBg[2]);
}
public ReadOnlyObjectProperty<Color> bgColorProperty() {
@ -478,7 +487,7 @@ final class ContrastChecker extends GridPane {
}
public float[] getRgbaArithmeticColor() {
float[] hsl = new float[] {getHue(), getSaturation(), getLightness()};
float[] hsl = new float[] { getHue(), getSaturation(), getLightness() };
var color = JColor.color(hsl, getAlpha());
return new float[] {
color.getRedArithmetic(),
@ -489,7 +498,7 @@ final class ContrastChecker extends GridPane {
}
public String getColorHexWithAlpha() {
float[] hsl = new float[] {getHue(), getSaturation(), getLightness()};
float[] hsl = new float[] { getHue(), getSaturation(), getLightness() };
return JColor.color(hsl, getAlpha()).getColorHexWithAlpha();
}
}
@ -510,7 +519,7 @@ final class ContrastChecker extends GridPane {
var hexItem = new MenuItem("Copy as HEX");
hexItem.setOnAction(e -> {
var c = JColor.color(
new float[] {color.getHue(), color.getSaturation(), color.getLightness(), color.getAlpha()});
new float[] { color.getHue(), color.getSaturation(), color.getLightness(), color.getAlpha() });
PlatformUtils.copyToClipboard(color.getAlpha() < 1
? toHexWithAlpha(color.getColor()) : c.getColorHex()
);
@ -519,7 +528,7 @@ final class ContrastChecker extends GridPane {
var rgbItem = new MenuItem("Copy as RGB");
rgbItem.setOnAction(e -> {
var c = JColor.color(
new float[] {color.getHue(), color.getSaturation(), color.getLightness(), color.getAlpha()});
new float[] { color.getHue(), color.getSaturation(), color.getLightness(), color.getAlpha() });
PlatformUtils.copyToClipboard(color.getAlpha() < 1
? String.format(
"rgba(%d,%d,%d, %.1f)", c.getGreen(), c.getGreen(), c.getBlue(), c.getAlphaArithmetic()

@ -2,31 +2,32 @@
package atlantafx.sampler.page.general;
import atlantafx.sampler.layout.OverlayDialog;
import atlantafx.sampler.layout.ModalDialog;
import atlantafx.sampler.util.JColorUtils;
import atlantafx.sampler.util.NodeUtils;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.paint.Color;
class ContrastCheckerDialog extends OverlayDialog<ContrastChecker> {
class ContrastCheckerDialog extends ModalDialog {
private final ContrastChecker contrastChecker;
public ContrastCheckerDialog(ReadOnlyObjectProperty<Color> bgBaseColor) {
super();
this.contrastChecker = new ContrastChecker(bgBaseColor);
contrastChecker.bgColorProperty().addListener((obs, old, val) -> updateStyle());
contrastChecker.fgColorProperty().addListener((obs, old, val) -> updateStyle());
getStyleClass().add("contrast-checker-dialog");
setTitle("Contrast Checker");
setContent(contrastChecker);
NodeUtils.toggleVisibility(footerBox, false);
header.setTitle("Contrast Checker");
content.setBody(contrastChecker);
content.setFooter(null);
}
private void updateStyle() {
setStyle(String.format("-color-dialog-bg:%s;-color-dialog-fg:%s;",
JColorUtils.toHexWithAlpha(contrastChecker.getBgColor()),
setStyle(String.format("-color-contrast-checker-bg:%s;-color-contrast-checker-fg:%s;",
JColorUtils.toHexWithAlpha(contrastChecker.getFlatBgColor()),
JColorUtils.toHexWithAlpha(contrastChecker.getSafeFgColor())
));
}

@ -11,7 +11,7 @@ import atlantafx.base.theme.Styles;
import atlantafx.sampler.Resources;
import atlantafx.sampler.event.BrowseEvent;
import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.layout.OverlayDialog;
import atlantafx.sampler.layout.ModalDialog;
import atlantafx.sampler.page.general.SceneBuilderDialogModel.Screen;
import atlantafx.sampler.util.NodeUtils;
import java.io.File;
@ -46,7 +46,7 @@ import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
class SceneBuilderDialog extends OverlayDialog<DeckPane> {
class SceneBuilderDialog extends ModalDialog {
private final DeckPane deck;
private final Button backBtn;
@ -62,6 +62,8 @@ class SceneBuilderDialog extends OverlayDialog<DeckPane> {
private final SceneBuilderDialogModel model = new SceneBuilderDialogModel();
public SceneBuilderDialog() {
super();
deck = createContent();
backBtn = new Button("Previous", new FontIcon(Material2AL.ARROW_BACK));
@ -72,10 +74,17 @@ class SceneBuilderDialog extends OverlayDialog<DeckPane> {
closeBtn = new Button("Close");
NodeUtils.toggleVisibility(closeBtn, false);
footerBox.getChildren().setAll(backBtn, new Spacer(), forwardBtn, closeBtn);
var footer = new HBox(10);
footer.getChildren().setAll(backBtn, new Spacer(), forwardBtn, closeBtn);
footer.getStyleClass().add("footer");
footer.setAlignment(Pos.CENTER_RIGHT);
VBox.setVgrow(footer, Priority.NEVER);
header.setTitle("SceneBuilder Integration");
content.setBody(deck);
content.setFooter(footer);
content.setPrefSize(600, 440);
setTitle("SceneBuilder Integration");
setContent(deck);
init();
}
@ -91,8 +100,6 @@ class SceneBuilderDialog extends OverlayDialog<DeckPane> {
deck.setAnimationDuration(Duration.millis(250));
deck.setId("scene-builder-wizard");
deck.setPrefSize(600, 440);
return deck;
}

@ -3,6 +3,7 @@
package atlantafx.sampler.page.general;
import static atlantafx.sampler.event.ThemeEvent.EventType;
import static atlantafx.sampler.theme.ThemeManager.DEFAULT_FONT_SIZE;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.BBCodeParser;
@ -15,10 +16,8 @@ import atlantafx.sampler.theme.ThemeManager;
import atlantafx.sampler.util.Lazy;
import java.net.URI;
import java.util.Objects;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.geometry.HPos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
@ -72,11 +71,12 @@ public final class ThemePage extends OutlinePage {
///////////////////////////////////////////////////////////////////////////
private final ReadOnlyObjectWrapper<Color> bgBaseColor = new ReadOnlyObjectWrapper<>(Color.WHITE);
private final Lazy<ThemeRepoManagerDialog> themeRepoManagerDialog;
private final Lazy<ContrastCheckerDialog> contrastCheckerDialog;
private final Lazy<SceneBuilderDialog> sceneBuilderDialog;
private final ColorPalette colorPalette;
private final ColorScale colorScale = new ColorScale();
private final ColorScale colorScale = new ColorScale(bgBaseColor);
private final ChoiceBox<SamplerTheme> themeSelector = createThemeSelector();
private final ComboBox<String> fontFamilyChooser = createFontFamilyChooser();
private final Spinner<Integer> fontSizeSpinner = createFontSizeSpinner();
@ -84,48 +84,35 @@ public final class ThemePage extends OutlinePage {
public ThemePage() {
super();
themeRepoManagerDialog = new Lazy<>(() -> {
var dialog = new ThemeRepoManagerDialog();
dialog.setClearOnClose(true);
return dialog;
});
contrastCheckerDialog = new Lazy<>(() -> {
var dialog = new ContrastCheckerDialog(bgBaseColor);
dialog.setClearOnClose(true);
return dialog;
});
sceneBuilderDialog = new Lazy<>(() -> {
var dialog = new SceneBuilderDialog();
dialog.setClearOnClose(true);
dialog.setOnClose(dialog::reset);
return dialog;
});
colorPalette = new ColorPalette(colorBlock -> {
ContrastCheckerDialog dialog = getContrastCheckerDialog();
ContrastCheckerDialog dialog = contrastCheckerDialog.get();
dialog.getContent().setValues(
colorBlock.getFgColorName(),
colorBlock.getFgColor(),
colorBlock.getBgColorName(),
colorBlock.getBgColor()
);
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
});
themeRepoManagerDialog = new Lazy<>(() -> {
var dialog = new ThemeRepoManagerDialog();
dialog.setOnCloseRequest(() -> {
overlay.removeContent();
overlay.toBack();
});
return dialog;
});
contrastCheckerDialog = new Lazy<>(() -> {
var dialog = new ContrastCheckerDialog(getBgBaseColorProperty());
dialog.setOnCloseRequest(() -> {
overlay.removeContent();
overlay.toBack();
});
return dialog;
});
sceneBuilderDialog = new Lazy<>(() -> {
var dialog = new SceneBuilderDialog();
dialog.setOnCloseRequest(() -> {
overlay.removeContent();
overlay.toBack();
dialog.reset();
});
return dialog;
});
dialog.show(getScene());
}, bgBaseColor);
DefaultEventBus.getInstance().subscribe(ThemeEvent.class, e -> {
var eventType = e.getEventType();
@ -136,16 +123,26 @@ public final class ThemePage extends OutlinePage {
if (eventType == EventType.THEME_CHANGE || eventType == EventType.COLOR_CHANGE) {
colorPalette.updateColorInfo(Duration.seconds(1));
colorScale.updateColorInfo(Duration.seconds(1));
fontFamilyChooser.getSelectionModel().select(DEFAULT_FONT_ID);
fontSizeSpinner.getValueFactory().setValue(DEFAULT_FONT_SIZE);
}
});
// mandatory base bg for flatten color calc
Styles.appendStyle(this, "-fx-background-color", "-color-bg-default");
backgroundProperty().addListener(
(obs, old, val) -> bgBaseColor.set(val != null && !val.getFills().isEmpty()
? (Color) val.getFills().get(0).getFill()
: Color.WHITE
));
addPageHeader();
addNode(createThemeManagementSection());
addSection("Scene Builder", createSceneBuilderSection());
addSection("Color Palette", createColorPaletteSection());
addSection("Color Scale", createColorScaleSection());
Platform.runLater(this::selectCurrentTheme);
//Platform.runLater(this::selectCurrentTheme);
}
@Override
@ -161,9 +158,8 @@ public final class ThemePage extends OutlinePage {
themeRepoBtn.setTooltip(new Tooltip("Settings"));
themeRepoBtn.setOnAction(e -> {
ThemeRepoManagerDialog dialog = themeRepoManagerDialog.get();
overlay.setContent(dialog, HPos.CENTER);
dialog.getContent().update();
overlay.toFront();
dialog.show(getScene());
});
var accentSelector = new AccentColorSelector();
@ -185,8 +181,7 @@ public final class ThemePage extends OutlinePage {
sceneBuilderBtn.setGraphic(new ImageView(SCENE_BUILDER_ICON));
sceneBuilderBtn.setOnAction(e -> {
SceneBuilderDialog dialog = sceneBuilderDialog.get();
overlay.setContent(dialog, HPos.CENTER);
overlay.toFront();
dialog.show(getScene());
});
var description = BBCodeParser.createFormattedText("""
@ -231,7 +226,18 @@ public final class ThemePage extends OutlinePage {
private ChoiceBox<SamplerTheme> createThemeSelector() {
var choiceBox = new ChoiceBox<SamplerTheme>();
choiceBox.getItems().setAll(TM.getRepository().getAll());
var themes = TM.getRepository().getAll();
choiceBox.getItems().setAll(themes);
// set initial value
var currentTheme = Objects.requireNonNullElse(TM.getTheme(), TM.getDefaultTheme());
themes.stream()
.filter(t -> Objects.equals(currentTheme.getName(), t.getName()))
.findFirst()
.ifPresent(t -> choiceBox.getSelectionModel().select(t));
// must be after setting the initial value
choiceBox.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
if (val != null && getScene() != null) {
TM.setTheme(val);
@ -308,12 +314,4 @@ public final class ThemePage extends OutlinePage {
.ifPresent(t -> themeSelector.getSelectionModel().select(t));
}
}
private ContrastCheckerDialog getContrastCheckerDialog() {
return contrastCheckerDialog.get();
}
private ReadOnlyObjectProperty<Color> getBgBaseColorProperty() {
return colorPalette.bgBaseColorProperty();
}
}

@ -3,25 +3,22 @@
package atlantafx.sampler.page.general;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.layout.OverlayDialog;
import atlantafx.sampler.layout.ModalDialog;
import java.io.File;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2MZ;
class ThemeRepoManagerDialog extends OverlayDialog<ThemeRepoManager> {
class ThemeRepoManagerDialog extends ModalDialog {
private final ThemeRepoManager repoManager = new ThemeRepoManager();
public ThemeRepoManagerDialog() {
setId("theme-repo-manager-dialog");
setTitle("Theme Manager");
setContent(repoManager);
super();
var addBtn = new Button("Add", new FontIcon(Material2MZ.PLUS));
var addBtn = new Button("Add custom theme", new FontIcon(Material2MZ.PLUS));
addBtn.getStyleClass().add(Styles.ACCENT);
addBtn.setOnAction(e -> {
var fileChooser = new FileChooser();
@ -32,8 +29,15 @@ class ThemeRepoManagerDialog extends OverlayDialog<ThemeRepoManager> {
}
});
footerBox.getChildren().add(0, addBtn);
footerBox.setAlignment(Pos.CENTER_LEFT);
setId("theme-repo-manager-dialog");
header.setTitle("Theme Manager");
content.setBody(repoManager);
content.setMinSize(800, 500);
content.setMaxSize(800, 500);
var footer = createDefaultFooter();
footer.getChildren().add(0, addBtn);
content.setFooter(footer);
}
public ThemeRepoManager getContent() {

@ -26,7 +26,6 @@ record MediaFile(File file) {
// is costly and that instance is not even reusable.
public void readMetadata(Consumer<Metadata> callback) {
var media = new Media(file.toURI().toString());
System.out.println(file.toURI().toString());
var mediaPlayer = new MediaPlayer(media);
// The media information is obtained asynchronously and so not necessarily
@ -76,4 +75,4 @@ record MediaFile(File file) {
static final String NO_ARTIST = "Unknown artist";
static final String NO_ALBUM = "Unknown album";
}
}
}

@ -4,4 +4,3 @@
@use "root";
@use "main";
@use "sidebar";
@use "overlay";

@ -1,33 +0,0 @@
// SPDX-License-Identifier: MIT
.overlay {
-fx-background-color: transparent;
>.scroll-pane {
>.viewport {
>* {
>.scrollable-content {
-fx-background-color: rgba(0, 0, 0, 0.5);
}
}
}
}
}
.overlay-dialog {
-fx-background-color: -color-bg-default;
-fx-background-radius: 5px;
-fx-border-radius: 5px;
-fx-border-width: 1px;
-fx-border-color: -color-border-default;
>.header {
-fx-padding: 10px 5px 5px 20px;
}
>.footer {
-fx-border-width: 1 0 0 0;
-fx-border-color: -color-border-default;
-fx-padding: 10px 20px 10px 20px;
}
}

@ -22,7 +22,6 @@
}
.page {
>.scroll-pane .user-content {
-fx-padding: 0;
@ -67,4 +66,13 @@
@include ac.primerCoralDark();
}
}
.modal-dialog {
-fx-background-color: transparent;
.card {
-fx-border-width: 0;
-fx-padding: 10px 20px 10px 20px;
}
}
}

@ -4,9 +4,6 @@
-fx-hgap: 20px;
-fx-vgap: 20px;
// mandatory base bg for flatten color calc
-fx-background-color: -color-bg-default;
>.column {
>.cell {
-fx-text-fill: -color-fg-default;

@ -4,10 +4,8 @@
.contrast-checker {
-fx-background-color: -color-contrast-checker-bg;
-fx-hgap: 40px;
-fx-vgap: 20px;
-fx-padding: 20px;
.label {
-fx-text-fill: -color-contrast-checker-fg;
@ -118,20 +116,17 @@
-color-dialog-bg: white;
-color-dialog-fg: black;
>.header {
-fx-background-color: -color-dialog-bg;
-color-dialog-pane-bg: -color-contrast-checker-bg;
-color-dialog-pane-close-fg: -color-contrast-checker-fg;
-color-dialog-pane-close-bg-hover: derive(-color-contrast-checker-bg, 30%);
>.title {
-fx-text-fill: -color-dialog-fg;
}
-fx-background-color: transparent;
>.close-button {
-color-button-fg: -color-dialog-fg;
-color-button-bg-hover: derive(-color-dialog-bg, 30%);
-color-button-border-hover: transparent;
-color-button-border-focused: transparent;
.card {
-fx-background-color: -color-contrast-checker-bg;
.title {
-fx-text-fill: -color-contrast-checker-fg;
}
}
-fx-border-color: -color-dialog-bg;
}

@ -3,7 +3,6 @@
@use "color-palette" as palette;
#theme-repo-manager {
-fx-padding: 10px 20px 10px 20px;
-fx-spacing: 10px;
>.info {
@ -70,10 +69,3 @@
}
}
}
#theme-repo-manager-dialog {
-fx-min-width: 800px;
-fx-pref-width: 800px;
-fx-min-height: 500px;
-fx-max-height: 500px;
}