Обновление отображения дерева
This commit is contained in:
@@ -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<StructureField> Rows = [];
|
||||
private readonly List<StructureField> _rows = [];
|
||||
|
||||
private List<EntityTreeViewerRow> ViewerRows = [];
|
||||
private List<EntityTreeViewerRow> _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<Control>());
|
||||
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
public void UpdateView()
|
||||
{
|
||||
foreach (EntityTreeViewerRow row in ViewerRows)
|
||||
foreach (EntityTreeViewerRow row in _viewerRows)
|
||||
{
|
||||
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 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<EntityTreeViewerRow> rows)
|
||||
{
|
||||
List<EntityTreeViewerRow> rows = ViewerRows.Where(r => r.Item.Visible).ToList();
|
||||
_visibleRows = rows.Count;
|
||||
List<LadderType> lastLadder = [];
|
||||
|
||||
ContentPanel.VerticalScroll.Enabled = true;
|
||||
ContentPanel.VerticalScroll.Visible = true;
|
||||
for (int i = rows.Count - 1; i >= 0; i--)
|
||||
{
|
||||
EntityTreeViewerRow row = rows[i];
|
||||
List<LadderType> 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();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace Pf2eViewer.EntityTreeViewer;
|
||||
|
||||
partial class EntityTreeViewerRow
|
||||
sealed partial class EntityTreeViewerRow
|
||||
{
|
||||
/// <summary>
|
||||
/// Обязательная переменная конструктора.
|
||||
|
||||
@@ -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<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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (Item.GetType() == typeof(StructureFieldComplex))
|
||||
{
|
||||
StructureFieldComplex complex = (StructureFieldComplex)Item;
|
||||
|
||||
IconPictureBox.Image = complex.Collapsed
|
||||
? IconChar.PlusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor)
|
||||
: IconChar.MinusSquare.ToBitmap(IconFont.Auto, ParentControl.DepthSize, ForeColor);
|
||||
}
|
||||
|
||||
private Color BlendColor(Color color, double rate = 0.5)
|
||||
{
|
||||
return BlendColor(ForeColor, color, rate);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate)));
|
||||
}
|
||||
|
||||
public Color GetValueColor()
|
||||
{
|
||||
Func<Color, Color> blend = (Color color) => _colorBlend([ForeColor, color]);
|
||||
|
||||
switch (Item)
|
||||
return Item switch
|
||||
{
|
||||
case StructureFieldPrimitive primitive:
|
||||
return Type.GetTypeCode(primitive.Type) switch
|
||||
StructureFieldPrimitive primitive => 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),
|
||||
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,
|
||||
};
|
||||
|
||||
case StructureFieldEnum @enum:
|
||||
return blend(Color.Red);
|
||||
}
|
||||
|
||||
return ForeColor;
|
||||
}
|
||||
|
||||
private Color _colorBlend(List<Color> 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))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user