From 3b6e5e0916160fb2eb5201941cec22ecce87c866 Mon Sep 17 00:00:00 2001 From: Gary Gan Date: Mon, 24 Nov 2025 01:08:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E7=95=8C=E9=9D=A2,=E7=BC=96=E8=BE=91=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- README.md | 1 - pom.xml | 7 +- .../dev/bytevibe/hyperpoint/Controller.java | 353 ++++++++++++++++++ .../bytevibe/hyperpoint/DrawableObject.java | 79 ++++ .../bytevibe/hyperpoint/DrawingCanvas.java | 287 ++++++++++++++ .../bytevibe/hyperpoint/HelloController.java | 14 - .../dev/bytevibe/hyperpoint/ImageObject.java | 32 ++ .../dev/bytevibe/hyperpoint/Launcher.java | 9 - .../java/dev/bytevibe/hyperpoint/Main.java | 61 +-- .../dev/bytevibe/hyperpoint/PageContent.java | 65 ++++ .../dev/bytevibe/hyperpoint/Presentation.java | 78 ++++ .../bytevibe/hyperpoint/PropertyPanel.java | 317 ++++++++++++++++ .../dev/bytevibe/hyperpoint/ShapeObject.java | 117 ++++++ .../java/dev/bytevibe/hyperpoint/Slide.java | 109 ++++++ .../dev/bytevibe/hyperpoint/SlidePage.java | 68 ++++ .../dev/bytevibe/hyperpoint/TextObject.java | 78 ++++ .../dev/bytevibe/hyperpoint/hello-view.fxml | 16 - .../dev/bytevibe/hyperpoint/main.fxml | 54 +++ 19 files changed, 1681 insertions(+), 67 deletions(-) delete mode 100644 README.md create mode 100644 src/main/java/dev/bytevibe/hyperpoint/Controller.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/DrawingCanvas.java delete mode 100644 src/main/java/dev/bytevibe/hyperpoint/HelloController.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/ImageObject.java delete mode 100644 src/main/java/dev/bytevibe/hyperpoint/Launcher.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/PageContent.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/Presentation.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/PropertyPanel.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/ShapeObject.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/Slide.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/SlidePage.java create mode 100644 src/main/java/dev/bytevibe/hyperpoint/TextObject.java delete mode 100644 src/main/resources/dev/bytevibe/hyperpoint/hello-view.fxml create mode 100644 src/main/resources/dev/bytevibe/hyperpoint/main.fxml 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 @@ - - - - - - - - - - - - -