feat(render): 实现动画参数插值与图层元数据管理

- 增强 FrameInterpolator 类以支持更精确的动画参数匹配
- 添加对 animationParameter 的检查以提高插值安全性
- 修改关键帧查找逻辑以直接使用 keyframe 值- 为所有计算目标值的方法添加 currentAnimationParameter 参数
- 在 LayerOperationManager 中实现 LayerInfo 的序列化支持
- 添加 loadMetadata 方法以从文件加载图层元数据- 创建 LayerOperationManagerData 用于存储和传输图层信息- 更新 ModelLayerPanel以支持加载和应用图层元数据
- 引入主题颜色支持使界面更现代化
-限制面板最大宽度以优化布局结构
-修复 GLContextManager 中模型路径的可变性问题- 添加动态模型加载功能支持异步文件 I/O 操作
- 实现模型背景颜色自适应系统主题设置
- 在 MainWindow 中集成完整的模型加载和保存流程
- 添加菜单栏和工具栏以提供基本的文件操作功能
- 实现窗口关闭时的保存提示和确认机制
- 添加状态栏用于显示操作反馈和加载进度
- 改进布局管理器以获得更好的用户体验
-修复部分 UI 组件的启用/禁用逻辑
- 移除 ModelData 中冗余的 ParameterData 类定义
This commit is contained in:
tzdwindows 7
2025-11-07 21:32:17 +08:00
parent 7a04cc2a2d
commit 6e2fd5940d
18 changed files with 1992 additions and 561 deletions

View File

