初步实现创建界面,编辑界面

This commit is contained in:
2025-11-24 01:08:18 +08:00
parent 3d160c00f1
commit 3b6e5e0916
19 changed files with 1681 additions and 67 deletions
+2 -1
View File
@@ -36,4 +36,5 @@ build/
.vscode/ .vscode/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
-1
View File
@@ -1 +0,0 @@
2025秋季 Java大作业
+5 -2
View File
@@ -12,18 +12,19 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.12.1</junit.version> <junit.version>5.12.1</junit.version>
<javafx.version>25.0.1</javafx.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId> <artifactId>javafx-controls</artifactId>
<version>21.0.6</version> <version>${javafx.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId> <artifactId>javafx-fxml</artifactId>
<version>21.0.6</version> <version>${javafx.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -40,6 +41,8 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@@ -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<SlidePage> 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();
}
}
@@ -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();
}
@@ -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();
}
}
@@ -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!");
}
}
@@ -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";
}
}
@@ -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);
}
}
+37 -24
View File
@@ -1,35 +1,48 @@
package dev.bytevibe.hyperpoint; package dev.bytevibe.hyperpoint;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Group; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.control.Alert;
import javafx.scene.paint.Color; import javafx.scene.control.ButtonBar;
import javafx.scene.text.Text; import javafx.scene.control.ButtonType;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.awt.desktop.AppForegroundListener;
import java.io.IOException;
public class Main extends Application { public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage stage) throws Exception {
Group root = new Group(); try {
Scene scene = new Scene(root, Color.GRAY); Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
primaryStage.setScene(scene); Scene scene = new Scene(root, 1200, 600);
stage.setScene(scene);
stage.show();
// 设置主要窗口的属性 stage.setOnCloseRequest(event -> {this.logout(stage);});
Image icon = new Image(getClass().getResourceAsStream("icon.png")); } catch (Exception e) {
primaryStage.getIcons().add(icon); e.printStackTrace();
primaryStage.setTitle("HyperPoint"); }
primaryStage.setWidth(800);
primaryStage.setHeight(600);
primaryStage.setResizable(false);
primaryStage.show();
} }
}
// 窗口退出键对应的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);
}
}
@@ -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<DrawableObject> drawableObjects;
public PageContent() {
this.drawableObjects = FXCollections.observableArrayList();
}
public ObservableList<DrawableObject> 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();
}
}
@@ -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<Slide> 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<Slide> 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;
}
}
@@ -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<String> fontFamilyCombo;
private Spinner<Double> fontSizeSpinner;
private ComboBox<String> fontStyleCombo;
private ColorPicker textColorPicker;
private ColorPicker fillColorPicker;
private ColorPicker strokeColorPicker;
private Spinner<Double> 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();
}
}
}
@@ -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();
}
}
@@ -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<SlidePage> 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<SlidePage> 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;
}
}
@@ -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;
}
}
@@ -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";
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="dev.bytevibe.hyperpoint.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="scenePane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.bytevibe.hyperpoint.Controller">
<children>
<!-- 顶部菜单栏 -->
<VBox layoutX="0" layoutY="0" prefHeight="50.0" prefWidth="1200.0" style="-fx-border-color: #cccccc; -fx-border-width: 0 0 1 0;">
<children>
<HBox spacing="5" style="-fx-padding: 5;">
<children>
<Button fx:id="openSlideButton" mnemonicParsing="false" onAction="#onOpenSlide" text="打开幻灯片" />
<Button fx:id="newPageButton" mnemonicParsing="false" onAction="#onNewPage" text="新建页面" />
<Button fx:id="deletePageButton" mnemonicParsing="false" onAction="#onDeletePage" text="删除页面" />
<Label text=" | " />
<Button fx:id="addTextButton" mnemonicParsing="false" onAction="#onAddText" text="添加文本" />
<Button fx:id="addLineButton" mnemonicParsing="false" onAction="#onAddLine" text="直线" />
<Button fx:id="addRectButton" mnemonicParsing="false" onAction="#onAddRectangle" text="矩形" />
<Button fx:id="addCircleButton" mnemonicParsing="false" onAction="#onAddCircle" text="圆形" />
<Button fx:id="addEllipseButton" mnemonicParsing="false" onAction="#onAddEllipse" text="椭圆" />
<Button fx:id="addImageButton" mnemonicParsing="false" onAction="#onAddImage" text="插入图片" />
<Label text=" | " />
<Button fx:id="logout" mnemonicParsing="false" onAction="#logout" text="退出" />
</children>
</HBox>
</children>
</VBox>
<!-- 主内容区域 -->
<HBox layoutX="0" layoutY="50" prefHeight="550.0" prefWidth="1200.0">
<!-- 左侧页面列表 -->
<VBox prefWidth="200.0" style="-fx-border-color: #e0e0e0; -fx-border-width: 0 1 0 0;">
<Label text="页面列表" style="-fx-padding: 5; -fx-font-weight: bold;" />
<ListView fx:id="pageListView" VBox.vgrow="ALWAYS" />
</VBox>
<!-- 右侧编辑区域 -->
<VBox HBox.hgrow="ALWAYS" spacing="10" style="-fx-padding: 10;">
<Label fx:id="pageNameLabel" text="页面名称:" style="-fx-font-size: 14; -fx-font-weight: bold;" />
<!-- 绘图Canvas将在这里动态添加 -->
<AnchorPane fx:id="drawingCanvasContainer" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS" />
</VBox>
<!-- 右侧属性编辑面板 -->
<VBox fx:id="propertyPanel" prefWidth="200.0" />
</HBox>
</children>
</AnchorPane>