2025-11-07 21:32:17 +08:00
|
|
|
|
package com.chuangzhou.vivid2D.window;
|
|
|
|
|
|
|
2025-11-08 10:34:15 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.awt.*;
|
2025-11-07 21:32:17 +08:00
|
|
|
|
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.model.Model2D;
|
|
|
|
|
|
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
2025-11-08 10:34:15 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
2025-11-07 21:32:17 +08:00
|
|
|
|
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
|
2025-11-08 10:34:15 +08:00
|
|
|
|
import jnafilechooser.api.JnaFileChooser;
|
2025-11-07 21:32:17 +08:00
|
|
|
|
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 MainWindow() {
|
|
|
|
|
|
setTitle("Vivid2D Editor - [未加载文件]");
|
|
|
|
|
|
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
|
|
|
|
|
setLayout(new BorderLayout());
|
|
|
|
|
|
this.renderPanel = new ModelRenderPanel("", 1024, 768);
|
|
|
|
|
|
this.layerPanel = new ModelLayerPanel(renderPanel);
|
|
|
|
|
|
this.transformPanel = new TransformPanel(renderPanel);
|
|
|
|
|
|
this.parametersPanel = new ParametersPanel(renderPanel);
|
|
|
|
|
|
this.partInfoPanel = new ModelPartInfoPanel(renderPanel);
|
|
|
|
|
|
createMenuBar();
|
|
|
|
|
|
createToolBar();
|
|
|
|
|
|
createMainLayout();
|
|
|
|
|
|
createStatusBar();
|
|
|
|
|
|
setEditComponentsEnabled(false);
|
|
|
|
|
|
setupInitialListeners();
|
|
|
|
|
|
setSize(1600, 900);
|
|
|
|
|
|
setLocationRelativeTo(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建顶部菜单栏并设置事件。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void createMenuBar() {
|
|
|
|
|
|
menuBar = new JMenuBar();
|
|
|
|
|
|
JMenu fileMenu = new JMenu("文件");
|
2025-11-08 10:34:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 新增:新建模型菜单项
|
|
|
|
|
|
JMenuItem newItem = new JMenuItem("新建模型...");
|
|
|
|
|
|
newItem.addActionListener(e -> createNewModel());
|
|
|
|
|
|
fileMenu.add(newItem);
|
|
|
|
|
|
|
2025-11-07 21:32:17 +08:00
|
|
|
|
JMenuItem openItem = new JMenuItem("打开模型...");
|
|
|
|
|
|
openItem.addActionListener(e -> openModelFile());
|
|
|
|
|
|
fileMenu.add(openItem);
|
|
|
|
|
|
fileMenu.addSeparator();
|
2025-11-08 10:34:15 +08:00
|
|
|
|
|
2025-11-07 21:32:17 +08:00
|
|
|
|
JMenuItem saveItem = new JMenuItem("保存");
|
|
|
|
|
|
saveItem.setName("saveItem");
|
|
|
|
|
|
saveItem.addActionListener(e -> saveData(false));
|
|
|
|
|
|
fileMenu.add(saveItem);
|
2025-11-08 10:34:15 +08:00
|
|
|
|
|
2025-11-07 21:32:17 +08:00
|
|
|
|
JMenuItem exitItem = new JMenuItem("退出");
|
|
|
|
|
|
exitItem.addActionListener(e -> dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)));
|
|
|
|
|
|
fileMenu.add(exitItem);
|
2025-11-08 10:34:15 +08:00
|
|
|
|
|
2025-11-07 21:32:17 +08:00
|
|
|
|
menuBar.add(fileMenu);
|
|
|
|
|
|
JMenu editMenu = new JMenu("编辑");
|
|
|
|
|
|
editMenu.setName("editMenu");
|
|
|
|
|
|
menuBar.add(editMenu);
|
|
|
|
|
|
menuBar.add(new JMenu("显示"));
|
|
|
|
|
|
setJMenuBar(menuBar);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-08 10:34:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理新建模型的操作。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void createNewModel() {
|
|
|
|
|
|
String modelName = JOptionPane.showInputDialog(this, "请输入新模型的名称:", "新建模型", JOptionPane.PLAIN_MESSAGE);
|
|
|
|
|
|
if (modelName != null && !modelName.trim().isEmpty()) {
|
|
|
|
|
|
modelName = modelName.trim();
|
|
|
|
|
|
String finalModelName = modelName;
|
|
|
|
|
|
SwingUtilities.invokeLater(() -> {
|
|
|
|
|
|
Model2D newModel = new Model2D(finalModelName);
|
|
|
|
|
|
setEditComponentsEnabled(false);
|
|
|
|
|
|
statusBarLabel.setText("正在创建并加载新模型: " + finalModelName);
|
|
|
|
|
|
try {
|
|
|
|
|
|
renderPanel.loadModel(newModel);
|
|
|
|
|
|
renderPanel.setParametersManagement(ParametersManagement.getInstance(parametersPanel));
|
|
|
|
|
|
layerPanel.loadMetadata();
|
|
|
|
|
|
currentModelPath = null;
|
|
|
|
|
|
setTitle("Vivid2D Editor - " + finalModelName + " [新建]");
|
|
|
|
|
|
statusBarLabel.setText("新模型 " + finalModelName + " 创建并加载完毕。");
|
|
|
|
|
|
setEditComponentsEnabled(true);
|
|
|
|
|
|
layerPanel.setModel(newModel);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
System.err.println("新建模型加载失败: " + e.getMessage());
|
|
|
|
|
|
currentModelPath = null;
|
|
|
|
|
|
setTitle("Vivid2D Editor - [加载失败]");
|
|
|
|
|
|
statusBarLabel.setText("新模型加载失败!无法加载: " + finalModelName);
|
|
|
|
|
|
JOptionPane.showMessageDialog(this,
|
|
|
|
|
|
"无法加载新模型: " + finalModelName + "\n错误: " + e.getMessage(),
|
|
|
|
|
|
"加载错误",
|
|
|
|
|
|
JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
|
setEditComponentsEnabled(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (modelName != null) {
|
|
|
|
|
|
JOptionPane.showMessageDialog(this, "模型名称不能为空。", "输入错误", JOptionPane.WARNING_MESSAGE);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-11-07 21:32:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 创建顶部工具栏。
|
|
|
|
|
|
*/
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-08 10:34:15 +08:00
|
|
|
|
renderPanel.addModelClickListener(new ModelClickListener() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onModelClicked(Mesh2D mesh, float modelX, float modelY, int screenX, int screenY) {
|
|
|
|
|
|
List<ModelPart> selectedPart = renderPanel.getSelectedParts();
|
|
|
|
|
|
SwingUtilities.invokeLater(() -> {
|
|
|
|
|
|
layerPanel.setSelectedLayers(selectedPart);
|
|
|
|
|
|
transformPanel.setSelectedParts(selectedPart);
|
|
|
|
|
|
if (!selectedPart.isEmpty()) {
|
|
|
|
|
|
partInfoPanel.updatePanel(selectedPart.get(0));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
partInfoPanel.updatePanel(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onModelHover(Mesh2D mesh, float modelX, float modelY, int screenX, int screenY) {
|
|
|
|
|
|
onModelClicked(mesh, modelX, modelY, screenX, screenY);
|
|
|
|
|
|
}
|
2025-11-07 21:32:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据是否有模型加载来启用或禁用编辑组件。
|
|
|
|
|
|
*/
|
|
|
|
|
|
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() {
|
2025-11-08 10:34:15 +08:00
|
|
|
|
JnaFileChooser jnaFileChooser = new JnaFileChooser();
|
|
|
|
|
|
jnaFileChooser.setTitle("选择 Vivid2D 模型文件 (*.model)");
|
|
|
|
|
|
jnaFileChooser.addFilter("Vivid2D 模型文件 (*.model)", "model");
|
|
|
|
|
|
jnaFileChooser.setMultiSelectionEnabled(false);
|
|
|
|
|
|
jnaFileChooser.setMode(JnaFileChooser.Mode.Files);
|
|
|
|
|
|
if (jnaFileChooser.showOpenDialog(this)) {
|
|
|
|
|
|
File file = jnaFileChooser.getSelectedFile();
|
2025-11-07 21:32:17 +08:00
|
|
|
|
loadModel(file.getAbsolutePath());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载模型并更新 UI 状态。
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void loadModel(String modelPath) {
|
|
|
|
|
|
setEditComponentsEnabled(false);
|
|
|
|
|
|
statusBarLabel.setText("正在加载模型: " + modelPath);
|
|
|
|
|
|
CompletableFuture.runAsync(() -> {
|
|
|
|
|
|
Model2D model = null;
|
|
|
|
|
|
try {
|
2025-11-08 10:34:15 +08:00
|
|
|
|
// 假设 renderPanel.loadModel(String modelPath) 返回一个 CompletableFuture<Model2D>
|
2025-11-07 21:32:17 +08:00
|
|
|
|
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) {
|
2025-11-08 10:34:15 +08:00
|
|
|
|
JnaFileChooser jnaFileChooser = new JnaFileChooser();
|
|
|
|
|
|
jnaFileChooser.setTitle("另存为 Vivid2D 模型文件 (*.model)");
|
|
|
|
|
|
|
|
|
|
|
|
// JnaFileChooser 使用 addFilter() 来添加过滤器
|
|
|
|
|
|
jnaFileChooser.addFilter("Vivid2D 模型文件 (*.model)", "model");
|
|
|
|
|
|
jnaFileChooser.setMultiSelectionEnabled(false);
|
|
|
|
|
|
jnaFileChooser.setOpenButtonText("保存");
|
|
|
|
|
|
jnaFileChooser.setMode(JnaFileChooser.Mode.Files);
|
|
|
|
|
|
|
|
|
|
|
|
// 弹出保存对话框
|
|
|
|
|
|
if (jnaFileChooser.showSaveDialog(this)) {
|
|
|
|
|
|
File fileToSave = jnaFileChooser.getSelectedFile();
|
|
|
|
|
|
String path = fileToSave.getAbsolutePath();
|
|
|
|
|
|
|
|
|
|
|
|
// 确保文件以 .model 结尾 (原生对话框可能已经处理,但 Swing 风格代码保留以防万一)
|
|
|
|
|
|
if (!path.toLowerCase().endsWith(".model")) {
|
|
|
|
|
|
path += ".model";
|
|
|
|
|
|
fileToSave = new File(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.currentModelPath = path;
|
|
|
|
|
|
setTitle("Vivid2D Editor - " + fileToSave.getName());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
statusBarLabel.setText("保存操作已取消。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-07 21:32:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|