Javafx UndoFX:撤消/重做记录拖动的每个像素

Javafx UndoFX:撤消/重做记录拖动的每个像素,javafx,reactfx,Javafx,Reactfx,我正在使用UndoFX和ReactFX为我的2D形状应用程序实现撤消/重做功能 问题是,当我移动形状时,EventStream会记录每个X/Y像素的移动。我只想记录最后一个位置(当用户释放拖动时) 到目前为止,我所尝试的: 而不是使用changesOf(rect.xProperty()).map(c->newxchange(c))和 changesOf(rect.yProperty()).map(c->newychange(c)) 我创建了一个DoubleProperty x,y,并在释放用户鼠

我正在使用UndoFX和ReactFX为我的2D形状应用程序实现撤消/重做功能

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

到目前为止,我所尝试的:

而不是使用
changesOf(rect.xProperty()).map(c->newxchange(c))
changesOf(rect.yProperty()).map(c->newychange(c))
我创建了一个
DoubleProperty x,y
,并在释放用户鼠标时将shape x,y属性保存到这些变量中。 最后,我将changesOf更改为:
changesOf(this.x).map(c->newxchange(c))
更改(this.y).map(c->newychange(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
。。。。
私有类xChange扩展了矩形更改{
公共交换(双倍旧值,双倍新值){
超级(旧值、新值);
}
公共交易所(更改c){
超级(c.getOldValue().doubleValue(),c.getNewValue().doubleValue());
}
@重写void redo(){rect.setX(newValue);}
@重写xChange invert(){返回新的xChange(newValue,oldValue);}

@覆盖可选您需要将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);
    }
}
导入java.util.Objects;
导入java.util.Optional;
导入org.fxmisc.undo.UndoManager;
导入org.fxmisc.undo.UndoManagerFactory;
导入org.reactfx.EventStream;
导入org.reactfx.EventStreams;
导入org.reactfx.SuspendableEventStream;
导入javafx.application.application;
导入javafx.beans.binding.Bindings;
导入javafx.geometry.Insets;
导入javafx.geometry.Point2D;
导入javafx.geometry.Pos;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.HBox;
导入javafx.scene.layout.Pane;
导入javafx.scene.paint.Color;
导入javafx.scene.shape.Rectangle;
导入javafx.stage.stage;
公共类扩展应用程序{
@凌驾
公共无效开始(阶段primaryStage){
矩形rect=新矩形(50,50,150,100);
直毛(颜色:矢车菊蓝);
EventStream xChanges=EventStreams.changesOf(rect.xProperty()).map(c->{
double oldX=c.getOldValue().doubleValue();
double newX=c.getNewValue().doubleValue();
双y=rect.getY();
返回新位置更改(新点2D(旧X,y)、新点2D(新X,y));
});
EventStream yChanges=EventStreams.changesOf(rect.yProperty()).map(c->{
double oldY=c.getOldValue().doubleValue();
double newY=c.getNewValue().doubleValue();
double x=rect.getX();
返回新位置更改(新点2D(x,旧)、新点2D(x,新));
});
SuspendableEventStream posChanges=EventStreams.merge(xChanges,yChanges)
.可还原(位置更改::合并);
UndoManager UndoManager=UndoManagerFactory.unlimitedHistoryUndoManager(posChanges,
位置更改::反转,
c->posChanges.suspendWhile(()->{
rect.setX(c.getNewPosition().getX());
rect.setY(c.getNewPosition().getY());
}),
(c1,c2)->可选的(c1.合并(c2))
);
类MouseLoc{double x,y;}
MouseLoc MouseLoc=新的MouseLoc();
矩形设置鼠标按下(e->{
mouseLoc.x=e.getSceneX();
mouseLoc.y=e.getSceneY();
});
rect.setonMouseDrawed(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());
窗格=新窗格(矩形);
按钮撤消=新按钮(“撤消”);
undo.disableProperty().bind(Bindings.not(undoManager.undoAvailableProperty());
undo.setOnAction(e->undoManager.undo());
按钮重做=新按钮(“重做”);
redo.disableProperty().bind(Bindings.not(undoManager.redoavaailableProperty());
redo.setOnAction(e->undoManager.redo());
HBox按钮=新HBox(5,撤消,重做);
按钮。设置对齐(位置中心);
边框窗格。设置边距(按钮、新插图(5));
BorderPane根=新的BorderPane(窗格,null,null,按钮,null);
场景=新场景(root,600600);
初级阶段。场景(场景);
primaryStage.show();
}
公共静态类位置更改{
私人终点位置;
私人终点2D新位置;
公共位置更改(点2D旧位置、点2D新位置){
this.oldPosition=oldPos;
this.newPosition=newPos;
}
公共点2d getOldPosition(){
返回旧位置;
}
公共点2D getNewPosition(){
返回新位置;
}
公共职位变更合并(职位变更其他){
返回新位置更改(旧位置、其他.新位置);
}
公共位置更改反转(){
返回新位置更改(新位置、旧位置);
}
@凌驾
公共布尔等于(对象o){
如果(位置更改的o实例){
位置变更其他=(位置变更)o;
返回Objects.equals(oldPosition,other.oldPositi