diff --git a/Pf2eModel/Migrations/20250426125550_1.Designer.cs b/Pf2eModel/Migrations/20250428065903_1.Designer.cs
similarity index 99%
rename from Pf2eModel/Migrations/20250426125550_1.Designer.cs
rename to Pf2eModel/Migrations/20250428065903_1.Designer.cs
index 7e50928..21536e1 100644
--- a/Pf2eModel/Migrations/20250426125550_1.Designer.cs
+++ b/Pf2eModel/Migrations/20250428065903_1.Designer.cs
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
- [Migration("20250426125550_1")]
+ [Migration("20250428065903_1")]
partial class _1
{
///
diff --git a/Pf2eModel/Migrations/20250426125550_1.cs b/Pf2eModel/Migrations/20250428065903_1.cs
similarity index 100%
rename from Pf2eModel/Migrations/20250426125550_1.cs
rename to Pf2eModel/Migrations/20250428065903_1.cs
diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.Designer.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.Designer.cs
index 8c79c98..11a9dba 100644
--- a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.Designer.cs
+++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.Designer.cs
@@ -28,39 +28,28 @@ partial class EntityTreeViewerControl
///
private void InitializeComponent()
{
- VerticalScrollBar = new VScrollBar();
ContentPanel = new Panel();
SuspendLayout();
//
- // VerticalScrollBar
- //
- VerticalScrollBar.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
- VerticalScrollBar.Location = new Point(400, 0);
- VerticalScrollBar.Name = "VerticalScrollBar";
- VerticalScrollBar.Size = new Size(20, 520);
- VerticalScrollBar.TabIndex = 0;
- //
// ContentPanel
//
ContentPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
ContentPanel.Location = new Point(0, 0);
ContentPanel.Name = "ContentPanel";
- ContentPanel.Size = new Size(400, 520);
+ ContentPanel.Size = new Size(420, 520);
ContentPanel.TabIndex = 2;
+ ContentPanel.Resize += ContentPanel_Resize;
//
// EntityTreeViewerControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
Controls.Add(ContentPanel);
- Controls.Add(VerticalScrollBar);
Name = "EntityTreeViewerControl";
Size = new Size(420, 520);
ResumeLayout(false);
}
#endregion
-
- private VScrollBar VerticalScrollBar;
- private Panel ContentPanel;
+ public Panel ContentPanel;
}
diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs
index db1f4d5..27b2af8 100644
--- a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs
+++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerControl.cs
@@ -1,16 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Reflection;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-using System.Windows.Forms.VisualStyles;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using System.Data;
+using Pf2eViewer.EntityTreeViewer;
namespace Pf2eViewer;
@@ -20,6 +9,8 @@ public partial class EntityTreeViewerControl : UserControl
private EntityView? _entityView = null;
+ private int _depthSize = 20;
+
public int ItemHeight
{
get => _itemHeight;
@@ -36,16 +27,62 @@ public partial class EntityTreeViewerControl : UserControl
set
{
_entityView = value;
+ UpdateData();
+ }
+ }
+
+ public int DepthSize
+ {
+ get => _depthSize;
+ set
+ {
+ _depthSize = value;
UpdateView();
}
}
+ public int NameWidth { get; private set; } = 200;
+
+ private readonly List Rows = [];
+
+ private List ViewerRows = [];
+
+ public DarkModeCS? DarkMode;
+
public EntityTreeViewerControl()
{
InitializeComponent();
+ DoubleBuffered = true;
+
+ ContentPanel.AutoScroll = false;
+ ContentPanel.VerticalScroll.Enabled = true;
+ ContentPanel.VerticalScroll.Visible = true;
+
+ ContentPanel.MouseWheel += ContentPanel_MouseWheel;
}
- public void UpdateView()
+ 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()
{
ContentPanel.Controls.Clear();
@@ -56,174 +93,96 @@ public partial class EntityTreeViewerControl : UserControl
Rows.Clear();
- Rows.Add(new HeaderRow("Entity"));
- Rows.Add(new ObjectComplex(null, EntityView.Entity));
- Rows.Add(new HeaderRow(EntityView.SubEntity?.GetType().ToString() ?? "SubEntity"));
- Rows.Add(new ObjectComplex(null, EntityView.SubEntity));
+ StructureBuilder builder = new();
- List displayRows = Rows.SelectMany(r => r.GetTextRows()).ToList();
+ Rows.Add(builder.Create("Entity", EntityView.Entity));
- for (int i = 0; i < displayRows.Count; i++)
+ if (EntityView.SubEntity is not null)
{
- DisplayRow dr = displayRows[i];
+ string typeName = EntityView.SubEntity.GetType().Name;
- TextBox tb = new()
- {
- Top = i * ItemHeight,
- Left = 0,
- Width = ContentPanel.Width,
- Height = ItemHeight,
- Font = Font,
- Text = string.Join(
- "",
- [new(' ', dr.Depth * 2), dr.Name, dr.Value != "" ? " : " : "", dr.Value]
- ),
- Margin = new(0),
- };
-
- ContentPanel.Controls.Add(tb);
+ Rows.Add(builder.Create(typeName, EntityView.SubEntity));
}
+
+ UpdateRows();
+ UpdateView();
}
- private Panel GetObjectPanel(object obj)
+ public void UpdateRows()
{
- Panel result = new();
+ ViewerRows = Rows.SelectMany(r => r.GetTextRows(this)).ToList();
- Type type = obj.GetType();
+ ContentPanel.Controls.Clear();
- PropertyInfo[] properties = type.GetProperties();
+ ContentPanel.Controls.AddRange(ViewerRows.ToArray());
+
+ UpdateView();
+ }
+
+ public void UpdateView()
+ {
+ foreach (EntityTreeViewerRow row in ViewerRows)
+ {
+ row.Visible = row.Item.Visible;
+ }
+
+ List rows = ViewerRows.Where(r => r.Item.Visible).ToList();
+
+ int maxNameWidth = -1;
+ int viewRow = 0;
+
+ foreach (EntityTreeViewerRow row in rows)
+ {
+ row.ViewIndex = viewRow++;
+ int nameWidth = row.GetNameWidth() + row.Item.Depth * DepthSize;
+ maxNameWidth = Math.Max(maxNameWidth, nameWidth);
+ }
+
+ NameWidth = maxNameWidth;
+
+ foreach (EntityTreeViewerRow row in rows)
+ {
+ row.Refresh();
+ }
+
+ UpdateScroll();
+ }
+
+ public void UpdateScroll()
+ {
+ List rows = ViewerRows.Where(r => r.Item.Visible).ToList();
+ _visibleRows = rows.Count;
+
+ ContentPanel.VerticalScroll.Enabled = true;
+ ContentPanel.VerticalScroll.Visible = true;
+
+ ContentPanel.VerticalScroll.SmallChange = ItemHeight;
+ ContentPanel.VerticalScroll.LargeChange = ContentPanel.Height;
+ ContentPanel.VerticalScroll.Minimum = 0;
+ ContentPanel.VerticalScroll.Maximum = _visibleRows * ItemHeight;
+
+ // ContentPanel.VerticalScroll.Value = VerticalScrollBar.Value * ItemHeight;
/*
- FieldInfo[] fields = type.GetFields(
- BindingFlags.Instance
- | BindingFlags.Static
- | BindingFlags.NonPublic
- | BindingFlags.Public
- );
+ ContentPanel.Top = -VerticalScrollBar.Value * ItemHeight;
+ ContentPanel.Invalidate();
*/
- for (int i = 0; i < properties.Length; i++)
- {
- PropertyInfo property = properties[i];
-
- TextBox tb = new()
- {
- Top = i * ItemHeight,
- Left = 0,
- Width = ContentPanel.Width,
- Height = ItemHeight,
- Font = Font,
- Text = property.Name,
- Margin = new(0),
- };
-
- result.Controls.Add(tb);
- }
-
- result.Width = ContentPanel.Width;
- result.Height = properties.Length * ItemHeight * 2;
-
- return result;
}
- private List Rows = [];
+ // Events
- private class DisplayRow
+ private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
{
- public string Name = "";
-
- public string Value = "";
-
- public int Depth = 0;
+ UpdateScroll();
}
- private abstract class Row
+ private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e)
{
- public abstract ICollection GetTextRows();
-
- public int Depth = 0;
+ Focus();
+ // ScrollPosition -= e.Delta / 120;
}
- private class HeaderRow(string name) : Row
+ private void ContentPanel_Resize(object sender, EventArgs e)
{
- public readonly string Name = name;
-
- public override ICollection GetTextRows()
- {
- return [new() { Name = Name, Depth = Depth }];
- }
- }
-
- private abstract class ObjectField(PropertyInfo property, object? parent) : Row
- {
- public readonly PropertyInfo? Property = property;
-
- public readonly string Name = property.Name;
-
- public readonly Type Type = property.PropertyType;
-
- public abstract int Rows { get; }
-
- public static ObjectField Resolve(PropertyInfo property, object? parent)
- {
- return property.PropertyType.IsPrimitive
- ? new ObjectPrimitive(property, parent)
- : new ObjectComplex(property, parent);
- }
- }
-
- private class ObjectPrimitive : ObjectField
- {
- public readonly object? Value;
-
- public override int Rows => 1;
-
- public ObjectPrimitive(PropertyInfo? property, object? parent)
- : base(property, parent)
- {
- Value = Property.GetValue(parent, null);
- }
-
- public override ICollection GetTextRows()
- {
- return
- [
- new()
- {
- Name = Name,
- Value = Value?.ToString() ?? "null",
- Depth = Depth,
- },
- ];
- }
- }
-
- private class ObjectComplex : ObjectField
- {
- public readonly List Fields;
-
- public bool Collapsed = false;
-
- public override int Rows => 1 + Fields.Select(f => f.Rows).Sum();
-
- public ObjectComplex(PropertyInfo? property, object? parent)
- : base(property, parent)
- {
- PropertyInfo[] properties = Type.GetProperties();
-
- object? value = Property?.GetValue(parent, null) ?? parent;
-
- Fields = properties.Select(f => Resolve(f, value)).ToList();
- }
-
- public override ICollection GetTextRows()
- {
- List rows = [new() { Name = Name, Depth = Depth }];
-
- if (!Collapsed)
- {
- rows.AddRange(Fields.SelectMany(f => f.GetTextRows()).ToList());
- }
-
- return rows;
- }
+ UpdateView();
}
}
diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs
new file mode 100644
index 0000000..1bc1be8
--- /dev/null
+++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.Designer.cs
@@ -0,0 +1,130 @@
+namespace Pf2eViewer.EntityTreeViewer;
+
+partial class EntityTreeViewerRow
+{
+ ///
+ /// Обязательная переменная конструктора.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Освободить все используемые ресурсы.
+ ///
+ /// истинно, если управляемый ресурс должен быть удален; иначе ложно.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Код, автоматически созданный конструктором компонентов
+
+ ///
+ /// Требуемый метод для поддержки конструктора — не изменяйте
+ /// содержимое этого метода с помощью редактора кода.
+ ///
+ private void InitializeComponent()
+ {
+ NameLabel = new Label();
+ IconPictureBox = new PictureBox();
+ NamePanel = new Panel();
+ ValuePanel = new Panel();
+ TypeLabel = new Label();
+ ValueLabel = new Label();
+ ((System.ComponentModel.ISupportInitialize) IconPictureBox).BeginInit();
+ NamePanel.SuspendLayout();
+ ValuePanel.SuspendLayout();
+ SuspendLayout();
+ //
+ // NameLabel
+ //
+ NameLabel.Dock = DockStyle.Fill;
+ NameLabel.Location = new Point(0, 0);
+ NameLabel.Margin = new Padding(0);
+ NameLabel.Name = "NameLabel";
+ NameLabel.Size = new Size(200, 38);
+ NameLabel.TabIndex = 0;
+ NameLabel.Text = "Name";
+ NameLabel.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // IconPictureBox
+ //
+ IconPictureBox.Dock = DockStyle.Left;
+ IconPictureBox.Location = new Point(0, 0);
+ IconPictureBox.Margin = new Padding(0);
+ IconPictureBox.Name = "IconPictureBox";
+ IconPictureBox.Size = new Size(20, 38);
+ IconPictureBox.TabIndex = 1;
+ IconPictureBox.TabStop = false;
+ IconPictureBox.Click += IconPictureBox_Click;
+ //
+ // NamePanel
+ //
+ NamePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
+ NamePanel.Controls.Add(NameLabel);
+ NamePanel.Controls.Add(IconPictureBox);
+ NamePanel.Location = new Point(0, 0);
+ NamePanel.Name = "NamePanel";
+ NamePanel.Size = new Size(200, 38);
+ NamePanel.TabIndex = 2;
+ //
+ // ValuePanel
+ //
+ ValuePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ ValuePanel.BackColor = SystemColors.Control;
+ ValuePanel.Controls.Add(ValueLabel);
+ ValuePanel.Controls.Add(TypeLabel);
+ ValuePanel.Location = new Point(200, 0);
+ ValuePanel.Margin = new Padding(0);
+ ValuePanel.Name = "ValuePanel";
+ ValuePanel.Size = new Size(288, 38);
+ ValuePanel.TabIndex = 4;
+ //
+ // TypeLabel
+ //
+ TypeLabel.Dock = DockStyle.Left;
+ TypeLabel.Location = new Point(0, 0);
+ TypeLabel.Margin = new Padding(0);
+ TypeLabel.Name = "TypeLabel";
+ TypeLabel.Size = new Size(57, 38);
+ TypeLabel.TabIndex = 3;
+ TypeLabel.Text = "Type";
+ TypeLabel.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // ValueLabel
+ //
+ ValueLabel.AutoEllipsis = true;
+ ValueLabel.Dock = DockStyle.Fill;
+ ValueLabel.Location = new Point(0, 0);
+ ValueLabel.Name = "ValueLabel";
+ ValueLabel.Size = new Size(288, 38);
+ ValueLabel.TabIndex = 4;
+ ValueLabel.Text = "Value";
+ ValueLabel.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // EntityTreeViewerRow
+ //
+ AutoScaleDimensions = new SizeF(7F, 15F);
+ AutoScaleMode = AutoScaleMode.Font;
+ Controls.Add(NamePanel);
+ Controls.Add(ValuePanel);
+ Name = "EntityTreeViewerRow";
+ Size = new Size(488, 38);
+ ((System.ComponentModel.ISupportInitialize) IconPictureBox).EndInit();
+ NamePanel.ResumeLayout(false);
+ ValuePanel.ResumeLayout(false);
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private Label NameLabel;
+ private PictureBox IconPictureBox;
+ private Panel NamePanel;
+ private Panel ValuePanel;
+ private Label TypeLabel;
+ private Label ValueLabel;
+}
diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs
new file mode 100644
index 0000000..89cba62
--- /dev/null
+++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.cs
@@ -0,0 +1,158 @@
+using FontAwesome.Sharp;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header;
+
+namespace Pf2eViewer.EntityTreeViewer;
+
+internal partial class EntityTreeViewerRow : UserControl
+{
+ public readonly EntityTreeViewerControl ParentControl;
+
+ public readonly StructureField Item;
+
+ public int ViewIndex = 0;
+
+ public int Depth => Item.Depth;
+
+ public EntityTreeViewerRow(EntityTreeViewerControl parent, StructureField item)
+ {
+ ParentControl = parent;
+ Font = ParentControl.Font;
+
+ Item = item;
+
+ InitializeComponent();
+ ApplyItem();
+ Refresh();
+ }
+
+ private void ApplyItem()
+ {
+ TypeLabel.Font = new Font(Font.FontFamily, Font.Size - 2, FontStyle.Italic);
+
+ NameLabel.Text = Item.Name;
+ TypeLabel.Text = Item.GetTypeString();
+ if (TypeLabel.Text != "")
+ {
+ TypeLabel.Text = $"({TypeLabel.Text})";
+ }
+ ValueLabel.Text = Item.GetValueString();
+
+ switch (Item)
+ {
+ 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;
+ }
+
+ public new void Refresh()
+ {
+ base.Refresh();
+
+ Left = 0;
+ Top = ViewIndex * ParentControl.ItemHeight;
+ Width = ParentControl.ContentPanel.Width - Left - SystemInformation.VerticalScrollBarWidth;
+ Height = ParentControl.ItemHeight;
+
+ // Name Panel
+ NamePanel.Left = Depth * ParentControl.DepthSize;
+ NamePanel.Width = ParentControl.NameWidth - NamePanel.Left;
+
+ IconPictureBox.Width = ParentControl.ItemHeight;
+
+ // Value Panel
+ ValuePanel.Left = NamePanel.Left + NamePanel.Width;
+ ValuePanel.Width = Width - ValuePanel.Left;
+
+ 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);
+ }
+ }
+
+ public Color GetValueColor()
+ {
+ Func blend = (Color color) => _colorBlend([ForeColor, color]);
+
+ switch (Item)
+ {
+ 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;
+ }
+
+ 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);
+ }
+
+ private void IconPictureBox_Click(object sender, EventArgs e)
+ {
+ if (Item.GetType() == typeof(StructureFieldComplex))
+ {
+ StructureFieldComplex complex = (StructureFieldComplex)Item;
+
+ complex.Collapsed = !complex.Collapsed;
+ ParentControl.UpdateView();
+ }
+ }
+}
diff --git a/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.resx b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.resx
new file mode 100644
index 0000000..8b2ff64
--- /dev/null
+++ b/Pf2eViewer/EntityTreeViewer/EntityTreeViewerRow.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs b/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs
new file mode 100644
index 0000000..a8464b3
--- /dev/null
+++ b/Pf2eViewer/EntityTreeViewer/StructureBuilder.cs
@@ -0,0 +1,395 @@
+using System.Collections;
+using System.Reflection;
+using LinqToDB.Extensions;
+using Pf2eModel.Model.Common.I18n;
+
+namespace Pf2eViewer.EntityTreeViewer;
+
+internal class StructureBuilder
+{
+ private readonly List _processedObjects = [];
+
+ public StructureFieldComplex Create(string name, object value, int depth = 0)
+ {
+ _ = RegisterProcessed(value);
+
+ StructureFieldComplex field = new(this, value, name, depth);
+
+ CompleteProcessed(value, field);
+
+ return field;
+ }
+
+ public bool RegisterProcessed(object value)
+ {
+ if (_processedObjects.Any(p => p.Value == value))
+ {
+ return false;
+ }
+
+ StructureProcessedObject processed = new(value);
+
+ _processedObjects.Add(processed);
+
+ return true;
+ }
+
+ public void CompleteProcessed(object value, StructureFieldComplex field)
+ {
+ var q = _processedObjects.FirstOrDefault(p => p.Value == value);
+
+ if (q == null)
+ {
+ return;
+ }
+
+ q.Field = field;
+ }
+
+ public StructureField Resolve(string name, object? value, int depth)
+ {
+ switch (GetFieldType(value))
+ {
+ case StructureFieldType.Null:
+ return new StructureFieldNull(this, name, depth);
+
+ case StructureFieldType.Primitive:
+ return new StructureFieldPrimitive(this, value!, name, depth);
+
+ case StructureFieldType.Enum:
+ return new StructureFieldEnum(this, value!, name, depth);
+
+ case StructureFieldType.Complex:
+ default:
+
+ StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p =>
+ p.Value == value
+ );
+
+ if (processed is not null)
+ {
+ var reference = new StructureFieldReference(this, name, null, depth);
+ processed.AddReference(reference);
+ return reference;
+ }
+
+ return Create(name, value!, depth);
+ }
+ }
+
+ public static StructureFieldType GetFieldType(object? value)
+ {
+ if (value is null)
+ {
+ return StructureFieldType.Null;
+ }
+
+ Type type = value.GetType();
+
+ if (type.IsEnum)
+ {
+ return StructureFieldType.Enum;
+ }
+
+ if (type.IsPrimitive || type == typeof(string))
+ {
+ return StructureFieldType.Primitive;
+ }
+
+ return StructureFieldType.Complex;
+ }
+}
+
+internal class StructureProcessedObject(object value)
+{
+ public readonly object Value = value;
+
+ private StructureFieldComplex? _field = null;
+
+ public StructureFieldComplex? Field
+ {
+ get => _field;
+ set
+ {
+ _field = value;
+ foreach (var reference in _references)
+ {
+ reference.Reference = Field;
+ }
+ }
+ }
+
+ private readonly List _references = [];
+
+ public void AddReference(StructureFieldReference reference)
+ {
+ _references.Add(reference);
+
+ if (_field is not null)
+ {
+ reference.Reference = Field;
+ }
+ }
+}
+
+internal enum StructureFieldType
+{
+ Null,
+ Primitive,
+ Enum,
+ Complex,
+ Reference,
+}
+
+internal abstract class StructureField(StructureBuilder builder, string name, int depth)
+{
+ public readonly string Name = name;
+
+ public abstract StructureFieldType FieldType { get; }
+
+ public readonly StructureBuilder Builder = builder;
+
+ public virtual bool Visible { get; set; } = true;
+
+ public int Depth = depth;
+
+ public abstract string GetTypeString();
+
+ public abstract string GetValueString();
+
+ public virtual ICollection GetTextRows(EntityTreeViewerControl control)
+ {
+ return [new EntityTreeViewerRow(control, this)];
+ }
+}
+
+internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
+ : StructureField(builder, name, depth)
+{
+ public override StructureFieldType FieldType => StructureFieldType.Null;
+
+ public override string GetTypeString() => "null";
+
+ public override string GetValueString() => "";
+}
+
+internal class StructureFieldPrimitive(
+ StructureBuilder builder,
+ object value,
+ string name,
+ int depth
+) : StructureField(builder, name, depth)
+{
+ public override StructureFieldType FieldType => StructureFieldType.Primitive;
+
+ public object Value = value;
+
+ public readonly Type Type = value.GetType();
+
+ public override string GetTypeString()
+ {
+ return Type.GetTypeCode(Type) switch
+ {
+ TypeCode.Boolean => "bool",
+ TypeCode.Int32 => "int",
+ TypeCode.String => "string",
+ TypeCode.Double => "double",
+ _ => Type.Name,
+ };
+ }
+
+ public override string GetValueString()
+ {
+ if (Type == typeof(string))
+ {
+ return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
+ }
+
+ return Value.ToString() ?? string.Empty;
+ }
+}
+
+internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
+ : StructureField(builder, name, depth)
+{
+ public override StructureFieldType FieldType => StructureFieldType.Enum;
+
+ public object Value = value;
+
+ public readonly Type Type = value.GetType();
+
+ public override string GetTypeString() => "enum";
+
+ public override string GetValueString() => $"{Type.Name}.{Value}";
+}
+
+internal class StructureFieldComplex : StructureField
+{
+ public override StructureFieldType FieldType => StructureFieldType.Complex;
+
+ public readonly List Fields = [];
+
+ public readonly Type Type;
+
+ private bool _collapsed = false;
+
+ public bool Collapsed
+ {
+ get => _collapsed;
+ set
+ {
+ _collapsed = value;
+ UpdateFieldVisibility();
+ }
+ }
+
+ private bool _visible = true;
+
+ public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
+ : base(builder, name, depth)
+ {
+ Type = value.GetType();
+
+ SetFields((dynamic)value);
+ }
+
+ public override bool Visible
+ {
+ get => _visible;
+ set
+ {
+ _visible = value;
+ UpdateFieldVisibility();
+ }
+ }
+
+ private void UpdateFieldVisibility()
+ {
+ foreach (var field in Fields)
+ {
+ field.Visible = Visible && !Collapsed;
+ }
+ }
+
+ private void SetFields(LocaleString localeString)
+ {
+ List records = [];
+
+ foreach (var field in localeString)
+ {
+ records.Add(new FieldRecord(field.Locale.ToString(), field.Value));
+ }
+
+ SetFieldsFromRecords(records);
+ }
+
+ private void SetFields(IEnumerable enumrable)
+ {
+ int i = 0;
+ List records = [];
+
+ foreach (var item in enumrable)
+ {
+ records.Add(new FieldRecord($"[{i++}]", item));
+ }
+
+ SetFieldsFromRecords(records);
+ }
+
+ private void SetFields(object value)
+ {
+ Type type = value.GetType();
+ PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+ SetFieldsFromRecords(
+ properties
+ .Where(property =>
+ property.GetAttribute() is null
+ )
+ .Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
+ .ToList()
+ );
+ }
+
+ private void SetFieldsFromRecords(List records)
+ {
+ foreach (FieldRecord record in records)
+ {
+ if (record.FieldType == StructureFieldType.Complex)
+ {
+ record.Allowed = Builder.RegisterProcessed(record.Value!);
+ }
+ }
+
+ foreach (var record in records)
+ {
+ if (record.FieldType == StructureFieldType.Complex && record.Allowed)
+ {
+ Fields.Add(Builder.Create(record.Name, record.Value!, Depth + 1));
+ }
+ else
+ {
+ Fields.Add(Builder.Resolve(record.Name, record.Value!, Depth + 1));
+ }
+ }
+ }
+
+ public override ICollection GetTextRows(EntityTreeViewerControl control)
+ {
+ List rows = base.GetTextRows(control).ToList();
+
+ rows.AddRange(Fields.SelectMany(f => f.GetTextRows(control)).ToList());
+
+ return rows;
+ }
+
+ private class FieldRecord(string name, object? value)
+ {
+ public string Name = name;
+
+ public object? Value = value;
+
+ public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
+
+ public bool Allowed = true;
+ }
+
+ public override string GetTypeString()
+ {
+ if (Type.IsGenericEnumerableType())
+ {
+ Type elementType = Type.GetGenericArguments().Single();
+
+ return $"{elementType.Name}[]";
+ }
+ else
+ {
+ return Type.Name;
+ }
+ }
+
+ public override string GetValueString()
+ {
+ if (Type.IsGenericEnumerableType())
+ {
+ return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
+ }
+ return "";
+ }
+}
+
+internal class StructureFieldReference(
+ StructureBuilder builder,
+ string name,
+ StructureFieldComplex? reference,
+ int depth
+) : StructureField(builder, name, depth)
+{
+ public StructureFieldComplex? Reference = reference;
+
+ public override StructureFieldType FieldType => StructureFieldType.Reference;
+
+ public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
+
+ public override string GetValueString() => "";
+}
diff --git a/Pf2eViewer/Pf2eViewer.csproj b/Pf2eViewer/Pf2eViewer.csproj
index 59761fe..ab9617d 100644
--- a/Pf2eViewer/Pf2eViewer.csproj
+++ b/Pf2eViewer/Pf2eViewer.csproj
@@ -7,6 +7,7 @@
enable
+
diff --git a/Pf2eViewer/TestForm.Designer.cs b/Pf2eViewer/TestForm.Designer.cs
index 519a836..bd44c60 100644
--- a/Pf2eViewer/TestForm.Designer.cs
+++ b/Pf2eViewer/TestForm.Designer.cs
@@ -28,17 +28,32 @@ partial class TestForm
///
private void InitializeComponent()
{
+ RandomButton = new Button();
entityTreeViewerControl1 = new EntityTreeViewerControl();
SuspendLayout();
//
+ // RandomButton
+ //
+ RandomButton.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
+ RandomButton.Location = new Point(12, 12);
+ RandomButton.Name = "RandomButton";
+ RandomButton.Size = new Size(426, 23);
+ RandomButton.TabIndex = 0;
+ RandomButton.Text = "Random";
+ RandomButton.UseVisualStyleBackColor = true;
+ RandomButton.Click += RandomButton_Click;
+ //
// entityTreeViewerControl1
//
entityTreeViewerControl1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ entityTreeViewerControl1.DepthSize = 20;
entityTreeViewerControl1.EntityView = null;
- entityTreeViewerControl1.Location = new Point(12, 12);
+ entityTreeViewerControl1.Font = new Font("Consolas", 9F);
+ entityTreeViewerControl1.ItemHeight = 20;
+ entityTreeViewerControl1.Location = new Point(12, 38);
entityTreeViewerControl1.Name = "entityTreeViewerControl1";
- entityTreeViewerControl1.Size = new Size(426, 632);
- entityTreeViewerControl1.TabIndex = 0;
+ entityTreeViewerControl1.Size = new Size(426, 606);
+ entityTreeViewerControl1.TabIndex = 1;
//
// TestForm
//
@@ -46,12 +61,15 @@ partial class TestForm
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(450, 656);
Controls.Add(entityTreeViewerControl1);
+ Controls.Add(RandomButton);
Name = "TestForm";
Text = "TestForm";
+ Load += TestForm_Load;
ResumeLayout(false);
}
#endregion
+ private Button RandomButton;
private EntityTreeViewerControl entityTreeViewerControl1;
}
\ No newline at end of file
diff --git a/Pf2eViewer/TestForm.cs b/Pf2eViewer/TestForm.cs
index c4ae87b..b356267 100644
--- a/Pf2eViewer/TestForm.cs
+++ b/Pf2eViewer/TestForm.cs
@@ -14,19 +14,41 @@ namespace Pf2eViewer;
public partial class TestForm : Form
{
+ public DarkModeCS DarkMode;
+
+ public Pf2eDbContext Context = new();
+
+ public int Count = 0;
+
public TestForm()
{
InitializeComponent();
- _ = new DarkModeCS(this) { ColorMode = DarkModeCS.DisplayMode.SystemDefault };
+ DarkMode = new DarkModeCS(this) { ColorMode = DarkModeCS.DisplayMode.SystemDefault };
- Pf2eDbContext context = new();
- int count = context.Entities.Count();
- int id = Random.Shared.Next(1, count + 1);
+ entityTreeViewerControl1.DarkMode = DarkMode;
+ }
- Entity entity = context.Entities.FirstOrDefault(e => e.Id == id)!;
+ public void RandomEntity()
+ {
+ int id = Random.Shared.Next(1, Count + 1);
+
+ Entity entity = Context.Entities.FirstOrDefault(e => e.Id == id)!;
+
+ EntityView view = new(Context, entity.Type, id);
- EntityView view = new(context, entity.Type, id);
entityTreeViewerControl1.EntityView = view;
}
+
+ private void TestForm_Load(object sender, EventArgs e)
+ {
+ Count = Context.Entities.Count();
+
+ RandomEntity();
+ }
+
+ private void RandomButton_Click(object sender, EventArgs e)
+ {
+ RandomEntity();
+ }
}