Files
lity3d/EditorCS/MainForm.cs
T

370 lines
12 KiB
C#

using System.ComponentModel;
namespace Lity3DEditor;
public class MainForm : Form
{
private List<SceneNode> _nodes = new();
private string? _currentPath;
private bool _modified;
private TreeView _tree = null!;
private PropertyGrid _propertyGrid = null!;
private StatusStrip _status = null!;
private ToolStripButton _btnSave = null!;
public MainForm()
{
Text = "Lity3D Engine Scene Editor";
Size = new Size(1280, 800);
StartPosition = FormStartPosition.CenterScreen;
BackColor = Color.FromArgb(30, 30, 38);
Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular, GraphicsUnit.Point);
BuildMenu();
BuildToolbar();
BuildPanels();
BuildStatus();
Load += (_, _) =>
{
foreach (var c in Controls.OfType<SplitContainer>())
{
if (c.Orientation == Orientation.Vertical)
{
c.Panel1MinSize = 180;
c.Panel2MinSize = 300;
c.SplitterDistance = 260;
}
else
{
c.Panel1MinSize = 200;
c.Panel2MinSize = 120;
c.SplitterDistance = c.Height - 220;
}
}
TryOpenScene(Path.Combine(Application.StartupPath, "scene.txt"));
};
FormClosing += (_, e) => { if (!PromptSave()) e.Cancel = true; };
}
private void BuildMenu()
{
var menu = new MenuStrip();
var file = new ToolStripMenuItem("文件(&F)");
file.DropDownItems.Add(new ToolStripMenuItem("打开...", null, (_, _) => OpenScene()) { ShortcutKeys = Keys.Control | Keys.O });
file.DropDownItems.Add(new ToolStripMenuItem("保存", null, (_, _) => SaveScene()) { ShortcutKeys = Keys.Control | Keys.S });
file.DropDownItems.Add(new ToolStripMenuItem("另存为...", null, (_, _) => SaveSceneAs()));
file.DropDownItems.Add(new ToolStripSeparator());
file.DropDownItems.Add(new ToolStripMenuItem("退出", null, (_, _) => Close()) { ShortcutKeys = Keys.Alt | Keys.F4 });
menu.Items.Add(file);
var edit = new ToolStripMenuItem("编辑(&E)");
edit.DropDownItems.Add(new ToolStripMenuItem("添加立方体", null, (_, _) => AddNode(MeshType.CUBE, "cube")));
edit.DropDownItems.Add(new ToolStripMenuItem("添加球体", null, (_, _) => AddNode(MeshType.SPHERE, "sphere")));
edit.DropDownItems.Add(new ToolStripMenuItem("添加平面", null, (_, _) => AddNode(MeshType.PLANE, "plane")));
edit.DropDownItems.Add(new ToolStripSeparator());
edit.DropDownItems.Add(new ToolStripMenuItem("删除选中", null, (_, _) => DeleteSelected()) { ShortcutKeys = Keys.Delete });
menu.Items.Add(edit);
MainMenuStrip = menu;
Controls.Add(menu);
}
private void BuildToolbar()
{
var tool = new ToolStrip();
tool.GripStyle = ToolStripGripStyle.Hidden;
tool.BackColor = Color.FromArgb(40, 40, 50);
tool.ForeColor = Color.White;
tool.Renderer = new DarkToolStripRenderer();
_btnSave = new ToolStripButton("保存", null, (_, _) => SaveScene()) { Enabled = false };
tool.Items.Add(_btnSave);
tool.Items.Add(new ToolStripSeparator());
tool.Items.Add(new ToolStripButton("添加立方体", null, (_, _) => AddNode(MeshType.CUBE, "cube")));
tool.Items.Add(new ToolStripButton("添加球体", null, (_, _) => AddNode(MeshType.SPHERE, "sphere")));
tool.Items.Add(new ToolStripButton("添加平面", null, (_, _) => AddNode(MeshType.PLANE, "plane")));
tool.Items.Add(new ToolStripSeparator());
tool.Items.Add(new ToolStripButton("删除", null, (_, _) => DeleteSelected()));
tool.Dock = DockStyle.Top;
Controls.Add(tool);
}
private void BuildPanels()
{
var split = new SplitContainer
{
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(30, 30, 38)
};
split.Panel1.BackColor = Color.FromArgb(35, 35, 45);
split.Panel2.BackColor = Color.FromArgb(35, 35, 45);
_tree = new TreeView
{
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(35, 35, 45),
ForeColor = Color.White,
BorderStyle = BorderStyle.None,
FullRowSelect = true,
HideSelection = false,
ShowLines = true,
ShowPlusMinus = true,
LineColor = Color.FromArgb(100, 120, 160)
};
_tree.AfterSelect += (_, _) => OnNodeSelected();
split.Panel1.Controls.Add(_tree);
var rightSplit = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal,
BackColor = Color.FromArgb(30, 30, 38)
};
rightSplit.Panel1.BackColor = Color.FromArgb(35, 35, 45);
rightSplit.Panel2.BackColor = Color.FromArgb(28, 28, 36);
_propertyGrid = new PropertyGrid
{
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(35, 35, 45),
ForeColor = Color.White,
CategoryForeColor = Color.FromArgb(180, 180, 200),
CommandsBackColor = Color.FromArgb(35, 35, 45),
CommandsForeColor = Color.White,
DisabledItemForeColor = Color.Gray,
HelpBackColor = Color.FromArgb(28, 28, 36),
HelpForeColor = Color.White,
ViewBackColor = Color.FromArgb(35, 35, 45),
ViewForeColor = Color.White,
LineColor = Color.FromArgb(60, 60, 80),
ToolbarVisible = false,
PropertySort = PropertySort.Categorized
};
_propertyGrid.PropertyValueChanged += (_, _) => OnPropertyChanged();
rightSplit.Panel1.Controls.Add(_propertyGrid);
var previewLabel = new Label
{
Dock = DockStyle.Fill,
Text = "3D 预览区\n(由 Lity3D Engine 运行时渲染)",
ForeColor = Color.FromArgb(120, 120, 140),
BackColor = Color.FromArgb(28, 28, 36),
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font(Font.FontFamily, 11f, FontStyle.Regular)
};
rightSplit.Panel2.Controls.Add(previewLabel);
split.Panel2.Controls.Add(rightSplit);
Controls.Add(split);
}
private void BuildStatus()
{
_status = new StatusStrip();
_status.BackColor = Color.FromArgb(40, 40, 50);
_status.ForeColor = Color.White;
_status.Items.Add(new ToolStripStatusLabel("就绪"));
Controls.Add(_status);
}
private void TryOpenScene(string path)
{
if (!File.Exists(path)) return;
try
{
_nodes = SceneIO.Load(path);
_currentPath = path;
RebuildTree();
SetModified(false);
SetStatus($"已打开 {path}");
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "打开失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void OpenScene()
{
if (!PromptSave()) return;
using var dlg = new OpenFileDialog
{
Filter = "Lity3D 场景|*.txt|所有文件|*.*",
Title = "打开场景"
};
if (dlg.ShowDialog(this) == DialogResult.OK)
TryOpenScene(dlg.FileName);
}
private bool SaveScene()
{
if (string.IsNullOrEmpty(_currentPath))
return SaveSceneAs();
try
{
SceneIO.Save(_currentPath, _nodes);
SetModified(false);
SetStatus($"已保存 {_currentPath}");
return true;
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
private bool SaveSceneAs()
{
using var dlg = new SaveFileDialog
{
Filter = "Lity3D 场景|*.txt|所有文件|*.*",
Title = "另存为"
};
if (dlg.ShowDialog(this) != DialogResult.OK) return false;
_currentPath = dlg.FileName;
return SaveScene();
}
private bool PromptSave()
{
if (!_modified) return true;
var r = MessageBox.Show(this, "场景已修改,是否保存?", "保存确认",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (r == DialogResult.Cancel) return false;
if (r == DialogResult.Yes) return SaveScene();
return true;
}
private void AddNode(MeshType type, string baseName)
{
int idx = 1;
string name = baseName;
while (_nodes.Any(n => n.Name == name))
name = $"{baseName}_{idx++}";
var parentIdx = -1;
if (_tree.SelectedNode?.Tag is SceneNode sel && sel.Name != "root")
parentIdx = _nodes.IndexOf(sel);
else if (_nodes.Count > 0)
parentIdx = 0; // root
var node = new SceneNode
{
Name = name,
ParentIndex = parentIdx,
Mesh = type,
PosY = type == MeshType.PLANE ? -1.0f : 0.0f
};
_nodes.Add(node);
RebuildTree();
SelectNode(node);
SetModified(true);
SetStatus($"添加 {name}");
}
private void DeleteSelected()
{
if (_tree.SelectedNode?.Tag is not SceneNode node || node.Name == "root")
{
MessageBox.Show(this, "不能删除根节点", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
_nodes.Remove(node);
RebuildTree();
SetModified(true);
SetStatus($"删除 {node.Name}");
}
private void RebuildTree()
{
_tree.Nodes.Clear();
var treeMap = new Dictionary<int, TreeNode>();
for (int i = 0; i < _nodes.Count; i++)
{
var n = _nodes[i];
var tn = new TreeNode(n.Name) { Tag = n, ForeColor = Color.White };
treeMap[i] = tn;
if (n.ParentIndex >= 0 && treeMap.TryGetValue(n.ParentIndex, out var parent))
parent.Nodes.Add(tn);
else
_tree.Nodes.Add(tn);
}
_tree.ExpandAll();
}
private void SelectNode(SceneNode node)
{
foreach (TreeNode root in _tree.Nodes)
{
var found = FindNode(root, node);
if (found != null)
{
_tree.SelectedNode = found;
return;
}
}
}
private static TreeNode? FindNode(TreeNode parent, SceneNode node)
{
if (parent.Tag == node) return parent;
foreach (TreeNode c in parent.Nodes)
{
var r = FindNode(c, node);
if (r != null) return r;
}
return null;
}
private void OnNodeSelected()
{
_propertyGrid.SelectedObject = _tree.SelectedNode?.Tag;
if (_tree.SelectedNode?.Tag is SceneNode n)
SetStatus(n.Name);
}
private void OnPropertyChanged()
{
SetModified(true);
_tree.Refresh();
}
private void SetModified(bool modified)
{
_modified = modified;
_btnSave.Enabled = modified;
Text = "Lity3D Engine Scene Editor" + (modified ? " *" : "") + (string.IsNullOrEmpty(_currentPath) ? "" : $" - {_currentPath}");
}
private void SetStatus(string msg)
{
if (_status.Items[0] is ToolStripStatusLabel lbl)
lbl.Text = msg;
}
}
public class DarkToolStripRenderer : ToolStripProfessionalRenderer
{
public DarkToolStripRenderer() : base(new DarkColorTable()) { }
}
public class DarkColorTable : ProfessionalColorTable
{
public override Color ToolStripBorder => Color.FromArgb(60, 60, 80);
public override Color ToolStripGradientBegin => Color.FromArgb(40, 40, 50);
public override Color ToolStripGradientEnd => Color.FromArgb(40, 40, 50);
public override Color ToolStripGradientMiddle => Color.FromArgb(45, 45, 55);
public override Color ButtonSelectedHighlight => Color.FromArgb(70, 70, 90);
public override Color ButtonSelectedBorder => Color.FromArgb(100, 120, 160);
public override Color ButtonPressedHighlight => Color.FromArgb(60, 80, 120);
public override Color ButtonCheckedHighlight => Color.FromArgb(60, 80, 120);
public override Color MenuBorder => Color.FromArgb(60, 60, 80);
public override Color MenuItemSelected => Color.FromArgb(70, 70, 90);
public override Color MenuItemBorder => Color.FromArgb(100, 120, 160);
}