All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dua3.utility.fx.controls.InputGrid Maven / Gradle / Ivy

The newest version!
package com.dua3.utility.fx.controls;

import com.dua3.cabe.annotations.Nullable;
import com.dua3.utility.fx.FxUtil;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Dimension2D;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;


public class InputGrid extends GridPane {

    /**
     * Logger
     */
    protected static final Logger LOG = LogManager.getLogger(InputGrid.class);

    private static final String MARKER_INITIAL = "";
    private static final String MARKER_ERROR = "⚠";
    private static final String MARKER_OK = "";

    protected final BooleanProperty valid = new SimpleBooleanProperty(false);
    private Collection> data = null;
    private int columns = 1;

    public InputGrid() {
    }

    /**
     * Get valid state property.
     *
     * @return the valid state property of the input
     */
    public ReadOnlyBooleanProperty validProperty() {
        return valid;
    }

    public Map get() {
        Map result = new HashMap<>();
        // Collectors.toMap() does not support null values!
        //noinspection SimplifyForEach
        data.forEach(e -> result.put(e.id, e.control.get()));
        return result;
    }

    void setContent(Collection> data, int columns) {
        this.data = data;
        this.columns = columns;
    }

    public void init() {
        getChildren().clear();

        List> controls = new ArrayList<>();

        // create grid with input controls
        Insets insets = new Insets(2);
        Insets markerInsets = new Insets(0);
        int r = 0, c = 0;
        for (var entry : data) {
            // add label and control
            int gridX = 3 * c;
            int gridY = r;

            int span;
            if (entry.label != null) {
                addToGrid(entry.label, gridX, gridY, 1, insets);
                gridX++;
                span = 1;
            } else {
                span = 2;
            }

            controls.add(entry.control);

            addToGrid(entry.control.node(), gridX, gridY, span, insets);
            gridX += span;

            addToGrid(entry.marker, gridX, gridY, 1, markerInsets);

            entry.control.init();

            // move to next position in grid
            c = (c + 1) % columns;
            if (c == 0) {
                r++;
            }
        }

        // valid state is true if all inputs are valid
        valid.bind(Bindings.createBooleanBinding(
                () -> controls.stream().allMatch(control -> {
                    boolean v = control.isValid();
                    LOG.info("validate: {} -> {}", control, v);
                    return v;
                }),
                controls.stream().flatMap(control -> Stream.of(control.valueProperty(), control.validProperty())).toArray(ObservableValue[]::new)
        ));

        // todo: request focus once to do what?
        for (var entry : data) {
            entry.control.node().requestFocus();
            break;
        }
    }

    private void addToGrid(Node child, int c, int r, int span, Insets insets) {
        add(child, c, r, span, 1);
        setMargin(child, insets);
    }

    public void reset() {
        data.forEach(entry -> entry.control.reset());
    }

    /**
     * Meta data for a single input field consisting of ID, label text, default value etc.
     *
     * @param  the input's value type
     */
    static final class Meta {
        final String id;
        final Class cls;
        final Supplier dflt;
        final InputControl control;
        final Label label;
        final Label marker = new Label();

        Meta(String id, @Nullable String label, Class cls, Supplier dflt, InputControl control) {
            this.id = id;
            this.label = label != null ? new Label(label) : null;
            this.cls = cls;
            this.dflt = dflt;
            this.control = control;

            Dimension2D dimMarker = new Dimension2D(0, 0);
            dimMarker = FxUtil.growToFit(dimMarker, marker.getBoundsInLocal());
            marker.setMinSize(dimMarker.getWidth(), dimMarker.getHeight());
            marker.setText(MARKER_INITIAL);
        }

        void reset() {
            control.set(dflt.get());
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy