feat(render): 实现关键帧详细编辑对话框及相关数据转换工具- 新增 KeyframeDetailsDialog 类,用于编辑单个关键帧值并管理同时间点的其他参数
- 添加参数ID中文映射显示功能,提升用户界面友好性 - 实现同一时间点多个参数的批量删除和快捷键支持 - 集成搜索过滤功能,便于查找特定参数- 新增 ManagementDataToJsonConverter 工具类,支持将序列化管理数据转为JSON格式 - 添加 ModelDataJsonConverter 工具类,支持模型数据序列化文件转JSON - 修改 MainWindow 保存逻辑,自动生成对应的JSON数据文件-优化界面布局和组件结构,改善用户体验
This commit is contained in:
@@ -0,0 +1,684 @@
|
|||||||
|
package com.chuangzhou.vivid2D.render.awt;
|
||||||
|
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.ParametersManagement;
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.ParametersManagement.Parameter;
|
||||||
|
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
||||||
|
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import javax.swing.table.TableCellEditor;
|
||||||
|
import javax.swing.table.TableCellRenderer;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于编辑单个关键帧值并显示同一时间点其他参数信息的对话框。
|
||||||
|
*/
|
||||||
|
public class KeyframeDetailsDialog extends JDialog {
|
||||||
|
|
||||||
|
private static final Color COLOR_BACKGROUND = new Color(50, 50, 50);
|
||||||
|
private static final Color COLOR_FOREGROUND = new Color(220, 220, 220);
|
||||||
|
private static final Color COLOR_HEADER = new Color(70, 70, 70);
|
||||||
|
private static final Color COLOR_ACCENT_1 = new Color(230, 80, 80); // 用于删除按钮
|
||||||
|
private static final Color COLOR_ACCENT_2 = new Color(80, 150, 230);
|
||||||
|
private static final Color COLOR_GRID = new Color(60, 60, 60);
|
||||||
|
private static final Border DIALOG_PADDING = new EmptyBorder(10, 10, 10, 10);
|
||||||
|
private static final float FLOAT_TOLERANCE = 1e-6f; // 浮点数比较容差
|
||||||
|
|
||||||
|
private final AnimationParameter parameter;
|
||||||
|
private final ParametersManagement parametersManagement;
|
||||||
|
private final ModelPart modelPart;
|
||||||
|
private final SortedSet<Float> keyframesSet;
|
||||||
|
private final Float originalValue;
|
||||||
|
private Float confirmedValue = null;
|
||||||
|
|
||||||
|
private final JTextField valueField = new JTextField(15);
|
||||||
|
private final JSlider valueSlider = new JSlider(0, 1000); // 使用 0-1000 归一化
|
||||||
|
|
||||||
|
// [新增] 搜索字段
|
||||||
|
private final JTextField searchField = new JTextField(15);
|
||||||
|
|
||||||
|
// 内部类,用于存储和显示同一时间点的其他参数信息
|
||||||
|
private final RelatedParametersTableModel relatedTableModel;
|
||||||
|
private final JTable relatedTable;
|
||||||
|
|
||||||
|
// [新增] 用于存储所有相关参数的完整列表(过滤前)
|
||||||
|
private List<RelatedParameterInfo> allRelatedParameters = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
public KeyframeDetailsDialog(Window owner, AnimationParameter parameter, Float value, SortedSet<Float> keyframesSet,
|
||||||
|
ParametersManagement parametersManagement, ModelPart modelPart) {
|
||||||
|
super(owner, "编辑关键帧: " + parameter.getId(), ModalityType.APPLICATION_MODAL);
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.originalValue = value;
|
||||||
|
this.keyframesSet = keyframesSet;
|
||||||
|
this.parametersManagement = parametersManagement;
|
||||||
|
this.modelPart = modelPart;
|
||||||
|
|
||||||
|
// 字段初始化
|
||||||
|
// [修改] 传递中文 ID 映射
|
||||||
|
this.relatedTableModel = new RelatedParametersTableModel(modelPart, parametersManagement, this::refreshTableData);
|
||||||
|
this.relatedTable = new JTable(relatedTableModel);
|
||||||
|
|
||||||
|
initUI();
|
||||||
|
loadData(value);
|
||||||
|
fetchRelatedParameters(); // 查询并加载所有相关参数,并初始化表格
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已修改的 record:新增 recordIndex,用于精确指向 ModelPart 完整记录中的条目
|
||||||
|
private record RelatedParameterInfo(String paramId, Object value, int recordIndex) {}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// 内部类:RelatedParametersTableModel (处理表格数据和删除逻辑)
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
private class RelatedParametersTableModel extends AbstractTableModel {
|
||||||
|
private final String[] columnNames = {"参数 ID", "值", "操作"};
|
||||||
|
private final List<RelatedParameterInfo> data = new ArrayList<>();
|
||||||
|
private final ModelPart modelPart;
|
||||||
|
private final ParametersManagement management;
|
||||||
|
private final Runnable refreshCallback;
|
||||||
|
|
||||||
|
// [新增] 参数ID 中文映射
|
||||||
|
private final Map<String, String> paramIdMap;
|
||||||
|
|
||||||
|
|
||||||
|
public RelatedParametersTableModel(ModelPart modelPart, ParametersManagement management, Runnable refreshCallback) {
|
||||||
|
this.modelPart = modelPart;
|
||||||
|
this.management = management;
|
||||||
|
this.refreshCallback = refreshCallback;
|
||||||
|
// 初始化中文映射
|
||||||
|
this.paramIdMap = new HashMap<>();
|
||||||
|
this.paramIdMap.put("position", "位置");
|
||||||
|
this.paramIdMap.put("rotate", "旋转");
|
||||||
|
this.paramIdMap.put("secondaryVertex", "二级顶点变形器(顶点位置)");
|
||||||
|
this.paramIdMap.put("scale", "缩放");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<RelatedParameterInfo> list) {
|
||||||
|
data.clear();
|
||||||
|
data.addAll(list);
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() { return data.size(); }
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() { return columnNames.length; }
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int column) { return columnNames[column]; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
|
RelatedParameterInfo info = data.get(rowIndex);
|
||||||
|
return switch (columnIndex) {
|
||||||
|
case 0 -> getDisplayParamId(info.paramId()); // [修改] 使用显示 ID
|
||||||
|
case 1 -> info.value();
|
||||||
|
case 2 -> "删除"; // Button text
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新增] 获取用于显示的参数 ID (中文映射)
|
||||||
|
*/
|
||||||
|
private String getDisplayParamId(String paramId) {
|
||||||
|
return paramIdMap.getOrDefault(paramId, paramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
// "值"列 (1) 不可编辑,"操作"列 (2) 可点击 (仅允许删除)
|
||||||
|
return columnIndex == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 setValueAt 方法,禁用修改功能。
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单行删除操作 (用于按钮点击)。
|
||||||
|
*/
|
||||||
|
public void deleteRow(int rowIndex) {
|
||||||
|
if (rowIndex >= 0 && rowIndex < data.size()) {
|
||||||
|
RelatedParameterInfo info = data.get(rowIndex);
|
||||||
|
String paramId = info.paramId();
|
||||||
|
int recordIndex = info.recordIndex();
|
||||||
|
|
||||||
|
int confirm = JOptionPane.showConfirmDialog(KeyframeDetailsDialog.this,
|
||||||
|
String.format("确定要删除参数 '%s' 的此关键帧记录吗?", getDisplayParamId(paramId)),
|
||||||
|
"确认删除", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
|
|
||||||
|
if (confirm == JOptionPane.YES_OPTION) {
|
||||||
|
// 调用 ParametersManagement 的新 API 进行精确删除
|
||||||
|
management.removeParameterAt(modelPart, recordIndex);
|
||||||
|
|
||||||
|
// [修改] 不直接调用 fetchRelatedParameters,而是调用 refreshCallback,它会重新查询并刷新 UI
|
||||||
|
refreshCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理多行删除操作 (用于快捷键)。
|
||||||
|
* @param modelRows 模型索引数组。
|
||||||
|
*/
|
||||||
|
public void deleteRows(int[] modelRows) {
|
||||||
|
if (modelRows.length == 0) return;
|
||||||
|
|
||||||
|
// 提取要删除的 ModelPart 记录的原始索引,并按降序排序。
|
||||||
|
List<Integer> recordIndices = Arrays.stream(modelRows)
|
||||||
|
.mapToObj(data::get)
|
||||||
|
.map(RelatedParameterInfo::recordIndex)
|
||||||
|
.sorted(Collections.reverseOrder())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 确认对话框
|
||||||
|
int confirm = JOptionPane.showConfirmDialog(KeyframeDetailsDialog.this,
|
||||||
|
String.format("确定要删除选中的 %d 个关键帧参数记录吗?", recordIndices.size()),
|
||||||
|
"确认批量删除", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
|
|
||||||
|
if (confirm == JOptionPane.YES_OPTION) {
|
||||||
|
for (int index : recordIndices) {
|
||||||
|
// 调用 ParametersManagement 的新 API 进行精确删除
|
||||||
|
management.removeParameterAt(modelPart, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [修改] 重新获取数据以刷新 UI (只需要一次)
|
||||||
|
refreshCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// 内部类:ButtonRenderer & ButtonEditor (处理删除按钮)
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
private class ButtonRenderer extends JButton implements TableCellRenderer {
|
||||||
|
public ButtonRenderer() {
|
||||||
|
setOpaque(true);
|
||||||
|
setBackground(COLOR_ACCENT_1);
|
||||||
|
setForeground(Color.WHITE);
|
||||||
|
setFocusPainted(false);
|
||||||
|
setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID),
|
||||||
|
new EmptyBorder(0, 0, 0, 0)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||||
|
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
setText((value == null) ? "" : value.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ButtonEditor extends AbstractCellEditor implements TableCellEditor {
|
||||||
|
private final JButton button;
|
||||||
|
private int currentRow;
|
||||||
|
|
||||||
|
public ButtonEditor() {
|
||||||
|
button = new JButton();
|
||||||
|
button.setOpaque(true);
|
||||||
|
button.setBackground(COLOR_ACCENT_1.darker());
|
||||||
|
button.setForeground(Color.WHITE);
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID),
|
||||||
|
new EmptyBorder(0, 0, 0, 0)
|
||||||
|
));
|
||||||
|
|
||||||
|
button.addActionListener((ActionEvent e) -> {
|
||||||
|
// 在 Event Dispatch Thread 中执行删除逻辑
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
fireEditingStopped();
|
||||||
|
relatedTableModel.deleteRow(currentRow); // 调用单行删除
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellEditorComponent(JTable table, Object value,
|
||||||
|
boolean isSelected, int row, int column) {
|
||||||
|
currentRow = row;
|
||||||
|
button.setText((value == null) ? "" : value.toString());
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCellEditorValue() {
|
||||||
|
return button.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [修改] 查询在当前关键帧值处,ModelPart 中所有已记录的参数变化,并存储在 allRelatedParameters 中。
|
||||||
|
*/
|
||||||
|
private void fetchRelatedParameters() {
|
||||||
|
// 获取该 ModelPart 的所有历史记录
|
||||||
|
Parameter fullRecord = parametersManagement.getModelPartParameters(modelPart);
|
||||||
|
if (fullRecord == null) {
|
||||||
|
allRelatedParameters = new ArrayList<>();
|
||||||
|
relatedTableModel.setData(allRelatedParameters); // 刷新表格
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AnimationParameter> anims = fullRecord.animationParameter();
|
||||||
|
List<Float> keyframes = fullRecord.keyframe();
|
||||||
|
List<String> paramIds = fullRecord.paramId();
|
||||||
|
List<Object> values = fullRecord.value();
|
||||||
|
|
||||||
|
int size = Math.min(keyframes.size(), Math.min(anims.size(), Math.min(paramIds.size(), values.size())));
|
||||||
|
|
||||||
|
List<RelatedParameterInfo> related = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Float currentKeyframe = keyframes.get(i);
|
||||||
|
AnimationParameter recordAnimParam = anims.get(i);
|
||||||
|
|
||||||
|
// 检查 1 (参数): 使用 equals 判断 AnimationParameter 是否与当前编辑的参数相等
|
||||||
|
boolean isSameAnimationParameter = recordAnimParam != null && recordAnimParam.equals(parameter);
|
||||||
|
|
||||||
|
// 检查 2 (时间点): 使用 Objects.equals 判断 keyframe 是否与 originalValue 相等 (处理 null)
|
||||||
|
boolean isSameKeyframe = Objects.equals(currentKeyframe, originalValue);
|
||||||
|
|
||||||
|
if (isSameAnimationParameter && isSameKeyframe) {
|
||||||
|
// [修改] 不再排除当前正在编辑的参数本身
|
||||||
|
related.add(new RelatedParameterInfo(
|
||||||
|
paramIds.get(i),
|
||||||
|
values.get(i),
|
||||||
|
i // <-- 记录此条目在 ModelPart 完整记录中的原始索引 i
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allRelatedParameters = related; // 存储完整列表
|
||||||
|
applyFilter(); // 应用过滤器刷新表格
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新增] 根据搜索框内容过滤 allRelatedParameters 并更新表格。
|
||||||
|
*/
|
||||||
|
private void applyFilter() {
|
||||||
|
String searchText = searchField.getText().trim().toLowerCase();
|
||||||
|
|
||||||
|
if (searchText.isEmpty()) {
|
||||||
|
relatedTableModel.setData(allRelatedParameters);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RelatedParameterInfo> filteredList = allRelatedParameters.stream()
|
||||||
|
.filter(info -> {
|
||||||
|
// 过滤逻辑:匹配原始 ID 或中文显示 ID
|
||||||
|
String paramId = info.paramId().toLowerCase();
|
||||||
|
String displayId = relatedTableModel.getDisplayParamId(info.paramId()).toLowerCase();
|
||||||
|
|
||||||
|
return paramId.contains(searchText) || displayId.contains(searchText);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
relatedTableModel.setData(filteredList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [新增] 重新查询所有数据并应用当前过滤器。用于删除操作后的刷新。
|
||||||
|
*/
|
||||||
|
private void refreshTableData() {
|
||||||
|
fetchRelatedParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 处理表格选中的多行删除
|
||||||
|
private void deleteSelectedRelatedRows() {
|
||||||
|
int[] selectedViewRows = relatedTable.getSelectedRows();
|
||||||
|
if (selectedViewRows.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换视图索引到模型索引
|
||||||
|
int[] modelRowsToDelete = new int[selectedViewRows.length];
|
||||||
|
for(int i = 0; i < selectedViewRows.length; i++) {
|
||||||
|
modelRowsToDelete[i] = relatedTable.convertRowIndexToModel(selectedViewRows[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用批量删除方法。确认框已移至 model 内部。
|
||||||
|
relatedTableModel.deleteRows(modelRowsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void initUI() {
|
||||||
|
setSize(550, 450);
|
||||||
|
setMinimumSize(new Dimension(500, 350));
|
||||||
|
setLocationRelativeTo(getOwner());
|
||||||
|
getContentPane().setBackground(COLOR_BACKGROUND);
|
||||||
|
setLayout(new BorderLayout(10, 10));
|
||||||
|
((JPanel) getContentPane()).setBorder(DIALOG_PADDING);
|
||||||
|
|
||||||
|
// --- 顶部信息面板 (ID, Range, Default) ---
|
||||||
|
JPanel topPanel = new JPanel(new GridLayout(4, 2, 5, 5));
|
||||||
|
topPanel.setBackground(COLOR_BACKGROUND);
|
||||||
|
topPanel.add(createLabel("参数 ID:"));
|
||||||
|
// [修改] 显示参数 ID 的中文映射
|
||||||
|
topPanel.add(createValueLabel(relatedTableModel.getDisplayParamId(parameter.getId())));
|
||||||
|
topPanel.add(createLabel("Model Part:"));
|
||||||
|
topPanel.add(createValueLabel(modelPart != null ? modelPart.getName() : "N/A"));
|
||||||
|
topPanel.add(createLabel("值域:"));
|
||||||
|
String range = String.format("[%.3f, %.3f]", parameter.getMinValue(), parameter.getMaxValue());
|
||||||
|
topPanel.add(createValueLabel(range));
|
||||||
|
topPanel.add(createLabel("默认值:"));
|
||||||
|
topPanel.add(createValueLabel(String.format("%.3f", parameter.getDefaultValue())));
|
||||||
|
|
||||||
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// --- 中部主编辑/信息区 ---
|
||||||
|
JSplitPane centerSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||||
|
centerSplit.setOpaque(false);
|
||||||
|
centerSplit.setDividerLocation(150);
|
||||||
|
centerSplit.setDividerSize(5);
|
||||||
|
centerSplit.setBorder(null);
|
||||||
|
|
||||||
|
|
||||||
|
// 1. 关键帧值编辑面板
|
||||||
|
JPanel editPanel = createEditPanel();
|
||||||
|
centerSplit.setTopComponent(editPanel);
|
||||||
|
|
||||||
|
// 2. 相关参数列表面板
|
||||||
|
JPanel listPanel = createRelatedParametersListPanel();
|
||||||
|
centerSplit.setBottomComponent(listPanel);
|
||||||
|
|
||||||
|
add(centerSplit, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
||||||
|
// --- 底部操作栏 ---
|
||||||
|
add(createBottomPanel(), BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
// Esc 键关闭 = Cancel
|
||||||
|
getRootPane().registerKeyboardAction(e -> onCancel(),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
|
||||||
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createEditPanel() {
|
||||||
|
JPanel panel = new JPanel(new GridBagLayout());
|
||||||
|
panel.setBackground(COLOR_BACKGROUND);
|
||||||
|
panel.setBorder(BorderFactory.createTitledBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID), "关键帧值编辑",
|
||||||
|
0, 0, getFont(), COLOR_ACCENT_2
|
||||||
|
));
|
||||||
|
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
// 标签
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
gbc.weightx = 0.0;
|
||||||
|
panel.add(createLabel("关键帧值:"), gbc);
|
||||||
|
|
||||||
|
// 文本字段
|
||||||
|
styleTextField(valueField);
|
||||||
|
valueField.addActionListener(e -> updateSliderFromField());
|
||||||
|
valueField.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusLost(java.awt.event.FocusEvent e) {
|
||||||
|
updateSliderFromField();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
panel.add(valueField, gbc);
|
||||||
|
|
||||||
|
// 滑块
|
||||||
|
valueSlider.setBackground(COLOR_BACKGROUND);
|
||||||
|
valueSlider.setForeground(COLOR_FOREGROUND);
|
||||||
|
valueSlider.setPaintTicks(false);
|
||||||
|
valueSlider.setPaintLabels(false);
|
||||||
|
valueSlider.addChangeListener(e -> updateFieldFromSlider());
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
panel.add(valueSlider, gbc);
|
||||||
|
|
||||||
|
// 底部留白
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 2;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weighty = 1.0;
|
||||||
|
panel.add(new JPanel() {{ setOpaque(false); }}, gbc);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createRelatedParametersListPanel() {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout(5, 5)); // [修改] 增加边距
|
||||||
|
panel.setBackground(COLOR_BACKGROUND);
|
||||||
|
panel.setBorder(BorderFactory.createTitledBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID), "在该关键帧值上所有 ModelPart 参数值",
|
||||||
|
0, 0, getFont(), COLOR_FOREGROUND
|
||||||
|
));
|
||||||
|
|
||||||
|
// --- 搜索面板 ---
|
||||||
|
JPanel searchPanel = new JPanel(new BorderLayout(5, 0));
|
||||||
|
searchPanel.setBackground(COLOR_BACKGROUND);
|
||||||
|
searchPanel.add(createLabel("搜索参数 ID:"), BorderLayout.WEST);
|
||||||
|
styleTextField(searchField);
|
||||||
|
searchPanel.add(searchField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// [新增] 搜索框事件监听
|
||||||
|
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) { applyFilter(); }
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) { applyFilter(); }
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) { applyFilter(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.add(searchPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
|
||||||
|
relatedTable.setBackground(COLOR_BACKGROUND);
|
||||||
|
relatedTable.setForeground(COLOR_FOREGROUND);
|
||||||
|
relatedTable.setGridColor(COLOR_GRID);
|
||||||
|
relatedTable.getTableHeader().setBackground(COLOR_HEADER);
|
||||||
|
relatedTable.getTableHeader().setForeground(COLOR_FOREGROUND);
|
||||||
|
relatedTable.setFont(getFont().deriveFont(12f));
|
||||||
|
relatedTable.setRowHeight(20);
|
||||||
|
|
||||||
|
// 允许批量选择
|
||||||
|
relatedTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
|
|
||||||
|
// 值列右对齐 (columnIndex == 1)
|
||||||
|
DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
|
||||||
|
rightRenderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||||
|
rightRenderer.setBackground(COLOR_BACKGROUND);
|
||||||
|
rightRenderer.setForeground(COLOR_FOREGROUND);
|
||||||
|
relatedTable.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);
|
||||||
|
|
||||||
|
// 设置 "操作" 列的 Renderer 和 Editor (columnIndex == 2)
|
||||||
|
relatedTable.getColumnModel().getColumn(2).setCellRenderer(new ButtonRenderer());
|
||||||
|
relatedTable.getColumnModel().getColumn(2).setCellEditor(new ButtonEditor());
|
||||||
|
relatedTable.getColumnModel().getColumn(2).setMaxWidth(60); // 限制按钮列宽度
|
||||||
|
relatedTable.getColumnModel().getColumn(2).setMinWidth(60);
|
||||||
|
|
||||||
|
// 添加 DELETE 和 BACK_SPACE 快捷键绑定
|
||||||
|
InputMap inputMap = relatedTable.getInputMap(JComponent.WHEN_FOCUSED);
|
||||||
|
ActionMap actionMap = relatedTable.getActionMap();
|
||||||
|
|
||||||
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteSelected");
|
||||||
|
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "deleteSelected");
|
||||||
|
|
||||||
|
actionMap.put("deleteSelected", new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
deleteSelectedRelatedRows();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
JScrollPane scroll = new JScrollPane(relatedTable);
|
||||||
|
scroll.setBackground(COLOR_BACKGROUND);
|
||||||
|
scroll.getViewport().setBackground(COLOR_BACKGROUND);
|
||||||
|
scroll.setBorder(BorderFactory.createLineBorder(COLOR_GRID));
|
||||||
|
|
||||||
|
panel.add(scroll, BorderLayout.CENTER);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createBottomPanel() {
|
||||||
|
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
||||||
|
panel.setBackground(COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
JButton okButton = new JButton("确定");
|
||||||
|
JButton cancelButton = new JButton("取消");
|
||||||
|
styleButton(okButton);
|
||||||
|
styleButton(cancelButton);
|
||||||
|
|
||||||
|
panel.add(okButton);
|
||||||
|
panel.add(cancelButton);
|
||||||
|
|
||||||
|
okButton.addActionListener(e -> onOK());
|
||||||
|
cancelButton.addActionListener(e -> onCancel());
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createLabel(String text) {
|
||||||
|
JLabel label = new JLabel(text);
|
||||||
|
label.setForeground(COLOR_FOREGROUND.darker());
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createValueLabel(String text) {
|
||||||
|
JLabel label = new JLabel(text);
|
||||||
|
label.setForeground(COLOR_FOREGROUND);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData(Float value) {
|
||||||
|
// 设置滑块和文本字段的初始值
|
||||||
|
if (value != null) {
|
||||||
|
valueField.setText(String.format("%.6f", value));
|
||||||
|
|
||||||
|
float range = parameter.getMaxValue() - parameter.getMinValue();
|
||||||
|
if (range > 0) {
|
||||||
|
int sliderValue = (int) (((value - parameter.getMinValue()) / range) * 1000f);
|
||||||
|
valueSlider.setValue(sliderValue);
|
||||||
|
} else {
|
||||||
|
valueSlider.setValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFieldFromSlider() {
|
||||||
|
float normalized = valueSlider.getValue() / 1000f;
|
||||||
|
float value = parameter.getMinValue() + normalized * (parameter.getMaxValue() - parameter.getMinValue());
|
||||||
|
valueField.setText(String.format("%.6f", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSliderFromField() {
|
||||||
|
try {
|
||||||
|
float val = Float.parseFloat(valueField.getText().trim());
|
||||||
|
// 钳位
|
||||||
|
val = Math.max(parameter.getMinValue(), Math.min(parameter.getMaxValue(), val));
|
||||||
|
valueField.setText(String.format("%.6f", val)); // 格式化并显示钳位后的值
|
||||||
|
|
||||||
|
float range = parameter.getMaxValue() - parameter.getMinValue();
|
||||||
|
if (range > 0) {
|
||||||
|
int sliderValue = (int) (((val - parameter.getMinValue()) / range) * 1000f);
|
||||||
|
valueSlider.setValue(sliderValue);
|
||||||
|
} else {
|
||||||
|
valueSlider.setValue(0);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "无效的数值", "格式错误", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onOK() {
|
||||||
|
try {
|
||||||
|
float newValue = Float.parseFloat(valueField.getText().trim());
|
||||||
|
// 钳位
|
||||||
|
newValue = Math.max(parameter.getMinValue(), Math.min(parameter.getMaxValue(), newValue));
|
||||||
|
|
||||||
|
if (originalValue != null && Math.abs(originalValue - newValue) > FLOAT_TOLERANCE) {
|
||||||
|
// 只有值发生变化时才移除旧值
|
||||||
|
keyframesSet.remove(originalValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查新值是否已存在
|
||||||
|
if (keyframesSet.contains(newValue)) {
|
||||||
|
// 如果是原来的值或已存在的值,直接确认
|
||||||
|
this.confirmedValue = newValue;
|
||||||
|
} else {
|
||||||
|
// 添加新值
|
||||||
|
keyframesSet.add(newValue);
|
||||||
|
this.confirmedValue = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "请输入有效的浮点数", "格式错误", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
this.confirmedValue = null;
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void styleButton(JButton button) {
|
||||||
|
button.setBackground(COLOR_HEADER);
|
||||||
|
button.setForeground(COLOR_FOREGROUND);
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID),
|
||||||
|
new EmptyBorder(5, 10, 5, 10)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void styleTextField(JTextField field) {
|
||||||
|
field.setBackground(COLOR_HEADER);
|
||||||
|
field.setForeground(COLOR_FOREGROUND);
|
||||||
|
field.setCaretColor(COLOR_FOREGROUND);
|
||||||
|
field.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(COLOR_GRID),
|
||||||
|
new EmptyBorder(4, 4, 4, 4)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示对话框。
|
||||||
|
* @param owner 父窗口
|
||||||
|
* @param parameter 动画参数
|
||||||
|
* @param currentValue 要编辑的关键帧值
|
||||||
|
* @param keyframesSet 关键帧集合 (用于添加/移除操作)
|
||||||
|
* @param parametersManagement 参数管理实例
|
||||||
|
* @param modelPart 模型部件
|
||||||
|
* @return 如果用户点击确定,返回新的关键帧值;否则返回 null。
|
||||||
|
*/
|
||||||
|
public static Float showEditor(Window owner, AnimationParameter parameter, Float currentValue, SortedSet<Float> keyframesSet,
|
||||||
|
ParametersManagement parametersManagement, ModelPart modelPart) {
|
||||||
|
if (parameter == null || currentValue == null || parametersManagement == null || modelPart == null) return null;
|
||||||
|
KeyframeDetailsDialog dialog = new KeyframeDetailsDialog(owner, parameter, currentValue, keyframesSet, parametersManagement, modelPart);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.confirmedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.chuangzhou.vivid2D.render.awt;
|
package com.chuangzhou.vivid2D.render.awt;
|
||||||
|
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.ParametersManagement;
|
||||||
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
||||||
|
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
@@ -36,6 +38,8 @@ public class KeyframeEditorDialog extends JDialog {
|
|||||||
* 临时存储编辑,直到用户点击 "OK"
|
* 临时存储编辑,直到用户点击 "OK"
|
||||||
*/
|
*/
|
||||||
private final TreeSet<Float> tempKeyframes;
|
private final TreeSet<Float> tempKeyframes;
|
||||||
|
private final ParametersManagement parametersManagement;
|
||||||
|
private final ModelPart modelPart;
|
||||||
|
|
||||||
// 用于跟踪 OK/Cancel 状态
|
// 用于跟踪 OK/Cancel 状态
|
||||||
private boolean confirmed = false;
|
private boolean confirmed = false;
|
||||||
@@ -345,15 +349,17 @@ public class KeyframeEditorDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public KeyframeEditorDialog(Window owner, AnimationParameter parameter) {
|
public KeyframeEditorDialog(Window owner, AnimationParameter parameter, ParametersManagement parametersManagement, ModelPart modelPart) {
|
||||||
super(owner, "关键帧编辑器: " + parameter.getId(), ModalityType.APPLICATION_MODAL);
|
super(owner, "关键帧编辑器: " + parameter.getId(), ModalityType.APPLICATION_MODAL);
|
||||||
this.parameter = parameter;
|
this.parameter = parameter;
|
||||||
|
this.modelPart = modelPart;
|
||||||
|
|
||||||
this.tempKeyframes = new TreeSet<>(parameter.getKeyframes());
|
this.tempKeyframes = new TreeSet<>(parameter.getKeyframes());
|
||||||
|
|
||||||
this.ruler = new EditorRuler();
|
this.ruler = new EditorRuler();
|
||||||
this.tableModel = new KeyframeTableModel();
|
this.tableModel = new KeyframeTableModel();
|
||||||
this.keyframeTable = new JTable(tableModel);
|
this.keyframeTable = new JTable(tableModel);
|
||||||
|
this.parametersManagement = parametersManagement;
|
||||||
|
|
||||||
initUI();
|
initUI();
|
||||||
updateAllUI();
|
updateAllUI();
|
||||||
@@ -396,16 +402,12 @@ public class KeyframeEditorDialog extends JDialog {
|
|||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2) {
|
||||||
int row = keyframeTable.rowAtPoint(e.getPoint());
|
int row = keyframeTable.rowAtPoint(e.getPoint());
|
||||||
int col = keyframeTable.columnAtPoint(e.getPoint());
|
//int col = keyframeTable.columnAtPoint(e.getPoint()); // 不需要列判断
|
||||||
// 确保是 "值" 列 (索引 1)
|
|
||||||
if (row >= 0 && col == 1) {
|
if (row >= 0) {
|
||||||
// 启动编辑
|
Float selectedValue = tableModel.getValueAtRow(row);
|
||||||
if (keyframeTable.editCellAt(row, col)) {
|
if (selectedValue != null) {
|
||||||
// 尝试选中编辑器中的文本
|
showKeyframeDetailsDialog(selectedValue, row);
|
||||||
Component editor = keyframeTable.getEditorComponent();
|
|
||||||
if (editor instanceof JTextField) {
|
|
||||||
((JTextField)editor).selectAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,6 +416,37 @@ public class KeyframeEditorDialog extends JDialog {
|
|||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示关键帧详细信息对话框
|
||||||
|
*/
|
||||||
|
private void showKeyframeDetailsDialog(Float currentValue, int currentRow) {
|
||||||
|
// KeyframeDetailsDialog 将负责处理值的更新和在 tempKeyframes 中的替换
|
||||||
|
Float newValue = KeyframeDetailsDialog.showEditor(
|
||||||
|
this,
|
||||||
|
parameter,
|
||||||
|
currentValue,
|
||||||
|
tempKeyframes, // 将 Set 传递给子对话框进行修改
|
||||||
|
parametersManagement, // 传递 ParametersManagement
|
||||||
|
modelPart // 传递 ModelPart
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newValue != null) {
|
||||||
|
// 对话框已更新 tempKeyframes
|
||||||
|
|
||||||
|
// 彻底刷新UI (因为 Set 排序可能已改变)
|
||||||
|
updateAllUI();
|
||||||
|
|
||||||
|
// 刷新后,重新定位并选中新值的行
|
||||||
|
int newRow = tableModel.getRowForValue(newValue);
|
||||||
|
if (newRow != -1) {
|
||||||
|
keyframeTable.setRowSelectionInterval(newRow, newRow);
|
||||||
|
keyframeTable.scrollRectToVisible(keyframeTable.getCellRect(newRow, 0, true));
|
||||||
|
} else {
|
||||||
|
keyframeTable.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private JPanel createListActionsPanel() {
|
private JPanel createListActionsPanel() {
|
||||||
JPanel actionsPanel = new JPanel();
|
JPanel actionsPanel = new JPanel();
|
||||||
actionsPanel.setBackground(COLOR_BACKGROUND);
|
actionsPanel.setBackground(COLOR_BACKGROUND);
|
||||||
@@ -668,9 +701,9 @@ public class KeyframeEditorDialog extends JDialog {
|
|||||||
/**
|
/**
|
||||||
* 显示对话框。
|
* 显示对话框。
|
||||||
*/
|
*/
|
||||||
public static boolean showEditor(Window owner, AnimationParameter parameter) {
|
public static boolean showEditor(Window owner, AnimationParameter parameter,ParametersManagement parametersManagement, ModelPart modelPart) {
|
||||||
if (parameter == null) return false;
|
if (parameter == null) return false;
|
||||||
KeyframeEditorDialog dialog = new KeyframeEditorDialog(owner, parameter);
|
KeyframeEditorDialog dialog = new KeyframeEditorDialog(owner, parameter,parametersManagement,modelPart);
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
return dialog.isConfirmed();
|
return dialog.isConfirmed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.chuangzhou.vivid2D.render.awt;
|
package com.chuangzhou.vivid2D.render.awt;
|
||||||
|
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.ParametersManagement;
|
||||||
import com.chuangzhou.vivid2D.render.awt.tools.VertexDeformationTool;
|
import com.chuangzhou.vivid2D.render.awt.tools.VertexDeformationTool;
|
||||||
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
import com.chuangzhou.vivid2D.render.model.AnimationParameter;
|
||||||
import com.chuangzhou.vivid2D.render.model.Model2D;
|
import com.chuangzhou.vivid2D.render.model.Model2D;
|
||||||
@@ -33,7 +34,7 @@ public class ParametersPanel extends JPanel {
|
|||||||
private final ModelRenderPanel renderPanel;
|
private final ModelRenderPanel renderPanel;
|
||||||
private final Model2D model;
|
private final Model2D model;
|
||||||
private AnimationParameter selectParameter;
|
private AnimationParameter selectParameter;
|
||||||
|
public ParametersManagement parametersManagement;
|
||||||
// UI
|
// UI
|
||||||
private final CardLayout cardLayout = new CardLayout();
|
private final CardLayout cardLayout = new CardLayout();
|
||||||
private final JPanel cardRoot = new JPanel(cardLayout);
|
private final JPanel cardRoot = new JPanel(cardLayout);
|
||||||
@@ -152,7 +153,7 @@ public class ParametersPanel extends JPanel {
|
|||||||
AnimationParameter p = parameterList.getSelectedValue();
|
AnimationParameter p = parameterList.getSelectedValue();
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
// 弹出编辑器
|
// 弹出编辑器
|
||||||
KeyframeEditorDialog.showEditor(SwingUtilities.getWindowAncestor(ParametersPanel.this), p);
|
KeyframeEditorDialog.showEditor(SwingUtilities.getWindowAncestor(ParametersPanel.this), p, parametersManagement, currentPart);
|
||||||
// 编辑器关闭后,刷新滑块的显示
|
// 编辑器关闭后,刷新滑块的显示
|
||||||
valueSlider.repaint();
|
valueSlider.repaint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ParametersManagement {
|
public class ParametersManagement {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class);
|
private static final Logger logger = LoggerFactory.getLogger(ParametersManagement.class);
|
||||||
@@ -26,6 +28,7 @@ public class ParametersManagement {
|
|||||||
public static ParametersManagement getInstance(ParametersPanel parametersPanel) {
|
public static ParametersManagement getInstance(ParametersPanel parametersPanel) {
|
||||||
String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data";
|
String managementFilePath = parametersPanel.getRenderPanel().getGlContextManager().getModelPath() + ".data";
|
||||||
File managementFile = new File(managementFilePath);
|
File managementFile = new File(managementFilePath);
|
||||||
|
ParametersManagement instance = new ParametersManagement(parametersPanel);
|
||||||
if (managementFile.exists()) {
|
if (managementFile.exists()) {
|
||||||
logger.info("已找到参数管理数据文件: {}", managementFilePath);
|
logger.info("已找到参数管理数据文件: {}", managementFilePath);
|
||||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(managementFile))) {
|
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(managementFile))) {
|
||||||
@@ -35,9 +38,10 @@ public class ParametersManagement {
|
|||||||
List<ModelPart> parts = parametersPanel.getRenderPanel().getModel().getParts();
|
List<ModelPart> parts = parametersPanel.getRenderPanel().getModel().getParts();
|
||||||
ParametersManagement management = managementData.toParametersManagement(parametersPanel, parts);
|
ParametersManagement management = managementData.toParametersManagement(parametersPanel, parts);
|
||||||
//logger.info("参数管理数据转换成功: {}", management);
|
//logger.info("参数管理数据转换成功: {}", management);
|
||||||
ParametersManagement instance = new ParametersManagement(parametersPanel);
|
instance = new ParametersManagement(parametersPanel);
|
||||||
instance.oldValues = management.oldValues;
|
instance.oldValues = management.oldValues;
|
||||||
//logger.info("参数管理数据加载成功: {}", management.oldValues);
|
//logger.info("参数管理数据加载成功: {}", management.oldValues);
|
||||||
|
parametersPanel.parametersManagement = instance;
|
||||||
return instance;
|
return instance;
|
||||||
} else {
|
} else {
|
||||||
logger.warn("加载参数管理数据失败: 预期第二个对象为ParametersManagementData,但实际为 {}", o != null ? o.getClass().getName() : "null");
|
logger.warn("加载参数管理数据失败: 预期第二个对象为ParametersManagementData,但实际为 {}", o != null ? o.getClass().getName() : "null");
|
||||||
@@ -48,7 +52,8 @@ public class ParametersManagement {
|
|||||||
} else {
|
} else {
|
||||||
logger.info("未找到参数管理数据文件 {},创建新的参数管理实例", managementFilePath);
|
logger.info("未找到参数管理数据文件 {},创建新的参数管理实例", managementFilePath);
|
||||||
}
|
}
|
||||||
return new ParametersManagement(parametersPanel);
|
parametersPanel.parametersManagement = instance;
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +93,46 @@ public class ParametersManagement {
|
|||||||
return parametersPanel.getSelectParameter().copy();
|
return parametersPanel.getSelectParameter().copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精确地从 ModelPart 的记录中删除指定索引的参数条目。
|
||||||
|
* * @param targetModelPart 目标 ModelPart
|
||||||
|
* @param indexToRemove 在该 ModelPart 记录内部的索引
|
||||||
|
*/
|
||||||
|
public void removeParameterAt(ModelPart targetModelPart, int indexToRemove) {
|
||||||
|
for (int i = 0; i < oldValues.size(); i++) {
|
||||||
|
Parameter existingParameter = oldValues.get(i);
|
||||||
|
if (existingParameter.modelPart().equals(targetModelPart)) {
|
||||||
|
int size = existingParameter.keyframe().size();
|
||||||
|
if (indexToRemove >= 0 && indexToRemove < size) {
|
||||||
|
List<AnimationParameter> newAnimationParameters = new ArrayList<>(existingParameter.animationParameter());
|
||||||
|
List<String> newParamIds = new ArrayList<>(existingParameter.paramId());
|
||||||
|
List<Object> newValues = new ArrayList<>(existingParameter.value());
|
||||||
|
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
|
||||||
|
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
|
||||||
|
newAnimationParameters.remove(indexToRemove);
|
||||||
|
newParamIds.remove(indexToRemove);
|
||||||
|
newValues.remove(indexToRemove);
|
||||||
|
newKeyframes.remove(indexToRemove);
|
||||||
|
newIsKeyframes.remove(indexToRemove);
|
||||||
|
if (newKeyframes.isEmpty()) {
|
||||||
|
oldValues.remove(i);
|
||||||
|
} else {
|
||||||
|
Parameter updatedParameter = new Parameter(
|
||||||
|
targetModelPart,
|
||||||
|
newAnimationParameters,
|
||||||
|
newParamIds,
|
||||||
|
newValues,
|
||||||
|
newKeyframes,
|
||||||
|
newIsKeyframes
|
||||||
|
);
|
||||||
|
oldValues.set(i, updatedParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听参数变化 (强制添加新记录,即使 paramId 已存在)
|
* 监听参数变化 (强制添加新记录,即使 paramId 已存在)
|
||||||
* 如果列表中已存在相同 modelPart 的记录,则添加新参数到该记录的列表尾部;否则添加新记录。
|
* 如果列表中已存在相同 modelPart 的记录,则添加新参数到该记录的列表尾部;否则添加新记录。
|
||||||
@@ -96,43 +141,80 @@ public class ParametersManagement {
|
|||||||
* @param value 最终值
|
* @param value 最终值
|
||||||
*/
|
*/
|
||||||
public void broadcast(ModelPart modelPart, String paramId, Object value) {
|
public void broadcast(ModelPart modelPart, String paramId, Object value) {
|
||||||
if (getSelectParameter() == null){
|
if (getSelectParameter() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean isKeyframe = getSelectedKeyframe(false) != null;
|
// 获取当前正在编辑的 AnimationParameter 实例
|
||||||
|
AnimationParameter currentAnimParam = getSelectParameter();
|
||||||
|
|
||||||
|
// 获取当前的关键帧时间点 (Float) 和是否为关键帧 (Boolean)
|
||||||
Float currentKeyframe = getSelectedKeyframe(false);
|
Float currentKeyframe = getSelectedKeyframe(false);
|
||||||
|
|
||||||
|
// 如果当前没有选中的关键帧,通常我们不应该记录,但为了安全,先检查 null
|
||||||
|
if (currentKeyframe == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 重新判断是否为关键帧,确保 isKeyframe 准确
|
||||||
|
boolean isKeyframe = currentAnimParam.getKeyframes().contains(currentKeyframe);
|
||||||
|
|
||||||
// 查找是否已存在该ModelPart的记录
|
// 查找是否已存在该ModelPart的记录
|
||||||
for (int i = 0; i < oldValues.size(); i++) {
|
for (int i = 0; i < oldValues.size(); i++) {
|
||||||
Parameter existingParameter = oldValues.get(i);
|
Parameter existingParameter = oldValues.get(i);
|
||||||
|
|
||||||
|
// 步骤 1: 找到对应的 ModelPart
|
||||||
if (existingParameter.modelPart().equals(modelPart)) {
|
if (existingParameter.modelPart().equals(modelPart)) {
|
||||||
// 更新现有记录(复制所有列表以确保记录的不可变性)
|
// 步骤 2: 复制所有列表(保持不可变性)
|
||||||
List<AnimationParameter> newAnimationParameters = new ArrayList<>(existingParameter.animationParameter());
|
List<AnimationParameter> newAnimationParameters = new ArrayList<>(existingParameter.animationParameter());
|
||||||
List<String> newParamIds = new ArrayList<>(existingParameter.paramId());
|
List<String> newParamIds = new ArrayList<>(existingParameter.paramId());
|
||||||
List<Object> newValues = new ArrayList<>(existingParameter.value());
|
List<Object> newValues = new ArrayList<>(existingParameter.value());
|
||||||
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
|
List<Float> newKeyframes = new ArrayList<>(existingParameter.keyframe());
|
||||||
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
|
List<Boolean> newIsKeyframes = new ArrayList<>(existingParameter.isKeyframe());
|
||||||
|
|
||||||
newAnimationParameters.add(getSelectParameter());
|
// 步骤 3: 查找相同 keyframe + paramId + AnimationParameter 的记录
|
||||||
newParamIds.add(paramId);
|
int existingIndex = -1;
|
||||||
newValues.add(value);
|
for (int j = 0; j < newKeyframes.size(); j++) {
|
||||||
newKeyframes.add(currentKeyframe);
|
// 检查 keyframe 是否相同 (使用 Objects.equals 比较 Float)
|
||||||
newIsKeyframes.add(isKeyframe);
|
boolean keyframeMatches = Objects.equals(newKeyframes.get(j), currentKeyframe);
|
||||||
|
|
||||||
Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes); // NEW
|
// 检查 paramId 是否相同
|
||||||
|
boolean paramIdMatches = paramId.equals(newParamIds.get(j));
|
||||||
|
|
||||||
|
// 检查 AnimationParameter 是否相同 (使用 equals)
|
||||||
|
AnimationParameter recordAnimParam = newAnimationParameters.get(j);
|
||||||
|
boolean animParamMatches = recordAnimParam != null && recordAnimParam.equals(currentAnimParam);
|
||||||
|
|
||||||
|
// 如果时间点、参数ID和参数实例都匹配,则找到了现有记录
|
||||||
|
if (keyframeMatches && paramIdMatches && animParamMatches) {
|
||||||
|
existingIndex = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
// 找到了相同的记录位置: 执行 UPDATE (设置) 操作
|
||||||
|
newValues.set(existingIndex, value);
|
||||||
|
} else {
|
||||||
|
// 没有找到相同的记录: 执行 ADD (新增) 操作
|
||||||
|
newAnimationParameters.add(currentAnimParam);
|
||||||
|
newParamIds.add(paramId);
|
||||||
|
newValues.add(value);
|
||||||
|
newKeyframes.add(currentKeyframe);
|
||||||
|
newIsKeyframes.add(isKeyframe);
|
||||||
|
}
|
||||||
|
Parameter updatedParameter = new Parameter(modelPart, newAnimationParameters, newParamIds, newValues, newKeyframes, newIsKeyframes);
|
||||||
oldValues.set(i, updatedParameter);
|
oldValues.set(i, updatedParameter);
|
||||||
return;
|
return; // ModelPart 记录已处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有找到现有记录,创建新记录
|
// 如果没有找到 ModelPart 的现有记录,创建新记录
|
||||||
Parameter parameter = new Parameter(
|
Parameter parameter = new Parameter(
|
||||||
modelPart,
|
modelPart,
|
||||||
java.util.Collections.singletonList(getSelectParameter()), // NEW
|
Collections.singletonList(currentAnimParam),
|
||||||
java.util.Collections.singletonList(paramId),
|
Collections.singletonList(paramId),
|
||||||
java.util.Collections.singletonList(value),
|
Collections.singletonList(value),
|
||||||
java.util.Collections.singletonList(currentKeyframe),
|
Collections.singletonList(currentKeyframe),
|
||||||
java.util.Collections.singletonList(isKeyframe)
|
Collections.singletonList(isKeyframe)
|
||||||
);
|
);
|
||||||
oldValues.add(parameter);
|
oldValues.add(parameter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MeshTextureUtil {
|
public class MeshTextureUtil {
|
||||||
|
|
||||||
public static Mesh2D createQuadForImage(BufferedImage img, String meshName) {
|
public static Mesh2D createQuadForImage(BufferedImage img, String meshName) {
|
||||||
float w = img.getWidth();
|
float w = img.getWidth();
|
||||||
float h = img.getHeight();
|
float h = img.getHeight();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
|
|||||||
import com.chuangzhou.vivid2D.render.model.data.ModelData;
|
import com.chuangzhou.vivid2D.render.model.data.ModelData;
|
||||||
import com.chuangzhou.vivid2D.render.model.data.ModelMetadata;
|
import com.chuangzhou.vivid2D.render.model.data.ModelMetadata;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.*;
|
import com.chuangzhou.vivid2D.render.model.util.*;
|
||||||
|
import com.chuangzhou.vivid2D.util.ModelDataJsonConverter;
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
@@ -516,8 +517,10 @@ public class Model2D {
|
|||||||
try {
|
try {
|
||||||
ModelData data = serialize();
|
ModelData data = serialize();
|
||||||
data.saveToFile(filePath);
|
data.saveToFile(filePath);
|
||||||
|
String jsonFilePath = getJsonFilePath(filePath);
|
||||||
|
ModelDataJsonConverter.convert(filePath, jsonFilePath, false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to save model to: " + filePath, e);
|
throw new RuntimeException("Failed to save model and convert to JSON: " + filePath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,11 +551,22 @@ public class Model2D {
|
|||||||
try {
|
try {
|
||||||
ModelData data = serialize();
|
ModelData data = serialize();
|
||||||
data.saveToCompressedFile(filePath);
|
data.saveToCompressedFile(filePath);
|
||||||
|
String jsonFilePath = getJsonFilePath(filePath);
|
||||||
|
ModelDataJsonConverter.convert(filePath, jsonFilePath, true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to save compressed model to: " + filePath, e);
|
throw new RuntimeException("Failed to save compressed model and convert to JSON: " + filePath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getJsonFilePath(String originalFilePath) {
|
||||||
|
int lastDotIndex = originalFilePath.lastIndexOf('.');
|
||||||
|
if (lastDotIndex > 0) {
|
||||||
|
String baseName = originalFilePath.substring(0, lastDotIndex);
|
||||||
|
return baseName + ".json";
|
||||||
|
} else {
|
||||||
|
return originalFilePath + ".json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从压缩文件加载模型
|
* 从压缩文件加载模型
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.chuangzhou.vivid2D.util;
|
||||||
|
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.data.LayerOperationManagerData;
|
||||||
|
import com.chuangzhou.vivid2D.render.awt.manager.data.ParametersManagementData;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ManagementDataToJsonConverter {
|
||||||
|
/**
|
||||||
|
* 从 Java 序列化文件加载两个对象,并将它们组合成一个 JSON 对象保存。
|
||||||
|
*
|
||||||
|
* @param inputFilePath Java 序列化文件(.data)的路径
|
||||||
|
* @param outputFilePath 目标 JSON 文件(.json)的路径
|
||||||
|
*/
|
||||||
|
public static void convert(String inputFilePath, String outputFilePath) {
|
||||||
|
LayerOperationManagerData layerData = null;
|
||||||
|
ParametersManagementData managementData = null;
|
||||||
|
|
||||||
|
File inputFile = new File(inputFilePath);
|
||||||
|
if (!inputFile.exists()) {
|
||||||
|
System.err.println("错误:输入文件未找到:" + inputFilePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(inputFile))) {
|
||||||
|
layerData = (LayerOperationManagerData) ois.readObject();
|
||||||
|
System.out.println("成功读取第一个对象: LayerOperationManagerData");
|
||||||
|
managementData = (ParametersManagementData) ois.readObject();
|
||||||
|
System.out.println("成功读取第二个对象: ParametersManagementData");
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("读取序列化文件失败(可能是文件已损坏或写入方式不匹配): " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
System.err.println("无法找到类定义,请检查类路径是否包含 LayerOperationManagerData 和 ParametersManagementData: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> dataMap = new HashMap<>();
|
||||||
|
dataMap.put("layerData", layerData);
|
||||||
|
dataMap.put("parametersManagementData", managementData);
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
// ==============================================================
|
||||||
|
// 关键修改:禁用 FAIL_ON_EMPTY_BEANS 以避免 InvalidDefinitionException
|
||||||
|
// 但会导致 LayerInfo 内部数据丢失
|
||||||
|
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||||
|
// ==============================================================
|
||||||
|
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
mapper.writeValue(new File(outputFilePath), dataMap);
|
||||||
|
System.out.println("成功将序列化数据转换为 JSON 并保存至: " + outputFilePath);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("写入 JSON 文件失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String input = "C:\\Users\\Administrator\\Desktop\\testing.model.data";
|
||||||
|
String output = "C:\\Users\\Administrator\\Desktop\\management_data.json";
|
||||||
|
convert(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.chuangzhou.vivid2D.util;
|
||||||
|
|
||||||
|
import com.chuangzhou.vivid2D.render.model.data.ModelData; // 确保导入了你的 ModelData 类
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Java 序列化的 ModelData 文件转换为 JSON 格式的工具。
|
||||||
|
*/
|
||||||
|
public class ModelDataJsonConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Java 序列化文件加载 ModelData,并将其保存为 JSON 文件。
|
||||||
|
* * @param inputFilePath Java 序列化文件(.model)的路径
|
||||||
|
* @param outputFilePath 目标 JSON 文件(.json)的路径
|
||||||
|
* @param isCompressed 输入文件是否使用了 GZIP 压缩(ModelData.saveToCompressedFile 生成)
|
||||||
|
*/
|
||||||
|
public static void convert(String inputFilePath, String outputFilePath, boolean isCompressed) {
|
||||||
|
ModelData modelData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
File inputFile = new File(inputFilePath);
|
||||||
|
|
||||||
|
// 1. 从 Java 序列化文件加载 ModelData 对象
|
||||||
|
if (isCompressed) {
|
||||||
|
// 使用 ModelData 中已有的 loadFromCompressedFile 方法
|
||||||
|
modelData = ModelData.loadFromCompressedFile(inputFile);
|
||||||
|
System.out.println("成功从压缩文件加载模型数据: " + modelData.getName());
|
||||||
|
} else {
|
||||||
|
// 使用 ModelData 中已有的 loadFromFile 方法
|
||||||
|
modelData = ModelData.loadFromFile(inputFile);
|
||||||
|
System.out.println("成功从标准序列化文件加载模型数据: " + modelData.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 将对象转换为 JSON 格式
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
// 启用 Pretty Print 使 JSON 文件格式化,方便阅读和调试
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
|
||||||
|
// 3. 将 JSON 写入文件
|
||||||
|
mapper.writeValue(new File(outputFilePath), modelData);
|
||||||
|
|
||||||
|
System.out.println("成功将数据转换为 JSON 并保存至: " + outputFilePath);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("文件操作失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
System.err.println("类定义未找到(ModelData 可能已更改): " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("转换过程中发生未知错误: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例运行入口
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String input = "C:\\Users\\Administrator\\Desktop\\testing.model";
|
||||||
|
String output = "C:\\Users\\Administrator\\Desktop\\model.json";
|
||||||
|
convert(input, output, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import com.chuangzhou.vivid2D.render.model.Model2D;
|
|||||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||||
import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex;
|
import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex;
|
||||||
|
import com.chuangzhou.vivid2D.util.ManagementDataToJsonConverter;
|
||||||
|
import com.chuangzhou.vivid2D.util.ModelDataJsonConverter;
|
||||||
import jnafilechooser.api.JnaFileChooser;
|
import jnafilechooser.api.JnaFileChooser;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -59,14 +61,12 @@ public class MainWindow extends JFrame {
|
|||||||
this.transformPanel = new TransformPanel(renderPanel);
|
this.transformPanel = new TransformPanel(renderPanel);
|
||||||
this.parametersPanel = new ParametersPanel(renderPanel);
|
this.parametersPanel = new ParametersPanel(renderPanel);
|
||||||
this.partInfoPanel = new ModelPartInfoPanel(renderPanel);
|
this.partInfoPanel = new ModelPartInfoPanel(renderPanel);
|
||||||
|
|
||||||
// 【新增】初始化 SecondaryVertexPanel
|
|
||||||
this.secondaryVertexPanel = new SecondaryVertexPanel();
|
this.secondaryVertexPanel = new SecondaryVertexPanel();
|
||||||
|
|
||||||
createMenuBar();
|
createMenuBar();
|
||||||
createToolBar();
|
createToolBar();
|
||||||
createMainLayout();
|
createMainLayout();
|
||||||
createStatusBar(); // 确保在构造函数中调用,以便进度条被初始化
|
createStatusBar();
|
||||||
setEditComponentsEnabled(false);
|
setEditComponentsEnabled(false);
|
||||||
setupInitialListeners();
|
setupInitialListeners();
|
||||||
setSize(1600, 900);
|
setSize(1600, 900);
|
||||||
@@ -174,6 +174,7 @@ public class MainWindow extends JFrame {
|
|||||||
JScrollPane layerScroll = new JScrollPane(layerPanel);
|
JScrollPane layerScroll = new JScrollPane(layerPanel);
|
||||||
layerScroll.setMinimumSize(new Dimension(240, 100));
|
layerScroll.setMinimumSize(new Dimension(240, 100));
|
||||||
layerScroll.setPreferredSize(new Dimension(260, 600));
|
layerScroll.setPreferredSize(new Dimension(260, 600));
|
||||||
|
layerScroll.setBorder(BorderFactory.createTitledBorder("图层"));
|
||||||
|
|
||||||
JPanel centerPanelWrapper = new JPanel(new BorderLayout());
|
JPanel centerPanelWrapper = new JPanel(new BorderLayout());
|
||||||
centerPanelWrapper.add(renderPanel, BorderLayout.CENTER);
|
centerPanelWrapper.add(renderPanel, BorderLayout.CENTER);
|
||||||
@@ -194,7 +195,7 @@ public class MainWindow extends JFrame {
|
|||||||
JSplitPane infoSplit = new JSplitPane(
|
JSplitPane infoSplit = new JSplitPane(
|
||||||
JSplitPane.VERTICAL_SPLIT,
|
JSplitPane.VERTICAL_SPLIT,
|
||||||
partInfoPanel,
|
partInfoPanel,
|
||||||
secondaryVertexPanel // 【新增】加入二级顶点面板
|
secondaryVertexPanel // 加入二级顶点面板
|
||||||
);
|
);
|
||||||
infoSplit.setResizeWeight(0.5);
|
infoSplit.setResizeWeight(0.5);
|
||||||
infoSplit.setOneTouchExpandable(true);
|
infoSplit.setOneTouchExpandable(true);
|
||||||
@@ -203,6 +204,7 @@ public class MainWindow extends JFrame {
|
|||||||
// 右侧面板从上到下:参数、变换控制、顶点信息
|
// 右侧面板从上到下:参数、变换控制、顶点信息
|
||||||
JSplitPane rightPanelSplit = getjSplitPane(paramScroll, transformScroll, infoSplit);
|
JSplitPane rightPanelSplit = getjSplitPane(paramScroll, transformScroll, infoSplit);
|
||||||
|
|
||||||
|
// 【修改主分割】使用新的 leftPanelSplit 替换原来的 layerScroll
|
||||||
JSplitPane mainSplit = getjSplitPane(new JSplitPane(
|
JSplitPane mainSplit = getjSplitPane(new JSplitPane(
|
||||||
JSplitPane.HORIZONTAL_SPLIT,
|
JSplitPane.HORIZONTAL_SPLIT,
|
||||||
centerPanelWrapper,
|
centerPanelWrapper,
|
||||||
@@ -212,12 +214,16 @@ public class MainWindow extends JFrame {
|
|||||||
add(mainSplit, BorderLayout.CENTER);
|
add(mainSplit, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull JSplitPane getjSplitPane(JSplitPane HORIZONTAL_SPLIT, double value, int horizontalSplit, JScrollPane layerScroll, double value1) {
|
/**
|
||||||
|
* 辅助方法:创建主分割面板。
|
||||||
|
* 修复:将第四个参数从 JScrollPane 更改为 Component。
|
||||||
|
*/
|
||||||
|
private static @NotNull JSplitPane getjSplitPane(JSplitPane HORIZONTAL_SPLIT, double value, int horizontalSplit, Component componentForLeft, double value1) {
|
||||||
HORIZONTAL_SPLIT.setResizeWeight(value);
|
HORIZONTAL_SPLIT.setResizeWeight(value);
|
||||||
HORIZONTAL_SPLIT.setOneTouchExpandable(true);
|
HORIZONTAL_SPLIT.setOneTouchExpandable(true);
|
||||||
JSplitPane mainSplit = new JSplitPane(
|
JSplitPane mainSplit = new JSplitPane(
|
||||||
horizontalSplit,
|
horizontalSplit,
|
||||||
layerScroll,
|
componentForLeft, // 接受 JSplitPane 或 JScrollPane
|
||||||
HORIZONTAL_SPLIT
|
HORIZONTAL_SPLIT
|
||||||
);
|
);
|
||||||
mainSplit.setResizeWeight(value1);
|
mainSplit.setResizeWeight(value1);
|
||||||
@@ -225,7 +231,7 @@ public class MainWindow extends JFrame {
|
|||||||
return mainSplit;
|
return mainSplit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 【修改】调整右侧面板的布局逻辑
|
// 辅助方法:调整右侧面板的布局逻辑
|
||||||
private @NotNull JSplitPane getjSplitPane(JScrollPane paramScroll, JScrollPane transformScroll, JSplitPane infoSplit) {
|
private @NotNull JSplitPane getjSplitPane(JScrollPane paramScroll, JScrollPane transformScroll, JSplitPane infoSplit) {
|
||||||
// 上层分割:参数面板 (上) + 下层分割 (下)
|
// 上层分割:参数面板 (上) + 下层分割 (下)
|
||||||
JSplitPane upperSplit = new JSplitPane(
|
JSplitPane upperSplit = new JSplitPane(
|
||||||
@@ -324,7 +330,8 @@ public class MainWindow extends JFrame {
|
|||||||
transformPanel.setSelectedParts(selectedPart);
|
transformPanel.setSelectedParts(selectedPart);
|
||||||
if (!selectedPart.isEmpty()) {
|
if (!selectedPart.isEmpty()) {
|
||||||
setModelModified(true);
|
setModelModified(true);
|
||||||
partInfoPanel.updatePanel(selectedPart.get(0));
|
ModelPart selected = selectedPart.get(0);
|
||||||
|
partInfoPanel.updatePanel(selected);
|
||||||
} else {
|
} else {
|
||||||
partInfoPanel.updatePanel(null);
|
partInfoPanel.updatePanel(null);
|
||||||
}
|
}
|
||||||
@@ -377,7 +384,7 @@ public class MainWindow extends JFrame {
|
|||||||
transformPanel.setEnabled(enabled);
|
transformPanel.setEnabled(enabled);
|
||||||
parametersPanel.setEnabled(enabled);
|
parametersPanel.setEnabled(enabled);
|
||||||
partInfoPanel.setEnabled(enabled);
|
partInfoPanel.setEnabled(enabled);
|
||||||
secondaryVertexPanel.setEnabled(enabled); // 【新增】
|
secondaryVertexPanel.setEnabled(enabled);
|
||||||
renderPanel.setEnabled(enabled);
|
renderPanel.setEnabled(enabled);
|
||||||
for (Component comp : menuBar.getComponents()) {
|
for (Component comp : menuBar.getComponents()) {
|
||||||
if (comp instanceof JMenu menu) {
|
if (comp instanceof JMenu menu) {
|
||||||
@@ -490,20 +497,20 @@ public class MainWindow extends JFrame {
|
|||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Exception {
|
protected Void doInBackground() throws Exception {
|
||||||
if (renderPanel.getModel() != null) {
|
if (renderPanel.getModel() != null) {
|
||||||
System.out.println("正在保存模型: " + currentModelPath);
|
|
||||||
renderPanel.getModel().saveToFile(currentModelPath);
|
renderPanel.getModel().saveToFile(currentModelPath);
|
||||||
}
|
}
|
||||||
LayerOperationManager layerManager = layerPanel.getLayerOperationManager();
|
LayerOperationManager layerManager = layerPanel.getLayerOperationManager();
|
||||||
LayerOperationManagerData layerData = new LayerOperationManagerData(layerManager.layerMetadata);
|
LayerOperationManagerData layerData = new LayerOperationManagerData(layerManager.layerMetadata);
|
||||||
ParametersManagementData managementData = new ParametersManagementData(renderPanel.getParametersManagement());
|
ParametersManagementData managementData = new ParametersManagementData(renderPanel.getParametersManagement());
|
||||||
String managementFilePath = currentModelPath + ".data";
|
String managementFilePath = currentModelPath + ".data";
|
||||||
|
String managementJsonFilePath = managementFilePath + ".json";
|
||||||
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(managementFilePath))) {
|
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(managementFilePath))) {
|
||||||
oos.writeObject(layerData);
|
oos.writeObject(layerData);
|
||||||
oos.writeObject(managementData);
|
oos.writeObject(managementData);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// 必须在 doInBackground 中处理或抛出
|
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
ManagementDataToJsonConverter.convert(managementFilePath, managementJsonFilePath);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user