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

org.jhotdraw8.draw.render.InteractiveHandleRenderer Maven / Gradle / Ivy

The newest version!
/*
 * @(#)InteractiveHandleRenderer.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */

package org.jhotdraw8.draw.render;

import javafx.animation.Transition;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlySetProperty;
import javafx.beans.property.ReadOnlySetWrapper;
import javafx.beans.property.SetProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleSetProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.util.Duration;
import org.jspecify.annotations.Nullable;
import org.jhotdraw8.base.event.Listener;
import org.jhotdraw8.draw.DrawingEditor;
import org.jhotdraw8.draw.DrawingView;
import org.jhotdraw8.draw.figure.Figure;
import org.jhotdraw8.draw.handle.Handle;
import org.jhotdraw8.draw.handle.HandleType;
import org.jhotdraw8.draw.model.DrawingModel;
import org.jhotdraw8.draw.model.SimpleDrawingModel;
import org.jhotdraw8.fxbase.beans.NonNullObjectProperty;
import org.jhotdraw8.fxbase.tree.TreeModelEvent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SequencedMap;
import java.util.Set;

public class InteractiveHandleRenderer {
    private static final String DRAWING_VIEW = "drawingView";
    private final Group handlesPane = new Group();
    private final ObjectProperty drawingView = new SimpleObjectProperty<>(this, DRAWING_VIEW);
    /**
     * This is the set of handles which are out of sync with their JavaFX node.
     */
    private final Set
dirtyHandles = new HashSet<>(); /** * The selectedFiguresProperty holds the list of selected figures in the * sequence they were selected by the user. */ private final SetProperty
selectedFigures = new SimpleSetProperty<>(this, DrawingView.SELECTED_FIGURES_PROPERTY, FXCollections.observableSet(new LinkedHashSet<>())); private final ObjectProperty editor = new SimpleObjectProperty<>(this, DrawingView.EDITOR_PROPERTY, null); /** * Maps each JavaFX node to a handle in the drawing view. */ private final SequencedMap nodeToHandleMap = new LinkedHashMap<>(); private final Listener> treeModelListener = this::onTreeModelEvent; /** * The set of all handles which were produced by selected figures. */ private final SequencedMap> handles = new LinkedHashMap<>(); private final ObservableSet handlesView = FXCollections.observableSet(new LinkedHashSet<>()); /** * Provides a read-only view on the current set of handles. */ private final ReadOnlySetWrapper handlesProperty = new ReadOnlySetWrapper<>(this, "handles", FXCollections.unmodifiableObservableSet(handlesView)); /** * The set of all secondary handles. One handle at a time may create * secondary handles. */ private final ArrayList secondaryHandles = new ArrayList<>(); private final ObjectProperty clipBounds = new SimpleObjectProperty<>(this, "clipBounds", new BoundingBox(0, 0, 800, 600)); private final NonNullObjectProperty model // = new NonNullObjectProperty<>(this, "model", new SimpleDrawingModel()); private boolean recreateHandles; private boolean handlesAreValid; private @Nullable Runnable repainter = null; public InteractiveHandleRenderer() { handlesPane.setManaged(false); handlesPane.setAutoSizeChildren(false); model.addListener(this::onDrawingModelChanged); clipBounds.addListener(this::onClipBoundsChanged); selectedFigures.addListener((SetChangeListener
) change -> recreateHandles()); } private void onDrawingModelChanged(Observable o, @Nullable DrawingModel oldValue, @Nullable DrawingModel newValue) { if (oldValue != null) { oldValue.removeTreeModelListener(treeModelListener); } if (newValue != null) { newValue.addTreeModelListener(treeModelListener); onRootChanged(); } } /** * Creates selection handles and adds them to the provided list. * * @param handles The provided list */ protected void createHandles(Map> handles) { List
selection = new ArrayList<>(getSelectedFigures()); if (selection.size() > 1) { if (getEditor().getAnchorHandleType() != null) { Figure anchor = selection.getFirst(); List list = handles.computeIfAbsent(anchor, k -> new ArrayList<>()); anchor.createHandles(getEditor().getAnchorHandleType(), list); } if (getEditor().getLeadHandleType() != null) { Figure anchor = selection.getLast(); List list = handles.computeIfAbsent(anchor, k -> new ArrayList<>()); anchor.createHandles(getEditor().getLeadHandleType(), list); } } HandleType handleType = getEditor().getHandleType(); if (handleType != null) { ArrayList list = new ArrayList<>(); for (Figure figure : selection) { figure.createHandles(handleType, list); } for (Handle h : list) { Figure figure = h.getOwner(); handles.computeIfAbsent(figure, k -> new ArrayList<>()).add(h); } } } public ObjectProperty editorProperty() { return editor; } @SuppressWarnings("unused") public ObjectProperty drawingViewProperty() { return drawingView; } public @Nullable Handle findHandle(double vx, double vy) { if (recreateHandles) { return null; } final double tolerance = getEditor().getTolerance(); ArrayList> entries = new ArrayList<>(nodeToHandleMap.entrySet()); for (int i = entries.size() - 1; i >= 0; i--) { Map.Entry e = entries.get(i); final Handle handle = e.getValue(); if (!handle.isSelectable()) { continue; } if (handle.contains(getDrawingViewNonNull(), vx, vy, tolerance)) { return handle; } } return null; } private DrawingView getDrawingViewNonNull() { return Objects.requireNonNull(drawingView.get(), "drawingView"); } @Nullable DrawingEditor getEditor() { return editorProperty().get(); } public Set
getFiguresWithCompatibleHandle(Collection
figures, Handle master) { validateHandles(); Map result = new HashMap<>(); for (Map.Entry> entry : handles.entrySet()) { if (figures.contains(entry.getKey())) { for (Handle h : entry.getValue()) { if (h.isCompatible(master)) { result.put(entry.getKey(), null); break; } } } } return result.keySet(); } public Node getNode() { return handlesPane; } ObservableSet
getSelectedFigures() { return selectedFiguresProperty().get(); } public void invalidateHandleNodes() { handlesAreValid = false; dirtyHandles.addAll(handles.keySet()); } public void invalidateHandles() { handlesAreValid = false; } public void revalidateHandles() { invalidateHandles(); repaint(); } public void jiggleHandles() { validateHandles(); List copiedList = handles.values().stream().flatMap(List::stream).toList(); // We scale the handles back and forth. double amount = 0.1; Transition flash = new Transition() { { setCycleDuration(Duration.millis(100)); setCycleCount(2); setAutoReverse(true); } @Override protected void interpolate(double frac) { for (Handle h : copiedList) { Node node = h.getNode(getDrawingViewNonNull()); node.setScaleX(1 + frac * amount); node.setScaleY(1 + frac * amount); } } }; flash.play(); } public Property modelProperty() { return model; } private void onClipBoundsChanged(Observable observable) { invalidateHandles(); repaint(); } private void onFigureRemoved(Figure figure) { invalidateHandles(); } private void onRootChanged() { //clearSelection() // is performed by DrawingView repaint(); } private void onSubtreeNodesChanged(Figure figure) { for (Figure f : figure.preorderIterable()) { dirtyHandles.add(f); } } private void onTreeModelEvent(TreeModelEvent
event) { Figure f = event.getNode(); switch (event.getEventType()) { case NODE_ADDED_TO_PARENT, NODE_REMOVED_FROM_TREE, NODE_ADDED_TO_TREE: break; case NODE_REMOVED_FROM_PARENT: onFigureRemoved(f); break; case NODE_CHANGED: onNodeChanged(f); break; case ROOT_CHANGED: onRootChanged(); break; case SUBTREE_NODES_CHANGED: onSubtreeNodesChanged(f); break; default: throw new UnsupportedOperationException(event.getEventType() + " not supported"); } } private void onNodeChanged(Figure f) { if (selectedFigures.contains(f)) { dirtyHandles.add(f); revalidateHandles(); } } public void recreateHandles() { handlesAreValid = false; recreateHandles = true; repaint(); } public void repaint() { if (repainter == null) { repainter = () -> { repainter = null;// must be set at the beginning, because we may need to repaint again //updateRenderContext(); validateHandles(); }; Platform.runLater(repainter); } } /** * The selected figures. *

* Note: The selection is represented by a {@code SequencedSet} because the * sequence of the selection is important. * * @return a list of the selected figures */ ReadOnlySetProperty

selectedFiguresProperty() { return selectedFigures; } public void setDrawingView(DrawingView newValue) { drawingView.set(newValue); } private void updateHandles() { if (recreateHandles) { for (Map.Entry> entry : handles.entrySet()) { for (Handle h : entry.getValue()) { h.dispose(); } } nodeToHandleMap.clear(); handles.clear(); handlesPane.getChildren().clear(); dirtyHandles.clear(); createHandles(handles); handlesView.clear(); for (List value : handles.values()) { handlesView.addAll(value); } recreateHandles = false; // Bounds visibleRect = getVisibleRect(); for (Map.Entry> entry : handles.entrySet()) { for (Handle handle : entry.getValue()) { Node n = handle.getNode(getDrawingViewNonNull()); if (nodeToHandleMap.put(n, handle) == null) { handlesPane.getChildren().add(n); n.applyCss(); } handle.updateNode(getDrawingViewNonNull()); } } } else { Figure[] copyOfDirtyHandles = dirtyHandles.toArray(new Figure[0]); dirtyHandles.clear(); for (Figure f : copyOfDirtyHandles) { List hh = handles.get(f); if (hh != null) { for (Handle h : hh) { h.updateNode(getDrawingViewNonNull()); } } } } for (Handle h : secondaryHandles) { h.updateNode(getDrawingViewNonNull()); } } /** * Validates the handles. */ private void validateHandles() { // Validate handles only, if they are invalid/*, and if // the DrawingView has a DrawingEditor.*/ if (!handlesAreValid) { handlesAreValid = true; updateHandles(); } } public void setSelectedFigures(ObservableSet
selectedFigures) { this.selectedFigures.set(selectedFigures); } public ReadOnlySetProperty handlesProperty() { return handlesProperty.getReadOnlyProperty(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy