UndoFX:Undo/Redo 记录拖动的每个像素

UndoFX: Undo/Redo recording every pixel of drag

我正在使用 UndoFX 和 ReactFX 为我的 2D 形状应用程序实现 Undo/Redo 功能。

问题是当我移动形状时,EventStream 会记录每个 X/Y 像素的移动。我只想记录最后的位置(当用户释放拖动时)。

到目前为止我尝试了什么:

而不是使用 changesOf(rect.xProperty()).map(c -> new xChange(c));changesOf(rect.yProperty()).map(c -> new yChange(c)); 我创建了一个 DoubleProperty x,y,并在释放用户鼠标时将形状 x,y 属性 保存到这些变量中。 最后,我将 changesOf 更改为:changesOf(this.x).map(c -> new xChange(c));changesOf(this.y).map(c -> new yChange(c));

但这没有用,它的行为和以前一样。

....
private class xChange extends RectangleChange<Double> {

    public xChange(Double oldValue, Double newValue) {
        super(oldValue, newValue);
    }
    public xChange(Change<Number> c) {
        super(c.getOldValue().doubleValue(), c.getNewValue().doubleValue());
    }
    @Override void redo() { rect.setX(newValue); }
    @Override xChange invert() { return new xChange(newValue, oldValue); }
    @Override Optional<RectangleChange<?>> mergeWith(RectangleChange<?> other) {
        if(other instanceof xChange) {
            return Optional.of(new xChange(oldValue, ((xChange) other).newValue));
        } else {
            return Optional.empty();
        }
    }

    @Override
    public boolean equals(Object other) {
        if(other instanceof xChange) {
            xChange that = (xChange) other;
            return Objects.equals(this.oldValue, that.oldValue)
                && Objects.equals(this.newValue, that.newValue);
        } else {
            return false;
        }
    }
}

...
    EventStream<xChange> xChanges = changesOf(rect.xProperty()).map(c -> new xChange(c));
    EventStream<yChange> yChanges = changesOf(rect.yProperty()).map(c -> new yChange(c));
    changes = merge(widthChanges, heightChanges, xChanges, yChanges);
    undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(
            changes, // stream of changes to observe
            c -> c.invert(), // function to invert a change
            c -> c.redo(), // function to undo a change
            (c1, c2) -> c1.mergeWith(c2)); // function to merge two changes

您需要将 x 中的更改与 y 中的更改合并。目前,无法合并 x 的变化后跟 y 的变化,因此如果您移动形状使其交替改变 x 和 y(例如,沿对角线移动),那么每个单独的变化都不会与前一个变化合并。

执行此操作的一种方法是生成更改,其旧值和新值是位置,例如由 Point2D 个对象表示。这是一个简单的例子:

import java.util.Objects;
import java.util.Optional;

import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class UndoRectangle extends Application {



    @Override
    public void start(Stage primaryStage) {
        Rectangle rect = new Rectangle(50, 50, 150, 100);
        rect.setFill(Color.CORNFLOWERBLUE);

        EventStream<PositionChange> xChanges = EventStreams.changesOf(rect.xProperty()).map(c -> {
            double oldX = c.getOldValue().doubleValue();
            double newX = c.getNewValue().doubleValue();
            double y = rect.getY();
            return new PositionChange(new Point2D(oldX, y), new Point2D(newX, y));
        });

        EventStream<PositionChange> yChanges = EventStreams.changesOf(rect.yProperty()).map(c -> {
            double oldY = c.getOldValue().doubleValue();
            double newY = c.getNewValue().doubleValue();
            double x = rect.getX();
            return new PositionChange(new Point2D(x, oldY), new Point2D(x, newY));
        });

        SuspendableEventStream<PositionChange> posChanges = EventStreams.merge(xChanges, yChanges)
                .reducible(PositionChange::merge);

        UndoManager undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(posChanges, 
            PositionChange::invert,
            c -> posChanges.suspendWhile(() -> {
                rect.setX(c.getNewPosition().getX());
                rect.setY(c.getNewPosition().getY());
            }),
            (c1, c2) -> Optional.of(c1.merge(c2))
        );

        class MouseLoc { double x, y ; }

        MouseLoc mouseLoc = new MouseLoc();

        rect.setOnMousePressed(e -> {
            mouseLoc.x = e.getSceneX();
            mouseLoc.y = e.getSceneY();
        });

        rect.setOnMouseDragged(e -> {
            rect.setX(rect.getX() + e.getSceneX() - mouseLoc.x);
            rect.setY(rect.getY() + e.getSceneY() - mouseLoc.y);
            mouseLoc.x = e.getSceneX();
            mouseLoc.y = e.getSceneY();
        });

        rect.setOnMouseReleased(e -> undoManager.preventMerge());

        Pane pane = new Pane(rect);

        Button undo = new Button("Undo");
        undo.disableProperty().bind(Bindings.not(undoManager.undoAvailableProperty()));
        undo.setOnAction(e -> undoManager.undo());

        Button redo = new Button("Redo");
        redo.disableProperty().bind(Bindings.not(undoManager.redoAvailableProperty()));
        redo.setOnAction(e -> undoManager.redo());

        HBox buttons = new HBox(5, undo, redo);
        buttons.setAlignment(Pos.CENTER);
        BorderPane.setMargin(buttons, new Insets(5));
        BorderPane root = new BorderPane(pane, null, null, buttons, null);

        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class PositionChange {
        private final Point2D oldPosition ;
        private final Point2D newPosition ;

        public PositionChange(Point2D oldPos, Point2D newPos) {
            this.oldPosition = oldPos ;
            this.newPosition = newPos ;
        }

        public Point2D getOldPosition() {
            return oldPosition;
        }

        public Point2D getNewPosition() {
            return newPosition;
        }

        public PositionChange merge(PositionChange other) {
            return new PositionChange(oldPosition, other.newPosition);
        }

        public PositionChange invert() {
            return new PositionChange(newPosition, oldPosition);
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof PositionChange) {
                PositionChange other = (PositionChange) o ;
                return Objects.equals(oldPosition, other.oldPosition)
                        && Objects.equals(newPosition, other.newPosition);
            } else return false ;
        }

        @Override
        public int hashCode() {
            return Objects.hash(oldPosition, newPosition);
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}

请注意,"undo" 作为 "atomic" 更改实施很重要,因此撤消管理器会在您实施撤消时看到(并忽略)单个更改。这可以通过在撤消期间暂停事件流来实现。