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

dejv.commons.jfx.geometry.CompositeObservableBounds Maven / Gradle / Ivy

package dejv.commons.jfx.geometry;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.List;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A variant of {@link dejv.commons.jfx.geometry.ObservableBounds}, that calculates a union of multiple {@link javafx.geometry.Bounds}.
 *
 * @author dejv78 (dejv78.github.io)
 * @since 1.0.0
 */
public class CompositeObservableBounds
        extends ObservableBounds {

    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeObservableBounds.class);

    private final List> sourceBounds = new ArrayList<>();
    private boolean rounded = false;


    public CompositeObservableBounds() {
        clear();
    }


    public CompositeObservableBounds(ReadOnlyObjectProperty nodeBounds) {
        LOGGER.trace("CompositeObservableBounds(nodeBounds={})", nodeBounds);

        add(nodeBounds);
    }


    /**
     * Decides, whether the resulting bounds should be rounded to nearest "integer" values.
     * (Useful, when sharp visual representation is desired on antialiased canvas)
     * @param rounded
     * @return
     */
    public CompositeObservableBounds setRounded(boolean rounded) {
        LOGGER.trace("setRounded({})", rounded);

        this.rounded = rounded;
        return this;
    }


    @SuppressWarnings("UnusedReturnValue")
    public final CompositeObservableBounds add(ReadOnlyObjectProperty nodeBounds) {
        LOGGER.trace("add({})", nodeBounds);

        requireNonNull(nodeBounds, "nodeBounds is null");

        if (!sourceBounds.contains(nodeBounds)) {
            sourceBounds.add(nodeBounds);

            observedBoundsChanged(nodeBounds, null, nodeBounds.get());
            nodeBounds.addListener(this::observedBoundsChanged);
        }
        return this;
    }


    public final void clear() {
        LOGGER.trace("clear()");

        sourceBounds.stream().forEach((bounds) -> bounds.removeListener(this::observedBoundsChanged));
        sourceBounds.clear();

        minX.set(0);
        minY.set(0);
        minZ.set(0);
        maxX.set(0);
        maxY.set(0);
        maxZ.set(0);
    }


    @Override
    public String toString() {
        return "CompositeObservableBounds: [minX: " + getMinX() + ", minY: " + getMinY() + ", minZ: " + getMinZ() + "]" +
                "[maxX: " + getMaxX() + ", maxY: " + getMaxY() + ", maxZ: " + getMaxZ() + "]";
    }


    private void observedBoundsChanged(ObservableValue observable, Bounds oldValue, Bounds newValue) {
        invalidate();
    }


    private void invalidate() {
        boolean initialized = false;

        for (ReadOnlyObjectProperty property : sourceBounds) {
            Bounds bounds = property.get();

            if (!initialized) {
                minX.set(floorIfNeeded(bounds.getMinX()));
                minY.set(floorIfNeeded(bounds.getMinY()));
                minZ.set(floorIfNeeded(bounds.getMinZ()));
                maxX.set(ceilIfNeeded(bounds.getMaxX()));
                maxY.set(ceilIfNeeded(bounds.getMaxY()));
                maxZ.set(ceilIfNeeded(bounds.getMaxZ()));
                initialized = true;
            } else {
                minX.set(Double.min(minX.get(), floorIfNeeded(bounds.getMinX())));
                minY.set(Double.min(minY.get(), floorIfNeeded(bounds.getMinY())));
                minZ.set(Double.min(minZ.get(), floorIfNeeded(bounds.getMinZ())));
                maxX.set(Double.max(maxX.get(), ceilIfNeeded(bounds.getMaxX())));
                maxY.set(Double.max(maxY.get(), ceilIfNeeded(bounds.getMaxY())));
                maxZ.set(Double.max(maxZ.get(), ceilIfNeeded(bounds.getMaxZ())));
            }
        }
    }


    private double floorIfNeeded(double value) {
        return (rounded) ? Math.floor(value) : value;
    }


    private double ceilIfNeeded(double value) {
        return (rounded) ? Math.ceil(value) : value;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy