2016-05-24 3 views
0

Я использую UndoFX & ReactFX для реализации функции Undo/Redo для моего приложения 2D-формы.UndoFX: Undo/Redo запись каждого пикселя перетаскивания

Проблема в том, когда я перемещаю свою фигуру, EventStream записывает каждый пиксель движения X/Y. Я просто хочу записать последнюю позицию (когда пользователь отпускает перетаскивание).

Что я пытался до сих пор:

Вместо использования changesOf(rect.xProperty()).map(c -> new xChange(c)); и changesOf(rect.yProperty()).map(c -> new yChange(c)); Я создал DoubleProperty x,y и сохранили форму х, свойство у этих переменных при наведении курсора мыши пользователь освобождается. И наконец, я меняю свои измененияOf на: 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 
+0

Я предлагаю _merging_ последующие изменения одного и того же вида (здесь перемещение фигуры) в одно изменение. Это продемонстрировано в [demo] (https://github.com/TomasMikula/UndoFX/#demo) (см. Методы 'mergeWith' в' CenterXChange' и 'CenterYChange'). –

+0

Я просто пробовал это, не работал: (такое же поведение снова!) На самом деле точная проблема заключается в том, что я перемещаю фигуру по горизонтали и по вертикали в одно и то же время. Если я переместил ее точно в одно направление, то это будет нормально работать. – Senpai

+0

whoa, i просто понял, что вы создали UndoFX: D. Спасибо за вашу работу! – Senpai

ответ

2

Вам необходимо объединить изменения в 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); 
    } 
} 

Обратите внимание, что это важно «отменить» реализуются как «атомные» изменения, поэтому менеджер уничтожьте видит (и игнорирует) единственное изменение при реализации отмен. Это может быть достигнуто путем приостановки потока событий во время отмены.

+0

@TomasMikula обычно имеет способы сделать мои решения более элегантными, хотя ... –

+0

Большое вам спасибо, так оно и работает. У меня возникли трудности с включением ширины и высоты EventStream в undoManager. – Senpai

+0

Сделайте то же самое, то есть создайте потоки событий DimensionChange и слияния из свойств ширины и высоты в поток событий из DimensionChange's. (Или , если вы хотите объединить изменения как в позиции, так и в размере, создайте нечто вроде «BoundsChange», которое инкапсулирует x, y, width, и высота.) –