@@ -0,0 +1,367 @@
package com.chuangzhou.vivid2D.window;
import com.chuangzhou.vivid2D.render.awt.ModelLayerPanel;
import com.chuangzhou.vivid2D.render.awt.ModelRenderPanel;
import com.chuangzhou.vivid2D.render.awt.ParametersPanel;
import com.chuangzhou.vivid2D.render.awt.TransformPanel;
import com.chuangzhou.vivid2D.render.awt.manager.LayerOperationManager;
import com.chuangzhou.vivid2D.render.awt.manager.ParametersManagement;
import com.chuangzhou.vivid2D.render.awt.manager.data.LayerOperationManagerData;
import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
import com.chuangzhou.vivid2D.render.awt.ModelPartInfoPanel;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 现代化的主应用程序窗口,布局类似于 Live2D Cubism Editor。
* 它组织并展示核心的渲染和编辑面板。
*/
public class MainWindow extends JFrame {
private final ModelRenderPanel renderPanel;
private final ModelLayerPanel layerPanel;
private final TransformPanel transformPanel;
private final ParametersPanel parametersPanel;
private final ModelPartInfoPanel partInfoPanel;
private String currentModelPath = null;
private JLabel statusBarLabel;
private JMenuBar menuBar;
/**
* 启动器。
*/
public static void main(String[] args) {
// 设置 Look and Feel
try {
UIManager.setLookAndFeel(new FlatMacDarkLaf());
} catch (UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
}
// 确保控制台输出使用 UTF-8
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
// 在 EDT (Event Dispatch Thread) 上创建和显示 GUI
SwingUtilities.invokeLater(() -> {
MainWindow mainWin = new MainWindow();
mainWin.setVisible(true);
});
}
/**
* 构造主窗口。
*/
public MainWindow() {
setTitle("Vivid2D Editor - [未加载文件]");
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setLayout(new BorderLayout());
// 1. 初始化核心渲染器和面板
// ModelRenderPanel 传入空路径 ""
this.renderPanel = new ModelRenderPanel("", 1024, 768);
this.layerPanel = new ModelLayerPanel(renderPanel);
this.transformPanel = new TransformPanel(renderPanel);
this.parametersPanel = new ParametersPanel(renderPanel);
// 【重要】使用我们新实现的 ModelPartInfoPanel
this.partInfoPanel = new ModelPartInfoPanel(renderPanel);
// 关联参数管理器
//renderPanel.setParametersManagement(ParametersManagement.getInstance(parametersPanel));
// 2. 构建模块化的 UI
createMenuBar();
createToolBar();
createMainLayout();
createStatusBar();
// 3. 设置初始状态:所有编辑功能禁用
setEditComponentsEnabled(false);
setupInitialListeners();
// 4. 设置窗口
setSize(1600, 900);
setLocationRelativeTo(null);
}
/**
* 创建顶部菜单栏并设置事件。
*/
private void createMenuBar() {
menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("文件");
JMenuItem openItem = new JMenuItem("打开模型...");
openItem.addActionListener(e -> openModelFile());
fileMenu.add(openItem);
fileMenu.addSeparator();
JMenuItem saveItem = new JMenuItem("保存");
saveItem.setName("saveItem");
saveItem.addActionListener(e -> saveData(false));
fileMenu.add(saveItem);
JMenuItem exitItem = new JMenuItem("退出");
exitItem.addActionListener(e -> dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)));
fileMenu.add(exitItem);
menuBar.add(fileMenu);
JMenu editMenu = new JMenu("编辑");
editMenu.setName("editMenu");
menuBar.add(editMenu);
menuBar.add(new JMenu("显示"));
setJMenuBar(menuBar);
}
/**
* 创建顶部工具栏。
*/
private void createToolBar() {
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.setName("toolBar");
toolBar.add(new JButton("建模"));
toolBar.add(new JButton("动画"));
toolBar.addSeparator();
add(toolBar, BorderLayout.NORTH);
}
/**
* 创建主工作区布局(左、中、右面板)。
*/
private void createMainLayout() {
JScrollPane layerScroll = new JScrollPane(layerPanel);
layerScroll.setMinimumSize(new Dimension(240, 100));
layerScroll.setPreferredSize(new Dimension(260, 600));
JPanel centerPanelWrapper = new JPanel(new BorderLayout());
centerPanelWrapper.add(renderPanel, BorderLayout.CENTER);
centerPanelWrapper.setMinimumSize(new Dimension(400, 300));
JScrollPane transformScroll = new JScrollPane(transformPanel);
transformScroll.setBorder(BorderFactory.createTitledBorder("变换控制"));
transformScroll.setPreferredSize(new Dimension(300, 200));
JScrollPane paramScroll = new JScrollPane(parametersPanel);
paramScroll.setBorder(BorderFactory.createTitledBorder("参数管理"));
paramScroll.setPreferredSize(new Dimension(300, 200));
JSplitPane rightPanelSplit = getjSplitPane(paramScroll, transformScroll);
JSplitPane mainSplit = getjSplitPane(new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
centerPanelWrapper,
rightPanelSplit
), 0.75, JSplitPane.HORIZONTAL_SPLIT, layerScroll, 0.2);
add(mainSplit, BorderLayout.CENTER);
}
private static @NotNull JSplitPane getjSplitPane(JSplitPane HORIZONTAL_SPLIT, double value, int horizontalSplit, JScrollPane layerScroll, double value1) {
HORIZONTAL_SPLIT.setResizeWeight(value);
HORIZONTAL_SPLIT.setOneTouchExpandable(true);
JSplitPane mainSplit = new JSplitPane(
horizontalSplit,
layerScroll,
HORIZONTAL_SPLIT
);
mainSplit.setResizeWeight(value1);
mainSplit.setOneTouchExpandable(true);
return mainSplit;
}
private @NotNull JSplitPane getjSplitPane(JScrollPane paramScroll, JScrollPane transformScroll) {
JSplitPane rightPanelSplit = getjSplitPane(new JSplitPane(
JSplitPane.VERTICAL_SPLIT,
paramScroll,
partInfoPanel
), 0.5, JSplitPane.VERTICAL_SPLIT, transformScroll, 0.33);
rightPanelSplit.setPreferredSize(new Dimension(300, 600));
return rightPanelSplit;
}
/**
* 创建底部状态栏。
*/
private void createStatusBar() {
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT));
statusBar.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));
this.statusBarLabel = new JLabel("未加载模型。请通过 [文件] -> [打开模型...] 启动编辑。");
statusBar.add(statusBarLabel);
add(statusBar, BorderLayout.SOUTH);
}
/**
* 设置初始的监听器,特别是窗口关闭监听。
*/
private void setupInitialListeners() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (currentModelPath == null) {
shutdown();
return;
}
int confirm = JOptionPane.showConfirmDialog(
MainWindow.this,
"是否在退出前保存更改?",
"退出确认",
JOptionPane.YES_NO_CANCEL_OPTION
);
if (confirm == JOptionPane.CANCEL_OPTION) {
return;
}
if (confirm == JOptionPane.YES_OPTION) {
saveData(true);
} else {
shutdown();
}
}
});
renderPanel.addModelClickListener((mesh, modelX, modelY, screenX, screenY) -> {
List<ModelPart> selectedPart = renderPanel.getSelectedParts();
SwingUtilities.invokeLater(() -> {
transformPanel.setSelectedParts(selectedPart);
if (!selectedPart.isEmpty()) {
partInfoPanel.updatePanel(selectedPart.get(0));
} else {
partInfoPanel.updatePanel(null);
}
});
});
}
/**
* 根据是否有模型加载来启用或禁用编辑组件。
*/
private void setEditComponentsEnabled(boolean enabled) {
layerPanel.setEnabled(enabled);
transformPanel.setEnabled(enabled);
parametersPanel.setEnabled(enabled);
partInfoPanel.setEnabled(enabled);
renderPanel.setEnabled(enabled);
for (Component comp : menuBar.getComponents()) {
if (comp instanceof JMenu) {
JMenu menu = (JMenu) comp;
if ("编辑".equals(menu.getName())) {
menu.setEnabled(enabled);
}
for (Component item : menu.getMenuComponents()) {
if ("saveItem".equals(item.getName())) {
item.setEnabled(enabled);
}
}
}
}
for (Component comp : getContentPane().getComponents()) {
if (comp instanceof JToolBar && "toolBar".equals(comp.getName())) {
comp.setEnabled(enabled);
for (Component button : ((JToolBar) comp).getComponents()) {
button.setEnabled(enabled);
}
}
}
}
/**
* 打开文件对话框并加载模型。
*/
private void openModelFile() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("选择 Vivid2D 模型文件 (*.model)");
FileNameExtensionFilter filter = new FileNameExtensionFilter("Vivid2D 模型文件 (*.model)", "model");
fileChooser.setFileFilter(filter);
fileChooser.setAcceptAllFileFilterUsed(false); // 这一行可选,用于禁用 "All Files" 选项
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
loadModel(file.getAbsolutePath());
}
}
/**
* 加载模型并更新 UI 状态。
*/
private void loadModel(String modelPath) {
setEditComponentsEnabled(false);
statusBarLabel.setText("正在加载模型: " + modelPath);
CompletableFuture.runAsync(() -> {
Model2D model = null;
try {
model = renderPanel.loadModel(modelPath).get();
} catch (InterruptedException | ExecutionException e) {
System.err.println("模型异步加载失败: " + e.getMessage());
}
Model2D finalModel = model;
SwingUtilities.invokeLater(() -> {
if (finalModel == null || !renderPanel.getGlContextManager().isRunning()) {
currentModelPath = null;
setTitle("Vivid2D Editor - [加载失败]");
statusBarLabel.setText("模型加载失败!无法加载: " + modelPath);
JOptionPane.showMessageDialog(this,
"无法加载模型: " + modelPath,
"加载错误",
JOptionPane.ERROR_MESSAGE);
} else {
renderPanel.setParametersManagement(ParametersManagement.getInstance(parametersPanel));
layerPanel.loadMetadata();
currentModelPath = modelPath;
setTitle("Vivid2D Editor - " + new File(modelPath).getName());
statusBarLabel.setText("模型加载完毕。");
setEditComponentsEnabled(true);
layerPanel.setModel(finalModel);
}
});
});
}
/**
* 保存模型和参数数据。
* @param exitOnComplete 如果为 true则在保存后调用 shutdown()。
*/
private void saveData(boolean exitOnComplete) {
if (currentModelPath == null) {
statusBarLabel.setText("没有加载模型,无法保存。");
return;
}
statusBarLabel.setText("正在保存...");
if (renderPanel.getModel() != null) {
System.out.println("正在保存模型: " + currentModelPath);
renderPanel.getModel().saveToFile(currentModelPath);
}
LayerOperationManager layerManager = layerPanel.getLayerOperationManager();
LayerOperationManagerData layerData = new LayerOperationManagerData(layerManager.layerMetadata);
//System.out.println("正在保存参数: " + renderPanel.getParametersManagement());
ParametersManagementData managementData = new ParametersManagementData(renderPanel.getParametersManagement());
String managementFilePath = currentModelPath + ".data";
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(managementFilePath))) {
oos.writeObject(layerData);
oos.writeObject(managementData);
} catch (IOException ex) {
ex.printStackTrace(System.err);
statusBarLabel.setText("保存参数失败!");
}
statusBarLabel.setText("保存成功。");
if (exitOnComplete) {
shutdown();
}
}
/**
* 清理资源并退出应用程序。
*/
private void shutdown() {
statusBarLabel.setText("正在关闭...");
try {
renderPanel.getGlContextManager().dispose();
} catch (Throwable ignored) {}
dispose();
System.exit(0);
}
}