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 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 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); } }