feature:初步实现操作的撤回重做

This commit is contained in:
2025-11-27 09:08:06 +08:00
parent 5fc98d3d9b
commit f88dac9d2c
11 changed files with 433 additions and 8 deletions
+94
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -26,6 +26,11 @@
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
@@ -0,0 +1,25 @@
package dev.bytevibe.hyperpoint;
/**
* 添加对象命令
*/
public class AddObjectCommand implements Command {
private PageContent pageContent;
private DrawableObject object;
public AddObjectCommand(PageContent pageContent, DrawableObject object) {
this.pageContent = pageContent;
this.object = object;
}
@Override
public void execute() {
pageContent.addObject(object);
}
@Override
public void undo() {
pageContent.removeObject(object);
}
}
@@ -0,0 +1,17 @@
package dev.bytevibe.hyperpoint;
/**
* 命令接口,用于撤销和重做功能
*/
public interface Command {
/**
* 执行命令
*/
void execute();
/**
* 撤销命令
*/
void undo();
}
@@ -0,0 +1,91 @@
package dev.bytevibe.hyperpoint;
import java.util.Stack;
/**
* 命令历史管理器,支持撤销和重做
*/
public class CommandHistory {
private Stack<Command> undoStack;
private Stack<Command> redoStack;
private Runnable onHistoryChanged;
public CommandHistory() {
this.undoStack = new Stack<>();
this.redoStack = new Stack<>();
}
/**
* 执行命令并将其添加到历史记录
*/
public void execute(Command command) {
command.execute();
undoStack.push(command);
redoStack.clear(); // 执行新命令后,清空重做栈
notifyHistoryChanged();
}
/**
* 撤销上一个命令
*/
public void undo() {
if (!undoStack.isEmpty()) {
Command command = undoStack.pop();
command.undo();
redoStack.push(command);
notifyHistoryChanged();
}
}
/**
* 重做上一个被撤销的命令
*/
public void redo() {
if (!redoStack.isEmpty()) {
Command command = redoStack.pop();
command.execute();
undoStack.push(command);
notifyHistoryChanged();
}
}
/**
* 检查是否可以撤销
*/
public boolean canUndo() {
return !undoStack.isEmpty();
}
/**
* 检查是否可以重做
*/
public boolean canRedo() {
return !redoStack.isEmpty();
}
/**
* 清空历史记录
*/
public void clear() {
undoStack.clear();
redoStack.clear();
notifyHistoryChanged();
}
/**
* 设置历史记录变化监听器
*/
public void setOnHistoryChanged(Runnable callback) {
this.onHistoryChanged = callback;
}
/**
* 通知历史记录已改变
*/
private void notifyHistoryChanged() {
if (onHistoryChanged != null) {
onHistoryChanged.run();
}
}
}
@@ -26,14 +26,23 @@ public class Controller implements Initializable {
private VBox propertyPanelContainer;
@FXML
private AnchorPane scenePane;
@FXML
private MenuItem undoMenuItem;
@FXML
private MenuItem redoMenuItem;
private Slide currentSlide;
private DrawingCanvas drawingCanvas;
private PropertyPanel propertyPanelComponent;
private CommandHistory commandHistory;
Stage stage;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// 初始化命令历史
commandHistory = new CommandHistory();
commandHistory.setOnHistoryChanged(this::updateUndoRedoButtons);
pageListView.setDisable(true);
// 添加页面列表选择监听
@@ -103,6 +112,7 @@ public class Controller implements Initializable {
// 创建新的Canvas
PageContent pageContent = page.getPageContent();
drawingCanvas = new DrawingCanvas(pageContent);
drawingCanvas.setCommandHistory(commandHistory); // 设置命令历史
drawingCanvasContainer.getChildren().add(drawingCanvas);
// 使用AnchorPane的约束来填充容器
@@ -211,10 +221,18 @@ public class Controller implements Initializable {
String text = dialog.getResult();
if (!text.trim().isEmpty()) {
TextObject textObj = new TextObject(50, 50, text);
// 使用命令历史记录此操作
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), textObj);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(textObj);
}
}
}
}
/**
* 添加直线按钮的事件处理
@@ -226,8 +244,15 @@ public class Controller implements Initializable {
return;
}
ShapeObject line = new ShapeObject(50, 50, 100, 100, ShapeObject.ShapeType.LINE);
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), line);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(line);
}
}
/**
* 添加矩形按钮的事件处理
@@ -239,8 +264,15 @@ public class Controller implements Initializable {
return;
}
ShapeObject rect = new ShapeObject(50, 50, 100, 60, ShapeObject.ShapeType.RECTANGLE);
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), rect);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(rect);
}
}
/**
* 添加圆形按钮的事件处理
@@ -252,8 +284,15 @@ public class Controller implements Initializable {
return;
}
ShapeObject circle = new ShapeObject(50, 50, 100, 100, ShapeObject.ShapeType.CIRCLE);
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), circle);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(circle);
}
}
/**
* 添加椭圆按钮的事件处理
@@ -265,8 +304,15 @@ public class Controller implements Initializable {
return;
}
ShapeObject ellipse = new ShapeObject(50, 50, 150, 80, ShapeObject.ShapeType.ELLIPSE);
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), ellipse);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(ellipse);
}
}
/**
* 添加图片按钮的事件处理
@@ -290,9 +336,16 @@ public class Controller implements Initializable {
if (file != null) {
ImageObject imgObj = new ImageObject(50, 50, 200, 150, file.getAbsolutePath());
SlidePage currentPage = pageListView.getSelectionModel().getSelectedItem();
if (currentPage != null) {
AddObjectCommand command = new AddObjectCommand(currentPage.getPageContent(), imgObj);
commandHistory.execute(command);
drawingCanvas.redraw();
} else {
drawingCanvas.addObject(imgObj);
}
}
}
/**
* 退出按钮的事件处理
@@ -550,6 +603,48 @@ public class Controller implements Initializable {
presentationWindow.show();
}
/**
* 撤销操作
*/
@FXML
public void onUndo(ActionEvent actionEvent) {
commandHistory.undo();
if (drawingCanvas != null) {
drawingCanvas.redraw();
}
}
/**
* 重做操作
*/
@FXML
public void onRedo(ActionEvent actionEvent) {
commandHistory.redo();
if (drawingCanvas != null) {
drawingCanvas.redraw();
}
}
/**
* 更新撤销和重做按钮的状态
*/
private void updateUndoRedoButtons() {
// 确保菜单项不为null
if (undoMenuItem != null) {
undoMenuItem.setDisable(!commandHistory.canUndo());
}
if (redoMenuItem != null) {
redoMenuItem.setDisable(!commandHistory.canRedo());
}
}
/**
* 获取命令历史
*/
public CommandHistory getCommandHistory() {
return commandHistory;
}
/**
* 显示警告对话框
*/
@@ -0,0 +1,27 @@
package dev.bytevibe.hyperpoint;
/**
* 删除对象命令
*/
public class DeleteObjectCommand implements Command {
private PageContent pageContent;
private DrawableObject object;
private int originalIndex;
public DeleteObjectCommand(PageContent pageContent, DrawableObject object) {
this.pageContent = pageContent;
this.object = object;
this.originalIndex = pageContent.getObjectIndex(object);
}
@Override
public void execute() {
pageContent.removeObject(object);
}
@Override
public void undo() {
pageContent.addObjectAt(originalIndex, object);
}
}
@@ -28,6 +28,7 @@ public class DrawingCanvas extends Pane {
private DrawableObject selectedObject;
private double lastX, lastY;
private Runnable onSelectionChanged;
private CommandHistory commandHistory;
// 缩放点有关的属性
private ResizePoint resizePoint = ResizePoint.NONE;
@@ -241,6 +242,13 @@ public class DrawingCanvas extends Pane {
this.onSelectionChanged = callback;
}
/**
* 设置命令历史
*/
public void setCommandHistory(CommandHistory commandHistory) {
this.commandHistory = commandHistory;
}
/**
* 重新绘制画布
*/
@@ -409,4 +417,3 @@ public class DrawingCanvas extends Pane {
redraw();
}
}
@@ -0,0 +1,41 @@
package dev.bytevibe.hyperpoint;
/**
* 修改对象命令
*/
public class ModifyObjectCommand implements Command {
private DrawableObject object;
private double oldX, oldY, oldWidth, oldHeight;
private double newX, newY, newWidth, newHeight;
public ModifyObjectCommand(DrawableObject object,
double oldX, double oldY, double oldWidth, double oldHeight,
double newX, double newY, double newWidth, double newHeight) {
this.object = object;
this.oldX = oldX;
this.oldY = oldY;
this.oldWidth = oldWidth;
this.oldHeight = oldHeight;
this.newX = newX;
this.newY = newY;
this.newWidth = newWidth;
this.newHeight = newHeight;
}
@Override
public void execute() {
object.setX(newX);
object.setY(newY);
object.setWidth(newWidth);
object.setHeight(newHeight);
}
@Override
public void undo() {
object.setX(oldX);
object.setY(oldY);
object.setWidth(oldWidth);
object.setHeight(oldHeight);
}
}
@@ -61,5 +61,20 @@ public class PageContent implements Serializable {
public void clear() {
drawableObjects.clear();
}
/**
* 获取对象在列表中的索引
*/
public int getObjectIndex(DrawableObject object) {
return drawableObjects.indexOf(object);
}
/**
* 在指定索引处添加对象
*/
public void addObjectAt(int index, DrawableObject object) {
if (object != null && index >= 0 && index <= drawableObjects.size()) {
drawableObjects.add(index, object);
}
}
}
@@ -26,6 +26,14 @@
</items>
</MenuButton>
<!-- 编辑菜单 -->
<MenuButton mnemonicParsing="false" text="编辑">
<items>
<MenuItem fx:id="undoMenuItem" mnemonicParsing="false" onAction="#onUndo" text="撤销(Ctrl+Z)" />
<MenuItem fx:id="redoMenuItem" mnemonicParsing="false" onAction="#onRedo" text="重做(Ctrl+Y)" />
</items>
</MenuButton>
<!-- 页面菜单 -->
<MenuButton mnemonicParsing="false" text="页面">
<items>