初步实现创建界面,编辑界面
This commit is contained in:
@@ -37,3 +37,4 @@ build/
|
|||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
Reference in New Issue
Block a user