Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/window/MainWindow.java
tzdwindows 7 bec9ccf64f feat(vivid2D): 实现多选图层与文件拖放功能
- 添加 JnaFileChooser 库支持,替换原有 JFileChooser
- 实现图层面板的多选功能与批量操作
- 支持通过拖放方式导入 PSD 和图片文件
- 新增新建模型功能,完善文件菜单选项
-优化模型加载逻辑,支持直接加载 Model2D 对象
- 重构图层重排序逻辑,支持多图层块移动- 改进鼠标点击与悬停事件处理机制
- 修复图层操作后选中状态与缩略图刷新问题
- 添加命令行启动任务 runBoxClient与 runVivid2DClient
- 升级主窗口初始化流程与界面组件配置
2025-11-08 10:34:15 +08:00

412 lines
17 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.chuangzhou.vivid2D.window;
import com.chuangzhou.vivid2D.render.awt.*;
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;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import jnafilechooser.api.JnaFileChooser;
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("文件");
// 新增:新建模型菜单项
JMenuItem newItem = new JMenuItem("新建模型...");
newItem.addActionListener(e -> createNewModel());
fileMenu.add(newItem);
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 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);
}
}
/**
* 创建顶部工具栏。
*/
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(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);
}
});
}
/**
* 根据是否有模型加载来启用或禁用编辑组件。
*/
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() {
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();
loadModel(file.getAbsolutePath());
}
}
/**
* 加载模型并更新 UI 状态。
*/
private void loadModel(String modelPath) {
setEditComponentsEnabled(false);
statusBarLabel.setText("正在加载模型: " + modelPath);
CompletableFuture.runAsync(() -> {
Model2D model = null;
try {
// 假设 renderPanel.loadModel(String modelPath) 返回一个 CompletableFuture<Model2D>
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) {
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;
}
}
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);
}
}