Обновление отображения дерева

This commit is contained in:
2025-04-30 16:10:58 +04:00
parent e856f91f9e
commit fda74b661a
4 changed files with 249 additions and 150 deletions
@@ -1,5 +1,4 @@
using System.Data; using System.Collections.Generic;
using Pf2eViewer.EntityTreeViewer;
namespace Pf2eViewer.EntityTreeViewer; namespace Pf2eViewer.EntityTreeViewer;
@@ -7,7 +6,7 @@ public sealed partial class EntityTreeViewerControl : UserControl
{ {
private int _itemHeight = 20; private int _itemHeight = 20;
private EntityView? _entityView = null; private EntityView? _entityView;
private int _depthSize = 20; private int _depthSize = 20;
@@ -43,9 +42,9 @@ public sealed partial class EntityTreeViewerControl : UserControl
public int NameWidth { get; private set; } = 200; public int NameWidth { get; private set; } = 200;
private readonly List<StructureField> Rows = []; private readonly List<StructureField> _rows = [];
private List<EntityTreeViewerRow> ViewerRows = []; private List<EntityTreeViewerRow> _viewerRows = [];
public EntityTreeViewerControl() public EntityTreeViewerControl()
{ {
@@ -53,31 +52,9 @@ public sealed partial class EntityTreeViewerControl : UserControl
DoubleBuffered = true; DoubleBuffered = true;
ContentPanel.AutoScroll = false; ContentPanel.AutoScroll = false;
ContentPanel.VerticalScroll.Enabled = true; ContentPanel.HorizontalScroll.Maximum = 0;
ContentPanel.VerticalScroll.Visible = true; ContentPanel.HorizontalScroll.Visible = false;
ContentPanel.AutoScroll = 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();
}
}
} }
public void UpdateData() public void UpdateData()
@@ -89,17 +66,17 @@ public sealed partial class EntityTreeViewerControl : UserControl
return; return;
} }
Rows.Clear(); _rows.Clear();
StructureBuilder builder = new(); StructureBuilder builder = new();
Rows.Add(builder.Create("Entity", EntityView.Entity)); _rows.Add(builder.Create("Entity", EntityView.Entity));
if (EntityView.SubEntity is not null) if (EntityView.SubEntity is not null)
{ {
string typeName = EntityView.SubEntity.GetType().Name; string typeName = EntityView.SubEntity.GetType().Name;
Rows.Add(builder.Create(typeName, EntityView.SubEntity)); _rows.Add(builder.Create(typeName, EntityView.SubEntity));
} }
UpdateRows(); UpdateRows();
@@ -108,23 +85,23 @@ public sealed partial class EntityTreeViewerControl : UserControl
public void UpdateRows() public void UpdateRows()
{ {
ViewerRows = Rows.SelectMany(r => r.GetTextRows(this)).ToList(); _viewerRows = _rows.SelectMany(r => r.GetTextRows(this)).ToList();
ContentPanel.Controls.Clear(); ContentPanel.Controls.Clear();
ContentPanel.Controls.AddRange(ViewerRows.ToArray()); ContentPanel.Controls.AddRange(_viewerRows.ToArray<Control>());
UpdateView(); UpdateView();
} }
public void UpdateView() public void UpdateView()
{ {
foreach (EntityTreeViewerRow row in ViewerRows) foreach (EntityTreeViewerRow row in _viewerRows)
{ {
row.Visible = row.Item.Visible; row.Visible = row.Item.Visible;
} }
List<EntityTreeViewerRow> rows = ViewerRows.Where(r => r.Item.Visible).ToList(); List<EntityTreeViewerRow> rows = _viewerRows.Where(r => r.Item.Visible).ToList();
int maxNameWidth = -1; int maxNameWidth = -1;
int viewRow = 0; int viewRow = 0;
@@ -132,53 +109,67 @@ public sealed partial class EntityTreeViewerControl : UserControl
foreach (EntityTreeViewerRow row in rows) foreach (EntityTreeViewerRow row in rows)
{ {
row.ViewIndex = viewRow++; row.ViewIndex = viewRow++;
int nameWidth = row.GetNameWidth() + row.Item.Depth * DepthSize; int nameWidth = row.NameWidth + row.Item.Depth * DepthSize;
maxNameWidth = Math.Max(maxNameWidth, nameWidth); maxNameWidth = Math.Max(maxNameWidth, nameWidth);
} }
NameWidth = maxNameWidth; NameWidth = maxNameWidth;
BuildLadder(rows);
foreach (EntityTreeViewerRow row in rows) foreach (EntityTreeViewerRow row in rows)
{ {
row.Refresh(); row.Refresh();
} }
UpdateScroll();
} }
public void UpdateScroll() private void BuildLadder(List<EntityTreeViewerRow> rows)
{ {
List<EntityTreeViewerRow> rows = ViewerRows.Where(r => r.Item.Visible).ToList(); List<LadderType> lastLadder = [];
_visibleRows = rows.Count;
ContentPanel.VerticalScroll.Enabled = true; for (int i = rows.Count - 1; i >= 0; i--)
ContentPanel.VerticalScroll.Visible = true; {
EntityTreeViewerRow row = rows[i];
List<LadderType> ladder;
ContentPanel.VerticalScroll.SmallChange = ItemHeight; if (row.Depth == 0)
ContentPanel.VerticalScroll.LargeChange = ContentPanel.Height; {
ContentPanel.VerticalScroll.Minimum = 0; ladder = [];
ContentPanel.VerticalScroll.Maximum = _visibleRows * ItemHeight; }
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; row.Ladder = ladder;
/*
ContentPanel.Top = -VerticalScrollBar.Value * ItemHeight; lastLadder = ladder
ContentPanel.Invalidate(); .Select(l => (l == LadderType.Last) ? LadderType.Through : l)
*/ .ToList();
}
} }
// Events // 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) private void ContentPanel_Resize(object sender, EventArgs e)
{ {
UpdateView(); UpdateView();
+1 -1
View File
@@ -1,6 +1,6 @@
namespace Pf2eViewer.EntityTreeViewer; namespace Pf2eViewer.EntityTreeViewer;
partial class EntityTreeViewerRow sealed partial class EntityTreeViewerRow
{ {
/// <summary> /// <summary>
/// Обязательная переменная конструктора. /// Обязательная переменная конструктора.
@@ -1,9 +1,9 @@
using FontAwesome.Sharp; using System.Diagnostics.CodeAnalysis;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header; using FontAwesome.Sharp;
namespace Pf2eViewer.EntityTreeViewer; namespace Pf2eViewer.EntityTreeViewer;
internal partial class EntityTreeViewerRow : UserControl internal sealed partial class EntityTreeViewerRow : UserControl
{ {
public readonly EntityTreeViewerControl ParentControl; public readonly EntityTreeViewerControl ParentControl;
@@ -11,12 +11,66 @@ internal partial class EntityTreeViewerRow : UserControl
public int ViewIndex = 0; public int ViewIndex = 0;
public Bitmap IconCache
{
get
{
if (_iconCache == null)
{
UpdateCache();
}
return _iconCache!;
}
set => _iconCache = value;
}
private Bitmap? _iconCache;
public List<LadderType> Ladder
{
get => _ladder;
set
{
_ladder = value;
_iconCache = null;
}
}
private List<LadderType> _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 Depth => Item.Depth;
public int NameWidth => TextRenderer.MeasureText(NameLabel.Text, Font).Width + DepthSize;
public EntityTreeViewerRow(EntityTreeViewerControl parent, StructureField item) public EntityTreeViewerRow(EntityTreeViewerControl parent, StructureField item)
{ {
ParentControl = parent; ParentControl = parent;
Font = ParentControl.Font;
Item = item; Item = item;
@@ -27,52 +81,25 @@ internal partial class EntityTreeViewerRow : UserControl
private void ApplyItem() 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; NameLabel.Text = Item.Name;
TypeLabel.Text = Item.GetTypeString(); TypeLabel.Text = Item.GetTypeString();
if (TypeLabel.Text != "") if (TypeLabel.Text != "")
{ {
TypeLabel.Text = $"({TypeLabel.Text})"; TypeLabel.Text = $@"({TypeLabel.Text})";
} }
ValueLabel.Text = Item.GetValueString(); ValueLabel.Text = Item.GetValueString();
TypeLabel.ForeColor = ValueLabel.ForeColor = GetValueColor();
switch (Item) if (Item.FieldType == StructureFieldType.Complex)
{ {
case StructureFieldPrimitive primitive: ValueLabel.Font = new(Font, FontStyle.Italic);
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;
} }
}
public int GetNameWidth() UpdateCache();
{ Refresh();
Size textSize = TextRenderer.MeasureText(NameLabel.Text, Font);
return textSize.Width + ParentControl.DepthSize;
} }
public new void Refresh() public new void Refresh()
@@ -85,10 +112,14 @@ internal partial class EntityTreeViewerRow : UserControl
Height = ParentControl.ItemHeight; Height = ParentControl.ItemHeight;
// Name Panel // Name Panel
NamePanel.Left = Depth * ParentControl.DepthSize; NamePanel.Left = 0;
NamePanel.Width = ParentControl.NameWidth - NamePanel.Left; 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 // Value Panel
ValuePanel.Left = NamePanel.Left + NamePanel.Width; ValuePanel.Left = NamePanel.Left + NamePanel.Width;
@@ -96,63 +127,133 @@ internal partial class EntityTreeViewerRow : UserControl
Size typeSize = TextRenderer.MeasureText(TypeLabel.Text, TypeLabel.Font); Size typeSize = TextRenderer.MeasureText(TypeLabel.Text, TypeLabel.Font);
TypeLabel.Width = typeSize.Width + 3; 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)) private Color BlendColor(Color color1, Color color2, double rate)
{ {
StructureFieldComplex complex = (StructureFieldComplex)Item; 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 int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate)));
? IconChar.PlusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor)
: IconChar.MinusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor);
}
} }
public Color GetValueColor() public Color GetValueColor()
{ {
Func<Color, Color> blend = (Color color) => _colorBlend([ForeColor, color]); return Item switch
switch (Item)
{ {
case StructureFieldPrimitive primitive: StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch
return Type.GetTypeCode(primitive.Type) switch {
{ TypeCode.Boolean => BlendColor(Color.DarkGoldenrod),
TypeCode.Boolean => blend(Color.DarkGoldenrod), TypeCode.Int32 => BlendColor(Color.Blue),
TypeCode.Int32 => blend(Color.Blue), TypeCode.Double => BlendColor(Color.Aqua),
TypeCode.Double => blend(Color.Aqua), TypeCode.String => BlendColor(Color.Green),
TypeCode.String => blend(Color.Green), _ => ForeColor,
}; },
StructureFieldEnum => BlendColor(Color.Red),
case StructureFieldEnum @enum: _ => ForeColor,
return blend(Color.Red); };
}
return ForeColor;
} }
private Color _colorBlend(List<Color> clrArr) // Events
{
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);
}
private void IconPictureBox_Click(object sender, EventArgs e) private void IconPictureBox_Click(object sender, EventArgs e)
{ {
if (Item.GetType() == typeof(StructureFieldComplex)) if (Item.GetType() != typeof(StructureFieldComplex))
{ {
StructureFieldComplex complex = (StructureFieldComplex)Item; return;
complex.Collapsed = !complex.Collapsed;
ParentControl.UpdateView();
} }
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();
} }
} }
@@ -141,6 +141,13 @@ internal enum StructureFieldType
Reference, Reference,
} }
internal enum LadderType
{
Empty,
Last,
Through,
}
internal abstract class StructureField(StructureBuilder builder, string name, int depth) internal abstract class StructureField(StructureBuilder builder, string name, int depth)
{ {
public readonly string Name = name; public readonly string Name = name;