395 lines
11 KiB
C#
395 lines
11 KiB
C#
using System.ComponentModel;
|
|
|
|
namespace Pf2eViewer.EntityTreeViewer;
|
|
|
|
/// <summary>
|
|
/// Элемент управления для отображения структуры сущности.
|
|
/// </summary>
|
|
public sealed partial class EntityTreeViewerControl : UserControl
|
|
{
|
|
#region "Поля элемента управления"
|
|
|
|
/// <summary>
|
|
/// Высота строки поля структуры.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// Сущность, которая будет отображаться в элементе управления.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public EntityView? EntityView
|
|
{
|
|
get => _entityView;
|
|
set
|
|
{
|
|
_entityView = value;
|
|
UpdateData();
|
|
}
|
|
}
|
|
private EntityView? _entityView;
|
|
|
|
/// <summary>
|
|
/// Ширина отступа глубины.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
|
[Category("Entity Render")]
|
|
[Description("The size of tab.")]
|
|
public int DepthSize
|
|
{
|
|
get => _depthSize;
|
|
set
|
|
{
|
|
_depthSize = value;
|
|
UpdateView();
|
|
}
|
|
}
|
|
private int _depthSize = 20;
|
|
|
|
/// <summary>
|
|
/// Размер шрифта типа поля сущности.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
|
[Category("Entity Render")]
|
|
[Description("The size of tab.")]
|
|
public float TypeFontSize
|
|
{
|
|
get => _typeFontSize;
|
|
set
|
|
{
|
|
_typeFontSize = value;
|
|
UpdateView();
|
|
}
|
|
}
|
|
private float _typeFontSize = 9.5f;
|
|
|
|
/// <summary>
|
|
/// Размер кнопки плюс/минус.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// Ширина имени поля сущности.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public int NameWidth { get; private set; } = 200;
|
|
|
|
/// <summary>
|
|
/// Цвет фона строки поля при наведении на элемент управления.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// Изображение, используемое для отображения содержимого элемента управления.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public Bitmap ContentImage { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region "Вычисляемые поля"
|
|
|
|
/// <summary>
|
|
/// Ширина области содержимого элемента управления.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public int ContentWidth => Width - SystemInformation.VerticalScrollBarWidth - 2;
|
|
|
|
#endregion
|
|
|
|
#region "Приватные поля"
|
|
|
|
|
|
/// <summary>
|
|
/// Коллекция полей структуры.
|
|
/// </summary>
|
|
private readonly List<StructureField> _rows = [];
|
|
|
|
/// <summary>
|
|
/// Коллекция строк полей структуры, которые будут отображаться в элементе управления.
|
|
/// </summary>
|
|
private List<EntityTreeViewerControlRow> _viewerRows = [];
|
|
|
|
/// <summary>
|
|
/// Флаг, указывающий, что элемент управления в данный момент перерисовывается.
|
|
/// </summary>
|
|
private bool _rendering;
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Конструктор класса <see cref="EntityTreeViewerControl"/>.
|
|
/// </summary>
|
|
public EntityTreeViewerControl()
|
|
{
|
|
InitializeComponent();
|
|
ContentImage = new(ContentWidth, Height);
|
|
DoubleBuffered = true;
|
|
VerticalScroll.Visible = true;
|
|
HorizontalScroll.Visible = false;
|
|
|
|
ContentPanel.MouseWheel += ContentPanel_MouseWheel;
|
|
}
|
|
|
|
#region "Методы обновления"
|
|
|
|
/// <summary>
|
|
/// Обновление данных в элементе управления.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обновление отрисовок в элементе управления.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обновление отображения в элементе управления.
|
|
/// </summary>
|
|
public void UpdateView()
|
|
{
|
|
_viewerRows.ForEach(row => row.UpdateButton());
|
|
|
|
List<EntityTreeViewerControlRow> 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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region "Вспомогательные методы"
|
|
|
|
/// <summary>
|
|
/// Построение лестницы для строк полей структуры.
|
|
/// </summary>
|
|
/// <param name="rows">Набор строк полей структуры.</param>
|
|
private void BuildLadder(List<EntityTreeViewerControlRow> rows)
|
|
{
|
|
List<LadderType> lastLadder = [];
|
|
|
|
for (int i = rows.Count - 1; i >= 0; i--)
|
|
{
|
|
EntityTreeViewerControlRow row = rows[i];
|
|
List<LadderType> 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();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region "События элемента управления"
|
|
|
|
/// <summary>
|
|
/// Обработчик события изменения размера элемента управления.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект события.</param>
|
|
private void EntityTreeViewerControl_Resize(object sender, EventArgs e)
|
|
{
|
|
if (_rendering)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ContentPanel.Width = Width;
|
|
|
|
UpdateRenders();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события прокрутки элемента управления.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект события.</param>
|
|
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();
|
|
}
|
|
*/
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события движения мыши над элементом управления.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект события.</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|