diff --git a/.gitignore b/.gitignore
index 480bdf5..ee1e26b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,4 +36,5 @@ build/
.vscode/
### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
+
diff --git a/README.md b/README.md
deleted file mode 100644
index 1345e45..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-2025秋季 Java大作业
diff --git a/pom.xml b/pom.xml
index 3a97856..2fbbc80 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,18 +12,19 @@
UTF-8
5.12.1
+ 25.0.1
org.openjfx
javafx-controls
- 21.0.6
+ ${javafx.version}
org.openjfx
javafx-fxml
- 21.0.6
+ ${javafx.version}
@@ -40,6 +41,8 @@
+
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Controller.java b/src/main/java/dev/bytevibe/hyperpoint/Controller.java
new file mode 100644
index 0000000..47c9a69
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/Controller.java
@@ -0,0 +1,353 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.*;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class Controller implements Initializable {
+ @FXML
+ private Button openSlideButton;
+ @FXML
+ private Button newPageButton;
+ @FXML
+ private Button deletePageButton;
+ @FXML
+ private Button addTextButton;
+ @FXML
+ private Button addLineButton;
+ @FXML
+ private Button addRectButton;
+ @FXML
+ private Button addCircleButton;
+ @FXML
+ private Button addEllipseButton;
+ @FXML
+ private Button addImageButton;
+ @FXML
+ private Button logout;
+ @FXML
+ private ListView pageListView;
+ @FXML
+ private Label pageNameLabel;
+ @FXML
+ private AnchorPane drawingCanvasContainer;
+ @FXML
+ private VBox propertyPanel;
+ @FXML
+ private AnchorPane scenePane;
+
+ private Slide currentSlide;
+ private DrawingCanvas drawingCanvas;
+ private PropertyPanel propertyPanelComponent;
+ Stage stage;
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ pageListView.setDisable(true);
+ newPageButton.setDisable(true);
+ deletePageButton.setDisable(true);
+ addTextButton.setDisable(true);
+ addLineButton.setDisable(true);
+ addRectButton.setDisable(true);
+ addCircleButton.setDisable(true);
+ addEllipseButton.setDisable(true);
+ addImageButton.setDisable(true);
+
+ // 添加页面列表选择监听
+ pageListView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
+ if (newVal != null) {
+ displayPageContent(newVal);
+ }
+ });
+ }
+
+ /**
+ * 打开幻灯片按钮的事件处理
+ */
+ @FXML
+ public void onOpenSlide(ActionEvent actionEvent) {
+ TextInputDialog dialog = new TextInputDialog("我的幻灯片");
+ dialog.setTitle("打开/创建幻灯片");
+ dialog.setHeaderText("请输入幻灯片名称");
+ dialog.setContentText("幻灯片名称:");
+
+ if (dialog.showAndWait().isPresent()) {
+ String slideName = dialog.getResult();
+ if (!slideName.trim().isEmpty()) {
+ currentSlide = new Slide(slideName);
+ loadSlidePages();
+
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("成功");
+ alert.setHeaderText("幻灯片已打开");
+ alert.setContentText("幻灯片 \"" + slideName + "\" 已打开,共包含 " + currentSlide.getPages().size() + " 个页面。");
+ alert.showAndWait();
+ }
+ }
+ }
+
+ /**
+ * 加载幻灯片的所有页面到列表中
+ */
+ private void loadSlidePages() {
+ if (currentSlide != null) {
+ pageListView.setItems(currentSlide.getPages());
+ pageListView.setDisable(false);
+ newPageButton.setDisable(false);
+ deletePageButton.setDisable(false);
+ addTextButton.setDisable(false);
+ addLineButton.setDisable(false);
+ addRectButton.setDisable(false);
+ addCircleButton.setDisable(false);
+ addEllipseButton.setDisable(false);
+ addImageButton.setDisable(false);
+
+ if (!currentSlide.getPages().isEmpty()) {
+ pageListView.getSelectionModel().selectFirst();
+ }
+ }
+ }
+
+ /**
+ * 显示页面内容
+ */
+ private void displayPageContent(SlidePage page) {
+ if (page != null) {
+ pageNameLabel.setText("页面名称: " + page.getTitle());
+
+ // 移除旧的Canvas
+ drawingCanvasContainer.getChildren().clear();
+ propertyPanel.getChildren().clear();
+
+ // 创建新的Canvas
+ PageContent pageContent = page.getPageContent();
+ drawingCanvas = new DrawingCanvas(pageContent);
+ drawingCanvasContainer.getChildren().add(drawingCanvas);
+
+ // 绑定Canvas大小到容器
+ drawingCanvas.prefWidthProperty().bind(drawingCanvasContainer.widthProperty());
+ drawingCanvas.prefHeightProperty().bind(drawingCanvasContainer.heightProperty());
+
+ // 创建属性面板
+ propertyPanelComponent = new PropertyPanel(drawingCanvas);
+ propertyPanel.getChildren().add(propertyPanelComponent);
+ }
+ }
+
+ /**
+ * 新建页面按钮的事件处理
+ */
+ @FXML
+ public void onNewPage(ActionEvent actionEvent) {
+ if (currentSlide == null) {
+ showWarning("无幻灯片", "请先打开或创建一个幻灯片。");
+ return;
+ }
+
+ TextInputDialog dialog = new TextInputDialog("页面 " + (currentSlide.getPages().size() + 1));
+ dialog.setTitle("新建页面");
+ dialog.setHeaderText("请输入页面名称");
+ dialog.setContentText("页面名称:");
+
+ if (dialog.showAndWait().isPresent()) {
+ String pageName = dialog.getResult();
+ if (!pageName.trim().isEmpty()) {
+ SlidePage newPage = new SlidePage(pageName);
+ currentSlide.addPage(newPage);
+
+ pageListView.refresh();
+ pageListView.getSelectionModel().selectLast();
+
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("成功");
+ alert.setHeaderText("页面已创建");
+ alert.setContentText("新页面 \"" + pageName + "\" 已添加。");
+ alert.showAndWait();
+ }
+ }
+ }
+
+ /**
+ * 删除页面按钮的事件处理
+ */
+ @FXML
+ public void onDeletePage(ActionEvent actionEvent) {
+ if (currentSlide == null || currentSlide.getPages().isEmpty()) {
+ showWarning("无法删除", "没有可删除的页面。");
+ return;
+ }
+
+ int selectedIndex = pageListView.getSelectionModel().getSelectedIndex();
+ if (selectedIndex < 0) {
+ showWarning("请选择页面", "请先选择要删除的页面。");
+ return;
+ }
+
+ Alert confirmAlert = new Alert(Alert.AlertType.CONFIRMATION);
+ confirmAlert.setTitle("确认删除");
+ confirmAlert.setHeaderText("确认删除该页面?");
+ confirmAlert.setContentText("删除后无法恢复,确定继续吗?");
+
+ var result = confirmAlert.showAndWait();
+ if (result.isPresent() && result.get() == ButtonType.OK) {
+ if (currentSlide.removePage(selectedIndex)) {
+ pageListView.refresh();
+
+ if (selectedIndex >= currentSlide.getPages().size()) {
+ pageListView.getSelectionModel().selectLast();
+ } else {
+ pageListView.getSelectionModel().select(selectedIndex);
+ }
+
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("成功");
+ alert.setHeaderText("页面已删除");
+ alert.showAndWait();
+ } else {
+ showWarning("无法删除", "幻灯片至少需要保留一个页面。");
+ }
+ }
+ }
+
+ /**
+ * 添加文本按钮的事件处理
+ */
+ @FXML
+ public void onAddText(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+
+ TextInputDialog dialog = new TextInputDialog("输入文本");
+ dialog.setTitle("添加文本");
+ dialog.setHeaderText("请输入要添加的文本");
+ dialog.setContentText("文本内容:");
+
+ if (dialog.showAndWait().isPresent()) {
+ String text = dialog.getResult();
+ if (!text.trim().isEmpty()) {
+ TextObject textObj = new TextObject(50, 50, text);
+ drawingCanvas.addObject(textObj);
+ }
+ }
+ }
+
+ /**
+ * 添加直线按钮的事件处理
+ */
+ @FXML
+ public void onAddLine(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+ ShapeObject line = new ShapeObject(50, 50, 100, 100, ShapeObject.ShapeType.LINE);
+ drawingCanvas.addObject(line);
+ }
+
+ /**
+ * 添加矩形按钮的事件处理
+ */
+ @FXML
+ public void onAddRectangle(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+ ShapeObject rect = new ShapeObject(50, 50, 100, 60, ShapeObject.ShapeType.RECTANGLE);
+ drawingCanvas.addObject(rect);
+ }
+
+ /**
+ * 添加圆形按钮的事件处理
+ */
+ @FXML
+ public void onAddCircle(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+ ShapeObject circle = new ShapeObject(50, 50, 100, 100, ShapeObject.ShapeType.CIRCLE);
+ drawingCanvas.addObject(circle);
+ }
+
+ /**
+ * 添加椭圆按钮的事件处理
+ */
+ @FXML
+ public void onAddEllipse(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+ ShapeObject ellipse = new ShapeObject(50, 50, 150, 80, ShapeObject.ShapeType.ELLIPSE);
+ drawingCanvas.addObject(ellipse);
+ }
+
+ /**
+ * 添加图片按钮的事件处理
+ */
+ @FXML
+ public void onAddImage(ActionEvent actionEvent) {
+ if (drawingCanvas == null) {
+ showWarning("请先选择页面", "请先选择一个页面。");
+ return;
+ }
+
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle("选择图片");
+ fileChooser.getExtensionFilters().addAll(
+ new FileChooser.ExtensionFilter("图片文件", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp"),
+ new FileChooser.ExtensionFilter("所有文件", "*.*")
+ );
+
+ stage = (Stage) addImageButton.getScene().getWindow();
+ File file = fileChooser.showOpenDialog(stage);
+
+ if (file != null) {
+ ImageObject imgObj = new ImageObject(50, 50, 200, 150, file.getAbsolutePath());
+ drawingCanvas.addObject(imgObj);
+ }
+ }
+
+ /**
+ * 退出按钮的事件处理
+ */
+ @FXML
+ public void logout(ActionEvent actionEvent) {
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle("退出");
+ alert.setHeaderText("您确定要退出吗?");
+ alert.setContentText("点击确定退出,点击取消返回。");
+ ButtonType confirm = new ButtonType("确定", ButtonBar.ButtonData.OK_DONE);
+ ButtonType cancel = new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE);
+ alert.getButtonTypes().setAll(confirm, cancel);
+
+ if (alert.showAndWait().orElse(null) == confirm) {
+ stage = (Stage) logout.getScene().getWindow();
+ stage.close();
+ }
+ }
+
+ /**
+ * 显示警告对话框
+ */
+ private void showWarning(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.WARNING);
+ alert.setTitle(title);
+ alert.setHeaderText(title);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java b/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java
new file mode 100644
index 0000000..ea79310
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java
@@ -0,0 +1,79 @@
+package dev.bytevibe.hyperpoint;
+
+import java.io.Serializable;
+
+/**
+ * 可绘制对象的抽象基类
+ */
+public abstract class DrawableObject implements Serializable {
+ protected double x;
+ protected double y;
+ protected double width;
+ protected double height;
+ protected String id;
+ protected boolean selected;
+
+ public DrawableObject(double x, double y, double width, double height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.id = java.util.UUID.randomUUID().toString();
+ this.selected = false;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public void setWidth(double width) {
+ this.width = width;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ public void setHeight(double height) {
+ this.height = height;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * 检查点是否在对象内
+ */
+ public abstract boolean contains(double px, double py);
+
+ /**
+ * 获取对象类型名称
+ */
+ public abstract String getTypeName();
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/DrawingCanvas.java b/src/main/java/dev/bytevibe/hyperpoint/DrawingCanvas.java
new file mode 100644
index 0000000..ac1fae1
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/DrawingCanvas.java
@@ -0,0 +1,287 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.geometry.Bounds;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.image.Image;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontPosture;
+import javafx.scene.text.FontWeight;
+
+/**
+ * 绘图Canvas组件,用于渲染和交互所有可绘制对象
+ */
+public class DrawingCanvas extends Pane {
+ private Canvas canvas;
+ private PageContent pageContent;
+ private DrawableObject selectedObject;
+ private double lastX, lastY;
+ private Runnable onSelectionChanged;
+
+ public DrawingCanvas(PageContent pageContent) {
+ this.pageContent = pageContent;
+ this.canvas = new Canvas(755, 485);
+
+ getChildren().add(canvas);
+
+ // 设置鼠标事件处理
+ setOnMousePressed(this::handleMousePressed);
+ setOnMouseDragged(this::handleMouseDragged);
+ setOnMouseReleased(this::handleMouseReleased);
+
+ setPrefSize(755, 485);
+ setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1;");
+
+ // 初始绘制
+ redraw();
+ }
+
+ /**
+ * 鼠标按下事件处理
+ */
+ private void handleMousePressed(MouseEvent event) {
+ lastX = event.getX();
+ lastY = event.getY();
+
+ // 查找被点击的对象
+ DrawableObject clicked = pageContent.findObjectAt(lastX, lastY);
+
+ // 如果点击在已选中对象上,保持选中状态
+ if (clicked == null) {
+ setSelectedObject(null);
+ } else {
+ setSelectedObject(clicked);
+ }
+
+ redraw();
+ }
+
+ /**
+ * 鼠标拖动事件处理
+ */
+ private void handleMouseDragged(MouseEvent event) {
+ if (selectedObject != null) {
+ double dx = event.getX() - lastX;
+ double dy = event.getY() - lastY;
+
+ // 更新选中对象的位置
+ selectedObject.setX(selectedObject.getX() + dx);
+ selectedObject.setY(selectedObject.getY() + dy);
+
+ lastX = event.getX();
+ lastY = event.getY();
+
+ redraw();
+ }
+ }
+
+ /**
+ * 鼠标释放事件处理
+ */
+ private void handleMouseReleased(MouseEvent event) {
+ // 可以在这里添加其他逻辑,如取消选中等
+ }
+
+ /**
+ * 添加可绘制对象
+ */
+ public void addObject(DrawableObject object) {
+ pageContent.addObject(object);
+ redraw();
+ }
+
+ /**
+ * 移除可绘制对象
+ */
+ public void removeObject(DrawableObject object) {
+ if (selectedObject == object) {
+ selectedObject = null;
+ }
+ pageContent.removeObject(object);
+ redraw();
+ }
+
+ /**
+ * 设置选中的对象
+ */
+ public void setSelectedObject(DrawableObject object) {
+ selectedObject = object;
+ if (onSelectionChanged != null) {
+ onSelectionChanged.run();
+ }
+ }
+
+ /**
+ * 获取选中的对象
+ */
+ public DrawableObject getSelectedObject() {
+ return selectedObject;
+ }
+
+ /**
+ * 设置选择变化回调
+ */
+ public void setOnSelectionChanged(Runnable callback) {
+ this.onSelectionChanged = callback;
+ }
+
+ /**
+ * 重新绘制画布
+ */
+ public void redraw() {
+ GraphicsContext gc = canvas.getGraphicsContext2D();
+
+ // 清除画布(白色背景)
+ gc.setFill(Color.WHITE);
+ gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
+
+ // 绘制边框
+ gc.setStroke(Color.web("#cccccc"));
+ gc.setLineWidth(1);
+ gc.strokeRect(0, 0, canvas.getWidth(), canvas.getHeight());
+
+ // 绘制所有对象
+ for (DrawableObject obj : pageContent.getDrawableObjects()) {
+ drawObject(gc, obj, obj == selectedObject);
+ }
+ }
+
+ /**
+ * 绘制单个对象
+ */
+ private void drawObject(GraphicsContext gc, DrawableObject obj, boolean selected) {
+ if (obj instanceof TextObject) {
+ drawTextObject(gc, (TextObject) obj, selected);
+ } else if (obj instanceof ShapeObject) {
+ drawShapeObject(gc, (ShapeObject) obj, selected);
+ } else if (obj instanceof ImageObject) {
+ drawImageObject(gc, (ImageObject) obj, selected);
+ }
+ }
+
+ /**
+ * 绘制文本对象
+ */
+ private void drawTextObject(GraphicsContext gc, TextObject textObj, boolean selected) {
+ gc.setFill(textObj.getTextColorAsColor());
+
+ // 设置字体和风格
+ Font font = createFont(textObj.getFontFamily(), textObj.getFontSize(), textObj.getFontStyle());
+ gc.setFont(font);
+
+ // 绘制文本
+ gc.fillText(textObj.getText(), textObj.getX(), textObj.getY() + textObj.getFontSize());
+
+ // 如果选中,绘制边框
+ if (selected) {
+ drawSelectionBorder(gc, textObj);
+ }
+ }
+
+ /**
+ * 创建字体对象
+ */
+ private Font createFont(String family, double size, String style) {
+ FontWeight weight = style.contains("BOLD") ? FontWeight.BOLD : FontWeight.NORMAL;
+ FontPosture posture = style.contains("ITALIC") ? FontPosture.ITALIC : FontPosture.REGULAR;
+ return Font.font(family, weight, posture, size);
+ }
+
+ /**
+ * 绘制形状对象
+ */
+ private void drawShapeObject(GraphicsContext gc, ShapeObject shapeObj, boolean selected) {
+ gc.setFill(shapeObj.getFillColorAsColor());
+ gc.setStroke(shapeObj.getStrokeColorAsColor());
+ gc.setLineWidth(shapeObj.getStrokeWidth());
+
+ switch (shapeObj.getShapeType()) {
+ case RECTANGLE:
+ gc.fillRect(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
+ gc.strokeRect(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
+ break;
+ case CIRCLE:
+ double radius = Math.min(shapeObj.getWidth(), shapeObj.getHeight()) / 2;
+ double centerX = shapeObj.getX() + shapeObj.getWidth() / 2;
+ double centerY = shapeObj.getY() + shapeObj.getHeight() / 2;
+ gc.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
+ gc.strokeOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
+ break;
+ case ELLIPSE:
+ gc.fillOval(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
+ gc.strokeOval(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
+ break;
+ case LINE:
+ gc.strokeLine(shapeObj.getX(), shapeObj.getY(),
+ shapeObj.getX() + shapeObj.getWidth(), shapeObj.getY() + shapeObj.getHeight());
+ break;
+ }
+
+ // 如果选中,绘制边框
+ if (selected) {
+ drawSelectionBorder(gc, shapeObj);
+ }
+ }
+
+ /**
+ * 绘制图片对象
+ */
+ private void drawImageObject(GraphicsContext gc, ImageObject imageObj, boolean selected) {
+ try {
+ Image image = new Image("file:" + imageObj.getImagePath());
+ gc.drawImage(image, imageObj.getX(), imageObj.getY(), imageObj.getWidth(), imageObj.getHeight());
+ } catch (Exception e) {
+ // 如果图片加载失败,绘制占位符
+ gc.setFill(Color.LIGHTGRAY);
+ gc.fillRect(imageObj.getX(), imageObj.getY(), imageObj.getWidth(), imageObj.getHeight());
+ gc.setFill(Color.BLACK);
+ gc.fillText("[Image Not Found]", imageObj.getX() + 5, imageObj.getY() + 15);
+ }
+
+ // 如果选中,绘制边框
+ if (selected) {
+ drawSelectionBorder(gc, imageObj);
+ }
+ }
+
+ /**
+ * 绘制选中对象的边框和句柄
+ */
+ private void drawSelectionBorder(GraphicsContext gc, DrawableObject obj) {
+ gc.setStroke(Color.BLUE);
+ gc.setLineWidth(2);
+ gc.strokeRect(obj.getX() - 2, obj.getY() - 2, obj.getWidth() + 4, obj.getHeight() + 4);
+
+ // 绘制8个调整点
+ drawResizeHandle(gc, obj.getX() - 4, obj.getY() - 4); // 左上
+ drawResizeHandle(gc, obj.getX() + obj.getWidth(), obj.getY() - 4); // 右上
+ drawResizeHandle(gc, obj.getX() - 4, obj.getY() + obj.getHeight()); // 左下
+ drawResizeHandle(gc, obj.getX() + obj.getWidth(), obj.getY() + obj.getHeight()); // 右下
+ }
+
+ /**
+ * 绘制调整点
+ */
+ private void drawResizeHandle(GraphicsContext gc, double x, double y) {
+ gc.setFill(Color.BLUE);
+ gc.fillRect(x, y, 8, 8);
+ gc.setStroke(Color.WHITE);
+ gc.setLineWidth(1);
+ gc.strokeRect(x, y, 8, 8);
+ }
+
+ /**
+ * 调整Canvas大小
+ */
+ public void resize(double width, double height) {
+ canvas.setWidth(width);
+ canvas.setHeight(height);
+ setPrefSize(width, height);
+ redraw();
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/HelloController.java b/src/main/java/dev/bytevibe/hyperpoint/HelloController.java
deleted file mode 100644
index 1edeea4..0000000
--- a/src/main/java/dev/bytevibe/hyperpoint/HelloController.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package dev.bytevibe.hyperpoint;
-
-import javafx.fxml.FXML;
-import javafx.scene.control.Label;
-
-public class HelloController {
- @FXML
- private Label welcomeText;
-
- @FXML
- protected void onHelloButtonClick() {
- welcomeText.setText("Welcome to JavaFX Application!");
- }
-}
diff --git a/src/main/java/dev/bytevibe/hyperpoint/ImageObject.java b/src/main/java/dev/bytevibe/hyperpoint/ImageObject.java
new file mode 100644
index 0000000..baf34ef
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/ImageObject.java
@@ -0,0 +1,32 @@
+package dev.bytevibe.hyperpoint;
+
+/**
+ * 图片对象
+ */
+public class ImageObject extends DrawableObject {
+ private String imagePath; // 图片文件路径
+
+ public ImageObject(double x, double y, double width, double height, String imagePath) {
+ super(x, y, width, height);
+ this.imagePath = imagePath;
+ }
+
+ public String getImagePath() {
+ return imagePath;
+ }
+
+ public void setImagePath(String imagePath) {
+ this.imagePath = imagePath;
+ }
+
+ @Override
+ public boolean contains(double px, double py) {
+ return px >= x && px <= x + width && py >= y && py <= y + height;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "Image";
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Launcher.java b/src/main/java/dev/bytevibe/hyperpoint/Launcher.java
deleted file mode 100644
index 9b18f0a..0000000
--- a/src/main/java/dev/bytevibe/hyperpoint/Launcher.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package dev.bytevibe.hyperpoint;
-
-import javafx.application.Application;
-
-public class Launcher {
- public static void main(String[] args) {
- Application.launch(Main.class, args);
- }
-}
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Main.java b/src/main/java/dev/bytevibe/hyperpoint/Main.java
index dc8b299..5688f1c 100644
--- a/src/main/java/dev/bytevibe/hyperpoint/Main.java
+++ b/src/main/java/dev/bytevibe/hyperpoint/Main.java
@@ -1,35 +1,48 @@
package dev.bytevibe.hyperpoint;
+
+
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
-import javafx.scene.Group;
+import javafx.scene.Parent;
import javafx.scene.Scene;
-import javafx.scene.image.Image;
-import javafx.scene.paint.Color;
-import javafx.scene.text.Text;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonBar;
+import javafx.scene.control.ButtonType;
import javafx.stage.Stage;
-import java.awt.desktop.AppForegroundListener;
-import java.io.IOException;
-
public class Main extends Application {
- public static void main(String[] args) {
- Application.launch(args);
- }
@Override
- public void start(Stage primaryStage) throws Exception {
- Group root = new Group();
- Scene scene = new Scene(root, Color.GRAY);
- primaryStage.setScene(scene);
+ public void start(Stage stage) throws Exception {
+ try {
+ Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
+ Scene scene = new Scene(root, 1200, 600);
+ stage.setScene(scene);
+ stage.show();
- // 设置主要窗口的属性
- Image icon = new Image(getClass().getResourceAsStream("icon.png"));
- primaryStage.getIcons().add(icon);
- primaryStage.setTitle("HyperPoint");
- primaryStage.setWidth(800);
- primaryStage.setHeight(600);
- primaryStage.setResizable(false);
-
- primaryStage.show();
+ stage.setOnCloseRequest(event -> {this.logout(stage);});
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
-}
+
+ // 窗口退出键对应的action
+ public void logout(Stage stage) {
+
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle("退出登录");
+ alert.setHeaderText("您确定要退出登录吗?");
+ alert.setContentText("点击确定退出登录,点击取消返回。");
+ ButtonType confirm = new ButtonType("确定", ButtonBar.ButtonData.OK_DONE);
+ ButtonType cancel = new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE);
+ alert.getButtonTypes().setAll(confirm, cancel);
+
+ if (alert.showAndWait().orElse(null) == confirm) {
+ stage.close();
+ }
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/bytevibe/hyperpoint/PageContent.java b/src/main/java/dev/bytevibe/hyperpoint/PageContent.java
new file mode 100644
index 0000000..015d64a
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/PageContent.java
@@ -0,0 +1,65 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import java.io.Serializable;
+
+/**
+ * 页面内容容器,用于保存页面上的所有可绘制对象
+ */
+public class PageContent implements Serializable {
+ private ObservableList drawableObjects;
+
+ public PageContent() {
+ this.drawableObjects = FXCollections.observableArrayList();
+ }
+
+ public ObservableList getDrawableObjects() {
+ return drawableObjects;
+ }
+
+ /**
+ * 添加可绘制对象
+ */
+ public void addObject(DrawableObject object) {
+ if (object != null) {
+ drawableObjects.add(object);
+ }
+ }
+
+ /**
+ * 移除可绘制对象
+ */
+ public void removeObject(DrawableObject object) {
+ drawableObjects.remove(object);
+ }
+
+ /**
+ * 查找包含指定点的对象(从后到前,越后面的优先级越高)
+ */
+ public DrawableObject findObjectAt(double x, double y) {
+ for (int i = drawableObjects.size() - 1; i >= 0; i--) {
+ DrawableObject obj = drawableObjects.get(i);
+ if (obj.contains(x, y)) {
+ return obj;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 获取对象列表大小
+ */
+ public int size() {
+ return drawableObjects.size();
+ }
+
+ /**
+ * 清空所有对象
+ */
+ public void clear() {
+ drawableObjects.clear();
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Presentation.java b/src/main/java/dev/bytevibe/hyperpoint/Presentation.java
new file mode 100644
index 0000000..f73c373
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/Presentation.java
@@ -0,0 +1,78 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import java.io.Serializable;
+
+/**
+ * 表示整个演示文稿
+ */
+public class Presentation implements Serializable {
+ private String name;
+ private ObservableList slides;
+ private int currentSlideIndex;
+
+ public Presentation() {
+ this.name = "新演示文稿";
+ this.slides = FXCollections.observableArrayList();
+ this.currentSlideIndex = 0;
+ }
+
+ public Presentation(String name) {
+ this.name = name;
+ this.slides = FXCollections.observableArrayList();
+ this.currentSlideIndex = 0;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public ObservableList getSlides() {
+ return slides;
+ }
+
+ public int getCurrentSlideIndex() {
+ return currentSlideIndex;
+ }
+
+ public void setCurrentSlideIndex(int index) {
+ if (index >= 0 && index < slides.size()) {
+ this.currentSlideIndex = index;
+ }
+ }
+
+ public Slide getCurrentSlide() {
+ if (currentSlideIndex >= 0 && currentSlideIndex < slides.size()) {
+ return slides.get(currentSlideIndex);
+ }
+ return null;
+ }
+
+ /**
+ * 添加新幻灯片
+ */
+ public void addSlide(Slide slide) {
+ slides.add(slide);
+ }
+
+ /**
+ * 删除指定索引的幻灯片
+ */
+ public boolean removeSlide(int index) {
+ if (slides.size() > 1 && index >= 0 && index < slides.size()) {
+ slides.remove(index);
+ if (currentSlideIndex >= slides.size()) {
+ currentSlideIndex = slides.size() - 1;
+ }
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/PropertyPanel.java b/src/main/java/dev/bytevibe/hyperpoint/PropertyPanel.java
new file mode 100644
index 0000000..248a962
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/PropertyPanel.java
@@ -0,0 +1,317 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.geometry.Insets;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+
+/**
+ * 属性编辑面板,用于编辑选中对象的属性
+ */
+public class PropertyPanel extends VBox {
+ private DrawingCanvas canvas;
+ private Label objectTypeLabel;
+ private Label positionLabel;
+ private TextField textContentField;
+ private ComboBox fontFamilyCombo;
+ private Spinner fontSizeSpinner;
+ private ComboBox fontStyleCombo;
+ private ColorPicker textColorPicker;
+ private ColorPicker fillColorPicker;
+ private ColorPicker strokeColorPicker;
+ private Spinner strokeWidthSpinner;
+ private Button deleteButton;
+
+ public PropertyPanel(DrawingCanvas canvas) {
+ this.canvas = canvas;
+ setPrefWidth(200);
+ setPadding(new Insets(10));
+ setSpacing(8);
+ setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1 0 0 0;");
+
+ // 对象类型标签
+ objectTypeLabel = new Label("未选中对象");
+ objectTypeLabel.setStyle("-fx-font-size: 14; -fx-font-weight: bold;");
+
+ // 位置标签
+ positionLabel = new Label("X: 0, Y: 0");
+
+ // 文本内容
+ Label textLabel = new Label("文本内容:");
+ textContentField = new TextField();
+ textContentField.setOnKeyReleased(e -> updateTextContent());
+
+ // 字体选择
+ Label fontLabel = new Label("字体:");
+ fontFamilyCombo = new ComboBox<>();
+ fontFamilyCombo.getItems().addAll("Arial", "Times New Roman", "Courier New", "Verdana", "Georgia");
+ fontFamilyCombo.setValue("Arial");
+ fontFamilyCombo.setOnAction(e -> updateTextStyle());
+
+ // 字体大小
+ Label sizeLabel = new Label("大小:");
+ fontSizeSpinner = new Spinner<>(8.0, 72.0, 16.0, 2.0);
+ fontSizeSpinner.setEditable(true);
+ fontSizeSpinner.valueProperty().addListener((obs, oldVal, newVal) -> updateTextStyle());
+
+ // 字体风格
+ Label styleLabel = new Label("风格:");
+ fontStyleCombo = new ComboBox<>();
+ fontStyleCombo.getItems().addAll("NORMAL", "BOLD", "ITALIC", "BOLD_ITALIC");
+ fontStyleCombo.setValue("NORMAL");
+ fontStyleCombo.setOnAction(e -> updateTextStyle());
+
+ // 文本颜色
+ Label textColorLabel = new Label("文本颜色:");
+ textColorPicker = new ColorPicker(Color.BLACK);
+ textColorPicker.setOnAction(e -> updateTextColor());
+
+ // 填充颜色
+ Label fillColorLabel = new Label("填充颜色:");
+ fillColorPicker = new ColorPicker(Color.WHITE);
+ fillColorPicker.setOnAction(e -> updateFillColor());
+
+ // 边框颜色
+ Label strokeColorLabel = new Label("边框颜色:");
+ strokeColorPicker = new ColorPicker(Color.BLACK);
+ strokeColorPicker.setOnAction(e -> updateStrokeColor());
+
+ // 边框宽度
+ Label strokeWidthLabel = new Label("边框宽度:");
+ strokeWidthSpinner = new Spinner<>(1.0, 10.0, 2.0, 1.0);
+ strokeWidthSpinner.setEditable(true);
+ strokeWidthSpinner.valueProperty().addListener((obs, oldVal, newVal) -> updateStrokeWidth());
+
+ // 删除按钮
+ deleteButton = new Button("删除对象");
+ deleteButton.setStyle("-fx-font-size: 12;");
+ deleteButton.setOnAction(e -> deleteSelectedObject());
+
+ // 添加所有控件到面板
+ getChildren().addAll(
+ objectTypeLabel,
+ new Separator(),
+ positionLabel,
+ new Separator(),
+ textLabel,
+ textContentField,
+ fontLabel,
+ fontFamilyCombo,
+ sizeLabel,
+ fontSizeSpinner,
+ styleLabel,
+ fontStyleCombo,
+ textColorLabel,
+ textColorPicker,
+ fillColorLabel,
+ fillColorPicker,
+ strokeColorLabel,
+ strokeColorPicker,
+ strokeWidthLabel,
+ strokeWidthSpinner,
+ new Separator(),
+ deleteButton
+ );
+
+ // 默认隐藏所有编辑控件
+ setEditingVisible(false);
+
+ // 监听canvas的选择变化
+ canvas.setOnSelectionChanged(this::updatePropertyPanel);
+ }
+
+ /**
+ * 更新属性面板显示
+ */
+ private void updatePropertyPanel() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected == null) {
+ setEditingVisible(false);
+ objectTypeLabel.setText("未选中对象");
+ return;
+ }
+
+ objectTypeLabel.setText("选中: " + selected.getTypeName());
+ positionLabel.setText(String.format("X: %.0f, Y: %.0f", selected.getX(), selected.getY()));
+
+ // 清除之前的监听
+ textContentField.setOnKeyReleased(null);
+
+ if (selected instanceof TextObject) {
+ TextObject textObj = (TextObject) selected;
+ textContentField.setText(textObj.getText());
+ fontFamilyCombo.setValue(textObj.getFontFamily());
+ fontSizeSpinner.getValueFactory().setValue(textObj.getFontSize());
+ fontStyleCombo.setValue(textObj.getFontStyle());
+ textColorPicker.setValue(Color.web("#" + textObj.getTextColor()));
+
+ showTextControls();
+ } else if (selected instanceof ShapeObject) {
+ ShapeObject shapeObj = (ShapeObject) selected;
+ fillColorPicker.setValue(Color.web("#" + shapeObj.getFillColor()));
+ strokeColorPicker.setValue(Color.web("#" + shapeObj.getStrokeColor()));
+ strokeWidthSpinner.getValueFactory().setValue(shapeObj.getStrokeWidth());
+
+ showShapeControls();
+ } else if (selected instanceof ImageObject) {
+ showImageControls();
+ }
+
+ // 重新添加监听
+ textContentField.setOnKeyReleased(e -> updateTextContent());
+ }
+
+ /**
+ * 显示文本控件
+ */
+ private void showTextControls() {
+ textContentField.setDisable(false);
+ fontFamilyCombo.setDisable(false);
+ fontSizeSpinner.setDisable(false);
+ fontStyleCombo.setDisable(false);
+ textColorPicker.setDisable(false);
+ fillColorPicker.setDisable(true);
+ strokeColorPicker.setDisable(true);
+ strokeWidthSpinner.setDisable(true);
+ }
+
+ /**
+ * 显示形状控件
+ */
+ private void showShapeControls() {
+ textContentField.setDisable(true);
+ fontFamilyCombo.setDisable(true);
+ fontSizeSpinner.setDisable(true);
+ fontStyleCombo.setDisable(true);
+ textColorPicker.setDisable(true);
+ fillColorPicker.setDisable(false);
+ strokeColorPicker.setDisable(false);
+ strokeWidthSpinner.setDisable(false);
+ }
+
+ /**
+ * 显示图片控件
+ */
+ private void showImageControls() {
+ textContentField.setDisable(true);
+ fontFamilyCombo.setDisable(true);
+ fontSizeSpinner.setDisable(true);
+ fontStyleCombo.setDisable(true);
+ textColorPicker.setDisable(true);
+ fillColorPicker.setDisable(true);
+ strokeColorPicker.setDisable(true);
+ strokeWidthSpinner.setDisable(true);
+ }
+
+ /**
+ * 设置编辑控件的可见性
+ */
+ private void setEditingVisible(boolean visible) {
+ textContentField.setDisable(!visible);
+ fontFamilyCombo.setDisable(!visible);
+ fontSizeSpinner.setDisable(!visible);
+ fontStyleCombo.setDisable(!visible);
+ textColorPicker.setDisable(!visible);
+ fillColorPicker.setDisable(!visible);
+ strokeColorPicker.setDisable(!visible);
+ strokeWidthSpinner.setDisable(!visible);
+ deleteButton.setDisable(!visible);
+ }
+
+ /**
+ * 更新文本内容
+ */
+ private void updateTextContent() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof TextObject) {
+ ((TextObject) selected).setText(textContentField.getText());
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 更新文本风格
+ */
+ private void updateTextStyle() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof TextObject) {
+ TextObject textObj = (TextObject) selected;
+ textObj.setFontFamily(fontFamilyCombo.getValue());
+ textObj.setFontSize(fontSizeSpinner.getValue());
+ textObj.setFontStyle(fontStyleCombo.getValue());
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 更新文本颜色
+ */
+ private void updateTextColor() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof TextObject) {
+ Color color = textColorPicker.getValue();
+ String hexColor = String.format("%02X%02X%02X",
+ (int) (color.getRed() * 255),
+ (int) (color.getGreen() * 255),
+ (int) (color.getBlue() * 255));
+ ((TextObject) selected).setTextColor(hexColor);
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 更新填充颜色
+ */
+ private void updateFillColor() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof ShapeObject) {
+ Color color = fillColorPicker.getValue();
+ String hexColor = String.format("%02X%02X%02X",
+ (int) (color.getRed() * 255),
+ (int) (color.getGreen() * 255),
+ (int) (color.getBlue() * 255));
+ ((ShapeObject) selected).setFillColor(hexColor);
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 更新边框颜色
+ */
+ private void updateStrokeColor() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof ShapeObject) {
+ Color color = strokeColorPicker.getValue();
+ String hexColor = String.format("%02X%02X%02X",
+ (int) (color.getRed() * 255),
+ (int) (color.getGreen() * 255),
+ (int) (color.getBlue() * 255));
+ ((ShapeObject) selected).setStrokeColor(hexColor);
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 更新边框宽度
+ */
+ private void updateStrokeWidth() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected instanceof ShapeObject) {
+ ((ShapeObject) selected).setStrokeWidth(strokeWidthSpinner.getValue());
+ canvas.redraw();
+ }
+ }
+
+ /**
+ * 删除选中的对象
+ */
+ private void deleteSelectedObject() {
+ DrawableObject selected = canvas.getSelectedObject();
+ if (selected != null) {
+ canvas.removeObject(selected);
+ updatePropertyPanel();
+ }
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/ShapeObject.java b/src/main/java/dev/bytevibe/hyperpoint/ShapeObject.java
new file mode 100644
index 0000000..050b732
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/ShapeObject.java
@@ -0,0 +1,117 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.scene.paint.Color;
+
+/**
+ * 形状对象(矩形、圆、椭圆、直线)
+ */
+public class ShapeObject extends DrawableObject {
+ public enum ShapeType {
+ LINE, RECTANGLE, CIRCLE, ELLIPSE
+ }
+
+ private ShapeType shapeType;
+ private String fillColor; // 填充颜色
+ private String strokeColor; // 边框颜色
+ private double strokeWidth;
+
+ public ShapeObject(double x, double y, double width, double height, ShapeType shapeType) {
+ super(x, y, width, height);
+ this.shapeType = shapeType;
+ this.fillColor = "FFFFFF"; // 白色填充
+ this.strokeColor = "000000"; // 黑色边框
+ this.strokeWidth = 2.0;
+ }
+
+ public ShapeType getShapeType() {
+ return shapeType;
+ }
+
+ public void setShapeType(ShapeType shapeType) {
+ this.shapeType = shapeType;
+ }
+
+ public String getFillColor() {
+ return fillColor;
+ }
+
+ public void setFillColor(String fillColor) {
+ this.fillColor = fillColor;
+ }
+
+ public Color getFillColorAsColor() {
+ return Color.web("#" + fillColor);
+ }
+
+ public String getStrokeColor() {
+ return strokeColor;
+ }
+
+ public void setStrokeColor(String strokeColor) {
+ this.strokeColor = strokeColor;
+ }
+
+ public Color getStrokeColorAsColor() {
+ return Color.web("#" + strokeColor);
+ }
+
+ public double getStrokeWidth() {
+ return strokeWidth;
+ }
+
+ public void setStrokeWidth(double strokeWidth) {
+ this.strokeWidth = strokeWidth;
+ }
+
+ @Override
+ public boolean contains(double px, double py) {
+ // 对于直线,使用更宽松的碰撞检测
+ if (shapeType == ShapeType.LINE) {
+ // 直线碰撞检测:点到线段的距离
+ return distanceToLine(px, py) <= 5;
+ }
+ // 其他形状的简单矩形碰撞检测
+ return px >= x && px <= x + width && py >= y && py <= y + height;
+ }
+
+ /**
+ * 计算点到直线的距离
+ */
+ private double distanceToLine(double px, double py) {
+ double x1 = this.x;
+ double y1 = this.y;
+ double x2 = this.x + this.width;
+ double y2 = this.y + this.height;
+
+ double A = py - y1;
+ double B = px - x1;
+ double C = y2 - y1;
+ double D = x2 - x1;
+
+ double dot = A * C + B * D;
+ double lenSq = C * C + D * D;
+ double param = (lenSq != 0) ? dot / lenSq : -1;
+
+ double xx, yy;
+ if (param < 0) {
+ xx = x1;
+ yy = y1;
+ } else if (param > 1) {
+ xx = x2;
+ yy = y2;
+ } else {
+ xx = x1 + param * D;
+ yy = y1 + param * C;
+ }
+
+ double dx = px - xx;
+ double dy = py - yy;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ @Override
+ public String getTypeName() {
+ return "Shape_" + shapeType.name();
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/Slide.java b/src/main/java/dev/bytevibe/hyperpoint/Slide.java
new file mode 100644
index 0000000..50bdd3e
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/Slide.java
@@ -0,0 +1,109 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * 表示一个幻灯片,包含多个页面
+ */
+public class Slide implements Serializable {
+ private String id;
+ private String name;
+ private ObservableList pages;
+ private int currentPageIndex;
+
+ public Slide() {
+ this.id = UUID.randomUUID().toString();
+ this.name = "新幻灯片";
+ this.pages = FXCollections.observableArrayList();
+ this.currentPageIndex = 0;
+ // 创建一个初始页面
+ this.pages.add(new SlidePage("页面 1"));
+ }
+
+ public Slide(String name) {
+ this.id = UUID.randomUUID().toString();
+ this.name = name;
+ this.pages = FXCollections.observableArrayList();
+ this.currentPageIndex = 0;
+ // 创建一个初始页面
+ this.pages.add(new SlidePage("页面 1"));
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public ObservableList getPages() {
+ return pages;
+ }
+
+ public int getCurrentPageIndex() {
+ return currentPageIndex;
+ }
+
+ public void setCurrentPageIndex(int index) {
+ if (index >= 0 && index < pages.size()) {
+ this.currentPageIndex = index;
+ }
+ }
+
+ public SlidePage getCurrentPage() {
+ if (currentPageIndex >= 0 && currentPageIndex < pages.size()) {
+ return pages.get(currentPageIndex);
+ }
+ return null;
+ }
+
+ /**
+ * 添加新页面
+ */
+ public void addPage() {
+ int pageNumber = pages.size() + 1;
+ pages.add(new SlidePage("页面 " + pageNumber));
+ }
+
+ /**
+ * 添加指定的页面
+ */
+ public void addPage(SlidePage page) {
+ if (page != null) {
+ pages.add(page);
+ }
+ }
+
+ /**
+ * 删除指定索引的页面(如果页面数大于1)
+ */
+ public boolean removePage(int index) {
+ if (pages.size() > 1 && index >= 0 && index < pages.size()) {
+ pages.remove(index);
+ if (currentPageIndex >= pages.size()) {
+ currentPageIndex = pages.size() - 1;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
+
diff --git a/src/main/java/dev/bytevibe/hyperpoint/SlidePage.java b/src/main/java/dev/bytevibe/hyperpoint/SlidePage.java
new file mode 100644
index 0000000..072b716
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/SlidePage.java
@@ -0,0 +1,68 @@
+package dev.bytevibe.hyperpoint;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * 表示幻灯片中的单个页面
+ */
+public class SlidePage implements Serializable {
+ private String id;
+ private String title;
+ private String content; // 保留用于向后兼容
+ private PageContent pageContent; // 新增:用于存储绘图对象
+
+ public SlidePage() {
+ this.id = UUID.randomUUID().toString();
+ this.title = "新页面";
+ this.content = "";
+ this.pageContent = new PageContent();
+ }
+
+ public SlidePage(String title) {
+ this.id = UUID.randomUUID().toString();
+ this.title = title;
+ this.content = "";
+ this.pageContent = new PageContent();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public PageContent getPageContent() {
+ if (pageContent == null) {
+ pageContent = new PageContent();
+ }
+ return pageContent;
+ }
+
+ public void setPageContent(PageContent pageContent) {
+ this.pageContent = pageContent;
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+}
diff --git a/src/main/java/dev/bytevibe/hyperpoint/TextObject.java b/src/main/java/dev/bytevibe/hyperpoint/TextObject.java
new file mode 100644
index 0000000..0a33d98
--- /dev/null
+++ b/src/main/java/dev/bytevibe/hyperpoint/TextObject.java
@@ -0,0 +1,78 @@
+package dev.bytevibe.hyperpoint;
+
+import javafx.scene.paint.Color;
+
+/**
+ * 文本对象
+ */
+public class TextObject extends DrawableObject {
+ private String text;
+ private String fontFamily;
+ private double fontSize;
+ private String fontStyle; // NORMAL, ITALIC, BOLD
+ private String textColor;
+
+ public TextObject(double x, double y, String text) {
+ super(x, y, 200, 50);
+ this.text = text;
+ this.fontFamily = "Arial";
+ this.fontSize = 16;
+ this.fontStyle = "NORMAL";
+ this.textColor = "000000"; // 黑色
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public String getFontFamily() {
+ return fontFamily;
+ }
+
+ public void setFontFamily(String fontFamily) {
+ this.fontFamily = fontFamily;
+ }
+
+ public double getFontSize() {
+ return fontSize;
+ }
+
+ public void setFontSize(double fontSize) {
+ this.fontSize = fontSize;
+ }
+
+ public String getFontStyle() {
+ return fontStyle;
+ }
+
+ public void setFontStyle(String fontStyle) {
+ this.fontStyle = fontStyle;
+ }
+
+ public String getTextColor() {
+ return textColor;
+ }
+
+ public void setTextColor(String textColor) {
+ this.textColor = textColor;
+ }
+
+ public Color getTextColorAsColor() {
+ return Color.web("#" + textColor);
+ }
+
+ @Override
+ public boolean contains(double px, double py) {
+ return px >= x && px <= x + width && py >= y && py <= y + height;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "Text";
+ }
+}
+
diff --git a/src/main/resources/dev/bytevibe/hyperpoint/hello-view.fxml b/src/main/resources/dev/bytevibe/hyperpoint/hello-view.fxml
deleted file mode 100644
index 67b5ed9..0000000
--- a/src/main/resources/dev/bytevibe/hyperpoint/hello-view.fxml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/dev/bytevibe/hyperpoint/main.fxml b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml
new file mode 100644
index 0000000..1ce55e7
--- /dev/null
+++ b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+