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

griffon.javafx.scene.layout.NamedCardPane Maven / Gradle / Ivy

/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2008-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package griffon.javafx.scene.layout;

import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import static griffon.util.GriffonClassUtils.requireState;
import static griffon.util.GriffonNameUtils.isBlank;
import static griffon.util.GriffonNameUtils.requireNonBlank;
import static java.util.Objects.requireNonNull;

/**
 * @author Andres Almiray
 * @since 2.11.0
 */
public class NamedCardPane extends StackPane {
    private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
    private static final String ERROR_ID_NULL = "Argument 'id' must not be null";

    private final Map nodes = new ConcurrentHashMap<>();
    private final StringProperty selectedNodeId = new SimpleStringProperty(this, "selectedNodeId");
    private final ObjectProperty selectedNode = new SimpleObjectProperty<>(this, "selectedNode");
    private final AtomicBoolean adjusting = new AtomicBoolean(false);

    public NamedCardPane() {
        getStyleClass().add("named-cardpane");

        getChildren().addListener((ListChangeListener) c -> {
            if (adjusting.get()) {
                return;
            }

            while (c.next()) {
                if (c.wasAdded()) {
                    Node lastAddedNode = null;
                    for (Node node : c.getAddedSubList()) {
                        String id = node.getId();
                        requireNonBlank(id, ERROR_ID_NULL);
                        if (!nodes.containsKey(id)) {
                            nodes.put(id, node);
                            lastAddedNode = node;
                        }
                    }

                    if (lastAddedNode != null) {
                        String nextId = lastAddedNode.getId();
                        doShow(nextId);
                    }

                } else if (c.wasRemoved()) {
                    String selectedId = getSelectedNodeId();
                    boolean selectionChanged = false;
                    for (Node node : c.getRemoved()) {
                        String id = node.getId();
                        requireNonBlank(id, ERROR_ID_NULL);
                        if (!nodes.containsKey(id)) {
                            continue;
                        }

                        nodes.remove(id);

                        if (id.equals(selectedId)) {
                            selectionChanged = true;
                        }
                    }

                    if (selectionChanged) {
                        final String nextId = nodes.size() > 0 ? nodes.keySet().iterator().next() : null;
                        doShow(nextId);
                    }
                }
            }
        });

        widthProperty().addListener((observable, oldValue, newValue) -> updateBoundsInChildren());
        heightProperty().addListener((observable, oldValue, newValue) -> updateBoundsInChildren());
    }

    protected void updateBoundsInChildren() {
        nodes.values().forEach(this::updateChildBounds);
        layout();
    }

    protected void updateChildBounds(Node node) {
        if (node instanceof Region) {
            Region child = (Region) node;
            child.setPrefWidth(getWidth());
            child.setPrefHeight(getHeight());
        }
    }

    @Nonnull
    public ReadOnlyStringProperty selectedNodeIdProperty() {
        return selectedNodeId;
    }

    @Nonnull
    public ReadOnlyObjectProperty selectedNodeProperty() {
        return selectedNode;
    }

    @Nullable
    public String getSelectedNodeId() {
        return selectedNodeId.get();
    }

    @Nullable
    public Node getSelectedNode() {
        return selectedNode.get();
    }

    public boolean isEmpty() {
        return nodes.isEmpty();
    }

    public int size() {
        return nodes.size();
    }

    public void clear() {
        nodes.clear();
        getChildren().clear();
        selectedNodeId.set(null);
        selectedNode.set(null);
    }

    public void add(@Nonnull final String id, @Nonnull final Node node) {
        requireNonNull(node, ERROR_NODE_NULL);
        requireNonBlank(id, ERROR_ID_NULL);

        adjusting.set(true);
        nodes.put(id, node);
        adjusting.set(false);

        show(id);
    }

    public void remove(@Nonnull String id) {
        requireNonBlank(id, ERROR_ID_NULL);
        if (!nodes.containsKey(id)) {
            return;
        }

        String selectedId = getSelectedNodeId();
        adjusting.set(true);
        nodes.remove(id);
        adjusting.set(false);

        if (id.equals(selectedId)) {
            String nextId = null;
            if (nodes.size() > 0) {
                nextId = nodes.keySet().iterator().next();
            }
            doShow(nextId);
        }
    }

    public void show(@Nonnull String id) {
        requireNonBlank(id, ERROR_ID_NULL);
        requireState(nodes.containsKey(id), "No content associated with id '" + id + "'");
        doShow(id);
    }

    protected void doShow(@Nullable String id) {
        Platform.runLater(() -> {
            if (isBlank(id)) {
                adjusting.set(true);
                getChildren().clear();
                adjusting.set(false);
                selectedNodeId.set(null);
                selectedNode.set(null);
            } else {
                Node node = nodes.get(id);
                adjusting.set(true);
                getChildren().setAll(node);
                adjusting.set(false);
                selectedNodeId.set(id);
                selectedNode.set(node);
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy