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 }