using System.ComponentModel; using System.Diagnostics; namespace Pf2eViewer.EntityTreeViewer; public sealed partial class EntityTreeViewerControl : UserControl { [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Entity Render")] [Description("The height of each item in the tree view.")] public int ItemHeight { get => _itemHeight; set { _itemHeight = value; UpdateView(); } } private int _itemHeight = 20; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public EntityView? EntityView { get => _entityView; set { _entityView = value; UpdateData(); } } private EntityView? _entityView; [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Entity Render")] [Description("The size of tab.")] public int DepthSize { get => _depthSize; set { _depthSize = value; UpdateView(); } } private int _depthSize = 20; [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Entity Render")] [Description("The size of plus/minus button.")] public int ButtonSize { get => _buttonSize; set { _buttonSize = value; UpdateView(); } } private int _buttonSize = 20; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public int NameWidth { get; private set; } = 200; [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Entity Render")] [Description("Color on item hover.")] public Color HoverColor { get => _hoverColor; set { _hoverColor = value; UpdateView(); } } private Color _hoverColor = SystemColors.ControlDark; [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public int ContentWidth => Width - SystemInformation.VerticalScrollBarWidth - 2; private readonly List _rows = []; private List _viewerRows = []; private bool _rendering; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public Bitmap ContentImage { get; set; } public EntityTreeViewerControl() { InitializeComponent(); ContentImage = new(ContentWidth, Height); DoubleBuffered = true; VerticalScroll.Visible = true; HorizontalScroll.Visible = false; ContentPanel.MouseWheel += ContentPanel_MouseWheel; } public void UpdateData() { if (EntityView?.Entity is null) { return; } _rows.Clear(); ContentPanel.Controls.Clear(); StructureBuilder builder = new(); _rows.Add(builder.Create("Entity", EntityView.Entity)); if (EntityView.SubEntity is not null) { string typeName = EntityView.SubEntity.GetType().Name; _rows.Add(builder.Create(typeName, EntityView.SubEntity)); } _viewerRows = _rows .SelectMany(r => r.GetRows(row => new EntityTreeViewerControlRow(this, row))) .ToList(); UpdateRenders(); } public void UpdateRenders() { int maxNameWidth = -1; foreach (EntityTreeViewerControlRow row in _viewerRows) { int nameWidth = row.NameWidth + row.Item.Depth * DepthSize; maxNameWidth = Math.Max(maxNameWidth, nameWidth); } NameWidth = maxNameWidth + 3; // Debug.WriteLine($@"Height Inner {ContentWidth} / Outer {Width}"); // Debug.WriteLine($@"Name {NameWidth} / Full {ContentWidth}"); foreach (EntityTreeViewerControlRow row in _viewerRows) { row.UpdateCache(); } UpdateView(); } public void UpdateView() { _viewerRows.ForEach(row => row.UpdateButton()); List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); if (rows.Count == 0) { return; } BuildLadder(rows); _rendering = true; ContentImage = new(ContentWidth, rows.Count * ItemHeight); Graphics g = Graphics.FromImage(ContentImage); int viewRow = 0; foreach (EntityTreeViewerControlRow row in rows) { row.ViewIndex = viewRow++; row.Invalidate(g); } g.Save(); ContentPanel.Image = ContentImage; ContentPanel.Width = ContentImage.Width; ContentPanel.Height = ContentImage.Height; _rendering = false; } private void BuildLadder(List rows) { List lastLadder = []; for (int i = rows.Count - 1; i >= 0; i--) { EntityTreeViewerControlRow row = rows[i]; List ladder; if (row.Depth == 0) { ladder = []; } else if (row.Depth < lastLadder.Count) { ladder = [ .. lastLadder[..(row.Depth - 1)], lastLadder[row.Depth - 1] == LadderType.Empty ? LadderType.Last : LadderType.Through, ]; } else if (row.Depth == lastLadder.Count) { ladder = lastLadder; } else { ladder = [ .. lastLadder, .. Enumerable.Repeat(LadderType.Empty, row.Depth - lastLadder.Count - 1), LadderType.Last, ]; } row.Ladder = ladder; lastLadder = ladder .Select(l => (l == LadderType.Last) ? LadderType.Through : l) .ToList(); } } // Events private void EntityTreeViewerControl_Resize(object sender, EventArgs e) { if (_rendering) { return; } ContentPanel.Width = Width; UpdateRenders(); } private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e) { /* int initValue = VerticalScroll.Value; int delta = -e.Delta / 120; VerticalScroll.Value = Math.Clamp( initValue + delta, VerticalScrollBar.Minimum, VerticalScrollBar.Maximum - VerticalScrollBar.LargeChange + 1 ); if (VerticalScrollBar.Value != initValue) { ContentPanel.Invalidate(); } */ } private void ContentPanel_MouseMove(object sender, MouseEventArgs e) { PointF p = new(e.X, e.Y); foreach (EntityTreeViewerControlRow row in _viewerRows) { bool contained = row.Target.Contains(p); if (contained != row.Hovered) { Rectangle r = new(); row.RealTarget.Intersect(r); ContentPanel.Invalidate(r); } row.Hovered = contained; } } }