From fda74b661ae64f7d181441bc4a1490a8815201e4 Mon Sep 17 00:00:00 2001 From: kozhilya Date: Wed, 30 Apr 2025 16:10:58 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B5=D1=80=D0=B5=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EntityTreeViewerControl.cs | 121 ++++---- .../EntityTreeViewerRow.Designer.cs | 2 +- .../EntityTreeViewer/EntityTreeViewerRow.cs | 269 ++++++++++++------ .../EntityTreeViewer/StructureBuilder.cs | 7 + 4 files changed, 249 insertions(+), 150 deletions(-) diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs index 9895433..34b58f5 100644 --- a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs +++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs @@ -1,5 +1,4 @@ -using System.Data; -using Pf2eViewer.EntityTreeViewer; +using System.Collections.Generic; namespace Pf2eViewer.EntityTreeViewer; @@ -7,7 +6,7 @@ public sealed partial class EntityTreeViewerControl : UserControl { private int _itemHeight = 20; - private EntityView? _entityView = null; + private EntityView? _entityView; private int _depthSize = 20; @@ -43,9 +42,9 @@ public sealed partial class EntityTreeViewerControl : UserControl public int NameWidth { get; private set; } = 200; - private readonly List Rows = []; + private readonly List _rows = []; - private List ViewerRows = []; + private List _viewerRows = []; public EntityTreeViewerControl() { @@ -53,31 +52,9 @@ public sealed partial class EntityTreeViewerControl : UserControl DoubleBuffered = true; ContentPanel.AutoScroll = false; - ContentPanel.VerticalScroll.Enabled = true; - ContentPanel.VerticalScroll.Visible = true; - - ContentPanel.MouseWheel += ContentPanel_MouseWheel; - } - - private int _visibleRows; - - public int ScrollPosition - { - get => ContentPanel.VerticalScroll.Value / ItemHeight; - set - { - VScrollProperties scrollBar = ContentPanel.VerticalScroll; - int oldValue = scrollBar.Value / ItemHeight; - int visibleItems = scrollBar.LargeChange / ItemHeight; - - int scrollValue = Math.Min(Math.Max(value, 0), _visibleRows - visibleItems); - - if (oldValue != scrollValue) - { - ContentPanel.VerticalScroll.Value = scrollValue * ItemHeight; - UpdateScroll(); - } - } + ContentPanel.HorizontalScroll.Maximum = 0; + ContentPanel.HorizontalScroll.Visible = false; + ContentPanel.AutoScroll = true; } public void UpdateData() @@ -89,17 +66,17 @@ public sealed partial class EntityTreeViewerControl : UserControl return; } - Rows.Clear(); + _rows.Clear(); StructureBuilder builder = new(); - Rows.Add(builder.Create("Entity", EntityView.Entity)); + _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)); + _rows.Add(builder.Create(typeName, EntityView.SubEntity)); } UpdateRows(); @@ -108,23 +85,23 @@ public sealed partial class EntityTreeViewerControl : UserControl public void UpdateRows() { - ViewerRows = Rows.SelectMany(r => r.GetTextRows(this)).ToList(); + _viewerRows = _rows.SelectMany(r => r.GetTextRows(this)).ToList(); ContentPanel.Controls.Clear(); - ContentPanel.Controls.AddRange(ViewerRows.ToArray()); + ContentPanel.Controls.AddRange(_viewerRows.ToArray()); UpdateView(); } public void UpdateView() { - foreach (EntityTreeViewerRow row in ViewerRows) + foreach (EntityTreeViewerRow row in _viewerRows) { row.Visible = row.Item.Visible; } - List rows = ViewerRows.Where(r => r.Item.Visible).ToList(); + List rows = _viewerRows.Where(r => r.Item.Visible).ToList(); int maxNameWidth = -1; int viewRow = 0; @@ -132,53 +109,67 @@ public sealed partial class EntityTreeViewerControl : UserControl foreach (EntityTreeViewerRow row in rows) { row.ViewIndex = viewRow++; - int nameWidth = row.GetNameWidth() + row.Item.Depth * DepthSize; + int nameWidth = row.NameWidth + row.Item.Depth * DepthSize; maxNameWidth = Math.Max(maxNameWidth, nameWidth); } NameWidth = maxNameWidth; + BuildLadder(rows); + foreach (EntityTreeViewerRow row in rows) { row.Refresh(); } - - UpdateScroll(); } - public void UpdateScroll() + private void BuildLadder(List rows) { - List rows = ViewerRows.Where(r => r.Item.Visible).ToList(); - _visibleRows = rows.Count; + List lastLadder = []; - ContentPanel.VerticalScroll.Enabled = true; - ContentPanel.VerticalScroll.Visible = true; + for (int i = rows.Count - 1; i >= 0; i--) + { + EntityTreeViewerRow row = rows[i]; + List ladder; - ContentPanel.VerticalScroll.SmallChange = ItemHeight; - ContentPanel.VerticalScroll.LargeChange = ContentPanel.Height; - ContentPanel.VerticalScroll.Minimum = 0; - ContentPanel.VerticalScroll.Maximum = _visibleRows * ItemHeight; + 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, + ]; + } - // ContentPanel.VerticalScroll.Value = VerticalScrollBar.Value * ItemHeight; - /* - ContentPanel.Top = -VerticalScrollBar.Value * ItemHeight; - ContentPanel.Invalidate(); - */ + row.Ladder = ladder; + + lastLadder = ladder + .Select(l => (l == LadderType.Last) ? LadderType.Through : l) + .ToList(); + } } // Events - private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e) - { - UpdateScroll(); - } - - private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e) - { - Focus(); - // ScrollPosition -= e.Delta / 120; - } - private void ContentPanel_Resize(object sender, EventArgs e) { UpdateView(); diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs index 1bc1be8..8836e52 100644 --- a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs +++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs @@ -1,6 +1,6 @@ namespace Pf2eViewer.EntityTreeViewer; -partial class EntityTreeViewerRow +sealed partial class EntityTreeViewerRow { /// /// Обязательная переменная конструктора. diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs index 89cba62..00b94fd 100644 --- a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs +++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs @@ -1,9 +1,9 @@ -using FontAwesome.Sharp; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header; +using System.Diagnostics.CodeAnalysis; +using FontAwesome.Sharp; namespace Pf2eViewer.EntityTreeViewer; -internal partial class EntityTreeViewerRow : UserControl +internal sealed partial class EntityTreeViewerRow : UserControl { public readonly EntityTreeViewerControl ParentControl; @@ -11,12 +11,66 @@ internal partial class EntityTreeViewerRow : UserControl public int ViewIndex = 0; + public Bitmap IconCache + { + get + { + if (_iconCache == null) + { + UpdateCache(); + } + return _iconCache!; + } + set => _iconCache = value; + } + private Bitmap? _iconCache; + + public List Ladder + { + get => _ladder; + set + { + _ladder = value; + _iconCache = null; + } + } + private List _ladder = []; + + public override Font Font + { + get => ParentControl.Font; + set => ParentControl.Font = value; + } + public override Color ForeColor + { + get => ParentControl.ForeColor; + set => ParentControl.ForeColor = value; + } + public override Color BackColor + { + get => ParentControl.BackColor; + set => ParentControl.BackColor = value; + } + public int ItemHeight + { + get => ParentControl.ItemHeight; + set => ParentControl.ItemHeight = value; + } + public int DepthSize + { + get => ParentControl.ItemHeight; + set => ParentControl.ItemHeight = value; + } + + // Value functions + + public bool HasButton => Item.FieldType == StructureFieldType.Complex; public int Depth => Item.Depth; + public int NameWidth => TextRenderer.MeasureText(NameLabel.Text, Font).Width + DepthSize; public EntityTreeViewerRow(EntityTreeViewerControl parent, StructureField item) { ParentControl = parent; - Font = ParentControl.Font; Item = item; @@ -27,52 +81,25 @@ internal partial class EntityTreeViewerRow : UserControl private void ApplyItem() { - TypeLabel.Font = new Font(Font.FontFamily, Font.Size - 2, FontStyle.Italic); + TypeLabel.Font = new(Font.FontFamily, Font.Size - 2, FontStyle.Italic); NameLabel.Text = Item.Name; TypeLabel.Text = Item.GetTypeString(); if (TypeLabel.Text != "") { - TypeLabel.Text = $"({TypeLabel.Text})"; + TypeLabel.Text = $@"({TypeLabel.Text})"; } + ValueLabel.Text = Item.GetValueString(); + TypeLabel.ForeColor = ValueLabel.ForeColor = GetValueColor(); - switch (Item) + if (Item.FieldType == StructureFieldType.Complex) { - case StructureFieldPrimitive primitive: - switch (Type.GetTypeCode(primitive.Type)) - { - case TypeCode.Boolean: - ValuePanel.ForeColor = Color.Red; - break; - case TypeCode.Int32: - ValuePanel.ForeColor = Color.Blue; - break; - case TypeCode.String: - ValuePanel.ForeColor = Color.OrangeRed; - break; - } - - ValueLabel.ForeColor = ValuePanel.ForeColor; - TypeLabel.ForeColor = ValuePanel.ForeColor; - break; - - case StructureFieldComplex complex: - ValueLabel.Font = new(Font, FontStyle.Italic); - break; - - case StructureFieldNull @null: - break; - - case StructureFieldReference reference: - break; + ValueLabel.Font = new(Font, FontStyle.Italic); } - } - public int GetNameWidth() - { - Size textSize = TextRenderer.MeasureText(NameLabel.Text, Font); - return textSize.Width + ParentControl.DepthSize; + UpdateCache(); + Refresh(); } public new void Refresh() @@ -85,10 +112,14 @@ internal partial class EntityTreeViewerRow : UserControl Height = ParentControl.ItemHeight; // Name Panel - NamePanel.Left = Depth * ParentControl.DepthSize; + NamePanel.Left = 0; NamePanel.Width = ParentControl.NameWidth - NamePanel.Left; - IconPictureBox.Width = ParentControl.ItemHeight; + IconPictureBox.Width = IconCache.Width; + IconPictureBox.Image = IconCache; + + NameLabel.Left = IconPictureBox.Width; + NameLabel.Width = NamePanel.Width - NameLabel.Left; // Value Panel ValuePanel.Left = NamePanel.Left + NamePanel.Width; @@ -96,63 +127,133 @@ internal partial class EntityTreeViewerRow : UserControl Size typeSize = TextRenderer.MeasureText(TypeLabel.Text, TypeLabel.Font); TypeLabel.Width = typeSize.Width + 3; + } - TypeLabel.ForeColor = ValueLabel.ForeColor = GetValueColor(); + private Color BlendColor(Color color, double rate = 0.5) + { + return BlendColor(ForeColor, color, rate); + } - if (Item.GetType() == typeof(StructureFieldComplex)) - { - StructureFieldComplex complex = (StructureFieldComplex)Item; + private Color BlendColor(Color color1, Color color2, double rate) + { + return Color.FromArgb( + Mix(color1.A, color2.A), + Mix(color1.R, color2.R), + Mix(color1.G, color2.G), + Mix(color1.B, color2.B) + ); - IconPictureBox.Image = complex.Collapsed - ? IconChar.PlusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor) - : IconChar.MinusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor); - } + int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate))); } public Color GetValueColor() { - Func blend = (Color color) => _colorBlend([ForeColor, color]); - - switch (Item) + return Item switch { - case StructureFieldPrimitive primitive: - return Type.GetTypeCode(primitive.Type) switch - { - TypeCode.Boolean => blend(Color.DarkGoldenrod), - TypeCode.Int32 => blend(Color.Blue), - TypeCode.Double => blend(Color.Aqua), - TypeCode.String => blend(Color.Green), - }; - - case StructureFieldEnum @enum: - return blend(Color.Red); - } - - return ForeColor; + StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch + { + TypeCode.Boolean => BlendColor(Color.DarkGoldenrod), + TypeCode.Int32 => BlendColor(Color.Blue), + TypeCode.Double => BlendColor(Color.Aqua), + TypeCode.String => BlendColor(Color.Green), + _ => ForeColor, + }, + StructureFieldEnum => BlendColor(Color.Red), + _ => ForeColor, + }; } - private Color _colorBlend(List clrArr) - { - int r = 0; - int g = 0; - int b = 0; - foreach (Color color in clrArr) - { - r += color.R; - g += color.G; - b += color.B; - } - return Color.FromArgb(r / clrArr.Count, g / clrArr.Count, b / clrArr.Count); - } + // Events private void IconPictureBox_Click(object sender, EventArgs e) { - if (Item.GetType() == typeof(StructureFieldComplex)) + if (Item.GetType() != typeof(StructureFieldComplex)) { - StructureFieldComplex complex = (StructureFieldComplex)Item; - - complex.Collapsed = !complex.Collapsed; - ParentControl.UpdateView(); + return; } + + StructureFieldComplex complex = (StructureFieldComplex)Item; + + complex.Collapsed = !complex.Collapsed; + ParentControl.UpdateView(); + } + + private Bitmap? GetIconImage() + { + if (Item.GetType() != typeof(StructureFieldComplex)) + { + return null; + } + + StructureFieldComplex complex = (StructureFieldComplex)Item; + + int size = Convert.ToInt32(Math.Round(DepthSize * 0.6)); + + return complex.Collapsed + ? IconChar.PlusSquare.ToBitmap(IconFont.Auto, size, ForeColor) + : IconChar.MinusSquare.ToBitmap(IconFont.Auto, size, ForeColor); + } + + private void UpdateCache() + { + int n = Depth; + int h = ItemHeight; + int w = DepthSize; + + _iconCache = new(w * (n + 1), h); + + if (_ladder.Count < Depth) + { + return; + } + + Graphics g = Graphics.FromImage(_iconCache); + + Pen pen = new(Color.FromArgb(127, ForeColor.R, ForeColor.G, ForeColor.B), 1); + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; + + float cy = 0.5f * h; + + for (int i = 0; i < n; i++) + { + float cx = (i + 1.5f) * w; + + switch (_ladder[i]) + { + case LadderType.Last: + g.DrawLine(pen, cx, 0, cx, cy); + break; + case LadderType.Through: + g.DrawLine(pen, cx, 0, cx, h); + break; + case LadderType.Empty: + default: + break; + } + + if (i == n - 1) + { + g.DrawLine(pen, cx, cy, (i + 2) * w, cy); + } + } + + Bitmap? icon = GetIconImage(); + + if (icon is not null) + { + float cx = (n + 0.5f) * w; + + g.FillRectangle( + new SolidBrush(BackColor), + cx - 0.5f * icon.Width - 1, + cy - 0.5f * icon.Height - 1, + icon.Width + 2, + icon.Height + 2 + ); + + g.DrawImage(icon, new PointF(cx - 0.5f * icon.Width, cy - 0.5f * icon.Height)); + } + + g.Save(); } } diff --git a/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs b/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs index a8464b3..c2ef7ed 100644 --- a/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs +++ b/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs @@ -141,6 +141,13 @@ internal enum StructureFieldType Reference, } +internal enum LadderType +{ + Empty, + Last, + Through, +} + internal abstract class StructureField(StructureBuilder builder, string name, int depth) { public readonly string Name = name;