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>
|
</items>
|
||||||
</MenuButton>
|
</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=" | " />
|
<Label text=" | " />
|
||||||
|
|
||||||
<!-- 退出菜单 -->
|
<!-- 退出菜单 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user