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