feature: 初步实现全屏播放
This commit is contained in:
@@ -470,6 +470,86 @@ public class Controller implements Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始演示(默认淡出淡入效果)
|
||||
*/
|
||||
@FXML
|
||||
public void onStartPresentation(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.FADE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用淡出淡入效果播放演示
|
||||
*/
|
||||
@FXML
|
||||
public void onPresentationWithFade(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.FADE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用从右推入效果播放演示
|
||||
*/
|
||||
@FXML
|
||||
public void onPresentationWithPushLeft(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.PUSH_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用从左推入效果播放演示
|
||||
*/
|
||||
@FXML
|
||||
public void onPresentationWithPushRight(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.PUSH_RIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用缩放放大效果播放演示
|
||||
*/
|
||||
@FXML
|
||||
public void onPresentationWithZoomIn(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.ZOOM_IN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用旋转翻页效果播放演示
|
||||
*/
|
||||
@FXML
|
||||
public void onPresentationWithRotate(ActionEvent actionEvent) {
|
||||
if (currentSlide == null || currentSlide.getPages().isEmpty()) {
|
||||
showWarning("无幻灯片", "请先创建或打开一个幻灯片。");
|
||||
return;
|
||||
}
|
||||
startPresentation(TransitionEffect.ROTATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动演示
|
||||
*/
|
||||
private void startPresentation(TransitionEffect transition) {
|
||||
PresentationWindow presentationWindow = new PresentationWindow(currentSlide, transition);
|
||||
presentationWindow.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示警告对话框
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,677 @@
|
||||
package dev.bytevibe.hyperpoint;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* 全屏演示窗口,支持翻页和动画效果
|
||||
*/
|
||||
public class PresentationWindow {
|
||||
private final Slide slide;
|
||||
private final Stage stage;
|
||||
private final Canvas canvas;
|
||||
private final StackPane root;
|
||||
private int currentPageIndex = 0;
|
||||
private TransitionEffect currentTransition = TransitionEffect.FADE;
|
||||
private boolean isTransitioning = false;
|
||||
|
||||
public PresentationWindow(Slide slide, TransitionEffect transition) {
|
||||
this.slide = slide;
|
||||
this.currentTransition = transition;
|
||||
this.stage = new Stage();
|
||||
this.canvas = new Canvas();
|
||||
this.root = new StackPane();
|
||||
|
||||
initializeStage();
|
||||
}
|
||||
|
||||
private void initializeStage() {
|
||||
// 页面标准宽高比 (4:3)
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
final double ASPECT_RATIO = PAGE_WIDTH / PAGE_HEIGHT; // 4:3 = 1.333...
|
||||
|
||||
// 设置全屏,获取屏幕分辨率
|
||||
stage.setFullScreen(true);
|
||||
stage.setFullScreenExitHint("");
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
|
||||
// 绑定Canvas大小到root,Canvas会根据root大小自动调整
|
||||
canvas.widthProperty().bind(root.widthProperty());
|
||||
canvas.heightProperty().bind(root.heightProperty());
|
||||
|
||||
// 添加键盘和鼠标事件处理
|
||||
root.setOnKeyPressed(this::handleKeyEvent);
|
||||
root.setOnMouseClicked(this::handleMouseClick);
|
||||
|
||||
root.getChildren().add(canvas);
|
||||
root.setStyle("-fx-background-color: #000000;");
|
||||
root.setFocusTraversable(true);
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
stage.setScene(scene);
|
||||
|
||||
// 监听Scene大小变化,计算缩放因子
|
||||
scene.widthProperty().addListener((obs, oldVal, newVal) -> drawCurrentPage(null));
|
||||
scene.heightProperty().addListener((obs, oldVal, newVal) -> drawCurrentPage(null));
|
||||
|
||||
// 显示第一页
|
||||
drawCurrentPage(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示演示窗口
|
||||
*/
|
||||
public void show() {
|
||||
stage.show();
|
||||
root.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理键盘事件
|
||||
*/
|
||||
private void handleKeyEvent(KeyEvent event) {
|
||||
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.SPACE) {
|
||||
nextPage();
|
||||
} else if (event.getCode() == KeyCode.LEFT) {
|
||||
previousPage();
|
||||
} else if (event.getCode() == KeyCode.ESCAPE) {
|
||||
exitPresentation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理鼠标点击事件
|
||||
*/
|
||||
private void handleMouseClick(MouseEvent event) {
|
||||
if (event.getClickCount() == 1) {
|
||||
nextPage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
private void nextPage() {
|
||||
if (isTransitioning || currentPageIndex >= slide.getPages().size() - 1) {
|
||||
return;
|
||||
}
|
||||
currentPageIndex++;
|
||||
playTransition(currentTransition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
private void previousPage() {
|
||||
if (isTransitioning || currentPageIndex <= 0) {
|
||||
return;
|
||||
}
|
||||
currentPageIndex--;
|
||||
playTransition(currentTransition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放过渡动画
|
||||
*/
|
||||
private void playTransition(TransitionEffect effect) {
|
||||
isTransitioning = true;
|
||||
|
||||
switch (effect) {
|
||||
case FADE:
|
||||
playFadeTransition();
|
||||
break;
|
||||
case PUSH_LEFT:
|
||||
playPushTransition(-canvas.getWidth(), 0);
|
||||
break;
|
||||
case PUSH_RIGHT:
|
||||
playPushTransition(canvas.getWidth(), 0);
|
||||
break;
|
||||
case PUSH_DOWN:
|
||||
playPushTransition(0, canvas.getHeight());
|
||||
break;
|
||||
case PUSH_UP:
|
||||
playPushTransition(0, -canvas.getHeight());
|
||||
break;
|
||||
case ZOOM_IN:
|
||||
playZoomTransition(0.5, 1.0);
|
||||
break;
|
||||
case ZOOM_OUT:
|
||||
playZoomTransition(1.0, 0.5);
|
||||
break;
|
||||
case ROTATE:
|
||||
playRotateTransition();
|
||||
break;
|
||||
default:
|
||||
drawCurrentPage(null);
|
||||
isTransitioning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 淡出淡入动画
|
||||
*/
|
||||
private void playFadeTransition() {
|
||||
int duration = currentTransition.getDuration();
|
||||
int halfDuration = duration / 2;
|
||||
|
||||
// 保存当前页面索引
|
||||
int previousPageIndex = currentPageIndex - 1;
|
||||
if (previousPageIndex < 0) previousPageIndex = slide.getPages().size() - 1;
|
||||
|
||||
// 淡出旧页面,显示黑屏,淡入新页面
|
||||
AnimationTimer animationTimer = new AnimationTimer() {
|
||||
private long startTime = System.currentTimeMillis();
|
||||
private final long fadeOutDuration = halfDuration;
|
||||
private final long fadeInDuration = halfDuration;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (elapsed < fadeOutDuration) {
|
||||
// 淡出阶段
|
||||
double progress = (double) elapsed / fadeOutDuration;
|
||||
drawFadeTransition(progress, true);
|
||||
} else if (elapsed < fadeOutDuration + fadeInDuration) {
|
||||
// 淡入阶段
|
||||
double progress = (double) (elapsed - fadeOutDuration) / fadeInDuration;
|
||||
drawFadeTransition(progress, false);
|
||||
} else {
|
||||
// 动画完成
|
||||
drawCurrentPage(null);
|
||||
isTransitioning = false;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animationTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制淡出淡入过渡
|
||||
*/
|
||||
private void drawFadeTransition(double progress, boolean fadeOut) {
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
double canvasWidth = canvas.getWidth();
|
||||
double canvasHeight = canvas.getHeight();
|
||||
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
|
||||
double scaleX = canvasWidth / PAGE_WIDTH;
|
||||
double scaleY = canvasHeight / PAGE_HEIGHT;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
double scaledWidth = PAGE_WIDTH * scale;
|
||||
double scaledHeight = PAGE_HEIGHT * scale;
|
||||
|
||||
double offsetX = (canvasWidth - scaledWidth) / 2;
|
||||
double offsetY = (canvasHeight - scaledHeight) / 2;
|
||||
|
||||
if (fadeOut) {
|
||||
// 绘制当前页面并淡出
|
||||
drawCurrentPage(null);
|
||||
gc.setFill(Color.BLACK.interpolate(Color.TRANSPARENT, 1 - progress));
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
} else {
|
||||
// 绘制黑屏并淡入下一页
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(offsetX, offsetY, scaledWidth, scaledHeight);
|
||||
|
||||
gc.save();
|
||||
gc.translate(offsetX, offsetY);
|
||||
gc.scale(scale, scale);
|
||||
|
||||
SlidePage page = slide.getPages().get(currentPageIndex);
|
||||
for (DrawableObject obj : page.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
|
||||
gc.restore();
|
||||
|
||||
// 淡入效果
|
||||
gc.setFill(Color.color(1, 1, 1, 1 - progress));
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送动画
|
||||
*/
|
||||
private void playPushTransition(double offsetX, double offsetY) {
|
||||
int duration = currentTransition.getDuration();
|
||||
|
||||
AnimationTimer animationTimer = new AnimationTimer() {
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (elapsed < duration) {
|
||||
double progress = (double) elapsed / duration;
|
||||
drawPushTransition(progress, offsetX, offsetY);
|
||||
} else {
|
||||
drawCurrentPage(null);
|
||||
isTransitioning = false;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animationTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制推送过渡
|
||||
*/
|
||||
private void drawPushTransition(double progress, double offsetX, double offsetY) {
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
double canvasWidth = canvas.getWidth();
|
||||
double canvasHeight = canvas.getHeight();
|
||||
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
|
||||
double scaleX = canvasWidth / PAGE_WIDTH;
|
||||
double scaleY = canvasHeight / PAGE_HEIGHT;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
double scaledWidth = PAGE_WIDTH * scale;
|
||||
double scaledHeight = PAGE_HEIGHT * scale;
|
||||
|
||||
double centerOffsetX = (canvasWidth - scaledWidth) / 2;
|
||||
double centerOffsetY = (canvasHeight - scaledHeight) / 2;
|
||||
|
||||
// 清空背景
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 获取前一页索引(确保有效)
|
||||
int prevPageIndex = currentPageIndex - 1;
|
||||
if (prevPageIndex < 0) {
|
||||
prevPageIndex = slide.getPages().size() - 1; // 循环到最后一页
|
||||
}
|
||||
|
||||
// 绘制旧页面
|
||||
SlidePage prevPage = slide.getPages().get(prevPageIndex);
|
||||
double slideOffsetX = offsetX * progress;
|
||||
double slideOffsetY = offsetY * progress;
|
||||
|
||||
gc.save();
|
||||
gc.translate(centerOffsetX + slideOffsetX, centerOffsetY + slideOffsetY);
|
||||
gc.scale(scale, scale);
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
for (DrawableObject obj : prevPage.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
gc.restore();
|
||||
|
||||
// 绘制新页面推入
|
||||
SlidePage newPage = slide.getPages().get(currentPageIndex);
|
||||
double newPageOffsetX = centerOffsetX - offsetX * (1 - progress);
|
||||
double newPageOffsetY = centerOffsetY - offsetY * (1 - progress);
|
||||
|
||||
gc.save();
|
||||
gc.translate(newPageOffsetX, newPageOffsetY);
|
||||
gc.scale(scale, scale);
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
for (DrawableObject obj : newPage.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
gc.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放动画
|
||||
*/
|
||||
private void playZoomTransition(double startScale, double endScale) {
|
||||
int duration = currentTransition.getDuration();
|
||||
|
||||
AnimationTimer animationTimer = new AnimationTimer() {
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (elapsed < duration) {
|
||||
double progress = (double) elapsed / duration;
|
||||
double currentScale = startScale + (endScale - startScale) * progress;
|
||||
drawZoomTransition(currentScale, progress);
|
||||
} else {
|
||||
drawCurrentPage(null);
|
||||
isTransitioning = false;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animationTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制缩放过渡
|
||||
*/
|
||||
private void drawZoomTransition(double zoomScale, double progress) {
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
double canvasWidth = canvas.getWidth();
|
||||
double canvasHeight = canvas.getHeight();
|
||||
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
|
||||
double scaleX = canvasWidth / PAGE_WIDTH;
|
||||
double scaleY = canvasHeight / PAGE_HEIGHT;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
double scaledWidth = PAGE_WIDTH * scale;
|
||||
double scaledHeight = PAGE_HEIGHT * scale;
|
||||
|
||||
double offsetX = (canvasWidth - scaledWidth) / 2;
|
||||
double offsetY = (canvasHeight - scaledHeight) / 2;
|
||||
|
||||
// 清空背景
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 获取前一页索引(确保有效)
|
||||
int prevPageIndex = currentPageIndex - 1;
|
||||
if (prevPageIndex < 0) {
|
||||
prevPageIndex = slide.getPages().size() - 1; // 循环到最后一页
|
||||
}
|
||||
|
||||
// 绘制旧页面,逐渐缩小
|
||||
SlidePage prevPage = slide.getPages().get(prevPageIndex);
|
||||
double oldPageScale = scale * (1 - progress * 0.3); // 缩小30%
|
||||
double oldPageWidth = PAGE_WIDTH * oldPageScale;
|
||||
double oldPageHeight = PAGE_HEIGHT * oldPageScale;
|
||||
double oldPageOffsetX = (canvasWidth - oldPageWidth) / 2;
|
||||
double oldPageOffsetY = (canvasHeight - oldPageHeight) / 2;
|
||||
|
||||
gc.save();
|
||||
gc.translate(oldPageOffsetX, oldPageOffsetY);
|
||||
gc.scale(oldPageScale / scale, oldPageScale / scale);
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
for (DrawableObject obj : prevPage.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
gc.restore();
|
||||
|
||||
// 绘制新页面,逐渐放大
|
||||
SlidePage newPage = slide.getPages().get(currentPageIndex);
|
||||
double newPageScale = scale * (0.7 + progress * 0.3); // 从70%放大到100%
|
||||
double newPageWidth = PAGE_WIDTH * newPageScale;
|
||||
double newPageHeight = PAGE_HEIGHT * newPageScale;
|
||||
double newPageOffsetX = (canvasWidth - newPageWidth) / 2;
|
||||
double newPageOffsetY = (canvasHeight - newPageHeight) / 2;
|
||||
|
||||
gc.save();
|
||||
gc.translate(newPageOffsetX, newPageOffsetY);
|
||||
gc.scale(newPageScale / scale, newPageScale / scale);
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
for (DrawableObject obj : newPage.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
gc.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转动画
|
||||
*/
|
||||
private void playRotateTransition() {
|
||||
int duration = currentTransition.getDuration();
|
||||
|
||||
AnimationTimer animationTimer = new AnimationTimer() {
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (elapsed < duration) {
|
||||
double progress = (double) elapsed / duration;
|
||||
double rotation = progress * 360; // 360度旋转
|
||||
drawRotateTransition(rotation, progress);
|
||||
} else {
|
||||
drawCurrentPage(null);
|
||||
isTransitioning = false;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animationTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制旋转过渡
|
||||
*/
|
||||
private void drawRotateTransition(double rotation, double progress) {
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
double canvasWidth = canvas.getWidth();
|
||||
double canvasHeight = canvas.getHeight();
|
||||
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
|
||||
double scaleX = canvasWidth / PAGE_WIDTH;
|
||||
double scaleY = canvasHeight / PAGE_HEIGHT;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
double scaledWidth = PAGE_WIDTH * scale;
|
||||
double scaledHeight = PAGE_HEIGHT * scale;
|
||||
|
||||
double offsetX = (canvasWidth - scaledWidth) / 2;
|
||||
double offsetY = (canvasHeight - scaledHeight) / 2;
|
||||
|
||||
// 清空背景
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 绘制旋转的页面
|
||||
SlidePage newPage = slide.getPages().get(currentPageIndex);
|
||||
|
||||
gc.save();
|
||||
// 移到中心,旋转,再移回
|
||||
double centerX = offsetX + scaledWidth / 2;
|
||||
double centerY = offsetY + scaledHeight / 2;
|
||||
|
||||
gc.translate(centerX, centerY);
|
||||
gc.rotate(rotation);
|
||||
gc.translate(-scaledWidth / 2, -scaledHeight / 2);
|
||||
gc.scale(scale, scale);
|
||||
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
for (DrawableObject obj : newPage.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
|
||||
gc.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制当前页面
|
||||
*/
|
||||
private void drawCurrentPage(Animation animation) {
|
||||
if (currentPageIndex < 0 || currentPageIndex >= slide.getPages().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
double canvasWidth = canvas.getWidth();
|
||||
double canvasHeight = canvas.getHeight();
|
||||
|
||||
// 页面标准宽高比
|
||||
final double PAGE_WIDTH = 1024;
|
||||
final double PAGE_HEIGHT = 768;
|
||||
|
||||
// 计算缩放因子,保持宽高比
|
||||
double scaleX = canvasWidth / PAGE_WIDTH;
|
||||
double scaleY = canvasHeight / PAGE_HEIGHT;
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
// 计算内容区域的实际宽高
|
||||
double scaledWidth = PAGE_WIDTH * scale;
|
||||
double scaledHeight = PAGE_HEIGHT * scale;
|
||||
|
||||
// 计算居中偏移
|
||||
double offsetX = (canvasWidth - scaledWidth) / 2;
|
||||
double offsetY = (canvasHeight - scaledHeight) / 2;
|
||||
|
||||
// 清空背景(黑色)
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 白色背景区域
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(offsetX, offsetY, scaledWidth, scaledHeight);
|
||||
|
||||
SlidePage page = slide.getPages().get(currentPageIndex);
|
||||
|
||||
// 保存绘图上下文
|
||||
gc.save();
|
||||
|
||||
// 应用缩放和平移变换
|
||||
gc.translate(offsetX, offsetY);
|
||||
gc.scale(scale, scale);
|
||||
|
||||
// 绘制所有对象
|
||||
for (DrawableObject obj : page.getPageContent().getDrawableObjects()) {
|
||||
drawObject(gc, obj);
|
||||
}
|
||||
|
||||
gc.restore();
|
||||
|
||||
// 绘制页码(不受缩放影响)
|
||||
drawPageNumber(gc, canvasWidth, canvasHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制单个对象
|
||||
*/
|
||||
private void drawObject(GraphicsContext gc, DrawableObject obj) {
|
||||
if (obj instanceof TextObject) {
|
||||
drawTextObject(gc, (TextObject) obj);
|
||||
} else if (obj instanceof ShapeObject) {
|
||||
drawShapeObject(gc, (ShapeObject) obj);
|
||||
} else if (obj instanceof ImageObject) {
|
||||
drawImageObject(gc, (ImageObject) obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文本对象
|
||||
*/
|
||||
private void drawTextObject(GraphicsContext gc, TextObject textObj) {
|
||||
gc.setFill(textObj.getTextColorAsColor());
|
||||
gc.setFont(new javafx.scene.text.Font(textObj.getFontFamily(), textObj.getFontSize()));
|
||||
|
||||
double lineHeight = textObj.getFontSize() * 1.2;
|
||||
double currentY = textObj.getY();
|
||||
|
||||
for (String line : textObj.getLayoutLines()) {
|
||||
currentY += lineHeight;
|
||||
gc.fillText(line, textObj.getX() + 5, currentY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制形状对象
|
||||
*/
|
||||
private void drawShapeObject(GraphicsContext gc, ShapeObject shapeObj) {
|
||||
gc.setFill(Color.web("#" + shapeObj.getFillColor()));
|
||||
gc.setStroke(Color.web("#" + shapeObj.getStrokeColor()));
|
||||
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;
|
||||
gc.fillOval(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
|
||||
gc.strokeOval(shapeObj.getX(), shapeObj.getY(), shapeObj.getWidth(), shapeObj.getHeight());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图片对象
|
||||
*/
|
||||
private void drawImageObject(GraphicsContext gc, ImageObject imgObj) {
|
||||
try {
|
||||
javafx.scene.image.Image image = new javafx.scene.image.Image("file:" + imgObj.getImagePath());
|
||||
gc.drawImage(image, imgObj.getX(), imgObj.getY(), imgObj.getWidth(), imgObj.getHeight());
|
||||
} catch (Exception e) {
|
||||
// 图片加载失败,显示占位符
|
||||
gc.setFill(Color.LIGHTGRAY);
|
||||
gc.fillRect(imgObj.getX(), imgObj.getY(), imgObj.getWidth(), imgObj.getHeight());
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillText("图片加载失败", imgObj.getX() + 10, imgObj.getY() + 20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制页码(不受缩放影响)
|
||||
*/
|
||||
private void drawPageNumber(GraphicsContext gc, double canvasWidth, double canvasHeight) {
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(new javafx.scene.text.Font(16));
|
||||
String pageNum = String.format("%d / %d", currentPageIndex + 1, slide.getPages().size());
|
||||
gc.fillText(pageNum, canvasWidth - 100, canvasHeight - 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置翻页动画
|
||||
*/
|
||||
public void setTransitionEffect(TransitionEffect effect) {
|
||||
this.currentTransition = effect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出演示
|
||||
*/
|
||||
private void exitPresentation() {
|
||||
stage.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取舞台
|
||||
*/
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package dev.bytevibe.hyperpoint;
|
||||
|
||||
/**
|
||||
* 演示/播放模式下支持的翻页动画效果
|
||||
*/
|
||||
public enum TransitionEffect {
|
||||
/**
|
||||
* 无动画,直接切换
|
||||
*/
|
||||
NONE("无动画", 0),
|
||||
|
||||
/**
|
||||
* 淡出/淡入效果
|
||||
*/
|
||||
FADE("淡出淡入", 500),
|
||||
|
||||
/**
|
||||
* 从右向左推送效果
|
||||
*/
|
||||
PUSH_LEFT("从右推入", 400),
|
||||
|
||||
/**
|
||||
* 从左向右推送效果
|
||||
*/
|
||||
PUSH_RIGHT("从左推入", 400),
|
||||
|
||||
/**
|
||||
* 从上向下推送效果
|
||||
*/
|
||||
PUSH_DOWN("从上推入", 400),
|
||||
|
||||
/**
|
||||
* 从下向上推送效果
|
||||
*/
|
||||
PUSH_UP("从下推入", 400),
|
||||
|
||||
/**
|
||||
* 缩放效果 - 从小到大
|
||||
*/
|
||||
ZOOM_IN("缩放放大", 500),
|
||||
|
||||
/**
|
||||
* 缩放效果 - 从大到小
|
||||
*/
|
||||
ZOOM_OUT("缩放缩小", 500),
|
||||
|
||||
/**
|
||||
* 旋转效果
|
||||
*/
|
||||
ROTATE("旋转翻页", 600);
|
||||
|
||||
private final String displayName;
|
||||
private final int duration; // 毫秒
|
||||
|
||||
TransitionEffect(String displayName, int duration) {
|
||||
this.displayName = displayName;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,18 @@
|
||||
</items>
|
||||
</MenuButton>
|
||||
|
||||
<!-- 演示菜单 -->
|
||||
<MenuButton mnemonicParsing="false" text="演示">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" onAction="#onStartPresentation" text="开始演示(F5)" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#onPresentationWithFade" text="淡出淡入效果演示" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#onPresentationWithPushLeft" text="从右推入效果演示" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#onPresentationWithPushRight" text="从左推入效果演示" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#onPresentationWithZoomIn" text="缩放放大效果演示" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#onPresentationWithRotate" text="旋转翻页效果演示" />
|
||||
</items>
|
||||
</MenuButton>
|
||||
|
||||
<Label text=" | " />
|
||||
|
||||
<!-- 退出菜单 -->
|
||||
|
||||
Reference in New Issue
Block a user